CodeIgniter4 icon indicating copy to clipboard operation
CodeIgniter4 copied to clipboard

Add RawSql to BaseConnection->escape()

Open sclubricants opened this issue 3 years ago • 16 comments

This PR adds RawSql type to BaseConnection->escape(). It does not escape the RawSql.

This allows passing SQL functions in columns used for DML operations.

For instance you could pass CURRENT_TIMESTAMP() to a date time field instead of a literal value.

You could call your own SQL function as well.

Another is to pass SQL constants like DEFAULT in order to fill a column with default values.

<?php

    $data = [
        'id'          => new RawSql('DEFAULT'),
        'title'       => 'My title',
        'name'        => 'My Name',
        'date'        => '2022-01-01',
        'last_update' => new RawSql('CURRENT_TIMESTAMP()'),
    ];

    $builder->insert($data);
    /* Produces:
        INSERT INTO mytable (id, title, name, date, last_update)
        VALUES (DEFAULT, 'My title', 'My name', '2022-01-01', CURRENT_TIMESTAMP())
    */

Checklist:

  • [x] Securely signed commits
  • [x] Component(s) with PHPDoc blocks, only if necessary or adds value
  • [x] Unit testing, with >80% coverage
  • [x] User guide updated
  • [x] Conforms to style guide

sclubricants avatar Aug 02 '22 21:08 sclubricants

Looks like a cool feature! The changes look good to me but I'd rather someone with a better idea of the larger database implications handle reviews. @iRedds or @paulbalandan ?

MGatner avatar Aug 03 '22 12:08 MGatner

Please update the all notes in query_builder.rst:

.. note:: All values are escaped automatically producing safer queries.

kenjis avatar Aug 04 '22 06:08 kenjis

Looking good! Thanks for all the reviews.

MGatner avatar Aug 04 '22 09:08 MGatner

.. note:: All values are escaped automatically producing safer queries.

@kenjis What do you propose we do here?

How about:

.. note:: All values except ``RawSql`` are escaped automatically producing safer queries.

sclubricants avatar Aug 04 '22 22:08 sclubricants

I think the following section is weird. Because it explains escape(), but it explains about not escaping a lot.

Screenshot 2022-08-05 7 56 17

I think we should remove the sample and description for RawSql here. Because it is not needed here.

This section explains how to use escape() and its behavior. Devs never writes or should not write code like $db->escape(new RawSql('...')). It does not make sense.

See the first sample code. The assumption here is that SQL statements are generated by concatenating strings. Devs can write SQL functions and constants as they like in a string. But the sentence "This allows you to call SQL functions and constants" is an explanation when we use QueryBuilder.

kenjis avatar Aug 04 '22 23:08 kenjis

.. note:: All values except RawSql are escaped automatically producing safer queries.

Good. And can you add the following warning, too?

Screenshot 2022-08-05 8 16 12

kenjis avatar Aug 04 '22 23:08 kenjis

So you want to remove everything? Then none will know about its availability for use.

I think the example is good because it explains what the method does. The method does different things to different types of data. It could be possible to add other data types such as Datetime.

Its important to know how these datatypes are treated.

sclubricants avatar Aug 05 '22 00:08 sclubricants

Developers might should understand that the escape method is what they are relying on when they employ functions like insert. They might not write the actual code for db->escape() but they are none the less calling it via ignoring the escape parameter on functions like insert().

sclubricants avatar Aug 05 '22 00:08 sclubricants

So you want to remove everything?

I thought again, and yes. Because the page explains how to use $db->query() (and simpleQuery()). So RawSql is not needed for them.

Your explanation about RawSql and escape() is good, but it should not in the place.

Developers might should understand that the escape method is what they are relying on when they employ functions like insert. They might not write the actual code for db->escape() but they are none the less calling it via ignoring the escape parameter on functions like insert().

Yes, and the explanation should be in the insert() page. It is a QueryBuilder method.

kenjis avatar Aug 05 '22 00:08 kenjis

Maybe RawSql needs its own page

sclubricants avatar Aug 05 '22 01:08 sclubricants

I wonder if this will conflict with binds..

sclubricants avatar Aug 05 '22 05:08 sclubricants

I wonder if this will conflict with binds..

Sounds like a test case is in order 😊

MGatner avatar Aug 05 '22 10:08 MGatner

No conflict with binds but did have to fix objectToArray() in BaseBuilder

sclubricants avatar Aug 05 '22 20:08 sclubricants

No conflict with binds but did have to fix objectToArray() in BaseBuilder

Why doesn't the existing test fail? Is a test case missing?

kenjis avatar Aug 06 '22 00:08 kenjis

The existing test just tests escape(). Ill write some tests to test things like insert().

sclubricants avatar Aug 06 '22 21:08 sclubricants

Added RawSql test.. to test custom SQL function:

SELECT setDateTime('2022-06-01')

Returns: 2022-06-01 01:01:11

This way the RawSql has quotes in it to test and make sure quotes aren't being escaped.

sclubricants avatar Aug 08 '22 20:08 sclubricants

What do you guys think about adding a helper method:

/**
 * Creates RawSql class
 */
public function rawSql(string $sql): RawSql
{
    return new RawSql($sql);
}

$data = [
    'id'          => $this->db->rawSql('DEFAULT'),
    'title'       => 'My title',
    'name'        => 'My Name',
    'date'        => '2022-01-01',
    'last_update' => $this->db->rawSql('CURRENT_TIMESTAMP()'),
];

I've got some more uses of RawSql I'm working on. I think we should create a page in documentation just for RawSql.

sclubricants avatar Aug 15 '22 18:08 sclubricants

I think a dedicated documentation page is a good idea.

I don't like the rawSql() method - it functions as a named constructor which should belong on the class itself. In actuality though the constructor itself is sufficient, so this simply ends up creating a class dependency that wasn't there before.

Open to other ideas or push-back if I have kissed something.

MGatner avatar Aug 16 '22 10:08 MGatner

I don't see the benefit of $this->db->rawSql(). What's better?

kenjis avatar Aug 16 '22 10:08 kenjis

If there's impetus to wrap this in a method then it should be something like RawSql::of($sql), with an optional protected constructor. But I still don't know that it adds anything.

MGatner avatar Aug 16 '22 11:08 MGatner

I was just looking for a solution that made easy access to class without having to include the class path. I thought a method like would make development easier.

sclubricants avatar Aug 16 '22 19:08 sclubricants

I was just looking for a solution that made easy access to class without having to include the class path.

Why do you want not having the class path? Don't you use IDE? If you really want it, it would be good to have a helper (global function) for it.

kenjis avatar Aug 16 '22 21:08 kenjis

I won't look back through all of it, so unless anyone else has more comments this is good by me. Thanks for this feature @sclubricants - very nice addition!

MGatner avatar Aug 19 '22 10:08 MGatner

I made a couple of fixes. One is that I didn't get the logic quite right for the objectToArray() method.

The other is that I was using is_a($val, RawSql::class) and changed it to $val instanceof RawSql.

The php function is_a() requires the first parameter be an object or class, whereas instanceof does not.

https://www.php.net/manual/en/function.is-a.php

https://www.php.net/manual/en/language.operators.type.php

sclubricants avatar Aug 19 '22 19:08 sclubricants

is_a() defaults to objects only unless you use the third parameter, so what you had was fine but I think instanceof is the preferred choice here anyway.

MGatner avatar Aug 20 '22 10:08 MGatner

Sorry, can you rebase to resolve the conflict?

kenjis avatar Aug 22 '22 10:08 kenjis

The usage example is rather ambiguous. It can be replaced with.

$data = [
    'title'       => 'My title',
    'name'        => 'My Name',
    'date'        => '2022-01-01',
];

$builder->set([
    'id'          => 'DEFAULT',
    'last_update' => 'CURRENT_TIMESTAMP()',
], null, false)->insert($data);

iRedds avatar Aug 23 '22 06:08 iRedds

The usage example is rather ambiguous. It can be replaced with.

Your trick won't work on insertBatch()

sclubricants avatar Aug 23 '22 18:08 sclubricants

@iRedds To be honest, I would like to throw an Exception to such code. It is difficult to read.

By the way, what do you mean ambiguous ? It seems to me the sample code is easy to read and understand.

kenjis avatar Aug 24 '22 09:08 kenjis

I think @iRedds was pointing out that we can already accomplish the same thing with other tools. insertBatch() is a good counter-example. I also assume this is the only way to use a Model to insert non-escaped data?

MGatner avatar Aug 24 '22 11:08 MGatner

I agree that we can already accomplish the same thing with other tools. But I think the sample code is okay even if there is another way to do the same thing.

kenjis avatar Aug 25 '22 00:08 kenjis

Thanks all!

MGatner avatar Aug 25 '22 10:08 MGatner