diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index e5577c30e76d..5c07565344f9 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -2095,13 +2095,15 @@ public function getSetData(bool $clean = false): array */ public function getCompiledSelect(bool $reset = true): string { - $select = $this->compileSelect(); + $select = $this->compileFinalQuery($this->compileSelect()); if ($reset) { $this->resetSelect(); + $this->binds = []; + $this->bindsKeyCount = []; } - return $this->compileFinalQuery($select); + return $select; } /** @@ -2939,11 +2941,15 @@ public function getCompiledInsert(bool $reset = true) array_values($this->QBSet), ); + $sql = $this->compileFinalQuery($sql); + if ($reset) { $this->resetWrite(); + $this->binds = []; + $this->bindsKeyCount = []; } - return $this->compileFinalQuery($sql); + return $sql; } /** @@ -3110,11 +3116,15 @@ public function getCompiledUpdate(bool $reset = true) $sql = $this->_update($this->QBFrom[0], $this->QBSet); + $sql = $this->compileFinalQuery($sql); + if ($reset) { $this->resetWrite(); + $this->binds = []; + $this->bindsKeyCount = []; } - return $this->compileFinalQuery($sql); + return $sql; } /** @@ -3417,8 +3427,14 @@ public function getCompiledDelete(bool $reset = true): string { $sql = $this->testMode()->delete('', null, $reset); $this->testMode(false); + $sql = $this->compileFinalQuery($sql); - return $this->compileFinalQuery($sql); + if ($reset) { + $this->binds = []; + $this->bindsKeyCount = []; + } + + return $sql; } /** diff --git a/tests/system/Database/Builder/BindingLifecycleTest.php b/tests/system/Database/Builder/BindingLifecycleTest.php new file mode 100644 index 000000000000..675066d4b2f9 --- /dev/null +++ b/tests/system/Database/Builder/BindingLifecycleTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Builder; + +use CodeIgniter\Database\BaseBuilder; +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\Mock\MockConnection; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; + +/** + * @internal + */ +#[Group('Others')] +final class BindingLifecycleTest extends CIUnitTestCase +{ + #[DataProvider('provideCompiledQuery')] + public function testCompiledQueryClearsBindingsOnReset(string $method, string $sql): void + { + $builder = new BaseBuilder('jobs', new MockConnection([])); + + for ($id = 1; $id <= 3; $id++) { + $this->prepareQuery($builder, $method, $id); + + $this->assertSameSql(sprintf($sql, $id), $builder->{$method}()); + $this->assertSame([], $builder->getBinds()); + } + } + + #[DataProvider('provideCompiledQuery')] + public function testCompiledQueryPreservesBindingsWithoutReset(string $method, string $sql): void + { + $builder = new BaseBuilder('jobs', new MockConnection([])); + $this->prepareQuery($builder, $method, 1); + $binds = $builder->getBinds(); + + for ($i = 0; $i < 2; $i++) { + $this->assertSameSql(sprintf($sql, 1), $builder->{$method}(false)); + $this->assertSame($binds, $builder->getBinds()); + } + + $this->assertSameSql(sprintf($sql, 1), $builder->{$method}()); + $this->assertSame([], $builder->getBinds()); + } + + /** + * @return iterable + */ + public static function provideCompiledQuery(): iterable + { + yield 'select' => ['getCompiledSelect', 'SELECT * FROM "jobs" WHERE "id" = %d']; + + yield 'insert' => ['getCompiledInsert', 'INSERT INTO "jobs" ("name", "id") VALUES (\'O\'\'Reilly\', %d)']; + + yield 'update' => ['getCompiledUpdate', 'UPDATE "jobs" SET "name" = \'O\'\'Reilly\' WHERE "id" = %d']; + + yield 'delete' => ['getCompiledDelete', 'DELETE FROM "jobs" WHERE "id" = %d']; + } + + private function prepareQuery(BaseBuilder $builder, string $method, int $id): void + { + if ($method === 'getCompiledInsert' || $method === 'getCompiledUpdate') { + $builder->set('name', "O'Reilly"); + } + + if ($method === 'getCompiledInsert') { + $builder->set('id', $id); + } else { + $builder->where('id', $id); + } + } +} diff --git a/tests/system/Database/Builder/GroupTest.php b/tests/system/Database/Builder/GroupTest.php index 3600937551f5..e80021ae0411 100644 --- a/tests/system/Database/Builder/GroupTest.php +++ b/tests/system/Database/Builder/GroupTest.php @@ -94,7 +94,7 @@ public function testHavingBetweenMethods(string $method, string $sql): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -172,7 +172,7 @@ public function testHavingBetweenNoEscape(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } diff --git a/tests/system/Database/Builder/InsertTest.php b/tests/system/Database/Builder/InsertTest.php index f996fb2156e8..534a14482fca 100644 --- a/tests/system/Database/Builder/InsertTest.php +++ b/tests/system/Database/Builder/InsertTest.php @@ -57,7 +57,7 @@ public function testInsertArray(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledInsert()); + $this->assertSameSql($expectedSQL, $builder->getCompiledInsert(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -111,7 +111,7 @@ public function testInsertObject(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledInsert()); + $this->assertSameSql($expectedSQL, $builder->getCompiledInsert(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -155,7 +155,7 @@ public function testInsertWithTableAlias(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledInsert()); + $this->assertSameSql($expectedSQL, $builder->getCompiledInsert(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } diff --git a/tests/system/Database/Builder/LikeTest.php b/tests/system/Database/Builder/LikeTest.php index 6fff62e110dd..c456dbe3da83 100644 --- a/tests/system/Database/Builder/LikeTest.php +++ b/tests/system/Database/Builder/LikeTest.php @@ -50,7 +50,7 @@ public function testSimpleLike(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -72,7 +72,7 @@ public function testLikeAny(): void ], ]; - $this->assertSame($expectedSQL, (string) preg_replace('/\s+/', ' ', trim($builder->getCompiledSelect()))); + $this->assertSame($expectedSQL, (string) preg_replace('/\s+/', ' ', trim($builder->getCompiledSelect(false)))); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -118,7 +118,7 @@ public function testLikeAnyCaseInsensitiveSearch(): void ], ]; - $this->assertSame($expectedSQL, (string) preg_replace('/\s+/', ' ', trim($builder->getCompiledSelect()))); + $this->assertSame($expectedSQL, (string) preg_replace('/\s+/', ' ', trim($builder->getCompiledSelect(false)))); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -141,7 +141,7 @@ public function testLikeAnyWithRawSqlField(): void ], ]; - $this->assertSame($expectedSQL, (string) preg_replace('/\s+/', ' ', trim($builder->getCompiledSelect()))); + $this->assertSame($expectedSQL, (string) preg_replace('/\s+/', ' ', trim($builder->getCompiledSelect(false)))); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -191,7 +191,7 @@ public function testLikeWithRawSql(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -209,7 +209,7 @@ public function testLikeNoSide(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -227,7 +227,7 @@ public function testLikeBeforeOnly(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -245,7 +245,7 @@ public function testLikeAfterOnly(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -267,7 +267,7 @@ public function testOrLike(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -285,7 +285,7 @@ public function testNotLike(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -307,7 +307,7 @@ public function testOrNotLike(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -325,7 +325,7 @@ public function testCaseInsensitiveLike(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -348,7 +348,7 @@ public function testDBPrefixAndCoulmnWithTablename(): void true, ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -373,7 +373,7 @@ public function testLikeMultipleFields(): void ], ]; - $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect(false))); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -398,7 +398,7 @@ public function testLikeMultipleCallsWithRawSqlAndString(): void ], ]; - $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect(false))); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -423,7 +423,7 @@ public function testOrLikeMultipleFields(): void ], ]; - $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect(false))); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -445,7 +445,7 @@ public function testLikeMethodsWithEmptyArray(string $method): void ], ]; - $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); + $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect(false))); $this->assertSame($expectedBinds, $builder->getBinds()); } diff --git a/tests/system/Database/Builder/PrefixTest.php b/tests/system/Database/Builder/PrefixTest.php index dcd58a46e035..ae712eae2d50 100644 --- a/tests/system/Database/Builder/PrefixTest.php +++ b/tests/system/Database/Builder/PrefixTest.php @@ -86,7 +86,7 @@ public function testPrefixesSetOnTableNamesWithWhereBetweenClause(): void $builder->whereBetween('users.created_at', [1, 10]); - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } diff --git a/tests/system/Database/Builder/UpdateTest.php b/tests/system/Database/Builder/UpdateTest.php index 914d72831dad..876c20ab3986 100644 --- a/tests/system/Database/Builder/UpdateTest.php +++ b/tests/system/Database/Builder/UpdateTest.php @@ -53,7 +53,7 @@ public function testUpdateArray(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -76,7 +76,7 @@ public function testUpdateObject(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -98,7 +98,7 @@ public function testUpdateInternalWhereAndLimit(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -120,7 +120,7 @@ public function testUpdateWithSet(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -142,7 +142,7 @@ public function testUpdateWithSetAsInt(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -164,7 +164,7 @@ public function testUpdateWithSetAsBoolean(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -194,7 +194,7 @@ public function testUpdateWithSetAsArray(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -364,7 +364,7 @@ public function testUpdateWithWhereSameColumn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -390,7 +390,7 @@ public function testUpdateWithWhereSameColumn2(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -415,7 +415,7 @@ public function testUpdateWithWhereSameColumn3(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -439,7 +439,7 @@ public function testSetWithoutEscape(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -465,7 +465,7 @@ public function testSetWithAndWithoutEscape(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate()); + $this->assertSameSql($expectedSQL, $builder->getCompiledUpdate(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } diff --git a/tests/system/Database/Builder/WhereTest.php b/tests/system/Database/Builder/WhereTest.php index 8caa712935ad..de26db836859 100644 --- a/tests/system/Database/Builder/WhereTest.php +++ b/tests/system/Database/Builder/WhereTest.php @@ -54,7 +54,7 @@ public function testSimpleWhere(): void ]; $builder->where('id', 3); - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -71,7 +71,7 @@ public function testWhereNoEscape(): void ]; $builder->where('id', 3, false); - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -88,7 +88,7 @@ public function testWhereCustomKeyOperator(): void ]; $builder->where('id !=', 3); - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -114,7 +114,7 @@ public function testWhereAssociateArray(): void ]; $builder->where($where); - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -159,7 +159,7 @@ public function testWhereOperatorRegressionCases(string $key, $value, string $ex $builder->where($key, $value); - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -231,7 +231,7 @@ public function testWhereCustomStringWithoutOperatorEscapeFalse(): void $builder->where($where, "''", false); $expectedSQL = "SELECT * FROM \"jobs\" WHERE REPLACE(column, 'somestring', '') = ''"; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $expectedBinds = [ "REPLACE(column, 'somestring', '')" => [ @@ -371,7 +371,7 @@ public function testOrWhere(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -393,7 +393,7 @@ public function testOrWhereSameColumn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -478,7 +478,7 @@ public function testOrWhereColumn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -653,7 +653,7 @@ public function testWhereExistsWithOuterAndInnerBinds(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -710,7 +710,7 @@ public function testWhereBetweenMethods(string $method, string $sql): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -782,7 +782,7 @@ public function testWhereBetweenNoEscape(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -854,7 +854,7 @@ public function testWhereIn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -956,7 +956,7 @@ public function testWhereNotIn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -1004,7 +1004,7 @@ public function testOrWhereIn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } @@ -1052,7 +1052,7 @@ public function testOrWhereNotIn(): void ], ]; - $this->assertSameSql($expectedSQL, $builder->getCompiledSelect()); + $this->assertSameSql($expectedSQL, $builder->getCompiledSelect(false)); $this->assertSame($expectedBinds, $builder->getBinds()); } diff --git a/user_guide_src/source/changelogs/v4.8.0.rst b/user_guide_src/source/changelogs/v4.8.0.rst index 02d0ba4942a1..47810301e332 100644 --- a/user_guide_src/source/changelogs/v4.8.0.rst +++ b/user_guide_src/source/changelogs/v4.8.0.rst @@ -73,6 +73,7 @@ Behavior Changes - **Config:** ``BaseConfig`` now scans the environment once instead of checking every property against it, which makes building config classes several times faster without config caching and in Worker Mode. As a result, a bare ``putenv()`` call (adding, changing, or removing a variable) made after the first config class was built may not be seen until ``BaseConfig::reset()`` runs, including in later requests in Worker Mode. See :ref:`upgrade-480-config-env-lookups`. +- **Database:** ``BaseBuilder::getCompiledSelect()``, ``getCompiledInsert()``, ``getCompiledUpdate()`` and ``getCompiledDelete()`` now clear bindings and binding counters after compilation when ``$reset = true`` (the default), preventing binding accumulation when a builder is reused. ``getBinds()`` now returns an empty array after these calls. This aligns ``getCompiledSelect()`` with its documented reset behavior, which should match ``get()``. Pass ``false`` to retain the query and its bindings. See :ref:`upgrade-480-query-builder-bindings`. - **Database:** The Postgre driver's ``$db->error()['code']`` previously always returned ``''``. It now returns the 5-character SQLSTATE string for query and transaction failures (e.g., ``'42P01'``), or ``'08006'`` for connection-level failures. Code that relied on ``$db->error()['code'] === ''`` will need updating. - **Debug:** ``CodeIgniter\Debug\Toolbar\Collectors\BaseCollector`` is now ``abstract``. Custom toolbar collectors must extend it; code that instantiated it directly with ``new BaseCollector()`` will now throw an ``Error``. - **Filters:** HTTP method matching for method-based filters is now case-sensitive. The keys in ``Config\Filters::$methods`` must exactly match the request method diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index 05964c2b0e15..265241f5ff5e 100644 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -91,6 +91,8 @@ $query, which can be used to show the results: Please visit the :ref:`getResult() ` method for a full discussion regarding result generation. +.. _query-builder-get-compiled-select: + $builder->getCompiledSelect() ----------------------------- @@ -110,6 +112,11 @@ The key thing to notice in the above example is that the second query did not utilize ``limit(10, 20)`` but the generated SQL query has ``LIMIT 20, 10``. The reason for this outcome is because the parameter in the first query is set to ``false``, ``limit(10, 20)`` remained in the second query. +.. note:: + **Changed in version 4.8.0.** With ``$reset = true`` (the default), ``getCompiledSelect()``, ``getCompiledInsert()``, ``getCompiledUpdate()`` and ``getCompiledDelete()`` + also clear their bindings and binding counters after compilation. ``getBinds()`` therefore returns an empty array after these calls. + Pass ``false`` to retain the query clauses and bindings for reuse. + .. _query-builder-explain: $builder->explain() diff --git a/user_guide_src/source/installation/upgrade_480.rst b/user_guide_src/source/installation/upgrade_480.rst index 1679f79ad7c6..d71be11cfa04 100644 --- a/user_guide_src/source/installation/upgrade_480.rst +++ b/user_guide_src/source/installation/upgrade_480.rst @@ -20,6 +20,36 @@ Mandatory File Changes Breaking Changes **************** +.. _upgrade-480-query-builder-bindings: + +Compiled Query Builder Bindings +=============================== + +``getCompiledSelect()``, ``getCompiledInsert()``, ``getCompiledUpdate()`` and +``getCompiledDelete()`` now clear their bindings and binding counters after +compilation when ``$reset = true`` (the default). Previously, these methods +reset the query clauses but retained the bindings, which accumulated when the +same builder was reused and made subsequent query compilation increasingly +expensive. + +The :ref:`getCompiledSelect() documentation ` +already states that the default reset should work just like ``get()``. +Clearing the bindings corrects that inconsistency, but changes the result of +``getBinds()`` after compilation: it now returns an empty array. + +If your application reads the bindings after compilation or reuses the query, +pass ``false`` to preserve them: + +.. code-block:: php + + $sql = $builder->getCompiledSelect(false); + $binds = $builder->getBinds(); + +Passing ``false`` also retains the query clauses, including WHERE and LIMIT. +If you only need a snapshot of the bindings while retaining the default reset +behavior, call ``getBinds()`` before compiling the query instead. +The same migration applies to the compiled INSERT, UPDATE and DELETE methods. + Query Builder JOIN Overrides ============================