From 97d0ed5929018727a8efc5039e2b20b76e89f14e Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sat, 17 Aug 2024 15:28:02 -0700 Subject: [PATCH] Handle cached result column names and rows separately --- src/Cache/ArrayResult.php | 44 +++++------ src/Connection.php | 17 ++++- tests/Cache/ArrayStatementTest.php | 85 ++++++++++------------ tests/Connection/CachedQueryTest.php | 28 +++---- tests/ConnectionTest.php | 2 +- tests/Functional/Cache/ArrayResultTest.php | 45 ++++++++++++ 6 files changed, 133 insertions(+), 88 deletions(-) create mode 100644 tests/Functional/Cache/ArrayResultTest.php diff --git a/src/Cache/ArrayResult.php b/src/Cache/ArrayResult.php index e929d7f74de..32dc63eda82 100644 --- a/src/Cache/ArrayResult.php +++ b/src/Cache/ArrayResult.php @@ -8,24 +8,29 @@ use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Exception\InvalidColumnIndex; -use function array_keys; -use function array_values; +use function array_combine; use function count; -use function reset; /** @internal The class is internal to the caching layer implementation. */ final class ArrayResult implements Result { - private readonly int $columnCount; private int $num = 0; - /** @param list> $data */ - public function __construct(private array $data) + /** + * @param list $columnNames The names of the result columns. Must be non-empty. + * @param list> $rows The rows of the result. Each row must have the same number of columns + * as the number of column names. + */ + public function __construct(private readonly array $columnNames, private array $rows) { - $this->columnCount = $data === [] ? 0 : count($data[0]); } public function fetchNumeric(): array|false + { + return $this->fetch(); + } + + public function fetchAssociative(): array|false { $row = $this->fetch(); @@ -33,12 +38,7 @@ public function fetchNumeric(): array|false return false; } - return array_values($row); - } - - public function fetchAssociative(): array|false - { - return $this->fetch(); + return array_combine($this->columnNames, $row); } public function fetchOne(): mixed @@ -49,7 +49,7 @@ public function fetchOne(): mixed return false; } - return reset($row); + return $row[0]; } /** @@ -78,35 +78,35 @@ public function fetchFirstColumn(): array public function rowCount(): int { - return count($this->data); + return count($this->rows); } public function columnCount(): int { - return $this->columnCount; + return count($this->columnNames); } public function getColumnName(int $index): string { - if ($this->data === [] || $index > count($this->data[0])) { + if ($index > count($this->columnNames)) { throw InvalidColumnIndex::new($index); } - return array_keys($this->data[0])[$index]; + return $this->columnNames[$index]; } public function free(): void { - $this->data = []; + $this->rows = []; } - /** @return array|false */ + /** @return list|false */ private function fetch(): array|false { - if (! isset($this->data[$this->num])) { + if (! isset($this->rows[$this->num])) { return false; } - return $this->data[$this->num++]; + return $this->rows[$this->num++]; } } diff --git a/src/Connection.php b/src/Connection.php index 3bb951445ff..f4ba0963307 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -811,15 +811,24 @@ public function executeCacheQuery(string $sql, array $params, array $types, Quer } if (isset($value[$realKey])) { - return new Result(new ArrayResult($value[$realKey]), $this); + [$columnNames, $rows] = $value[$realKey]; + + return new Result(new ArrayResult($columnNames, $rows), $this); } } else { $value = []; } - $data = $this->fetchAllAssociative($sql, $params, $types); + $result = $this->executeQuery($sql, $params, $types); + + $columnNames = []; + for ($i = 0; $i < $result->columnCount(); $i++) { + $columnNames[] = $result->getColumnName($i); + } + + $rows = $result->fetchAllNumeric(); - $value[$realKey] = $data; + $value[$realKey] = [$columnNames, $rows]; $item->set($value); @@ -830,7 +839,7 @@ public function executeCacheQuery(string $sql, array $params, array $types, Quer $resultCache->save($item); - return new Result(new ArrayResult($data), $this); + return new Result(new ArrayResult($columnNames, $rows), $this); } /** diff --git a/tests/Cache/ArrayStatementTest.php b/tests/Cache/ArrayStatementTest.php index 361e8663429..38cd81f64c4 100644 --- a/tests/Cache/ArrayStatementTest.php +++ b/tests/Cache/ArrayStatementTest.php @@ -7,86 +7,75 @@ use Doctrine\DBAL\Cache\ArrayResult; use PHPUnit\Framework\TestCase; -use function array_values; - class ArrayStatementTest extends TestCase { - /** @var list> */ - private array $users = [ - [ - 'username' => 'jwage', - 'active' => true, - ], - [ - 'username' => 'romanb', - 'active' => false, - ], - ]; - - public function testCloseCursor(): void + private ArrayResult $result; + + protected function setUp(): void { - $statement = $this->createTestArrayStatement(); + parent::setUp(); + + $this->result = new ArrayResult(['username', 'active'], [ + ['jwage', true], + ['romanb', false], + ]); + } - self::assertSame(2, $statement->rowCount()); + public function testFree(): void + { + self::assertSame(2, $this->result->rowCount()); - $statement->free(); + $this->result->free(); - self::assertSame(0, $statement->rowCount()); + self::assertSame(0, $this->result->rowCount()); } public function testColumnCount(): void { - $statement = $this->createTestArrayStatement(); - - self::assertSame(2, $statement->columnCount()); + self::assertSame(2, $this->result->columnCount()); } public function testColumnNames(): void { - $statement = $this->createTestArrayStatement(); - - self::assertSame('username', $statement->getColumnName(0)); - self::assertSame('active', $statement->getColumnName(1)); + self::assertSame('username', $this->result->getColumnName(0)); + self::assertSame('active', $this->result->getColumnName(1)); } public function testRowCount(): void { - $statement = $this->createTestArrayStatement(); - - self::assertSame(2, $statement->rowCount()); + self::assertSame(2, $this->result->rowCount()); } public function testFetchAssociative(): void { - $statement = $this->createTestArrayStatement(); - - self::assertSame($this->users[0], $statement->fetchAssociative()); + self::assertSame([ + 'username' => 'jwage', + 'active' => true, + ], $this->result->fetchAssociative()); } public function testFetchNumeric(): void { - $statement = $this->createTestArrayStatement(); - - self::assertSame(array_values($this->users[0]), $statement->fetchNumeric()); + self::assertSame(['jwage', true], $this->result->fetchNumeric()); } public function testFetchOne(): void { - $statement = $this->createTestArrayStatement(); - - self::assertSame('jwage', $statement->fetchOne()); - self::assertSame('romanb', $statement->fetchOne()); - } - - public function testFetchAll(): void - { - $statement = $this->createTestArrayStatement(); - - self::assertSame($this->users, $statement->fetchAllAssociative()); + self::assertSame('jwage', $this->result->fetchOne()); + self::assertSame('romanb', $this->result->fetchOne()); } - private function createTestArrayStatement(): ArrayResult + public function testFetchAllAssociative(): void { - return new ArrayResult($this->users); + self::assertSame([ + [ + 'username' => 'jwage', + 'active' => true, + ], + [ + 'username' => 'romanb', + 'active' => false, + ], + ], $this->result->fetchAllAssociative()); } } diff --git a/tests/Connection/CachedQueryTest.php b/tests/Connection/CachedQueryTest.php index ae82a3c8c25..7c908a15541 100644 --- a/tests/Connection/CachedQueryTest.php +++ b/tests/Connection/CachedQueryTest.php @@ -16,31 +16,30 @@ class CachedQueryTest extends TestCase public function testCachedQuery(): void { $cache = new ArrayAdapter(); - $data = [['foo' => 'bar']]; - $connection = $this->createConnection(1, $data); + $connection = $this->createConnection(1, ['foo'], [['bar']]); $qcp = new QueryCacheProfile(0, __FUNCTION__, $cache); - self::assertSame($data, $connection->executeCacheQuery('SELECT 1', [], [], $qcp)->fetchAllAssociative()); - self::assertSame($data, $connection->executeCacheQuery('SELECT 1', [], [], $qcp)->fetchAllAssociative()); + self::assertSame([['foo' => 'bar']], $connection->executeCacheQuery('SELECT 1', [], [], $qcp) + ->fetchAllAssociative()); + self::assertSame([['foo' => 'bar']], $connection->executeCacheQuery('SELECT 1', [], [], $qcp) + ->fetchAllAssociative()); self::assertCount(1, $cache->getItem(__FUNCTION__)->get()); } public function testCachedQueryWithChangedImplementationIsExecutedTwice(): void { - $data = [['baz' => 'qux']]; + $connection = $this->createConnection(2, ['baz'], [['qux']]); - $connection = $this->createConnection(2, $data); - - self::assertSame($data, $connection->executeCacheQuery( + self::assertSame([['baz' => 'qux']], $connection->executeCacheQuery( 'SELECT 1', [], [], new QueryCacheProfile(0, __FUNCTION__, new ArrayAdapter()), )->fetchAllAssociative()); - self::assertSame($data, $connection->executeCacheQuery( + self::assertSame([['baz' => 'qux']], $connection->executeCacheQuery( 'SELECT 1', [], [], @@ -48,14 +47,17 @@ public function testCachedQueryWithChangedImplementationIsExecutedTwice(): void )->fetchAllAssociative()); } - /** @param list> $data */ - private function createConnection(int $expectedQueryCount, array $data): Connection + /** + * @param list $columnNames + * @param list> $rows + */ + private function createConnection(int $expectedQueryCount, array $columnNames, array $rows): Connection { $connection = $this->createMock(Driver\Connection::class); $connection->expects(self::exactly($expectedQueryCount)) ->method('query') - ->willReturnCallback(static function () use ($data): ArrayResult { - return new ArrayResult($data); + ->willReturnCallback(static function () use ($columnNames, $rows): ArrayResult { + return new ArrayResult($columnNames, $rows); }); $driver = $this->createMock(Driver::class); diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 5f966599099..29922683d4e 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -569,7 +569,7 @@ public function testConnectionParamsArePassedToTheQueryCacheProfileInExecuteCach { $cacheItemMock = $this->createMock(CacheItemInterface::class); $cacheItemMock->method('isHit')->willReturn(true); - $cacheItemMock->method('get')->willReturn(['realKey' => []]); + $cacheItemMock->method('get')->willReturn(['realKey' => [[], []]]); $resultCacheMock = $this->createMock(CacheItemPoolInterface::class); diff --git a/tests/Functional/Cache/ArrayResultTest.php b/tests/Functional/Cache/ArrayResultTest.php new file mode 100644 index 00000000000..f2c3f166d24 --- /dev/null +++ b/tests/Functional/Cache/ArrayResultTest.php @@ -0,0 +1,45 @@ +connection->getDatabasePlatform()); + $result = $this->connection->executeCacheQuery( + $query, + [], + [], + new QueryCacheProfile(0, __FUNCTION__, new ArrayAdapter()), + ); + self::assertSame('a', $result->getColumnName(0)); + } + + public function testSameColumnNames(): void + { + $query = TestUtil::generateResultSetQuery( + ['a', 'a'], + [[1, 2]], + $this->connection->getDatabasePlatform(), + ); + + $result = $this->connection->executeCacheQuery( + $query, + [], + [], + new QueryCacheProfile(0, __FUNCTION__, new ArrayAdapter()), + ); + self::assertSame('a', $result->getColumnName(0)); + self::assertSame('a', $result->getColumnName(1)); + + self::assertEquals([1, 2], $result->fetchNumeric()); + } +}