From 9210ab8b78622af87b7fc3eed9f163fabdb4db32 Mon Sep 17 00:00:00 2001
From: michalsn <michal@sniatala.pl>
Date: Thu, 26 Sep 2019 20:36:30 +0200
Subject: [PATCH 1/2] builder testMode

---
 system/Database/BaseBuilder.php               | 108 ++++++++++--------
 system/Database/Postgre/Builder.php           |  10 +-
 tests/system/Database/Builder/CountTest.php   |   6 +-
 tests/system/Database/Builder/DeleteTest.php  |   2 +-
 tests/system/Database/Builder/EmptyTest.php   |   2 +-
 tests/system/Database/Builder/GetTest.php     |  32 +++---
 tests/system/Database/Builder/InsertTest.php  |   4 +-
 tests/system/Database/Builder/ReplaceTest.php |   2 +-
 .../system/Database/Builder/TruncateTest.php  |   2 +-
 tests/system/Database/Builder/UpdateTest.php  |  23 ++--
 .../source/database/query_builder.rst         |  42 +++++--
 11 files changed, 134 insertions(+), 99 deletions(-)

diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php
index 8f556703e47f..164949b3a857 100644
--- a/system/Database/BaseBuilder.php
+++ b/system/Database/BaseBuilder.php
@@ -219,6 +219,13 @@ class BaseBuilder
 	 */
 	protected $canLimitWhereUpdates = true;
 
+	/**
+	 * Builder testing mode status.
+	 *
+	 * @var boolean
+	 */
+	protected $testMode = false;
+
 	//--------------------------------------------------------------------
 
 	/**
@@ -254,6 +261,23 @@ public function __construct($tableName, ConnectionInterface &$db, array $options
 
 	//--------------------------------------------------------------------
 
+	/**
+	 * Returns an array of bind values and their
+	 * named parameters for binding in the Query object later.
+	 *
+	 * @param boolean $mode Mode to set
+	 *
+	 * @return BaseBuilder
+	 */
+	public function testMode(bool $mode = true)
+	{
+		$this->testMode = $mode;
+
+		return $this;
+	}
+
+	//--------------------------------------------------------------------
+
 	/**
 	 * Returns an array of bind values and their
 	 * named parameters for binding in the Query object later.
@@ -1738,21 +1762,20 @@ protected function compileFinalQuery(string $sql): string
 	 * Compiles the select statement based on the other functions called
 	 * and runs the query
 	 *
-	 * @param integer $limit     The limit clause
-	 * @param integer $offset    The offset clause
-	 * @param boolean $returnSQL If true, returns the generate SQL, otherwise executes the query.
-	 * @param boolean $reset     Are we want to clear query builder values?
+	 * @param integer $limit  The limit clause
+	 * @param integer $offset The offset clause
+	 * @param boolean $reset  Are we want to clear query builder values?
 	 *
 	 * @return ResultInterface
 	 */
-	public function get(int $limit = null, int $offset = 0, bool $returnSQL = false, bool $reset = true)
+	public function get(int $limit = null, int $offset = 0, bool $reset = true)
 	{
 		if (! is_null($limit))
 		{
 			$this->limit($limit, $offset);
 		}
 
-		$result = $returnSQL
+		$result = $this->testMode
 			? $this->getCompiledSelect($reset)
 			: $this->db->query($this->compileSelect(), $this->binds, false);
 
@@ -1776,18 +1799,17 @@ public function get(int $limit = null, int $offset = 0, bool $returnSQL = false,
 	 * the specified database
 	 *
 	 * @param boolean $reset Are we want to clear query builder values?
-	 * @param boolean $test  Are we running automated tests?
 	 *
 	 * @return integer|string when $test = true
 	 */
-	public function countAll(bool $reset = true, bool $test = false)
+	public function countAll(bool $reset = true)
 	{
 		$table = $this->QBFrom[0];
 
 		$sql = $this->countString . $this->db->escapeIdentifiers('numrows') . ' FROM ' .
 				$this->db->protectIdentifiers($table, true, null, false);
 
-		if ($test)
+		if ($this->testMode)
 		{
 			return $sql;
 		}
@@ -1817,11 +1839,10 @@ public function countAll(bool $reset = true, bool $test = false)
 	 * returned by an Query Builder query.
 	 *
 	 * @param boolean $reset
-	 * @param boolean $test  The reset clause
 	 *
 	 * @return integer|string when $test = true
 	 */
-	public function countAllResults(bool $reset = true, bool $test = false)
+	public function countAllResults(bool $reset = true)
 	{
 		// ORDER BY usage is often problematic here (most notably
 		// on Microsoft SQL Server) and ultimately unnecessary
@@ -1843,7 +1864,7 @@ public function countAllResults(bool $reset = true, bool $test = false)
 			:
 			$this->compileSelect($this->countString . $this->db->protectIdentifiers('numrows'));
 
-		if ($test)
+		if ($this->testMode)
 		{
 			return $sql;
 		}
@@ -1894,15 +1915,14 @@ public function getCompiledQBWhere()
 	 *
 	 * Allows the where clause, limit and offset to be added directly
 	 *
-	 * @param string|array $where     Where condition
-	 * @param integer      $limit     Limit value
-	 * @param integer      $offset    Offset value
-	 * @param boolean      $returnSQL If true, returns the generate SQL, otherwise executes the query.
-	 * @param boolean      $reset     Are we want to clear query builder values?
+	 * @param string|array $where  Where condition
+	 * @param integer      $limit  Limit value
+	 * @param integer      $offset Offset value
+	 * @param boolean      $reset  Are we want to clear query builder values?
 	 *
 	 * @return ResultInterface
 	 */
-	public function getWhere($where = null, int $limit = null, ?int $offset = 0, bool $returnSQL = false, bool $reset = true)
+	public function getWhere($where = null, int $limit = null, ?int $offset = 0, bool $reset = true)
 	{
 		if ($where !== null)
 		{
@@ -1914,7 +1934,7 @@ public function getWhere($where = null, int $limit = null, ?int $offset = 0, boo
 			$this->limit($limit, $offset);
 		}
 
-		$result = $returnSQL
+		$result = $this->testMode
 			? $this->getCompiledSelect($reset)
 			: $this->db->query($this->compileSelect(), $this->binds, false);
 
@@ -1938,14 +1958,12 @@ public function getWhere($where = null, int $limit = null, ?int $offset = 0, boo
 	 *
 	 * @param array   $set       An associative array of insert values
 	 * @param boolean $escape    Whether to escape values and identifiers
-	 *
-	 * @param integer $batchSize
-	 * @param boolean $testing
+	 * @param integer $batchSize Batch size
 	 *
 	 * @return integer Number of rows inserted or FALSE on failure
 	 * @throws DatabaseException
 	 */
-	public function insertBatch(array $set = null, bool $escape = null, int $batchSize = 100, bool $testing = false)
+	public function insertBatch(array $set = null, bool $escape = null, int $batchSize = 100)
 	{
 		if ($set === null)
 		{
@@ -1982,7 +2000,7 @@ public function insertBatch(array $set = null, bool $escape = null, int $batchSi
 		{
 			$sql = $this->_insertBatch($this->db->protectIdentifiers($table, true, $escape, false), $this->QBKeys, array_slice($this->QBSet, $i, $batchSize));
 
-			if ($testing)
+			if ($this->testMode)
 			{
 				++ $affected_rows;
 			}
@@ -1993,7 +2011,7 @@ public function insertBatch(array $set = null, bool $escape = null, int $batchSi
 			}
 		}
 
-		if (! $testing)
+		if (! $this->testMode)
 		{
 			$this->resetWrite();
 		}
@@ -2117,11 +2135,10 @@ public function getCompiledInsert(bool $reset = true): string
 	 *
 	 * @param array   $set    An associative array of insert values
 	 * @param boolean $escape Whether to escape values and identifiers
-	 * @param boolean $test   Used when running tests
 	 *
 	 * @return BaseResult|Query|false
 	 */
-	public function insert(array $set = null, bool $escape = null, bool $test = false)
+	public function insert(array $set = null, bool $escape = null)
 	{
 		if ($set !== null)
 		{
@@ -2139,7 +2156,7 @@ public function insert(array $set = null, bool $escape = null, bool $test = fals
 				), array_keys($this->QBSet), array_values($this->QBSet)
 		);
 
-		if ($test === false)
+		if (! $this->testMode)
 		{
 			$this->resetWrite();
 
@@ -2206,13 +2223,12 @@ protected function _insert(string $table, array $keys, array $unescapedKeys): st
 	 *
 	 * Compiles an replace into string and runs the query
 	 *
-	 * @param array   $set       An associative array of insert values
-	 * @param boolean $returnSQL
+	 * @param array $set An associative array of insert values
 	 *
 	 * @return BaseResult|Query|string|false
 	 * @throws DatabaseException
 	 */
-	public function replace(array $set = null, bool $returnSQL = false)
+	public function replace(array $set = null)
 	{
 		if ($set !== null)
 		{
@@ -2234,7 +2250,7 @@ public function replace(array $set = null, bool $returnSQL = false)
 
 		$this->resetWrite();
 
-		return $returnSQL ? $sql : $this->db->query($sql, $this->binds, false);
+		return $this->testMode ? $sql : $this->db->query($sql, $this->binds, false);
 	}
 
 	//--------------------------------------------------------------------
@@ -2310,11 +2326,10 @@ public function getCompiledUpdate(bool $reset = true): string
 	 * @param array   $set   An associative array of update values
 	 * @param mixed   $where
 	 * @param integer $limit
-	 * @param boolean $test  Are we testing the code?
 	 *
 	 * @return boolean    TRUE on success, FALSE on failure
 	 */
-	public function update(array $set = null, $where = null, int $limit = null, bool $test = false): bool
+	public function update(array $set = null, $where = null, int $limit = null): bool
 	{
 		if ($set !== null)
 		{
@@ -2343,7 +2358,7 @@ public function update(array $set = null, $where = null, int $limit = null, bool
 
 		$sql = $this->_update($this->QBFrom[0], $this->QBSet);
 
-		if (! $test)
+		if (! $this->testMode)
 		{
 			$this->resetWrite();
 
@@ -2425,12 +2440,11 @@ protected function validateUpdate(): bool
 	 * @param array   $set       An associative array of update values
 	 * @param string  $index     The where key
 	 * @param integer $batchSize The size of the batch to run
-	 * @param boolean $returnSQL True means SQL is returned, false will execute the query
 	 *
 	 * @return mixed    Number of rows affected, SQL string, or FALSE on failure
 	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
 	 */
-	public function updateBatch(array $set = null, string $index = null, int $batchSize = 100, bool $returnSQL = false)
+	public function updateBatch(array $set = null, string $index = null, int $batchSize = 100)
 	{
 		if ($index === null)
 		{
@@ -2477,7 +2491,7 @@ public function updateBatch(array $set = null, string $index = null, int $batchS
 			$sql = $this->_updateBatch($table, array_slice($this->QBSet, $i, $batchSize), $this->db->protectIdentifiers($index)
 			);
 
-			if ($returnSQL)
+			if ($this->testMode)
 			{
 				$savedSQL[] = $sql;
 			}
@@ -2492,7 +2506,7 @@ public function updateBatch(array $set = null, string $index = null, int $batchS
 
 		$this->resetWrite();
 
-		return $returnSQL ? $savedSQL : $affected_rows;
+		return $this->testMode ? $savedSQL : $affected_rows;
 	}
 
 	//--------------------------------------------------------------------
@@ -2595,16 +2609,15 @@ public function setUpdateBatch($key, string $index = '', bool $escape = null)
 	 *
 	 * Compiles a delete string and runs "DELETE FROM table"
 	 *
-	 * @param  boolean $test
 	 * @return boolean    TRUE on success, FALSE on failure
 	 */
-	public function emptyTable(bool $test = false)
+	public function emptyTable()
 	{
 		$table = $this->QBFrom[0];
 
 		$sql = $this->_delete($table);
 
-		if ($test)
+		if ($this->testMode)
 		{
 			return $sql;
 		}
@@ -2623,17 +2636,15 @@ public function emptyTable(bool $test = false)
 	 * If the database does not support the truncate() command
 	 * This function maps to "DELETE FROM table"
 	 *
-	 * @param boolean $test Whether we're in test mode or not.
-	 *
 	 * @return boolean    TRUE on success, FALSE on failure
 	 */
-	public function truncate(bool $test = false)
+	public function truncate()
 	{
 		$table = $this->QBFrom[0];
 
 		$sql = $this->_truncate($table);
 
-		if ($test === true)
+		if ($this->testMode)
 		{
 			return $sql;
 		}
@@ -2692,12 +2703,11 @@ public function getCompiledDelete(bool $reset = true): string
 	 * @param mixed   $where      The where clause
 	 * @param integer $limit      The limit clause
 	 * @param boolean $reset_data
-	 * @param boolean $returnSQL
 	 *
 	 * @return mixed
 	 * @throws \CodeIgniter\Database\Exceptions\DatabaseException
 	 */
-	public function delete($where = '', int $limit = null, bool $reset_data = true, bool $returnSQL = false)
+	public function delete($where = '', int $limit = null, bool $reset_data = true)
 	{
 		$table = $this->db->protectIdentifiers($this->QBFrom[0], true, null, false);
 
@@ -2738,7 +2748,7 @@ public function delete($where = '', int $limit = null, bool $reset_data = true,
 			$this->resetWrite();
 		}
 
-		return ($returnSQL === true) ? $sql : $this->db->query($sql, $this->binds, false);
+		return $this->testMode ? $sql : $this->db->query($sql, $this->binds, false);
 	}
 
 	//--------------------------------------------------------------------
diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php
index 9704c8c47736..e86f37c77047 100644
--- a/system/Database/Postgre/Builder.php
+++ b/system/Database/Postgre/Builder.php
@@ -137,14 +137,13 @@ public function decrement(string $column, int $value = 1)
 	 * we simply do a DELETE and an INSERT on the first key/value
 	 * combo, assuming that it's either the primary key or a unique key.
 	 *
-	 * @param array   $set       An associative array of insert values
-	 * @param boolean $returnSQL
+	 * @param array $set An associative array of insert values
 	 *
 	 * @return   mixed
 	 * @throws   DatabaseException
 	 * @internal param true $bool returns the generated SQL, false executes the query.
 	 */
-	public function replace(array $set = null, bool $returnSQL = false)
+	public function replace(array $set = null)
 	{
 		if ($set !== null)
 		{
@@ -202,7 +201,6 @@ public function replace(array $set = null, bool $returnSQL = false)
 	 * @param mixed   $where
 	 * @param integer $limit
 	 * @param boolean $reset_data
-	 * @param boolean $returnSQL
 	 *
 	 * @return   mixed
 	 * @throws   DatabaseException
@@ -210,14 +208,14 @@ public function replace(array $set = null, bool $returnSQL = false)
 	 * @internal param the $mixed limit clause
 	 * @internal param $bool
 	 */
-	public function delete($where = '', int $limit = null, bool $reset_data = true, bool $returnSQL = false)
+	public function delete($where = '', int $limit = null, bool $reset_data = true)
 	{
 		if (! empty($limit) || ! empty($this->QBLimit))
 		{
 			throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.');
 		}
 
-		return parent::delete($where, $limit, $reset_data, $returnSQL);
+		return parent::delete($where, $limit, $reset_data);
 	}
 
 	//--------------------------------------------------------------------
diff --git a/tests/system/Database/Builder/CountTest.php b/tests/system/Database/Builder/CountTest.php
index a72f8792df65..d1d7e2c79f4e 100644
--- a/tests/system/Database/Builder/CountTest.php
+++ b/tests/system/Database/Builder/CountTest.php
@@ -21,10 +21,11 @@ protected function setUp(): void
 	public function testCountAll()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
+		$builder->testMode();
 
 		$expectedSQL = 'SELECT COUNT(*) AS "numrows" FROM "jobs"';
 
-		$this->assertEquals($expectedSQL, $builder->countAll(true, true));
+		$this->assertEquals($expectedSQL, $builder->countAll(true));
 	}
 
 	//--------------------------------------------------------------------
@@ -32,8 +33,9 @@ public function testCountAll()
 	public function testCountAllResults()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
+		$builder->testMode();
 
-		$answer = $builder->where('id >', 3)->countAllResults(false, true);
+		$answer = $builder->where('id >', 3)->countAllResults(false);
 
 		$expectedSQL = 'SELECT COUNT(*) AS "numrows" FROM "jobs" WHERE "id" > :id:';
 
diff --git a/tests/system/Database/Builder/DeleteTest.php b/tests/system/Database/Builder/DeleteTest.php
index 75e22d4286e7..cd4708943da2 100644
--- a/tests/system/Database/Builder/DeleteTest.php
+++ b/tests/system/Database/Builder/DeleteTest.php
@@ -21,7 +21,7 @@ public function testDelete()
 	{
 		$builder = $this->db->table('jobs');
 
-		$answer = $builder->delete(['id' => 1], null, true, true);
+		$answer = $builder->testMode()->delete(['id' => 1], null, true);
 
 		$expectedSQL   = 'DELETE FROM "jobs" WHERE "id" = :id:';
 		$expectedBinds = [
diff --git a/tests/system/Database/Builder/EmptyTest.php b/tests/system/Database/Builder/EmptyTest.php
index 812743e9d63e..46a59f2d3d18 100644
--- a/tests/system/Database/Builder/EmptyTest.php
+++ b/tests/system/Database/Builder/EmptyTest.php
@@ -22,7 +22,7 @@ public function testEmptyWithNoTable()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$answer = $builder->emptyTable(true);
+		$answer = $builder->testMode()->emptyTable();
 
 		$expectedSQL = 'DELETE FROM "jobs"';
 
diff --git a/tests/system/Database/Builder/GetTest.php b/tests/system/Database/Builder/GetTest.php
index e7f57e1dddb9..cbdaa0e284d0 100644
--- a/tests/system/Database/Builder/GetTest.php
+++ b/tests/system/Database/Builder/GetTest.php
@@ -34,14 +34,14 @@ public function testGet()
 	public function testGetWithReset()
 	{
 		$builder = $this->db->table('users');
-		$builder->where('username', 'bogus');
+		$builder->testMode()->where('username', 'bogus');
 
 		$expectedSQL           = 'SELECT * FROM "users" WHERE "username" = \'bogus\'';
 		$expectedSQLafterreset = 'SELECT * FROM "users"';
 
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->get(0, 50, true, false)));
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->get(0, 50, true, true)));
-		$this->assertEquals($expectedSQLafterreset, str_replace("\n", ' ', $builder->get(0, 50, true, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->get(0, 50, false)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->get(0, 50, true)));
+		$this->assertEquals($expectedSQLafterreset, str_replace("\n", ' ', $builder->get(0, 50, true)));
 	}
 
 	//--------------------------------------------------------------------
@@ -52,13 +52,14 @@ public function testGetWithReset()
 	public function testGetWhereWithLimit()
 	{
 		$builder = $this->db->table('users');
+		$builder->testMode();
 
 		$expectedSQL             = 'SELECT * FROM "users" WHERE "username" = \'bogus\'  LIMIT 5';
 		$expectedSQLWithoutReset = 'SELECT * FROM "users" WHERE "username" = \'bogus\' AND "username" = \'bogus\'  LIMIT 5';
 
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, null, true, false)));
-		$this->assertEquals($expectedSQLWithoutReset, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 0, true, true)));
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, null, true, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, null, false)));
+		$this->assertEquals($expectedSQLWithoutReset, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 0, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, null, true)));
 	}
 
 	//--------------------------------------------------------------------
@@ -66,13 +67,14 @@ public function testGetWhereWithLimit()
 	public function testGetWhereWithLimitAndOffset()
 	{
 		$builder = $this->db->table('users');
+		$builder->testMode();
 
 		$expectedSQL             = 'SELECT * FROM "users" WHERE "username" = \'bogus\'  LIMIT 10, 5';
 		$expectedSQLWithoutReset = 'SELECT * FROM "users" WHERE "username" = \'bogus\' AND "username" = \'bogus\'  LIMIT 10, 5';
 
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 10, true, false)));
-		$this->assertEquals($expectedSQLWithoutReset, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 10, true, true)));
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 10, true, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 10, false)));
+		$this->assertEquals($expectedSQLWithoutReset, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 10, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], 5, 10, true)));
 	}
 
 	//--------------------------------------------------------------------
@@ -80,13 +82,14 @@ public function testGetWhereWithLimitAndOffset()
 	public function testGetWhereWithWhereConditionOnly()
 	{
 		$builder = $this->db->table('users');
+		$builder->testMode();
 
 		$expectedSQL             = 'SELECT * FROM "users" WHERE "username" = \'bogus\'';
 		$expectedSQLWithoutReset = 'SELECT * FROM "users" WHERE "username" = \'bogus\' AND "username" = \'bogus\'';
 
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], null, null, true, false)));
-		$this->assertEquals($expectedSQLWithoutReset, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], null, null, true, true)));
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], null, null, true, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], null, null, false)));
+		$this->assertEquals($expectedSQLWithoutReset, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], null, null, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(['username' => 'bogus'], null, null, true)));
 	}
 
 	//--------------------------------------------------------------------
@@ -94,10 +97,11 @@ public function testGetWhereWithWhereConditionOnly()
 	public function testGetWhereWithoutArgs()
 	{
 		$builder = $this->db->table('users');
+		$builder->testMode();
 
 		$expectedSQL = 'SELECT * FROM "users"';
 
-		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(null, null, null, true, true)));
+		$this->assertEquals($expectedSQL, str_replace("\n", ' ', $builder->getWhere(null, null, null, true)));
 	}
 
 }
diff --git a/tests/system/Database/Builder/InsertTest.php b/tests/system/Database/Builder/InsertTest.php
index e8ce12773c2e..e24473e6bb6b 100644
--- a/tests/system/Database/Builder/InsertTest.php
+++ b/tests/system/Database/Builder/InsertTest.php
@@ -26,7 +26,7 @@ public function testSimpleInsert()
 			'id'   => 1,
 			'name' => 'Grocery Sales',
 		];
-		$builder->insert($insertData, true, true);
+		$builder->testMode()->insert($insertData, true);
 
 		$expectedSQL   = 'INSERT INTO "jobs" ("id", "name") VALUES (1, \'Grocery Sales\')';
 		$expectedBinds = [
@@ -53,7 +53,7 @@ public function testThrowsExceptionOnNoValuesSet()
 		$this->expectException('\CodeIgniter\Database\Exceptions\DatabaseException');
 		$this->expectExceptionMessage('You must use the "set" method to update an entry.');
 
-		$builder->insert(null, true, true);
+		$builder->testMode()->insert(null, true);
 	}
 
 	//--------------------------------------------------------------------
diff --git a/tests/system/Database/Builder/ReplaceTest.php b/tests/system/Database/Builder/ReplaceTest.php
index 28fbbd888878..0ae2a1948afa 100644
--- a/tests/system/Database/Builder/ReplaceTest.php
+++ b/tests/system/Database/Builder/ReplaceTest.php
@@ -29,7 +29,7 @@ public function testSimpleReplace()
 			'date'  => 'My date',
 		];
 
-		$this->assertSame($expected, $builder->replace($data, true));
+		$this->assertSame($expected, $builder->testMode()->replace($data));
 	}
 
 	//--------------------------------------------------------------------
diff --git a/tests/system/Database/Builder/TruncateTest.php b/tests/system/Database/Builder/TruncateTest.php
index 2a4a715bb94d..38adf16509a4 100644
--- a/tests/system/Database/Builder/TruncateTest.php
+++ b/tests/system/Database/Builder/TruncateTest.php
@@ -24,7 +24,7 @@ public function testTruncate()
 
 		$expectedSQL = 'TRUNCATE "user"';
 
-		$this->assertEquals($expectedSQL, $builder->truncate(true));
+		$this->assertEquals($expectedSQL, $builder->testMode()->truncate());
 	}
 
 	//--------------------------------------------------------------------
diff --git a/tests/system/Database/Builder/UpdateTest.php b/tests/system/Database/Builder/UpdateTest.php
index 82608ce6edeb..1c5405fe1955 100644
--- a/tests/system/Database/Builder/UpdateTest.php
+++ b/tests/system/Database/Builder/UpdateTest.php
@@ -23,7 +23,7 @@ public function testUpdate()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$builder->where('id', 1)->update(['name' => 'Programmer'], null, null, true);
+		$builder->testMode()->where('id', 1)->update(['name' => 'Programmer'], null, null);
 
 		$expectedSQL   = 'UPDATE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1';
 		$expectedBinds = [
@@ -47,7 +47,7 @@ public function testUpdateInternalWhereAndLimit()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$builder->update(['name' => 'Programmer'], ['id' => 1], 5, true);
+		$builder->testMode()->update(['name' => 'Programmer'], ['id' => 1], 5);
 
 		$expectedSQL   = 'UPDATE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1  LIMIT 5';
 		$expectedBinds = [
@@ -71,7 +71,7 @@ public function testUpdateWithSet()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$builder->set('name', 'Programmer')->where('id', 1)->update(null, null, null, true);
+		$builder->testMode()->set('name', 'Programmer')->where('id', 1)->update(null, null, null);
 
 		$expectedSQL   = 'UPDATE "jobs" SET "name" = \'Programmer\' WHERE "id" = 1';
 		$expectedBinds = [
@@ -193,7 +193,7 @@ public function testUpdateWithWhereSameColumn()
 	{
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$builder->update(['name' => 'foobar'], ['name' => 'Programmer'], null, true);
+		$builder->testMode()->update(['name' => 'foobar'], ['name' => 'Programmer'], null);
 
 		$expectedSQL   = 'UPDATE "jobs" SET "name" = \'foobar\' WHERE "name" = \'Programmer\'';
 		$expectedBinds = [
@@ -218,9 +218,10 @@ public function testUpdateWithWhereSameColumn2()
 		// calling order: set() -> where()
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$builder->set('name', 'foobar')
+		$builder->testMode()
+			->set('name', 'foobar')
 			->where('name', 'Programmer')
-			->update(null, null, null, true);
+			->update(null, null, null);
 
 		$expectedSQL   = 'UPDATE "jobs" SET "name" = \'foobar\' WHERE "name" = \'Programmer\'';
 		$expectedBinds = [
@@ -245,8 +246,9 @@ public function testUpdateWithWhereSameColumn3()
 		// calling order: where() -> set() in update()
 		$builder = new BaseBuilder('jobs', $this->db);
 
-		$builder->where('name', 'Programmer')
-			->update(['name' => 'foobar'], null, null, true);
+		$builder->testMode()
+			->where('name', 'Programmer')
+			->update(['name' => 'foobar'], null, null);
 
 		$expectedSQL   = 'UPDATE "jobs" SET "name" = \'foobar\' WHERE "name" = \'Programmer\'';
 		$expectedBinds = [
@@ -271,9 +273,10 @@ public function testSetWithoutEscape()
 	{
 		$builder = new BaseBuilder('mytable', $this->db);
 
-		$builder->set('field', 'field+1', false)
+		$builder->testMode()
+			->set('field', 'field+1', false)
 			->where('id', 2)
-			->update(null, null, null, true);
+			->update(null, null, null);
 
 		$expectedSQL   = 'UPDATE "mytable" SET field = field+1 WHERE "id" = 2';
 		$expectedBinds = [
diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst
index bb91673afefe..e8286de8b4e0 100755
--- a/user_guide_src/source/database/query_builder.rst
+++ b/user_guide_src/source/database/query_builder.rst
@@ -1246,22 +1246,22 @@ Class Reference
 		Generates a platform-specific query string that counts
 		all records returned by an Query Builder query.
 
-	.. php:method:: get([$limit = NULL[, $offset = NULL]])
+	.. php:method:: get([$limit = NULL[, $offset = NULL[, $reset = TRUE]]]])
 
 		:param	int	$limit: The LIMIT clause
 		:param	int	$offset: The OFFSET clause
+		:param 	bool $reset: Do we want to clear query builder values?
 		:returns:	\CodeIgniter\Database\ResultInterface instance (method chaining)
 		:rtype:	\CodeIgniter\Database\ResultInterface
 
 		Compiles and runs SELECT statement based on the already
 		called Query Builder methods.
 
-	.. php:method:: getWhere([$where = NULL[, $limit = NULL[, $offset = NULL[, $returnSQL = FALSE[, $reset = TRUE]]]]])
+	.. php:method:: getWhere([$where = NULL[, $limit = NULL[, $offset = NULL[, $reset = TRUE]]]]])
 
 		:param	string	$where: The WHERE clause
 		:param	int	$limit: The LIMIT clause
 		:param	int	$offset: The OFFSET clause
-		:param 	bool $returnSQL: If true, returns the generate SQL, otherwise executes the query.
 		:param 	bool $reset: Do we want to clear query builder values?
 		:returns:	\CodeIgniter\Database\ResultInterface instance (method chaining)
 		:rtype:	\CodeIgniter\Database\ResultInterface
@@ -1313,6 +1313,15 @@ Class Reference
 
 		Adds a SELECT SUM(field) clause to a query.
 
+	.. php:method:: selectCount([$select = ''[, $alias = '']])
+
+		:param	string	$select: Field to compute the average of
+		:param	string	$alias: Alias for the resulting value name
+		:returns:	BaseBuilder instance (method chaining)
+		:rtype:	BaseBuilder
+
+		Adds a SELECT COUNT(field) clause to a query.
+
 	.. php:method:: distinct([$val = TRUE])
 
 		:param	bool	$val: Desired value of the "distinct" flag
@@ -1322,9 +1331,10 @@ Class Reference
 		Sets a flag which tells the query builder to add
 		a DISTINCT clause to the SELECT portion of the query.
 
-	.. php:method:: from($from)
+	.. php:method:: from($from[, $overwrite = FALSE])
 
 		:param	mixed	$from: Table name(s); string or array
+		:param	bool	$overwrite Should we remove the first table existing?
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
@@ -1442,45 +1452,49 @@ Class Reference
 
 		Ends a group expression.
 
-	.. php:method:: like($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: like($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
 		Adds a LIKE clause to a query, separating multiple calls with AND.
 
-	.. php:method:: orLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: orLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
 		Adds a LIKE clause to a query, separating multiple class with OR.
 
-	.. php:method:: notLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: notLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
 		Adds a NOT LIKE clause to a query, separating multiple calls with AND.
 
-	.. php:method:: orNotLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: orNotLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
@@ -1544,46 +1558,50 @@ Class Reference
 		:param	string	        $key: Name of field to examine
 		:param	array|Closure   $values: Array of target values, or anonymous function for subquery
 		:param	bool	        $escape: Whether to escape values and identifiers
+		:param	bool            $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance
 		:rtype:	object
 
 		Generates a HAVING field NOT IN('item', 'item') SQL query,
                 joined with 'AND' if appropriate.
 
-	.. php:method:: havingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: havingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
 		Adds a LIKE clause to a HAVING part of the query, separating multiple calls with AND.
 
-	.. php:method:: orHavingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: orHavingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
 		Adds a LIKE clause to a HAVING part of the query, separating multiple class with OR.
 
-	.. php:method:: notHavingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: notHavingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	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
+		:param	bool    $insensitiveSearch: Whether to force a case-insensitive search
 		:returns:	BaseBuilder instance (method chaining)
 		:rtype:	BaseBuilder
 
 		Adds a NOT LIKE clause to a HAVING part of the query, separating multiple calls with AND.
 
-	.. php:method:: orNotHavingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL]]])
+	.. php:method:: orNotHavingLike($field[, $match = ''[, $side = 'both'[, $escape = NULL[, $insensitiveSearch = FALSE]]]])
 
 		:param	string	$field: Field name
 		:param	string	$match: Text portion to match

From 69ecefd0032665fe434dda62d9259c77c95303cf Mon Sep 17 00:00:00 2001
From: michalsn <michal@sniatala.pl>
Date: Thu, 26 Sep 2019 20:42:01 +0200
Subject: [PATCH 2/2] correct method description

---
 system/Database/BaseBuilder.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php
index 164949b3a857..977d1bc15faf 100644
--- a/system/Database/BaseBuilder.php
+++ b/system/Database/BaseBuilder.php
@@ -262,8 +262,7 @@ public function __construct($tableName, ConnectionInterface &$db, array $options
 	//--------------------------------------------------------------------
 
 	/**
-	 * Returns an array of bind values and their
-	 * named parameters for binding in the Query object later.
+	 * Sets a test mode status.
 	 *
 	 * @param boolean $mode Mode to set
 	 *