diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index b26b8484b13f..818d4d31deda 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -579,9 +579,11 @@ public function fromSubquery(BaseBuilder $from, string $alias): self /** * Generates the JOIN portion of the query * + * @param RawSql|string $cond + * * @return $this */ - public function join(string $table, string $cond, string $type = '', ?bool $escape = null) + public function join(string $table, $cond, string $type = '', ?bool $escape = null) { if ($type !== '') { $type = strtoupper(trim($type)); @@ -601,6 +603,17 @@ public function join(string $table, string $cond, string $type = '', ?bool $esca $escape = $this->db->protectIdentifiers; } + // Do we want to escape the table name? + if ($escape === true) { + $table = $this->db->protectIdentifiers($table, true, null, false); + } + + if ($cond instanceof RawSql) { + $this->QBJoin[] = $type . 'JOIN ' . $table . ' ON ' . $cond; + + return $this; + } + if (! $this->hasOperator($cond)) { $cond = ' USING (' . ($escape ? $this->db->escapeIdentifiers($cond) : $cond) . ')'; } elseif ($escape === false) { @@ -634,11 +647,6 @@ public function join(string $table, string $cond, string $type = '', ?bool $esca } } - // Do we want to escape the table name? - if ($escape === true) { - $table = $this->db->protectIdentifiers($table, true, null, false); - } - // Assemble the JOIN statement $this->QBJoin[] = $type . 'JOIN ' . $table . $cond; diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 80e9f278cf50..1d80f687568c 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -13,6 +13,7 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Database\RawSql; /** * Builder for Postgre @@ -300,9 +301,11 @@ protected function _like_statement(?string $prefix, string $column, ?string $not /** * Generates the JOIN portion of the query * + * @param RawSql|string $cond + * * @return BaseBuilder */ - public function join(string $table, string $cond, string $type = '', ?bool $escape = null) + public function join(string $table, $cond, string $type = '', ?bool $escape = null) { if (! in_array('FULL OUTER', $this->joinTypes, true)) { $this->joinTypes = array_merge($this->joinTypes, ['FULL OUTER']); diff --git a/system/Database/SQLSRV/Builder.php b/system/Database/SQLSRV/Builder.php index 55da29b7bf85..f84d1dfddd07 100755 --- a/system/Database/SQLSRV/Builder.php +++ b/system/Database/SQLSRV/Builder.php @@ -14,6 +14,7 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Exceptions\DataException; +use CodeIgniter\Database\RawSql; use CodeIgniter\Database\ResultInterface; /** @@ -88,9 +89,11 @@ protected function _truncate(string $table): string /** * Generates the JOIN portion of the query * + * @param RawSql|string $cond + * * @return $this */ - public function join(string $table, string $cond, string $type = '', ?bool $escape = null) + public function join(string $table, $cond, string $type = '', ?bool $escape = null) { if ($type !== '') { $type = strtoupper(trim($type)); diff --git a/tests/system/Database/Builder/JoinTest.php b/tests/system/Database/Builder/JoinTest.php index 7ebb188adf6d..59857c25ab98 100644 --- a/tests/system/Database/Builder/JoinTest.php +++ b/tests/system/Database/Builder/JoinTest.php @@ -13,6 +13,7 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Postgre\Builder as PostgreBuilder; +use CodeIgniter\Database\RawSql; use CodeIgniter\Database\SQLSRV\Builder as SQLSRVBuilder; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Mock\MockConnection; @@ -75,6 +76,28 @@ public function testJoinMultipleConditions() $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); } + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/3832 + */ + public function testJoinRawSql() + { + $builder = new BaseBuilder('device', $this->db); + + $sql = 'user.id = device.user_id + AND ( + (1=1 OR 1=1) + OR + (1=1 OR 1=1) + )'; + $builder->join('user', new RawSql($sql), 'LEFT'); + + $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) )'; + + $output = str_replace("\n", ' ', $builder->getCompiledSelect()); + $output = preg_replace('/\s+/', ' ', $output); + $this->assertSame($expectedSQL, $output); + } + public function testFullOuterJoin() { $builder = new PostgreBuilder('jobs', $this->db); diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst index b314dd892794..b1ccd3384ca4 100644 --- a/user_guide_src/source/changelogs/v4.2.0.rst +++ b/user_guide_src/source/changelogs/v4.2.0.rst @@ -12,7 +12,9 @@ Release Date: Not Released BREAKING ******** -- 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. +- The method signature of ``Validation::setRule()`` has been changed. +- The method signature of ``CodeIgniter\Database\BaseBuilder::join()`` and ``CodeIgniter\Database\*\Builder::join()`` have been changed. +- The method signature of ``CodeIgniter\CLI\CommandRunner::_remap()`` has been changed to fix a bug. - 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: - ``public/index.php`` - ``spark`` @@ -59,6 +61,18 @@ Commands - ``spark db:table my_table --metadata`` - The ``spark routes`` command now shows closure routes, auto routes, and filters. See :ref:`URI Routing `. +Database +======== + +- Added Subqueries in the FROM section. See :ref:`query-builder-from-subquery`. +- Added Subqueries in the SELECT section. See :ref:`query-builder-select`. +- The BaseBuilder::buildSubquery() method can take an optional third argument ``string $alias``. +- Added new OCI8 driver for database. + - It can access Oracle Database and supports SQL and PL/SQL statements. +- QueryBuilder raw SQL string support + - Added the class ``CodeIgniter\Database\RawSql`` which expresses raw SQL strings. + - :ref:`select() `, :ref:`where() `, :ref:`like() `, :ref:`join() ` accept the ``CodeIgniter\Database\RawSql`` instance. + Others ====== @@ -68,21 +82,14 @@ Others - Added the functions ``csp_script_nonce()`` and ``csp_style_nonce()`` to get nonce attributes - See :ref:`content-security-policy` for details. - New :doc:`../outgoing/view_decorators` allow modifying the generated HTML prior to caching. -- Added Subqueries in the FROM section. See :ref:`query-builder-from-subquery`. -- Added Subqueries in the SELECT section. See :ref:`query-builder-select`. -- The BaseBuilder::buildSubquery() method can take an optional third argument ``string $alias``. - Added Validation Strict Rules. See :ref:`validation-traditional-and-strict-rules`. -- Added new OCI8 driver for database. - - It can access Oracle Database and supports SQL and PL/SQL statements. +- The ``spark routes`` command now shows closure routes, auto routes, and filters. See :ref:`URI Routing `. - 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. - 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> (): " - Added support for webp files to **app/Config/Mimes.php**. - Added 4th parameter ``$includeDir`` to ``get_filenames()``. See :php:func:`get_filenames`. - HTML helper ``script_tag()`` now uses ``null`` values to write boolean attributes in minimized form: ``