Skip to content

Commit daff2d7

Browse files
authored
Merge pull request #5875 from kenjis/feat-qb-raw-sql-join
feat: QueryBuilder join() raw SQL string support
2 parents ca74b82 + ae40b35 commit daff2d7

8 files changed

Lines changed: 81 additions & 18 deletions

File tree

system/Database/BaseBuilder.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,9 +579,11 @@ public function fromSubquery(BaseBuilder $from, string $alias): self
579579
/**
580580
* Generates the JOIN portion of the query
581581
*
582+
* @param RawSql|string $cond
583+
*
582584
* @return $this
583585
*/
584-
public function join(string $table, string $cond, string $type = '', ?bool $escape = null)
586+
public function join(string $table, $cond, string $type = '', ?bool $escape = null)
585587
{
586588
if ($type !== '') {
587589
$type = strtoupper(trim($type));
@@ -601,6 +603,17 @@ public function join(string $table, string $cond, string $type = '', ?bool $esca
601603
$escape = $this->db->protectIdentifiers;
602604
}
603605

606+
// Do we want to escape the table name?
607+
if ($escape === true) {
608+
$table = $this->db->protectIdentifiers($table, true, null, false);
609+
}
610+
611+
if ($cond instanceof RawSql) {
612+
$this->QBJoin[] = $type . 'JOIN ' . $table . ' ON ' . $cond;
613+
614+
return $this;
615+
}
616+
604617
if (! $this->hasOperator($cond)) {
605618
$cond = ' USING (' . ($escape ? $this->db->escapeIdentifiers($cond) : $cond) . ')';
606619
} elseif ($escape === false) {
@@ -634,11 +647,6 @@ public function join(string $table, string $cond, string $type = '', ?bool $esca
634647
}
635648
}
636649

637-
// Do we want to escape the table name?
638-
if ($escape === true) {
639-
$table = $this->db->protectIdentifiers($table, true, null, false);
640-
}
641-
642650
// Assemble the JOIN statement
643651
$this->QBJoin[] = $type . 'JOIN ' . $table . $cond;
644652

system/Database/Postgre/Builder.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use CodeIgniter\Database\BaseBuilder;
1515
use CodeIgniter\Database\Exceptions\DatabaseException;
16+
use CodeIgniter\Database\RawSql;
1617

1718
/**
1819
* Builder for Postgre
@@ -300,9 +301,11 @@ protected function _like_statement(?string $prefix, string $column, ?string $not
300301
/**
301302
* Generates the JOIN portion of the query
302303
*
304+
* @param RawSql|string $cond
305+
*
303306
* @return BaseBuilder
304307
*/
305-
public function join(string $table, string $cond, string $type = '', ?bool $escape = null)
308+
public function join(string $table, $cond, string $type = '', ?bool $escape = null)
306309
{
307310
if (! in_array('FULL OUTER', $this->joinTypes, true)) {
308311
$this->joinTypes = array_merge($this->joinTypes, ['FULL OUTER']);

system/Database/SQLSRV/Builder.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use CodeIgniter\Database\BaseBuilder;
1515
use CodeIgniter\Database\Exceptions\DatabaseException;
1616
use CodeIgniter\Database\Exceptions\DataException;
17+
use CodeIgniter\Database\RawSql;
1718
use CodeIgniter\Database\ResultInterface;
1819

1920
/**
@@ -88,9 +89,11 @@ protected function _truncate(string $table): string
8889
/**
8990
* Generates the JOIN portion of the query
9091
*
92+
* @param RawSql|string $cond
93+
*
9194
* @return $this
9295
*/
93-
public function join(string $table, string $cond, string $type = '', ?bool $escape = null)
96+
public function join(string $table, $cond, string $type = '', ?bool $escape = null)
9497
{
9598
if ($type !== '') {
9699
$type = strtoupper(trim($type));

tests/system/Database/Builder/JoinTest.php

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

1414
use CodeIgniter\Database\BaseBuilder;
1515
use CodeIgniter\Database\Postgre\Builder as PostgreBuilder;
16+
use CodeIgniter\Database\RawSql;
1617
use CodeIgniter\Database\SQLSRV\Builder as SQLSRVBuilder;
1718
use CodeIgniter\Test\CIUnitTestCase;
1819
use CodeIgniter\Test\Mock\MockConnection;
@@ -75,6 +76,28 @@ public function testJoinMultipleConditions()
7576
$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
7677
}
7778

79+
/**
80+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/3832
81+
*/
82+
public function testJoinRawSql()
83+
{
84+
$builder = new BaseBuilder('device', $this->db);
85+
86+
$sql = 'user.id = device.user_id
87+
AND (
88+
(1=1 OR 1=1)
89+
OR
90+
(1=1 OR 1=1)
91+
)';
92+
$builder->join('user', new RawSql($sql), 'LEFT');
93+
94+
$expectedSQL = 'SELECT * FROM "device" LEFT JOIN "user" ON user.id = device.user_id AND ( (1=1 OR 1=1) OR (1=1 OR 1=1) )';
95+
96+
$output = str_replace("\n", ' ', $builder->getCompiledSelect());
97+
$output = preg_replace('/\s+/', ' ', $output);
98+
$this->assertSame($expectedSQL, $output);
99+
}
100+
78101
public function testFullOuterJoin()
79102
{
80103
$builder = new PostgreBuilder('jobs', $this->db);

user_guide_src/source/changelogs/v4.2.0.rst

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ Release Date: Not Released
1212
BREAKING
1313
********
1414

15-
- The method signature of ``Validation::setRule()`` has been changed. The ``string`` typehint on the ``$rules`` parameter was removed. Extending classes should likewise remove the parameter so as not to break LSP.
15+
- The method signature of ``Validation::setRule()`` has been changed.
16+
- The method signature of ``CodeIgniter\Database\BaseBuilder::join()`` and ``CodeIgniter\Database\*\Builder::join()`` have been changed.
17+
- The method signature of ``CodeIgniter\CLI\CommandRunner::_remap()`` has been changed to fix a bug.
1618
- The ``CodeIgniter\CodeIgniter`` class has a new property ``$context`` and it must have the correct context at runtime. So the following files have been changed:
1719
- ``public/index.php``
1820
- ``spark``
@@ -59,6 +61,18 @@ Commands
5961
- ``spark db:table my_table --metadata``
6062
- The ``spark routes`` command now shows closure routes, auto routes, and filters. See :ref:`URI Routing <spark-routes>`.
6163

64+
Database
65+
========
66+
67+
- Added Subqueries in the FROM section. See :ref:`query-builder-from-subquery`.
68+
- Added Subqueries in the SELECT section. See :ref:`query-builder-select`.
69+
- The BaseBuilder::buildSubquery() method can take an optional third argument ``string $alias``.
70+
- Added new OCI8 driver for database.
71+
- It can access Oracle Database and supports SQL and PL/SQL statements.
72+
- QueryBuilder raw SQL string support
73+
- Added the class ``CodeIgniter\Database\RawSql`` which expresses raw SQL strings.
74+
- :ref:`select() <query-builder-select-rawsql>`, :ref:`where() <query-builder-where-rawsql>`, :ref:`like() <query-builder-like-rawsql>`, :ref:`join() <query-builder-join-rawsql>` accept the ``CodeIgniter\Database\RawSql`` instance.
75+
6276
Others
6377
======
6478

@@ -68,21 +82,14 @@ Others
6882
- Added the functions ``csp_script_nonce()`` and ``csp_style_nonce()`` to get nonce attributes
6983
- See :ref:`content-security-policy` for details.
7084
- New :doc:`../outgoing/view_decorators` allow modifying the generated HTML prior to caching.
71-
- Added Subqueries in the FROM section. See :ref:`query-builder-from-subquery`.
72-
- Added Subqueries in the SELECT section. See :ref:`query-builder-select`.
73-
- The BaseBuilder::buildSubquery() method can take an optional third argument ``string $alias``.
7485
- Added Validation Strict Rules. See :ref:`validation-traditional-and-strict-rules`.
75-
- Added new OCI8 driver for database.
76-
- It can access Oracle Database and supports SQL and PL/SQL statements.
86+
- The ``spark routes`` command now shows closure routes, auto routes, and filters. See :ref:`URI Routing <spark-routes>`.
7787
- Exception information logged through ``log_message()`` has now improved. It now includes the file and line where the exception originated. It also does not truncate the message anymore.
7888
- The log format has also changed. If users are depending on the log format in their apps, the new log format is "<1-based count> <cleaned filepath>(<line>): <class><function><args>"
7989
- Added support for webp files to **app/Config/Mimes.php**.
8090
- Added 4th parameter ``$includeDir`` to ``get_filenames()``. See :php:func:`get_filenames`.
8191
- HTML helper ``script_tag()`` now uses ``null`` values to write boolean attributes in minimized form: ``<script src="..." defer />``. See the sample code for :php:func:`script_tag`.
8292
- RouteCollection::addRedirect() can now use placeholders.
83-
- QueryBuilder raw SQL string support
84-
- Added the class ``CodeIgniter\Database\RawSql`` which expresses raw SQL strings.
85-
- :ref:`select() <query-builder-select-rawsql>`, :ref:`where() <query-builder-where-rawsql>`, :ref:`like() <query-builder-like-rawsql>` accept the ``CodeIgniter\Database\RawSql`` instance.
8693
- Debugbar enhancements
8794
- Debug toolbar is now using ``microtime()`` instead of ``time()``.
8895

user_guide_src/source/database/query_builder.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@ outer``, and ``right outer``.
234234

235235
.. literalinclude:: query_builder/020.php
236236

237+
.. _query-builder-join-rawsql:
238+
239+
RawSql
240+
^^^^^^
241+
242+
Since v4.2.0, ``$builder->join()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings.
243+
244+
.. literalinclude:: query_builder/102.php
245+
246+
.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
247+
237248
*************************
238249
Looking for Specific Data
239250
*************************
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
use CodeIgniter\Database\RawSql;
4+
5+
$sql = 'user.id = device.user_id AND ((1=1 OR 1=1) OR (1=1 OR 1=1))';
6+
$builder->join('user', new RawSql($sql), 'LEFT');
7+
// Produces: LEFT JOIN "user" ON user.id = device.user_id AND ((1=1 OR 1=1) OR (1=1 OR 1=1))

user_guide_src/source/installation/upgrade_420.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ Breaking Changes
3737
Breaking Enhancements
3838
*********************
3939

40-
none.
40+
- The method signature of ``Validation::setRule()`` has been changed. The ``string`` typehint on the ``$rules`` parameter was removed. Extending classes should likewise remove the parameter so as not to break LSP.
41+
- The method signature of ``CodeIgniter\Database\BaseBuilder::join()`` and ``CodeIgniter\Database\*\Builder::join()`` have been changed. The ``string`` typehint on the ``$cond`` parameter was removed. Extending classes should likewise remove the parameter so as not to break LSP.
4142

4243
Project Files
4344
*************

0 commit comments

Comments
 (0)