diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php
index 1d0c241903e8..be1aee7e1a60 100644
--- a/system/Database/BaseBuilder.php
+++ b/system/Database/BaseBuilder.php
@@ -356,7 +356,7 @@ public function ignore(bool $ignore = true)
/**
* Generates the SELECT portion of the query
*
- * @param array|string $select
+ * @param array|RawSql|string $select
*
* @return $this
*/
@@ -371,6 +371,12 @@ public function select($select = '*', ?bool $escape = null)
$escape = $this->db->protectIdentifiers;
}
+ if ($select instanceof RawSql) {
+ $this->QBSelect[] = $select;
+
+ return $this;
+ }
+
foreach ($select as $val) {
$val = trim($val);
@@ -643,8 +649,8 @@ public function join(string $table, string $cond, string $type = '', ?bool $esca
* Generates the WHERE portion of the query.
* Separates multiple calls with 'AND'.
*
- * @param mixed $key
- * @param mixed $value
+ * @param array|RawSql|string $key
+ * @param mixed $value
*
* @return $this
*/
@@ -659,9 +665,9 @@ public function where($key, $value = null, ?bool $escape = null)
* Generates the WHERE portion of the query.
* Separates multiple calls with 'OR'.
*
- * @param mixed $key
- * @param mixed $value
- * @param bool $escape
+ * @param array|RawSql|string $key
+ * @param mixed $value
+ * @param bool $escape
*
* @return $this
*/
@@ -676,15 +682,20 @@ public function orWhere($key, $value = null, ?bool $escape = null)
* @used-by having()
* @used-by orHaving()
*
- * @param mixed $key
- * @param mixed $value
+ * @param array|RawSql|string $key
+ * @param mixed $value
*
* @return $this
*/
protected function whereHaving(string $qbKey, $key, $value = null, string $type = 'AND ', ?bool $escape = null)
{
- if (! is_array($key)) {
- $key = [$key => $value];
+ if ($key instanceof RawSql) {
+ $keyValue = [(string) $key => $key];
+ $escape = false;
+ } elseif (! is_array($key)) {
+ $keyValue = [$key => $value];
+ } else {
+ $keyValue = $key;
}
// If the escape value was not set will base it on the global setting
@@ -692,10 +703,13 @@ protected function whereHaving(string $qbKey, $key, $value = null, string $type
$escape = $this->db->protectIdentifiers;
}
- foreach ($key as $k => $v) {
+ foreach ($keyValue as $k => $v) {
$prefix = empty($this->{$qbKey}) ? $this->groupGetType('') : $this->groupGetType($type);
- if ($v !== null) {
+ if ($v instanceof RawSql) {
+ $k = '';
+ $op = '';
+ } elseif ($v !== null) {
$op = $this->getOperator($k, true);
if (! empty($op)) {
@@ -731,10 +745,17 @@ protected function whereHaving(string $qbKey, $key, $value = null, string $type
$op = '';
}
- $this->{$qbKey}[] = [
- 'condition' => $prefix . $k . $op . $v,
- 'escape' => $escape,
- ];
+ if ($v instanceof RawSql) {
+ $this->{$qbKey}[] = [
+ 'condition' => $v->with($prefix . $k . $op . $v),
+ 'escape' => $escape,
+ ];
+ } else {
+ $this->{$qbKey}[] = [
+ 'condition' => $prefix . $k . $op . $v,
+ 'escape' => $escape,
+ ];
+ }
}
return $this;
@@ -911,7 +932,7 @@ protected function _whereIn(?string $key = null, $values = null, bool $not = fal
* Generates a %LIKE% portion of the query.
* Separates multiple calls with 'AND'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -924,7 +945,7 @@ public function like($field, string $match = '', string $side = 'both', ?bool $e
* Generates a NOT LIKE portion of the query.
* Separates multiple calls with 'AND'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -937,7 +958,7 @@ public function notLike($field, string $match = '', string $side = 'both', ?bool
* Generates a %LIKE% portion of the query.
* Separates multiple calls with 'OR'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -950,7 +971,7 @@ public function orLike($field, string $match = '', string $side = 'both', ?bool
* Generates a NOT LIKE portion of the query.
* Separates multiple calls with 'OR'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -963,7 +984,7 @@ public function orNotLike($field, string $match = '', string $side = 'both', ?bo
* Generates a %LIKE% portion of the query.
* Separates multiple calls with 'AND'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -976,7 +997,7 @@ public function havingLike($field, string $match = '', string $side = 'both', ?b
* Generates a NOT LIKE portion of the query.
* Separates multiple calls with 'AND'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -989,7 +1010,7 @@ public function notHavingLike($field, string $match = '', string $side = 'both',
* Generates a %LIKE% portion of the query.
* Separates multiple calls with 'OR'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -1002,7 +1023,7 @@ public function orHavingLike($field, string $match = '', string $side = 'both',
* Generates a NOT LIKE portion of the query.
* Separates multiple calls with 'OR'.
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
@@ -1021,20 +1042,50 @@ public function orNotHavingLike($field, string $match = '', string $side = 'both
* @used-by notHavingLike()
* @used-by orNotHavingLike()
*
- * @param mixed $field
+ * @param array|RawSql|string $field
*
* @return $this
*/
protected function _like($field, string $match = '', string $type = 'AND ', string $side = 'both', string $not = '', ?bool $escape = null, bool $insensitiveSearch = false, string $clause = 'QBWhere')
{
- if (! is_array($field)) {
- $field = [$field => $match];
- }
-
$escape = is_bool($escape) ? $escape : $this->db->protectIdentifiers;
$side = strtolower($side);
- foreach ($field as $k => $v) {
+ if ($field instanceof RawSql) {
+ $k = (string) $field;
+ $v = $match;
+ $insensitiveSearch = false;
+
+ $prefix = empty($this->{$clause}) ? $this->groupGetType('') : $this->groupGetType($type);
+
+ if ($side === 'none') {
+ $bind = $this->setBind($field->getBindingKey(), $v, $escape);
+ } elseif ($side === 'before') {
+ $bind = $this->setBind($field->getBindingKey(), "%{$v}", $escape);
+ } elseif ($side === 'after') {
+ $bind = $this->setBind($field->getBindingKey(), "{$v}%", $escape);
+ } else {
+ $bind = $this->setBind($field->getBindingKey(), "%{$v}%", $escape);
+ }
+
+ $likeStatement = $this->_like_statement($prefix, $k, $not, $bind, $insensitiveSearch);
+
+ // some platforms require an escape sequence definition for LIKE wildcards
+ if ($escape === true && $this->db->likeEscapeStr !== '') {
+ $likeStatement .= sprintf($this->db->likeEscapeStr, $this->db->likeEscapeChar);
+ }
+
+ $this->{$clause}[] = [
+ 'condition' => $field->with($likeStatement),
+ 'escape' => $escape,
+ ];
+
+ return $this;
+ }
+
+ $keyValue = ! is_array($field) ? [$field => $match] : $field;
+
+ foreach ($keyValue as $k => $v) {
if ($insensitiveSearch === true) {
$v = strtolower($v);
}
@@ -1269,8 +1320,8 @@ public function groupBy($by, ?bool $escape = null)
/**
* Separates multiple calls with 'AND'.
*
- * @param array|string $key
- * @param mixed $value
+ * @param array|RawSql|string $key
+ * @param mixed $value
*
* @return $this
*/
@@ -1282,8 +1333,8 @@ public function having($key, $value = null, ?bool $escape = null)
/**
* Separates multiple calls with 'OR'.
*
- * @param array|string $key
- * @param mixed $value
+ * @param array|RawSql|string $key
+ * @param mixed $value
*
* @return $this
*/
@@ -2339,6 +2390,8 @@ protected function compileSelect($selectOverride = false): string
if (empty($this->QBSelect)) {
$sql .= '*';
+ } elseif ($this->QBSelect[0] instanceof RawSql) {
+ $sql .= (string) $this->QBSelect[0];
} else {
// Cycle through the "select" portion of the query and prep each column name.
// The reason we protect identifiers here rather than in the select() function
@@ -2407,6 +2460,12 @@ protected function compileWhereHaving(string $qbKey): string
continue;
}
+ if ($qbkey['condition'] instanceof RawSql) {
+ $qbkey = $qbkey['condition'];
+
+ continue;
+ }
+
if ($qbkey['escape'] === false) {
$qbkey = $qbkey['condition'];
diff --git a/system/Database/RawSql.php b/system/Database/RawSql.php
new file mode 100644
index 000000000000..7ecb7fd378ae
--- /dev/null
+++ b/system/Database/RawSql.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace CodeIgniter\Database;
+
+class RawSql
+{
+ /**
+ * @var string Raw SQL string
+ */
+ private string $string;
+
+ public function __construct(string $sqlString)
+ {
+ $this->string = $sqlString;
+ }
+
+ public function __toString(): string
+ {
+ return $this->string;
+ }
+
+ /**
+ * Create new instance with new SQL string
+ */
+ public function with(string $newSqlString): self
+ {
+ $new = clone $this;
+ $new->string = $newSqlString;
+
+ return $new;
+ }
+
+ /**
+ * Returns unique id for binding key
+ */
+ public function getBindingKey(): string
+ {
+ return 'RawSql' . spl_object_id($this);
+ }
+}
diff --git a/tests/system/Database/Builder/LikeTest.php b/tests/system/Database/Builder/LikeTest.php
index 2d81db2b90fa..22afc5c7549c 100644
--- a/tests/system/Database/Builder/LikeTest.php
+++ b/tests/system/Database/Builder/LikeTest.php
@@ -12,6 +12,7 @@
namespace CodeIgniter\Database\Builder;
use CodeIgniter\Database\BaseBuilder;
+use CodeIgniter\Database\RawSql;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;
@@ -47,6 +48,29 @@ public function testSimpleLike()
$this->assertSame($expectedBinds, $builder->getBinds());
}
+ /**
+ * @see https://github.com/codeigniter4/CodeIgniter4/issues/3970
+ */
+ public function testLikeWithRawSql()
+ {
+ $builder = new BaseBuilder('users', $this->db);
+
+ $sql = "concat(users.name, ' ', IF(users.surname IS NULL or users.surname = '', '', users.surname))";
+ $rawSql = new RawSql($sql);
+ $builder->like($rawSql, 'value', 'both');
+
+ $expectedSQL = "SELECT * FROM \"users\" WHERE {$sql} LIKE '%value%' ESCAPE '!' ";
+ $expectedBinds = [
+ $rawSql->getBindingKey() => [
+ '%value%',
+ true,
+ ],
+ ];
+
+ $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
+ $this->assertSame($expectedBinds, $builder->getBinds());
+ }
+
public function testLikeNoSide()
{
$builder = new BaseBuilder('job', $this->db);
diff --git a/tests/system/Database/Builder/SelectTest.php b/tests/system/Database/Builder/SelectTest.php
index 97460f0010da..808e581d6ce5 100644
--- a/tests/system/Database/Builder/SelectTest.php
+++ b/tests/system/Database/Builder/SelectTest.php
@@ -13,6 +13,7 @@
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DataException;
+use CodeIgniter\Database\RawSql;
use CodeIgniter\Database\SQLSRV\Builder as SQLSRVBuilder;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;
@@ -95,6 +96,20 @@ public function testSelectWorksWithComplexSelects()
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
}
+ /**
+ * @see https://github.com/codeigniter4/CodeIgniter4/issues/4355
+ */
+ public function testSelectWorksWithRawSql()
+ {
+ $builder = new BaseBuilder('users', $this->db);
+
+ $sql = 'REGEXP_SUBSTR(ral_anno,"[0-9]{1,2}([,.][0-9]{1,3})([,.][0-9]{1,3})") AS ral';
+ $builder->select(new RawSql($sql));
+
+ $expected = 'SELECT ' . $sql . ' FROM "users"';
+ $this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
+ }
+
public function testSelectMinWithNoAlias()
{
$builder = new BaseBuilder('invoices', $this->db);
diff --git a/tests/system/Database/Builder/WhereTest.php b/tests/system/Database/Builder/WhereTest.php
index ace628f6bc38..a10f20e8545d 100644
--- a/tests/system/Database/Builder/WhereTest.php
+++ b/tests/system/Database/Builder/WhereTest.php
@@ -12,6 +12,7 @@
namespace CodeIgniter\Database\Builder;
use CodeIgniter\Database\BaseBuilder;
+use CodeIgniter\Database\RawSql;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;
use stdClass;
@@ -140,6 +141,20 @@ public function testWhereCustomString()
$this->assertSame($expectedBinds, $builder->getBinds());
}
+ public function testWhereRawSql()
+ {
+ $builder = $this->db->table('jobs');
+
+ $sql = "id > 2 AND name != 'Accountant'";
+ $builder->where(new RawSql($sql));
+
+ $expectedSQL = "SELECT * FROM \"jobs\" WHERE id > 2 AND name != 'Accountant'";
+ $expectedBinds = [];
+
+ $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
+ $this->assertSame($expectedBinds, $builder->getBinds());
+ }
+
public function testWhereValueSubQuery()
{
$expectedSQL = 'SELECT * FROM "neworder" WHERE "advance_amount" < (SELECT MAX(advance_amount) FROM "orders" WHERE "id" > 2)';
diff --git a/tests/system/Database/RawSqlTest.php b/tests/system/Database/RawSqlTest.php
new file mode 100644
index 000000000000..a271486141a7
--- /dev/null
+++ b/tests/system/Database/RawSqlTest.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace CodeIgniter\Database;
+
+use CodeIgniter\Test\CIUnitTestCase;
+
+/**
+ * @internal
+ */
+final class RawSqlTest extends CIUnitTestCase
+{
+ public function testCanConvertToString()
+ {
+ $expected = 'REGEXP_SUBSTR(ral_anno,"[0-9]{1,2}([,.][0-9]{1,3})([,.][0-9]{1,3})") AS ral';
+ $rawSql = new RawSql($expected);
+
+ $this->assertSame($expected, (string) $rawSql);
+ }
+
+ public function testCanCreateNewObject()
+ {
+ $firstSql = 'a = 1 AND b = 2';
+ $rawSql = new RawSql($firstSql);
+
+ $secondSql = 'a = 1 AND b = 2 OR c = 3';
+ $newRawSQL = $rawSql->with($secondSql);
+
+ $this->assertSame($firstSql, (string) $rawSql);
+ $this->assertSame($secondSql, (string) $newRawSQL);
+ }
+
+ public function testGetBindingKey()
+ {
+ $firstSql = 'a = 1 AND b = 2';
+ $rawSql = new RawSql($firstSql);
+
+ $key = $rawSql->getBindingKey();
+
+ $this->assertMatchesRegularExpression('/\ARawSql[0-9]+\z/', $key);
+ }
+}
diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst
index 14e63e6990cb..04b42eff6f04 100644
--- a/user_guide_src/source/changelogs/v4.2.0.rst
+++ b/user_guide_src/source/changelogs/v4.2.0.rst
@@ -64,7 +64,9 @@ Others
- 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: ````. 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() `, :ref:`where() `, :ref:`like() ` accept the ``CodeIgniter\Database\RawSql`` instance.
Changes
*******
diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst
index 5e05f0ce6b0b..a0094d0b98a2 100755
--- a/user_guide_src/source/database/query_builder.rst
+++ b/user_guide_src/source/database/query_builder.rst
@@ -41,7 +41,8 @@ The following methods allow you to build SQL **SELECT** statements.
Get
===
-**$builder->get()**
+$builder->get()
+---------------
Runs the selection query and returns the result. Can be used by itself
to retrieve all records from a table:
@@ -61,7 +62,8 @@ $query, which can be used to show the results:
Please visit the :doc:`getResult*() method ` page for a full
discussion regarding result generation.
-**$builder->getCompiledSelect()**
+$builder->getCompiledSelect()
+-----------------------------
Compiles the selection query just like ``$builder->get()`` but does not *run*
the query. This method simply returns the SQL query as a string.
@@ -81,7 +83,8 @@ parameter. The reason for this outcome is because the query has not been
executed using ``$builder->get()`` which resets values or reset directly
using ``$builder->resetQuery()``.
-**$builder->getWhere()**
+$builder->getWhere()
+--------------------
Identical to the ``get()`` method except that it permits you to add a
"where" clause in the first parameter, instead of using the ``$builder->where()``
@@ -96,7 +99,8 @@ Please read about the ``where()`` method below for more information.
Select
======
-**$builder->select()**
+$builder->select()
+------------------
Permits you to write the **SELECT** portion of your query:
@@ -113,14 +117,27 @@ escaping of fields may break them.
.. literalinclude:: query_builder/009.php
-**$builder->selectMax()**
+.. _query-builder-select-rawsql:
+
+RawSql
+^^^^^^
+
+Since v4.2.0, ``$builder->select()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings.
+
+.. literalinclude:: query_builder/099.php
+
+.. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
+
+$builder->selectMax()
+---------------------
Writes a **SELECT MAX(field)** portion for your query. You can optionally
include a second parameter to rename the resulting field.
.. literalinclude:: query_builder/010.php
-**$builder->selectMin()**
+$builder->selectMin()
+---------------------
Writes a **SELECT MIN(field)** portion for your query. As with
``selectMax()``, You can optionally include a second parameter to rename
@@ -128,7 +145,8 @@ the resulting field.
.. literalinclude:: query_builder/011.php
-**$builder->selectAvg()**
+$builder->selectAvg()
+---------------------
Writes a **SELECT AVG(field)** portion for your query. As with
``selectMax()``, You can optionally include a second parameter to rename
@@ -136,7 +154,8 @@ the resulting field.
.. literalinclude:: query_builder/012.php
-**$builder->selectSum()**
+$builder->selectSum()
+---------------------
Writes a **SELECT SUM(field)** portion for your query. As with
``selectMax()``, You can optionally include a second parameter to rename
@@ -144,7 +163,8 @@ the resulting field.
.. literalinclude:: query_builder/013.php
-**$builder->selectCount()**
+$builder->selectCount()
+-----------------------
Writes a **SELECT COUNT(field)** portion for your query. As with
``selectMax()``, You can optionally include a second parameter to rename
@@ -155,7 +175,8 @@ the resulting field.
.. literalinclude:: query_builder/014.php
-**$builder->selectSubquery()**
+$builder->selectSubquery()
+--------------------------
Adds a subquery to the SELECT section.
@@ -165,7 +186,8 @@ Adds a subquery to the SELECT section.
From
====
-**$builder->from()**
+$builder->from()
+----------------
Permits you to write the **FROM** portion of your query:
@@ -180,7 +202,8 @@ Permits you to write the **FROM** portion of your query:
Subqueries
==========
-**$builder->fromSubquery()**
+$builder->fromSubquery()
+------------------------
Permits you to write part of a **FROM** query as a subquery.
@@ -195,7 +218,8 @@ Use the ``$db->newQuery()`` method to make a subquery the main table:
Join
====
-**$builder->join()**
+$builder->join()
+----------------
Permits you to write the **JOIN** portion of your query:
@@ -214,7 +238,11 @@ outer``, and ``right outer``.
Looking for Specific Data
*************************
-**$builder->where()**
+Where
+=====
+
+$builder->where()
+-----------------
This method enables you to set **WHERE** clauses using one of five
methods:
@@ -225,7 +253,8 @@ methods:
.. note:: ``$builder->where()`` accepts an optional third parameter. If you set it to
``false``, CodeIgniter will not try to protect your field or table names.
-#. **Simple key/value method:**
+1. Simple key/value method
+^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: query_builder/021.php
@@ -236,14 +265,16 @@ methods:
.. literalinclude:: query_builder/022.php
-#. **Custom key/value method:**
+2. Custom key/value method
+^^^^^^^^^^^^^^^^^^^^^^^^^^
You can include an operator in the first parameter in order to
control the comparison:
.. literalinclude:: query_builder/023.php
-#. **Associative array method:**
+3. Associative array method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: query_builder/024.php
@@ -251,7 +282,8 @@ methods:
.. literalinclude:: query_builder/025.php
-#. **Custom string:**
+4. Custom string
+^^^^^^^^^^^^^^^^
You can write your own clauses manually:
@@ -262,20 +294,34 @@ methods:
.. literalinclude:: query_builder/027.php
+.. _query-builder-where-rawsql:
+
+5. RawSql
+^^^^^^^^^
+
+ Since v4.2.0, ``$builder->where()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings.
+
+ .. literalinclude:: query_builder/100.php
+
+ .. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
+
.. _query-builder-where-subquery:
-5. **Subqueries:**
+6. Subqueries
+^^^^^^^^^^^^^
.. literalinclude:: query_builder/028.php
-**$builder->orWhere()**
+$builder->orWhere()
+-------------------
This method is identical to the one above, except that multiple
instances are joined by **OR**:
.. literalinclude:: query_builder/029.php
-**$builder->whereIn()**
+$builder->whereIn()
+-------------------
Generates a **WHERE** field IN ('item', 'item') SQL query joined with **AND** if
appropriate:
@@ -286,7 +332,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/031.php
-**$builder->orWhereIn()**
+$builder->orWhereIn()
+---------------------
Generates a **WHERE field IN ('item', 'item')** SQL query joined with **OR** if
appropriate:
@@ -297,7 +344,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/033.php
-**$builder->whereNotIn()**
+$builder->whereNotIn()
+----------------------
Generates a **WHERE field NOT IN ('item', 'item')** SQL query joined with
**AND** if appropriate:
@@ -308,7 +356,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/035.php
-**$builder->orWhereNotIn()**
+$builder->orWhereNotIn()
+------------------------
Generates a **WHERE field NOT IN ('item', 'item')** SQL query joined with **OR**
if appropriate:
@@ -323,7 +372,11 @@ You can use subqueries instead of an array of values:
Looking for Similar Data
************************
-**$builder->like()**
+Like
+====
+
+$builder->like()
+----------------
This method enables you to generate **LIKE** clauses, useful for doing
searches.
@@ -335,7 +388,8 @@ searches.
otherwise, will force the values to be lowercase, i.e., ``WHERE LOWER(column) LIKE '%search%'``. This
may require indexes to be made for ``LOWER(column)`` instead of ``column`` to be effective.
-#. **Simple key/value method:**
+1. Simple key/value method
+^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: query_builder/038.php
@@ -350,32 +404,48 @@ searches.
.. literalinclude:: query_builder/040.php
-#. **Associative array method:**
+2. Associative array method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: query_builder/041.php
-**$builder->orLike()**
+.. _query-builder-like-rawsql:
+
+3. RawSql
+^^^^^^^^^
+
+ Since v4.2.0, ``$builder->like()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings.
+
+ .. literalinclude:: query_builder/101.php
+
+ .. warning:: When you use ``RawSql``, you MUST escape the data manually. Failure to do so could result in SQL injections.
+
+$builder->orLike()
+------------------
This method is identical to the one above, except that multiple
instances are joined by **OR**:
.. literalinclude:: query_builder/042.php
-**$builder->notLike()**
+$builder->notLike()
+-------------------
This method is identical to ``like()``, except that it generates
**NOT LIKE** statements:
.. literalinclude:: query_builder/043.php
-**$builder->orNotLike()**
+$builder->orNotLike()
+---------------------
This method is identical to ``notLike()``, except that multiple
instances are joined by **OR**:
.. literalinclude:: query_builder/044.php
-**$builder->groupBy()**
+$builder->groupBy()
+-------------------
Permits you to write the **GROUP BY** portion of your query:
@@ -385,13 +455,15 @@ You can also pass an array of multiple values as well:
.. literalinclude:: query_builder/046.php
-**$builder->distinct()**
+$builder->distinct()
+--------------------
Adds the **DISTINCT** keyword to a query
.. literalinclude:: query_builder/047.php
-**$builder->having()**
+$builder->having()
+------------------
Permits you to write the **HAVING** portion of your query. There are 2
possible syntaxes, 1 argument or 2:
@@ -408,11 +480,13 @@ setting it to ``false``.
.. literalinclude:: query_builder/050.php
-**$builder->orHaving()**
+$builder->orHaving()
+--------------------
Identical to ``having()``, only separates multiple clauses with **OR**.
-**$builder->havingIn()**
+$builder->havingIn()
+--------------------
Generates a **HAVING field IN ('item', 'item')** SQL query joined with **AND** if
appropriate:
@@ -423,7 +497,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/052.php
-**$builder->orHavingIn()**
+$builder->orHavingIn()
+----------------------
Generates a **HAVING field IN ('item', 'item')** SQL query joined with **OR** if
appropriate
@@ -434,7 +509,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/054.php
-**$builder->havingNotIn()**
+$builder->havingNotIn()
+-----------------------
Generates a **HAVING field NOT IN ('item', 'item')** SQL query joined with
**AND** if appropriate
@@ -445,7 +521,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/056.php
-**$builder->orHavingNotIn()**
+$builder->orHavingNotIn()
+-------------------------
Generates a **HAVING field NOT IN ('item', 'item')** SQL query joined with **OR**
if appropriate
@@ -456,7 +533,8 @@ You can use subqueries instead of an array of values:
.. literalinclude:: query_builder/058.php
-**$builder->havingLike()**
+$builder->havingLike()
+----------------------
This method enables you to generate **LIKE** clauses for **HAVING** part or the query, useful for doing
searches.
@@ -468,7 +546,8 @@ searches.
otherwise, will force the values to be lowercase, i.e., ``HAVING LOWER(column) LIKE '%search%'``. This
may require indexes to be made for ``LOWER(column)`` instead of ``column`` to be effective.
-#. **Simple key/value method:**
+1. Simple key/value method
+^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: query_builder/059.php
@@ -483,25 +562,29 @@ searches.
.. literalinclude:: query_builder/061.php
-#. **Associative array method:**
+2. Associative array method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. literalinclude:: query_builder/062.php
-**$builder->orHavingLike()**
+$builder->orHavingLike()
+------------------------
This method is identical to the one above, except that multiple
instances are joined by **OR**:
.. literalinclude:: query_builder/063.php
-**$builder->notHavingLike()**
+$builder->notHavingLike()
+-------------------------
This method is identical to ``havingLike()``, except that it generates
**NOT LIKE** statements:
.. literalinclude:: query_builder/064.php
-**$builder->orNotHavingLike()**
+$builder->orNotHavingLike()
+---------------------------
This method is identical to ``notHavingLike()``, except that multiple
instances are joined by **OR**:
@@ -512,7 +595,11 @@ instances are joined by **OR**:
Ordering Results
****************
-**$builder->orderBy()**
+OrderBy
+=======
+
+$builder->orderBy()
+-------------------
Lets you set an **ORDER BY** clause.
@@ -540,7 +627,11 @@ be ignored, unless you specify a numeric seed value.
Limiting or Counting Results
****************************
-**$builder->limit()**
+Limit
+=====
+
+$builder->limit()
+-----------------
Lets you limit the number of rows you would like returned by the query:
@@ -550,7 +641,8 @@ The second parameter lets you set a result offset.
.. literalinclude:: query_builder/071.php
-**$builder->countAllResults()**
+$builder->countAllResults()
+---------------------------
Permits you to determine the number of rows in a particular Query
Builder query. Queries will accept Query Builder restrictors such as
@@ -564,7 +656,8 @@ first parameter.
.. literalinclude:: query_builder/073.php
-**$builder->countAll()**
+$builder->countAll()
+--------------------
Permits you to determine the number of rows in a particular table.
Example:
@@ -579,6 +672,9 @@ first parameter.
Query grouping
**************
+Group
+=====
+
Query grouping allows you to create groups of **WHERE** clauses by enclosing them in parentheses. This will allow
you to create queries with complex **WHERE** clauses. Nested groups are supported. Example:
@@ -586,43 +682,53 @@ you to create queries with complex **WHERE** clauses. Nested groups are supporte
.. note:: Groups need to be balanced, make sure every ``groupStart()`` is matched by a ``groupEnd()``.
-**$builder->groupStart()**
+$builder->groupStart()
+----------------------
Starts a new group by adding an opening parenthesis to the **WHERE** clause of the query.
-**$builder->orGroupStart()**
+$builder->orGroupStart()
+------------------------
Starts a new group by adding an opening parenthesis to the **WHERE** clause of the query, prefixing it with **OR**.
-**$builder->notGroupStart()**
+$builder->notGroupStart()
+-------------------------
Starts a new group by adding an opening parenthesis to the **WHERE** clause of the query, prefixing it with **NOT**.
-**$builder->orNotGroupStart()**
+$builder->orNotGroupStart()
+---------------------------
Starts a new group by adding an opening parenthesis to the **WHERE** clause of the query, prefixing it with **OR NOT**.
-**$builder->groupEnd()**
+$builder->groupEnd()
+--------------------
Ends the current group by adding a closing parenthesis to the **WHERE** clause of the query.
-**$builder->havingGroupStart()**
+$builder->havingGroupStart()
+----------------------------
Starts a new group by adding an opening parenthesis to the **HAVING** clause of the query.
-**$builder->orHavingGroupStart()**
+$builder->orHavingGroupStart()
+------------------------------
Starts a new group by adding an opening parenthesis to the **HAVING** clause of the query, prefixing it with **OR**.
-**$builder->notHavingGroupStart()**
+$builder->notHavingGroupStart()
+-------------------------------
Starts a new group by adding an opening parenthesis to the **HAVING** clause of the query, prefixing it with **NOT**.
-**$builder->orNotHavingGroupStart()**
+$builder->orNotHavingGroupStart()
+---------------------------------
Starts a new group by adding an opening parenthesis to the **HAVING** clause of the query, prefixing it with **OR NOT**.
-**$builder->havingGroupEnd()**
+$builder->havingGroupEnd()
+--------------------------
Ends the current group by adding a closing parenthesis to the **HAVING** clause of the query.
@@ -630,7 +736,11 @@ Ends the current group by adding a closing parenthesis to the **HAVING** clause
Inserting Data
**************
-**$builder->insert()**
+Insert
+======
+
+$builder->insert()
+------------------
Generates an insert string based on the data you supply, and runs the
query. You can either pass an **array** or an **object** to the
@@ -648,7 +758,8 @@ The first parameter is an object.
.. note:: All values are escaped automatically producing safer queries.
-**$builder->ignore()**
+$builder->ignore()
+------------------
Generates an insert ignore string based on the data you supply, and runs the
query. So if an entry with the same primary key already exists, the query won't be inserted.
@@ -656,7 +767,8 @@ You can optionally pass an **boolean** to the method. Here is an example using t
.. literalinclude:: query_builder/078.php
-**$builder->getCompiledInsert()**
+$builder->getCompiledInsert()
+-----------------------------
Compiles the insertion query just like ``$builder->insert()`` but does not
*run* the query. This method simply returns the SQL query as a string.
@@ -676,7 +788,11 @@ using ``$builder->insert()`` which resets values or reset directly using
.. note:: This method doesn't work for batch inserts.
-**$builder->insertBatch()**
+insertBatch
+===========
+
+$builder->insertBatch()
+-----------------------
Generates an insert string based on the data you supply, and runs the
query. You can either pass an **array** or an **object** to the
@@ -692,7 +808,11 @@ The first parameter is an associative array of values.
Updating Data
*************
-**$builder->replace()**
+Update
+======
+
+$builder->replace()
+-------------------
This method executes a **REPLACE** statement, which is basically the SQL
standard for (optional) **DELETE** + **INSERT**, using *PRIMARY* and *UNIQUE*
@@ -712,7 +832,8 @@ will be deleted with our new row data replacing it.
Usage of the ``set()`` method is also allowed and all fields are
automatically escaped, just like with ``insert()``.
-**$builder->set()**
+$builder->set()
+---------------
This method enables you to set values for inserts or updates.
@@ -741,7 +862,8 @@ Or an object:
.. literalinclude:: query_builder/087.php
-**$builder->update()**
+$builder->update()
+------------------
Generates an update string and runs the query based on the data you
supply. You can pass an **array** or an **object** to the method. Here
@@ -768,7 +890,11 @@ Or as an array:
You may also use the ``$builder->set()`` method described above when
performing updates.
-**$builder->updateBatch()**
+UpdateBatch
+===========
+
+$builder->updateBatch()
+-----------------------
Generates an update string based on the data you supply, and runs the query.
You can either pass an **array** or an **object** to the method.
@@ -784,7 +910,8 @@ The first parameter is an associative array of values, the second parameter is t
due to the very nature of how it works. Instead, ``updateBatch()``
returns the number of rows affected.
-**$builder->getCompiledUpdate()**
+$builder->getCompiledUpdate()
+-----------------------------
This works exactly the same way as ``$builder->getCompiledInsert()`` except
that it produces an **UPDATE** SQL string instead of an **INSERT** SQL string.
@@ -797,7 +924,11 @@ For more information view documentation for ``$builder->getCompiledInsert()``.
Deleting Data
*************
-**$builder->delete()**
+Delete
+======
+
+$builder->delete()
+------------------
Generates a **DELETE** SQL string and runs the query.
@@ -812,14 +943,16 @@ the data to the first parameter of the method:
If you want to delete all data from a table, you can use the ``truncate()``
method, or ``emptyTable()``.
-**$builder->emptyTable()**
+$builder->emptyTable()
+----------------------
Generates a **DELETE** SQL string and runs the
query:
.. literalinclude:: query_builder/095.php
-**$builder->truncate()**
+$builder->truncate()
+--------------------
Generates a **TRUNCATE** SQL string and runs the query.
@@ -828,7 +961,8 @@ Generates a **TRUNCATE** SQL string and runs the query.
.. note:: If the TRUNCATE command isn't available, ``truncate()`` will
execute as "DELETE FROM table".
-**$builder->getCompiledDelete()**
+$builder->getCompiledDelete()
+-----------------------------
This works exactly the same way as ``$builder->getCompiledInsert()`` except
that it produces a **DELETE** SQL string instead of an **INSERT** SQL string.
@@ -850,7 +984,11 @@ multiple methods. Consider this example:
Resetting Query Builder
***********************
-**$builder->resetQuery()**
+ResetQuery
+==========
+
+$builder->resetQuery()
+----------------------
Resetting Query Builder allows you to start fresh with your query without
executing it first using a method like ``$builder->get()`` or ``$builder->insert()``.
@@ -926,7 +1064,7 @@ Class Reference
.. php:method:: select([$select = '*'[, $escape = null]])
- :param string $select: The SELECT portion of a query
+ :param array|RawSql|string $select: The SELECT portion of a query
:param bool $escape: Whether to escape values and identifiers
:returns: ``BaseBuilder`` instance (method chaining)
:rtype: ``BaseBuilder``
@@ -978,7 +1116,7 @@ Class Reference
Adds a ``SELECT COUNT(field)`` clause to a query.
-.. php:method:: selectSubquery(BaseBuilder $subquery, string $as)
+ .. php:method:: selectSubquery(BaseBuilder $subquery, string $as)
:param string $subquery: Instance of BaseBuilder
:param string $as: Alias for the resulting value name
@@ -1027,7 +1165,7 @@ Class Reference
.. php:method:: where($key[, $value = null[, $escape = null]])
- :param mixed $key: Name of field to compare, or associative array
+ :param array|RawSql|string $key: Name of field to compare, or associative array
:param mixed $value: If a single key, compared to this value
:param bool $escape: Whether to escape values and identifiers
:returns: ``BaseBuilder`` instance (method chaining)
@@ -1122,7 +1260,7 @@ Class Reference
.. php:method:: like($field[, $match = ''[, $side = 'both'[, $escape = null[, $insensitiveSearch = false]]]])
- :param string $field: Field name
+ :param array|RawSql|string $field: Field name
:param string $match: Text portion to match
:param string $side: Which side of the expression to put the '%' wildcard on
:param bool $escape: Whether to escape values and identifiers
diff --git a/user_guide_src/source/database/query_builder/099.php b/user_guide_src/source/database/query_builder/099.php
new file mode 100644
index 000000000000..649a16951eea
--- /dev/null
+++ b/user_guide_src/source/database/query_builder/099.php
@@ -0,0 +1,7 @@
+select(new RawSql($sql));
+$query = $builder->get();
diff --git a/user_guide_src/source/database/query_builder/100.php b/user_guide_src/source/database/query_builder/100.php
new file mode 100644
index 000000000000..a06497870b3b
--- /dev/null
+++ b/user_guide_src/source/database/query_builder/100.php
@@ -0,0 +1,6 @@
+ 2 AND name != 'Accountant'";
+$builder->where(new RawSql($sql));
diff --git a/user_guide_src/source/database/query_builder/101.php b/user_guide_src/source/database/query_builder/101.php
new file mode 100644
index 000000000000..e00a0e84ac06
--- /dev/null
+++ b/user_guide_src/source/database/query_builder/101.php
@@ -0,0 +1,7 @@
+like($rawSql, 'value', 'both');