Skip to content

Commit

Permalink
Merge pull request #5875 from kenjis/feat-qb-raw-sql-join
Browse files Browse the repository at this point in the history
feat: QueryBuilder join() raw SQL string support
  • Loading branch information
kenjis committed May 24, 2022
2 parents ca74b82 + ae40b35 commit daff2d7
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 18 deletions.
20 changes: 14 additions & 6 deletions system/Database/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down
5 changes: 4 additions & 1 deletion system/Database/Postgre/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;

/**
* Builder for Postgre
Expand Down Expand Up @@ -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']);
Expand Down
5 changes: 4 additions & 1 deletion system/Database/SQLSRV/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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));
Expand Down
23 changes: 23 additions & 0 deletions tests/system/Database/Builder/JoinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 16 additions & 9 deletions user_guide_src/source/changelogs/v4.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down Expand Up @@ -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 <spark-routes>`.

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() <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.

Others
======

Expand All @@ -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 <spark-routes>`.
- 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> <cleaned filepath>(<line>): <class><function><args>"
- 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: ``<script src="..." defer />``. See the sample code for :php:func:`script_tag`.
- RouteCollection::addRedirect() can now use placeholders.
- QueryBuilder raw SQL string support
- Added the class ``CodeIgniter\Database\RawSql`` which expresses raw SQL strings.
- :ref:`select() <query-builder-select-rawsql>`, :ref:`where() <query-builder-where-rawsql>`, :ref:`like() <query-builder-like-rawsql>` accept the ``CodeIgniter\Database\RawSql`` instance.
- Debugbar enhancements
- Debug toolbar is now using ``microtime()`` instead of ``time()``.

Expand Down
11 changes: 11 additions & 0 deletions user_guide_src/source/database/query_builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@ outer``, and ``right outer``.

.. literalinclude:: query_builder/020.php

.. _query-builder-join-rawsql:

RawSql
^^^^^^

Since v4.2.0, ``$builder->join()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings.

.. literalinclude:: query_builder/102.php

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

*************************
Looking for Specific Data
*************************
Expand Down
7 changes: 7 additions & 0 deletions user_guide_src/source/database/query_builder/102.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use CodeIgniter\Database\RawSql;

$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');
// Produces: LEFT JOIN "user" ON user.id = device.user_id AND ((1=1 OR 1=1) OR (1=1 OR 1=1))
3 changes: 2 additions & 1 deletion user_guide_src/source/installation/upgrade_420.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Breaking Changes
Breaking Enhancements
*********************

none.
- 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 ``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.

Project Files
*************
Expand Down

0 comments on commit daff2d7

Please sign in to comment.