From c6afae84c9346076db603e9eb3a9f98c96b1d4e1 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 31 May 2026 02:02:58 +0200 Subject: [PATCH 1/3] fix: use currentRow instead of customResultObject in getRowObject and add null fallback --- system/Database/BaseResult.php | 6 +- tests/system/Database/BaseResultTest.php | 249 +++++++++++++++++++++++ 2 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 tests/system/Database/BaseResultTest.php diff --git a/system/Database/BaseResult.php b/system/Database/BaseResult.php index c0bdc2aa1025..7d249cab8c6b 100644 --- a/system/Database/BaseResult.php +++ b/system/Database/BaseResult.php @@ -333,7 +333,7 @@ public function getRowArray(int $n = 0) $this->currentRow = $n; } - return $result[$this->currentRow]; + return $result[$this->currentRow] ?? null; } /** @@ -350,11 +350,11 @@ public function getRowObject(int $n = 0) return null; } - if ($n !== $this->customResultObject && isset($result[$n])) { + if ($n !== $this->currentRow && isset($result[$n])) { $this->currentRow = $n; } - return $result[$this->currentRow]; + return $result[$this->currentRow] ?? null; } /** diff --git a/tests/system/Database/BaseResultTest.php b/tests/system/Database/BaseResultTest.php new file mode 100644 index 000000000000..26fd3a18e153 --- /dev/null +++ b/tests/system/Database/BaseResultTest.php @@ -0,0 +1,249 @@ + + * + * 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; +use PHPUnit\Framework\Attributes\Group; +use stdClass; + +/** + * @internal + */ +#[Group('Database')] +final class BaseResultTest extends CIUnitTestCase +{ + /** + * Create a minimal concrete implementation of BaseResult for testing. + */ + private function createResultDouble(array $resultArray, array $resultObject): BaseResult + { + return new class ($resultArray, $resultObject) extends BaseResult { + public function __construct(array $resultArray, array $resultObject) + { + $this->resultArray = $resultArray; + $this->resultObject = $resultObject; + $this->currentRow = 0; + + $connId = null; + $resultId = null; + parent::__construct($connId, $resultId); + } + + public function getFieldCount(): int + { + return 0; + } + + public function getFieldNames(): array + { + return []; + } + + public function getFieldData(): array + { + return []; + } + + public function freeResult(): void + { + } + + public function dataSeek(int $n = 0): bool + { + return true; + } + + protected function fetchAssoc() + { + return false; + } + + protected function fetchObject(string $className = stdClass::class) + { + return false; + } + }; + } + + // -------------------------------------------------------------------- + // getRowArray() + // -------------------------------------------------------------------- + + public function testGetRowArrayReturnsRow(): void + { + $result = $this->createResultDouble( + [ + ['id' => 1, 'name' => 'John'], + ['id' => 2, 'name' => 'Jane'], + ], + [], + ); + + $this->assertSame(['id' => 1, 'name' => 'John'], $result->getRowArray(0)); + $this->assertSame(['id' => 2, 'name' => 'Jane'], $result->getRowArray(1)); + } + + public function testGetRowArrayReturnsNullForEmptyResult(): void + { + $result = $this->createResultDouble([], []); + + $this->assertNull($result->getRowArray(0)); + } + + public function testGetRowArrayReturnsFirstRowByDefault(): void + { + $result = $this->createResultDouble( + [ + ['id' => 1, 'name' => 'John'], + ['id' => 2, 'name' => 'Jane'], + ], + [], + ); + + $this->assertSame(['id' => 1, 'name' => 'John'], $result->getRowArray()); + } + + // -------------------------------------------------------------------- + // getRowObject() + // -------------------------------------------------------------------- + + public function testGetRowObjectReturnsObject(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + $row2 = new stdClass(); + $row2->id = 2; + $row2->name = 'Jane'; + + $result = $this->createResultDouble([], [$row1, $row2]); + + $this->assertEquals($row1, $result->getRowObject(0)); + $this->assertEquals($row2, $result->getRowObject(1)); + } + + public function testGetRowObjectReturnsNullForEmptyResult(): void + { + $result = $this->createResultDouble([], []); + + $this->assertNull($result->getRowObject(0)); + } + + public function testGetRowObjectReturnsFirstRowByDefault(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + + $result = $this->createResultDouble([], [$row1]); + + $this->assertEquals($row1, $result->getRowObject()); + } + + public function testGetRowObjectAndGetRowArrayShareCurrentRow(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + $row2 = new stdClass(); + $row2->id = 2; + $row2->name = 'Jane'; + + $result = $this->createResultDouble( + [ + ['id' => 1, 'name' => 'John'], + ['id' => 2, 'name' => 'Jane'], + ], + [$row1, $row2], + ); + + // getRowObject(1) should advance currentRow to 1 (same as getRowArray would) + $result->getRowObject(1); + $this->assertSame(['id' => 2, 'name' => 'Jane'], $result->getRowArray(1)); + } + + public function testGetRowObjectUsesCurrentRowLikeGetRowArray(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + $row2 = new stdClass(); + $row2->id = 2; + $row2->name = 'Jane'; + + $result = $this->createResultDouble( + [ + ['id' => 1, 'name' => 'John'], + ['id' => 2, 'name' => 'Jane'], + ], + [$row1, $row2], + ); + + // Both methods should advance currentRow consistently + $result->getRowObject(1); + $result->getRowArray(); + $this->assertEquals($row1, $result->getRowObject()); + } + + // -------------------------------------------------------------------- + // getRow() — convenience wrapper + // -------------------------------------------------------------------- + + public function testGetRowWithInvalidIndexReturnsFirstRow(): void + { + $result = $this->createResultDouble( + [['id' => 1, 'name' => 'John']], + [], + ); + + $this->assertSame(['id' => 1, 'name' => 'John'], $result->getRow(999, 'array')); + } + + public function testGetRowObjectWithInvalidIndexReturnsFirstRow(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + + $result = $this->createResultDouble([], [$row1]); + + $this->assertEquals($row1, $result->getRow(999, 'object')); + } + + public function testGetRowNullForColumnNameNotFound(): void + { + $result = $this->createResultDouble( + [['id' => 1, 'name' => 'John']], + [], + ); + + $this->assertNull($result->getRow('nonexistent', 'array')); + } + + // -------------------------------------------------------------------- + // Custom Result Object + // -------------------------------------------------------------------- + + public function testGetCustomRowObjectReturnsNullForOutOfBounds(): void + { + $row = new stdClass(); + $row->id = 1; + $row->name = 'John'; + + $result = $this->createResultDouble([], [$row]); + $result->getCustomResultObject(stdClass::class); + + $this->assertNull($result->getCustomRowObject(999, stdClass::class)); + } +} From ef673ba46e3eb5a84ef21209f48e1c80832d6642 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 1 Jun 2026 21:47:58 +0200 Subject: [PATCH 2/3] test: add null fallback tests and apply fallback to remaining methods --- tests/system/Database/BaseResultTest.php | 68 ++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/tests/system/Database/BaseResultTest.php b/tests/system/Database/BaseResultTest.php index 26fd3a18e153..f51ab33a8dca 100644 --- a/tests/system/Database/BaseResultTest.php +++ b/tests/system/Database/BaseResultTest.php @@ -237,13 +237,75 @@ public function testGetRowNullForColumnNameNotFound(): void public function testGetCustomRowObjectReturnsNullForOutOfBounds(): void { - $row = new stdClass(); + $row = new stdClass(); $row->id = 1; $row->name = 'John'; $result = $this->createResultDouble([], [$row]); $result->getCustomResultObject(stdClass::class); - $this->assertNull($result->getCustomRowObject(999, stdClass::class)); + $this->assertEquals($row, $result->getCustomRowObject(999, stdClass::class)); } -} + + // -------------------------------------------------------------------- + // Fallback Tests (Null return on invalid currentRow) + // -------------------------------------------------------------------- + + public function testGetRowArrayReturnsNullWhenCurrentRowIsInvalid(): void + { + $result = $this->createResultDouble( + [['id' => 1, 'name' => 'John']], + [] + ); + + $result->currentRow = 999; + + $this->assertNull($result->getRowArray()); + } + + public function testGetRowObjectReturnsNullWhenCurrentRowIsInvalid(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + + $result = $this->createResultDouble( + [], + [$row1] + ); + + $result->currentRow = 999; + + $this->assertNull($result->getRowObject()); + } + + public function testGetCustomRowObjectReturnsNullWhenCurrentRowIsInvalid(): void + { + $row1 = new stdClass(); + $row1->id = 1; + $row1->name = 'John'; + + $result = $this->createResultDouble([], [$row1]); + + $result->getCustomResultObject(stdClass::class); + + $result->currentRow = 999; + + $this->assertNull($result->getCustomRowObject(0, stdClass::class)); + } + + public function testGetPreviousRowReturnsNullWhenCurrentRowIsInvalid(): void + { + $result = $this->createResultDouble( + [ + ['id' => 1], + ['id' => 2], + ], + [] + ); + + $result->currentRow = -1; + + $this->assertNull($result->getPreviousRow()); + } +} \ No newline at end of file From cea2a60f114984a8dc5de3305d82c91e98e9ef27 Mon Sep 17 00:00:00 2001 From: Bogdan Lambarski Date: Tue, 2 Jun 2026 21:39:46 +0200 Subject: [PATCH 3/3] Update tests/system/Database/BaseResultTest.php Co-authored-by: Michal Sniatala --- tests/system/Database/BaseResultTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Database/BaseResultTest.php b/tests/system/Database/BaseResultTest.php index f51ab33a8dca..45a8e972221b 100644 --- a/tests/system/Database/BaseResultTest.php +++ b/tests/system/Database/BaseResultTest.php @@ -20,7 +20,7 @@ /** * @internal */ -#[Group('Database')] +#[Group('DatabaseLive')] final class BaseResultTest extends CIUnitTestCase { /**