Skip to content

Commit 6344fc7

Browse files
authored
Merge pull request #6332 from sclubricants/DbEscape
Add RawSql to BaseConnection->escape()
2 parents 5ad816b + f11edae commit 6344fc7

File tree

10 files changed

+277
-12
lines changed

10 files changed

+277
-12
lines changed

system/Database/BaseBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2698,7 +2698,7 @@ protected function objectToArray($object)
26982698

26992699
foreach (get_object_vars($object) as $key => $val) {
27002700
// There are some built in keys we need to ignore for this conversion
2701-
if (! is_object($val) && ! is_array($val) && $key !== '_parent_name') {
2701+
if ((! is_object($val) || $val instanceof RawSql) && ! is_array($val) && $key !== '_parent_name') {
27022702
$array[$key] = $val;
27032703
}
27042704
}

system/Database/BaseConnection.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,10 @@ public function escape($str)
12431243
}
12441244

12451245
if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
1246+
if ($str instanceof RawSql) {
1247+
return $str->__toString();
1248+
}
1249+
12461250
return "'" . $this->escapeString($str) . "'";
12471251
}
12481252

system/Database/Postgre/Connection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use CodeIgniter\Database\BaseConnection;
1515
use CodeIgniter\Database\Exceptions\DatabaseException;
16+
use CodeIgniter\Database\RawSql;
1617
use ErrorException;
1718
use stdClass;
1819

@@ -181,6 +182,10 @@ public function escape($str)
181182
}
182183

183184
if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
185+
if ($str instanceof RawSql) {
186+
return $str->__toString();
187+
}
188+
184189
return pg_escape_literal($this->connID, $str);
185190
}
186191

tests/system/Database/Builder/InsertTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use CodeIgniter\Database\Exceptions\DatabaseException;
1515
use CodeIgniter\Database\Query;
16+
use CodeIgniter\Database\RawSql;
1617
use CodeIgniter\Test\CIUnitTestCase;
1718
use CodeIgniter\Test\Mock\MockConnection;
1819

@@ -85,6 +86,21 @@ public function testInsertObject()
8586
$this->assertSame($expectedBinds, $builder->getBinds());
8687
}
8788

89+
public function testInsertObjectWithRawSql()
90+
{
91+
$builder = $this->db->table('jobs');
92+
93+
$insertData = (object) [
94+
'id' => 1,
95+
'name' => new RawSql('CONCAT("id", \'Grocery Sales\')'),
96+
];
97+
$builder->testMode()->insert($insertData, true);
98+
99+
$expectedSQL = 'INSERT INTO "jobs" ("id", "name") VALUES (1, CONCAT("id", \'Grocery Sales\'))';
100+
101+
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledInsert()));
102+
}
103+
88104
/**
89105
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5365
90106
*/

tests/system/Database/Live/EscapeTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace CodeIgniter\Database\Live;
1313

14+
use CodeIgniter\Database\RawSql;
1415
use CodeIgniter\Test\CIUnitTestCase;
1516
use CodeIgniter\Test\DatabaseTestTrait;
1617

@@ -78,4 +79,22 @@ public function testEscapeLikeStringDirect()
7879
$this->expectNotToPerformAssertions();
7980
}
8081
}
82+
83+
public function testEscapeStringArray()
84+
{
85+
$stringArray = [' A simple string ', new RawSql('CURRENT_TIMESTAMP()'), false, null];
86+
87+
$escapedString = $this->db->escape($stringArray);
88+
89+
$this->assertSame("' A simple string '", $escapedString[0]);
90+
$this->assertSame('CURRENT_TIMESTAMP()', $escapedString[1]);
91+
92+
if ($this->db->DBDriver === 'Postgre') {
93+
$this->assertSame('FALSE', $escapedString[2]);
94+
} else {
95+
$this->assertSame(0, $escapedString[2]);
96+
}
97+
98+
$this->assertSame('NULL', $escapedString[3]);
99+
}
81100
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Database\Live\MySQLi;
13+
14+
use CodeIgniter\Database\RawSql;
15+
use CodeIgniter\Test\CIUnitTestCase;
16+
use CodeIgniter\Test\DatabaseTestTrait;
17+
use stdclass;
18+
use Tests\Support\Database\Seeds\CITestSeeder;
19+
20+
/**
21+
* @group DatabaseLive
22+
*
23+
* @internal
24+
*/
25+
final class RawSqlTest extends CIUnitTestCase
26+
{
27+
use DatabaseTestTrait;
28+
29+
protected $refresh = true;
30+
protected $seed = CITestSeeder::class;
31+
32+
protected function setUp(): void
33+
{
34+
parent::setUp();
35+
36+
if ($this->db->DBDriver !== 'MySQLi') {
37+
$this->markTestSkipped('Only MySQLi has its own implementation.');
38+
} else {
39+
$this->addSqlFunction();
40+
}
41+
}
42+
43+
protected function addSqlFunction()
44+
{
45+
$this->db->query('DROP FUNCTION IF EXISTS setDateTime');
46+
47+
$sql = "CREATE FUNCTION setDateTime ( setDate varchar(20) )
48+
RETURNS DATETIME
49+
READS SQL DATA
50+
DETERMINISTIC
51+
BEGIN
52+
RETURN CONVERT(CONCAT(setDate,' ','01:01:11'), DATETIME);
53+
END;";
54+
55+
$this->db->query($sql);
56+
}
57+
58+
public function testRawSqlUpdateObject()
59+
{
60+
$data = [];
61+
62+
$row = new stdclass();
63+
$row->email = '[email protected]';
64+
$row->created_at = new RawSql("setDateTime('2022-01-01')");
65+
$data[] = $row;
66+
67+
$row = new stdclass();
68+
$row->email = '[email protected]';
69+
$row->created_at = new RawSql("setDateTime('2022-01-01')");
70+
$data[] = $row;
71+
72+
$this->db->table('user')->updateBatch($data, 'email');
73+
74+
$row->created_at = new RawSql("setDateTime('2022-01-11')");
75+
76+
$this->db->table('user')->update($row, "email = '[email protected]'");
77+
78+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-01-01 01:01:11']);
79+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-01-11 01:01:11']);
80+
}
81+
82+
public function testRawSqlSetUpdateObject()
83+
{
84+
$data = [];
85+
86+
$row = new stdclass();
87+
$row->email = '[email protected]';
88+
$row->created_at = new RawSql("setDateTime('2022-02-01')");
89+
$data[] = $row;
90+
91+
$row = new stdclass();
92+
$row->email = '[email protected]';
93+
$row->created_at = new RawSql("setDateTime('2022-02-01')");
94+
$data[] = $row;
95+
96+
$this->db->table('user')->setUpdateBatch($data, 'email')->updateBatch(null, 'email');
97+
98+
$row->created_at = new RawSql("setDateTime('2022-02-11')");
99+
100+
$this->db->table('user')->set($row)->update(null, "email = '[email protected]'");
101+
102+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-02-01 01:01:11']);
103+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-02-11 01:01:11']);
104+
}
105+
106+
public function testRawSqlUpdateArray()
107+
{
108+
$data = [
109+
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-03-01')")],
110+
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-03-01')")],
111+
];
112+
113+
$this->db->table('user')->updateBatch($data, 'email');
114+
115+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-03-01 01:01:11']);
116+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-03-01 01:01:11']);
117+
118+
$data = ['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-03-11')")];
119+
120+
$this->db->table('user')->update($data, "email = '[email protected]'");
121+
122+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-03-11 01:01:11']);
123+
}
124+
125+
public function testRawSqlInsertArray()
126+
{
127+
$data = [
128+
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-04-01')")],
129+
['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-04-01')")],
130+
];
131+
132+
$this->db->table('user')->insertBatch($data);
133+
134+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-04-01 01:01:11']);
135+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-04-01 01:01:11']);
136+
137+
$data = ['email' => '[email protected]', 'created_at' => new RawSql("setDateTime('2022-04-11')")];
138+
139+
$this->db->table('user')->insert($data);
140+
141+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-04-11 01:01:11']);
142+
}
143+
144+
public function testRawSqlInsertObject()
145+
{
146+
$data = [];
147+
148+
$row = new stdclass();
149+
$row->email = '[email protected]';
150+
$row->created_at = new RawSql("setDateTime('2022-05-01')");
151+
$data[] = $row;
152+
153+
$row = new stdclass();
154+
$row->email = '[email protected]';
155+
$row->created_at = new RawSql("setDateTime('2022-05-01')");
156+
$data[] = $row;
157+
158+
$this->db->table('user')->insertBatch($data);
159+
160+
$row->email = '[email protected]';
161+
$row->created_at = new RawSql("setDateTime('2022-05-11')");
162+
163+
$this->db->table('user')->insert($row);
164+
165+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-05-01 01:01:11']);
166+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-05-01 01:01:11']);
167+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-05-11 01:01:11']);
168+
}
169+
170+
public function testRawSqlSetInsertObject()
171+
{
172+
$data = [];
173+
174+
$row = new stdclass();
175+
$row->email = '[email protected]';
176+
$row->created_at = new RawSql("setDateTime('2022-06-01')");
177+
$data[] = $row;
178+
179+
$row = new stdclass();
180+
$row->email = '[email protected]';
181+
$row->created_at = new RawSql("setDateTime('2022-06-01')");
182+
$data[] = $row;
183+
184+
$this->db->table('user')->setInsertBatch($data)->insertBatch();
185+
186+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-01 01:01:11']);
187+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-01 01:01:11']);
188+
189+
$row->email = '[email protected]';
190+
$row->created_at = new RawSql("setDateTime('2022-06-11')");
191+
192+
$this->db->table('user')->set($row)->insert();
193+
194+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-11 01:01:11']);
195+
196+
$this->db->table('user')
197+
->set('email', '[email protected]')
198+
->set('created_at', new RawSql("setDateTime('2022-06-13')"))
199+
->insert();
200+
201+
$this->seeInDatabase('user', ['email' => '[email protected]', 'created_at' => '2022-06-13 01:01:11']);
202+
}
203+
}

user_guide_src/source/changelogs/v4.3.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Others
6969
- Help information for a spark command can now be accessed using the ``--help`` option (e.g. ``php spark serve --help``)
7070
- Added new Form helper function :php:func:`validation_errors()`, :php:func:`validation_list_errors()` and :php:func:`validation_show_error()` to display Validation Errors.
7171
- Now you can autoload helpers by **app/Config/Autoload.php**.
72+
- ``BaseConnection::escape()`` now excludes the ``RawSql`` data type. This allows passing SQL strings into data.
7273

7374
Changes
7475
*******

user_guide_src/source/database/queries.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,14 @@ It's a very good security practice to escape your data before submitting
9999
it into your database. CodeIgniter has three methods that help you do
100100
this:
101101

102+
.. _database-queries-db_escape:
103+
102104
1. $db->escape()
103105
================
104106

105-
This function determines the data type so
106-
that it can escape only string data. It also automatically adds
107-
single quotes around the data so you don't have to:
107+
This function determines the data type so that it can escape only string
108+
data. It also automatically adds single quotes around the data so you
109+
don't have to:
108110

109111
.. literalinclude:: queries/009.php
110112

user_guide_src/source/database/query_builder.rst

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,9 @@ Here is an example using an object:
800800

801801
The first parameter is an object.
802802

803-
.. note:: All values are escaped automatically producing safer queries.
803+
.. note:: All values except ``RawSql`` are escaped automatically producing safer queries.
804+
805+
.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
804806

805807
$builder->ignore()
806808
------------------
@@ -845,7 +847,9 @@ method. Here is an example using an array:
845847

846848
The first parameter is an associative array of values.
847849

848-
.. note:: All values are escaped automatically producing safer queries.
850+
.. note:: All values except ``RawSql`` are escaped automatically producing safer queries.
851+
852+
.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
849853

850854
*************
851855
Updating Data
@@ -918,7 +922,9 @@ Or you can supply an object:
918922

919923
.. literalinclude:: query_builder/089.php
920924

921-
.. note:: All values are escaped automatically producing safer queries.
925+
.. note:: All values except ``RawSql`` are escaped automatically producing safer queries.
926+
927+
.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
922928

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

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

950-
.. note:: All values are escaped automatically producing safer queries.
956+
.. note:: All values except ``RawSql`` are escaped automatically producing safer queries.
957+
958+
.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
951959

952960
.. note:: ``affectedRows()`` won't give you proper results with this method,
953961
due to the very nature of how it works. Instead, ``updateBatch()``
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
<?php
22

3+
use CodeIgniter\Database\RawSql;
4+
35
$data = [
4-
'title' => 'My title',
5-
'name' => 'My Name',
6-
'date' => 'My date',
6+
'id' => new RawSql('DEFAULT'),
7+
'title' => 'My title',
8+
'name' => 'My Name',
9+
'date' => '2022-01-01',
10+
'last_update' => new RawSql('CURRENT_TIMESTAMP()'),
711
];
812

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

0 commit comments

Comments
 (0)