Skip to content

Add RawSql to BaseConnection->escape() #6332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion system/Database/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2698,7 +2698,7 @@ protected function objectToArray($object)

foreach (get_object_vars($object) as $key => $val) {
// There are some built in keys we need to ignore for this conversion
if (! is_object($val) && ! is_array($val) && $key !== '_parent_name') {
if ((! is_object($val) || $val instanceof RawSql) && ! is_array($val) && $key !== '_parent_name') {
$array[$key] = $val;
}
}
Expand Down
4 changes: 4 additions & 0 deletions system/Database/BaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,10 @@ public function escape($str)
}

if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
if ($str instanceof RawSql) {
return $str->__toString();
}

return "'" . $this->escapeString($str) . "'";
}

Expand Down
5 changes: 5 additions & 0 deletions system/Database/Postgre/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;
use ErrorException;
use stdClass;

Expand Down Expand Up @@ -181,6 +182,10 @@ public function escape($str)
}

if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
if ($str instanceof RawSql) {
return $str->__toString();
}

return pg_escape_literal($this->connID, $str);
}

Expand Down
16 changes: 16 additions & 0 deletions tests/system/Database/Builder/InsertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Query;
use CodeIgniter\Database\RawSql;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;

Expand Down Expand Up @@ -85,6 +86,21 @@ public function testInsertObject()
$this->assertSame($expectedBinds, $builder->getBinds());
}

public function testInsertObjectWithRawSql()
{
$builder = $this->db->table('jobs');

$insertData = (object) [
'id' => 1,
'name' => new RawSql('CONCAT("id", \'Grocery Sales\')'),
];
$builder->testMode()->insert($insertData, true);

$expectedSQL = 'INSERT INTO "jobs" ("id", "name") VALUES (1, CONCAT("id", \'Grocery Sales\'))';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledInsert()));
}

/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5365
*/
Expand Down
19 changes: 19 additions & 0 deletions tests/system/Database/Live/EscapeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace CodeIgniter\Database\Live;

use CodeIgniter\Database\RawSql;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;

Expand Down Expand Up @@ -78,4 +79,22 @@ public function testEscapeLikeStringDirect()
$this->expectNotToPerformAssertions();
}
}

public function testEscapeStringArray()
{
$stringArray = [' A simple string ', new RawSql('CURRENT_TIMESTAMP()'), false, null];

$escapedString = $this->db->escape($stringArray);

$this->assertSame("' A simple string '", $escapedString[0]);
$this->assertSame('CURRENT_TIMESTAMP()', $escapedString[1]);

if ($this->db->DBDriver === 'Postgre') {
$this->assertSame('FALSE', $escapedString[2]);
} else {
$this->assertSame(0, $escapedString[2]);
}

$this->assertSame('NULL', $escapedString[3]);
}
}
203 changes: 203 additions & 0 deletions tests/system/Database/Live/MySQLi/RawSqlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Database\Live\MySQLi;

use CodeIgniter\Database\RawSql;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use stdclass;
use Tests\Support\Database\Seeds\CITestSeeder;

/**
* @group DatabaseLive
*
* @internal
*/
final class RawSqlTest extends CIUnitTestCase
{
use DatabaseTestTrait;

protected $refresh = true;
protected $seed = CITestSeeder::class;

protected function setUp(): void
{
parent::setUp();

if ($this->db->DBDriver !== 'MySQLi') {
$this->markTestSkipped('Only MySQLi has its own implementation.');
} else {
$this->addSqlFunction();
}
}

protected function addSqlFunction()
{
$this->db->query('DROP FUNCTION IF EXISTS setDateTime');

$sql = "CREATE FUNCTION setDateTime ( setDate varchar(20) )
RETURNS DATETIME
READS SQL DATA
DETERMINISTIC
BEGIN
RETURN CONVERT(CONCAT(setDate,' ','01:01:11'), DATETIME);
END;";

$this->db->query($sql);
}

public function testRawSqlUpdateObject()
{
$data = [];

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-01-01')");
$data[] = $row;

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-01-01')");
$data[] = $row;

$this->db->table('user')->updateBatch($data, 'email');

$row->created_at = new RawSql("setDateTime('2022-01-11')");

$this->db->table('user')->update($row, "email = '[email protected]'");

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-01-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-01-11 01:01:11']);
}

public function testRawSqlSetUpdateObject()
{
$data = [];

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-02-01')");
$data[] = $row;

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-02-01')");
$data[] = $row;

$this->db->table('user')->setUpdateBatch($data, 'email')->updateBatch(null, 'email');

$row->created_at = new RawSql("setDateTime('2022-02-11')");

$this->db->table('user')->set($row)->update(null, "email = '[email protected]'");

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-02-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-02-11 01:01:11']);
}

public function testRawSqlUpdateArray()
{
$data = [
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-03-01')")],
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-03-01')")],
];

$this->db->table('user')->updateBatch($data, 'email');

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-03-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-03-01 01:01:11']);

$data = ['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-03-11')")];

$this->db->table('user')->update($data, "email = '[email protected]'");

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-03-11 01:01:11']);
}

public function testRawSqlInsertArray()
{
$data = [
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-04-01')")],
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-04-01')")],
];

$this->db->table('user')->insertBatch($data);

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-04-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-04-01 01:01:11']);

$data = ['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-04-11')")];

$this->db->table('user')->insert($data);

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-04-11 01:01:11']);
}

public function testRawSqlInsertObject()
{
$data = [];

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-05-01')");
$data[] = $row;

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-05-01')");
$data[] = $row;

$this->db->table('user')->insertBatch($data);

$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-05-11')");

$this->db->table('user')->insert($row);

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-05-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-05-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-05-11 01:01:11']);
}

public function testRawSqlSetInsertObject()
{
$data = [];

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-06-01')");
$data[] = $row;

$row = new stdclass();
$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-06-01')");
$data[] = $row;

$this->db->table('user')->setInsertBatch($data)->insertBatch();

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-01 01:01:11']);
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-01 01:01:11']);

$row->email = '[email protected]';
$row->created_at = new RawSql("setDateTime('2022-06-11')");

$this->db->table('user')->set($row)->insert();

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-11 01:01:11']);

$this->db->table('user')
->set('email', '[email protected]')
->set('created_at', new RawSql("setDateTime('2022-06-13')"))
->insert();

$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-13 01:01:11']);
}
}
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Others
- Help information for a spark command can now be accessed using the ``--help`` option (e.g. ``php spark serve --help``)
- Added new Form helper function :php:func:`validation_errors()`, :php:func:`validation_list_errors()` and :php:func:`validation_show_error()` to display Validation Errors.
- Now you can autoload helpers by **app/Config/Autoload.php**.
- ``BaseConnection::escape()`` now excludes the ``RawSql`` data type. This allows passing SQL strings into data.

Changes
*******
Expand Down
8 changes: 5 additions & 3 deletions user_guide_src/source/database/queries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,14 @@ It's a very good security practice to escape your data before submitting
it into your database. CodeIgniter has three methods that help you do
this:

.. _database-queries-db_escape:

1. $db->escape()
================

This function determines the data type so
that it can escape only string data. It also automatically adds
single quotes around the data so you don't have to:
This function determines the data type so that it can escape only string
data. It also automatically adds single quotes around the data so you
don't have to:

.. literalinclude:: queries/009.php

Expand Down
16 changes: 12 additions & 4 deletions user_guide_src/source/database/query_builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,9 @@ Here is an example using an object:

The first parameter is an object.

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

.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.

$builder->ignore()
------------------
Expand Down Expand Up @@ -845,7 +847,9 @@ method. Here is an example using an array:

The first parameter is an associative array of values.

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

.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.

*************
Updating Data
Expand Down Expand Up @@ -918,7 +922,9 @@ Or you can supply an object:

.. literalinclude:: query_builder/089.php

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

.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.

You'll notice the use of the ``$builder->where()`` method, enabling you
to set the **WHERE** clause. You can optionally pass this information
Expand Down Expand Up @@ -947,7 +953,9 @@ Here is an example using an array:

The first parameter is an associative array of values, the second parameter is the where key.

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

.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.

.. note:: ``affectedRows()`` won't give you proper results with this method,
due to the very nature of how it works. Instead, ``updateBatch()``
Expand Down
15 changes: 11 additions & 4 deletions user_guide_src/source/database/query_builder/076.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<?php

use CodeIgniter\Database\RawSql;

$data = [
'title' => 'My title',
'name' => 'My Name',
'date' => 'My date',
'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 (title, name, date) VALUES ('My title', 'My name', 'My date')
/* Produces:
INSERT INTO mytable (id, title, name, date, last_update)
VALUES (DEFAULT, 'My title', 'My name', '2022-01-01', CURRENT_TIMESTAMP())
*/