diff --git a/system/BaseModel.php b/system/BaseModel.php index d40a662724d9..467418b734ce 100644 --- a/system/BaseModel.php +++ b/system/BaseModel.php @@ -370,6 +370,13 @@ abstract class BaseModel */ protected $afterDelete = []; + /** + * Limit 0 as all + * + * @var bool + */ + protected $limitZeroAsAll; + public function __construct(?ValidationInterface $validation = null) { $this->tempReturnType = $this->returnType; @@ -378,6 +385,8 @@ public function __construct(?ValidationInterface $validation = null) $this->validation = $validation; + $this->limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property + $this->initialize(); $this->createDataConverter(); } @@ -653,8 +662,7 @@ public function findColumn(string $columnName) */ public function findAll(?int $limit = null, int $offset = 0) { - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll) { + if ($this->limitZeroAsAll) { $limit ??= 0; } diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 0a27b0b33abc..9e7452dd3f7f 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -295,6 +295,13 @@ class BaseBuilder */ protected $pregOperators = []; + /** + * Limit 0 as all + * + * @var bool + */ + protected $limitZeroAsAll; + /** * Constructor * @@ -334,6 +341,8 @@ public function __construct($tableName, ConnectionInterface $db, ?array $options } } } + + $this->limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property } /** @@ -1511,8 +1520,7 @@ public function orderBy(string $orderBy, string $direction = '', ?bool $escape = */ public function limit(?int $value = null, ?int $offset = 0) { - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll && $value === 0) { + if ($this->limitZeroAsAll && $value === 0) { $value = null; } @@ -1633,8 +1641,7 @@ protected function compileFinalQuery(string $sql): string */ public function get(?int $limit = null, int $offset = 0, bool $reset = true) { - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll && $limit === 0) { + if ($this->limitZeroAsAll && $limit === 0) { $limit = null; } @@ -1771,8 +1778,7 @@ public function getWhere($where = null, ?int $limit = null, ?int $offset = 0, bo $this->where($where); } - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll && $limit === 0) { + if ($this->limitZeroAsAll && $limit === 0) { $limit = null; } @@ -2498,8 +2504,7 @@ public function update($set = null, $where = null, ?int $limit = null): bool $this->where($where); } - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll && $limit === 0) { + if ($this->limitZeroAsAll && $limit === 0) { $limit = null; } @@ -2545,8 +2550,7 @@ protected function _update(string $table, array $values): string $valStr[] = $key . ' = ' . $val; } - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll) { + if ($this->limitZeroAsAll) { return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) . $this->compileWhereHaving('QBWhere') . $this->compileOrderBy() @@ -2829,8 +2833,7 @@ public function delete($where = '', ?int $limit = null, bool $resetData = true) $sql = $this->_delete($this->removeAlias($table)); - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll && $limit === 0) { + if ($this->limitZeroAsAll && $limit === 0) { $limit = null; } @@ -3104,8 +3107,7 @@ protected function compileSelect($selectOverride = false): string . $this->compileWhereHaving('QBHaving') . $this->compileOrderBy(); - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll) { + if ($this->limitZeroAsAll) { if ($this->QBLimit) { $sql = $this->_limit($sql . "\n"); } diff --git a/system/Database/SQLSRV/Builder.php b/system/Database/SQLSRV/Builder.php index 67f2f26c33f9..959c1425f75e 100644 --- a/system/Database/SQLSRV/Builder.php +++ b/system/Database/SQLSRV/Builder.php @@ -20,7 +20,6 @@ use CodeIgniter\Database\Query; use CodeIgniter\Database\RawSql; use CodeIgniter\Database\ResultInterface; -use Config\Feature; /** * Builder for SQLSRV @@ -339,8 +338,7 @@ protected function _limit(string $sql, bool $offsetIgnore = false): string // DatabaseException: // [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]The number of // rows provided for a FETCH clause must be greater then zero. - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if (! $limitZeroAsAll && $this->QBLimit === 0) { + if (! $this->limitZeroAsAll && $this->QBLimit === 0) { return "SELECT * \nFROM " . $this->_fromTables() . ' WHERE 1=0 '; } @@ -626,8 +624,7 @@ protected function compileSelect($selectOverride = false): string . $this->compileOrderBy(); // ORDER BY // LIMIT - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll) { + if ($this->limitZeroAsAll) { if ($this->QBLimit) { $sql = $this->_limit($sql . "\n"); } @@ -646,8 +643,7 @@ protected function compileSelect($selectOverride = false): string */ public function get(?int $limit = null, int $offset = 0, bool $reset = true) { - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll && $limit === 0) { + if ($this->limitZeroAsAll && $limit === 0) { $limit = null; } diff --git a/system/Model.php b/system/Model.php index 3599ba963176..4d8ef0b06fc6 100644 --- a/system/Model.php +++ b/system/Model.php @@ -25,7 +25,6 @@ use CodeIgniter\Exceptions\ModelException; use CodeIgniter\Validation\ValidationInterface; use Config\Database; -use Config\Feature; use stdClass; /** @@ -234,8 +233,7 @@ protected function doFindColumn(string $columnName) */ protected function doFindAll(?int $limit = null, int $offset = 0) { - $limitZeroAsAll = config(Feature::class)->limitZeroAsAll ?? true; // @phpstan-ignore nullCoalesce.property - if ($limitZeroAsAll) { + if ($this->limitZeroAsAll) { $limit ??= 0; } diff --git a/tests/system/Database/Builder/LimitTest.php b/tests/system/Database/Builder/LimitTest.php index 4e7bbfd40203..1644827f6ab9 100644 --- a/tests/system/Database/Builder/LimitTest.php +++ b/tests/system/Database/Builder/LimitTest.php @@ -13,9 +13,11 @@ namespace CodeIgniter\Database\Builder; +use CodeIgniter\Config\Factories; use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Mock\MockConnection; +use Config\Feature; use PHPUnit\Framework\Attributes\Group; /** @@ -65,4 +67,42 @@ public function testLimitAndOffsetMethod(): void $this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect())); } + + public function testLimitZeroAsAllOptimization(): void + { + $feature = new class () extends Feature { + public int $accessCount = 0; + + public function __construct() + { + parent::__construct(); + unset($this->limitZeroAsAll); + } + + public function __get(string $name): mixed + { + if ($name === 'limitZeroAsAll') { + $this->accessCount++; + + return true; + } + + return null; + } + }; + + Factories::injectMock('config', 'Feature', $feature); + + // Constructor accesses it once + $builder = new BaseBuilder('user', $this->db); + $this->assertSame(1, $feature->accessCount); + + // Should not access config again during typical query building + $builder->limit(0); + $builder->getCompiledSelect(); + + $this->assertSame(1, $feature->accessCount); + + Factories::reset('config'); + } }