From 6000d30f4a9fa5e370096698ba653f06aad10783 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 7 Apr 2022 19:06:18 +0900 Subject: [PATCH 1/5] feat: join() supports RawSql --- system/Database/BaseBuilder.php | 20 +++++++++++++------ system/Database/Postgre/Builder.php | 5 ++++- system/Database/SQLSRV/Builder.php | 5 ++++- tests/system/Database/Builder/JoinTest.php | 23 ++++++++++++++++++++++ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index be1aee7e1a60..24939d03e607 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); From c656564ddec0b946f7ac6cec75c245bfab084296 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 4 May 2022 15:49:21 +0900 Subject: [PATCH 2/5] docs: add section for database in Enhancements --- user_guide_src/source/changelogs/v4.2.0.rst | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst index 32129c30c4eb..c65c6f3959e9 100644 --- a/user_guide_src/source/changelogs/v4.2.0.rst +++ b/user_guide_src/source/changelogs/v4.2.0.rst @@ -58,6 +58,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() ` accept the ``CodeIgniter\Database\RawSql`` instance. + Others ====== @@ -67,21 +79,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: ``