Skip to content

Commit 3d3ef9f

Browse files
committed
PDOStatement replaced by Result
1 parent 3427ba4 commit 3d3ef9f

File tree

9 files changed

+151
-61
lines changed

9 files changed

+151
-61
lines changed

src/Database/Drivers/Accessory/LazyConnection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private function getConnection(): Drivers\Connection
2626
}
2727

2828

29-
public function query(string $sql, array $params = [])
29+
public function query(string $sql, array $params = []): Drivers\Result
3030
{
3131
return $this->getConnection()->query($sql, $params);
3232
}

src/Database/Drivers/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
interface Connection
1818
{
1919
/** Executes an SQL query with optional parameters and returns a result set. */
20-
function query(string $sql, array $params = []);
20+
function query(string $sql, array $params = []): Result;
2121

2222
/** Executes an SQL command and returns the number of affected rows. */
2323
function execute(string $sql): int;

src/Database/Drivers/PDO/Connection.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function __construct(
2424
}
2525

2626

27-
public function query(string $sql, array $params = [])
27+
public function query(string $sql, array $params = []): Result
2828
{
2929
$types = ['boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT, 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL];
3030

@@ -33,9 +33,8 @@ public function query(string $sql, array $params = [])
3333
$statement->bindValue(is_int($key) ? $key + 1 : $key, $value, $types[gettype($value)] ?? PDO::PARAM_STR);
3434
}
3535

36-
$statement->setFetchMode(PDO::FETCH_ASSOC);
3736
$statement->execute();
38-
return $statement;
37+
return new Result($statement, $this);
3938
}
4039

4140

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Database\Drivers\PDO;
11+
12+
use Nette\Database\DriverException;
13+
use Nette\Database\Drivers;
14+
15+
16+
class Result implements Drivers\Result
17+
{
18+
private array $columns;
19+
20+
21+
public function __construct(
22+
protected readonly \PDOStatement $result,
23+
protected readonly Connection $connection,
24+
) {
25+
}
26+
27+
28+
public function fetch(): ?array
29+
{
30+
$row = $this->fetchList();
31+
if (!$row) {
32+
return null;
33+
}
34+
35+
$res = [];
36+
foreach ($this->getColumnsInfo() as $i => $meta) {
37+
$res[$meta['name']] = $row[$i];
38+
}
39+
return $res;
40+
}
41+
42+
43+
public function fetchList(): ?array
44+
{
45+
$row = $this->result->fetch(\PDO::FETCH_NUM);
46+
if (!$row) {
47+
$this->free();
48+
return null;
49+
}
50+
return $row;
51+
}
52+
53+
54+
public function getColumnCount(): int
55+
{
56+
return $this->result->columnCount();
57+
}
58+
59+
60+
public function getRowCount(): int
61+
{
62+
return $this->result->rowCount();
63+
}
64+
65+
66+
public function getColumnsInfo(): array
67+
{
68+
return $this->columns ??= $this->collectColumnsInfo();
69+
}
70+
71+
72+
protected function collectColumnsInfo(): array
73+
{
74+
$res = [];
75+
$count = $this->result->columnCount();
76+
for ($i = 0; $i < $count; $i++) {
77+
$meta = $this->result->getColumnMeta($i) ?: throw new DriverException('Cannot fetch column metadata');
78+
$res[] = [
79+
'name' => $meta['name'],
80+
'nativeType' => $meta[$this->connection->metaTypeKey] ?? null,
81+
'size' => $meta['len'],
82+
'scale' => $meta['precision'],
83+
];
84+
}
85+
return $res;
86+
}
87+
88+
89+
public function free(): void
90+
{
91+
$this->result->closeCursor();
92+
}
93+
}

src/Database/Drivers/Result.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Database\Drivers;
11+
12+
13+
/**
14+
* Database query result set.
15+
*/
16+
interface Result
17+
{
18+
/** Fetches the next row from the result set as an associative array. */
19+
function fetch(): ?array;
20+
21+
/** Fetches the next row from the result set as an indexed array. */
22+
function fetchList(): ?array;
23+
24+
/** Returns the number of columns in the result set. */
25+
function getColumnCount(): int;
26+
27+
/** Returns the number of rows in the result set or number of affected rows */
28+
function getRowCount(): int;
29+
30+
/**
31+
* Returns metadata for all columns in a result set.
32+
* @return list<array{name: string, nativeType: ?string, size: ?int, scale: ?int}>
33+
*/
34+
function getColumnsInfo(): array;
35+
36+
/** Frees the result set. */
37+
function free(): void;
38+
}

src/Database/Helpers.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -277,29 +277,6 @@ public static function toPairs(array $rows, string|int|\Closure|null $key, strin
277277
}
278278

279279

280-
/**
281-
* Returns duplicate columns from result set.
282-
*/
283-
public static function findDuplicates(\PDOStatement $statement): string
284-
{
285-
$cols = [];
286-
for ($i = 0; $i < $statement->columnCount(); $i++) {
287-
$meta = $statement->getColumnMeta($i);
288-
$cols[$meta['name']][] = $meta['table'] ?? '';
289-
}
290-
291-
$duplicates = [];
292-
foreach ($cols as $name => $tables) {
293-
if (count($tables) > 1) {
294-
$tables = array_filter(array_unique($tables));
295-
$duplicates[] = "'$name'" . ($tables ? ' (from ' . implode(', ', $tables) . ')' : '');
296-
}
297-
}
298-
299-
return implode(', ', $duplicates);
300-
}
301-
302-
303280
/** @return array{type: ?string, size: ?int, scale: ?int, parameters: ?string} */
304281
public static function parseColumnType(string $type): array
305282
{

src/Database/Result.php

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020
class Result implements \Iterator
2121
{
22-
private ?\PDOStatement $pdoStatement = null;
22+
private ?Drivers\Result $result = null;
2323
private Row|false|null $lastRow = null;
2424
private int $lastRowKey = -1;
2525

@@ -41,7 +41,7 @@ public function __construct(
4141
if (str_starts_with($queryString, '::')) {
4242
$connection->getConnection()->{substr($queryString, 2)}();
4343
} else {
44-
$this->pdoStatement = $connection->getConnection()->query($queryString, $params);
44+
$this->result = $connection->getConnection()->query($queryString, $params);
4545
}
4646
} catch (\PDOException $e) {
4747
$e = $connection->getDatabaseEngine()->convertException($e);
@@ -62,13 +62,6 @@ public function getConnection(): Connection
6262
}
6363

6464

65-
/** @internal */
66-
public function getPdoStatement(): ?\PDOStatement
67-
{
68-
return $this->pdoStatement;
69-
}
70-
71-
7265
public function getQueryString(): string
7366
{
7467
return $this->queryString;
@@ -83,13 +76,13 @@ public function getParameters(): array
8376

8477
public function getColumnCount(): ?int
8578
{
86-
return $this->pdoStatement ? $this->pdoStatement->columnCount() : null;
79+
return $this->result?->getColumnCount();
8780
}
8881

8982

9083
public function getRowCount(): ?int
9184
{
92-
return $this->pdoStatement ? $this->pdoStatement->rowCount() : null;
85+
return $this->result?->getRowCount();
9386
}
9487

9588

@@ -162,14 +155,13 @@ public function fetchAssoc(?string $path = null): ?array
162155
return Arrays::associate($this->fetchAll(), $path);
163156
}
164157

165-
$data = $this->pdoStatement ? $this->pdoStatement->fetch() : null;
166-
if (!$data) {
167-
$this->pdoStatement->closeCursor();
158+
$data = $this->result?->fetch();
159+
if ($data === null) {
168160
return null;
169161

170-
} elseif ($this->lastRow === null && count($data) !== $this->pdoStatement->columnCount()) {
171-
$duplicates = Helpers::findDuplicates($this->pdoStatement);
172-
trigger_error("Found duplicate columns in database result set: $duplicates.");
162+
} elseif ($this->lastRow === null && count($data) !== $this->result->getColumnCount()) {
163+
$duplicates = array_filter(array_count_values(array_column($this->result->getColumnsInfo(), 'name')), fn($val) => $val > 1);
164+
trigger_error("Found duplicate columns in database result set: '" . implode("', '", array_keys($duplicates)) . "'.");
173165
}
174166

175167
return $this->normalizeRow($data);
@@ -259,17 +251,8 @@ private function normalizeRow(array $row): array
259251
private function getColumnsMeta(): array
260252
{
261253
$res = [];
262-
$metaTypeKey = $this->connection->getConnection()->metaTypeKey;
263-
$count = $this->pdoStatement->columnCount();
264-
for ($i = 0; $i < $count; $i++) {
265-
$meta = $this->pdoStatement->getColumnMeta($i);
266-
if (isset($meta[$metaTypeKey])) {
267-
$res[$meta['name']] = [
268-
'nativeType' => $meta[$metaTypeKey],
269-
'size' => $meta['len'],
270-
'scale' => $meta['precision'],
271-
];
272-
}
254+
foreach ($this->result->getColumnsInfo() as $meta) {
255+
$res[$meta['name']] = $meta;
273256
}
274257
return $res;
275258
}

src/Database/TypeConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private function detectType(string $nativeType): int
6363

6464
public function convertToPhp(mixed $value, array $meta): mixed
6565
{
66-
return match ($this->detectType($meta['nativeType'])) {
66+
return match ($this->detectType($meta['nativeType'] ?? '')) {
6767
self::Integer => $this->toInt($value),
6868
self::Float => $this->toFloat($value),
6969
self::Decimal => $this->convertDecimal

tests/Database/Result.fetch().phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName
1818
test('detects duplicate column names in simple query', function () use ($connection, $driverName) {
1919
$res = $connection->query('SELECT name, name FROM author');
2020
$message = match ($driverName) {
21-
'mysql' => "Found duplicate columns in database result set: 'name' (from author).",
21+
'mysql' => "Found duplicate columns in database result set: 'name'.",
2222
'pgsql' => "Found duplicate columns in database result set: 'name'%a%",
23-
'sqlite' => "Found duplicate columns in database result set: 'name' (from author).",
23+
'sqlite' => "Found duplicate columns in database result set: 'name'.",
2424
'sqlsrv' => "Found duplicate columns in database result set: 'name'.",
2525
default => Assert::fail("Unsupported driver $driverName"),
2626
};
@@ -54,9 +54,9 @@ test('handles cursor management in stored procedures', function () use ($connect
5454
test('detects duplicate columns in JOINed tables', function () use ($connection, $driverName) {
5555
$res = $connection->query('SELECT book.id, author.id, author.name, translator.name FROM book JOIN author ON (author.id = book.author_id) JOIN author translator ON (translator.id = book.translator_id)');
5656
$message = match ($driverName) {
57-
'mysql' => "Found duplicate columns in database result set: 'id' (from book, author), 'name' (from author, translator).",
57+
'mysql' => "Found duplicate columns in database result set: 'id', 'name'.",
5858
'pgsql' => "Found duplicate columns in database result set: 'id'%a% 'name'%a%",
59-
'sqlite' => "Found duplicate columns in database result set: 'id' (from book, author), 'name' (from author).",
59+
'sqlite' => "Found duplicate columns in database result set: 'id', 'name'.",
6060
'sqlsrv' => "Found duplicate columns in database result set: 'id', 'name'.",
6161
default => Assert::fail("Unsupported driver $driverName"),
6262
};

0 commit comments

Comments
 (0)