Skip to content

Commit

Permalink
added Mapper, replaces rowNormalizer (BC break)
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Aug 19, 2024
1 parent a066a4c commit 652d293
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 54 deletions.
24 changes: 18 additions & 6 deletions src/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ class Connection
private ?Drivers\Engine $engine;
private ?SqlPreprocessor $preprocessor;
private TypeConverter $typeConverter;

/** @var callable(array, ResultSet): array */
private $rowNormalizer = [Helpers::class, 'normalizeRow'];
private ?Mapping\Mapper $mapper = null;
private ?string $sql = null;
private int $transactionDepth = 0;

Expand Down Expand Up @@ -143,13 +141,27 @@ public function getTypeConverter(): TypeConverter
}


public function setRowNormalizer(?callable $normalizer): static
/** @internal experimental feature */
public function setMapper(?Mapping\Mapper $mapper): static
{
$this->rowNormalizer = $normalizer;
$this->mapper = $mapper;
return $this;
}


public function getMapper(): ?Mapping\Mapper
{
return $this->mapper;
}


/** @deprecated use setMapper() */
public function setRowNormalizer(?callable $normalizer): static
{
throw new Nette\DeprecatedException(__METHOD__ . "() is deprecated, use setMapper() or configure 'convert*' options.");
}


public function getInsertId(?string $sequence = null): int|string
{
return $this->getConnectionDriver()->getInsertId($sequence);
Expand Down Expand Up @@ -230,7 +242,7 @@ public function query(#[Language('SQL')] string $sql, #[Language('GenericSQL')]
$time = microtime(true);
$result = $this->connection->query($this->sql, $params);
$time = microtime(true) - $time;
$resultSet = new ResultSet($this, $result, new SqlLiteral($this->sql, $params), $this->rowNormalizer, $time);
$resultSet = new ResultSet($this, $result, new SqlLiteral($this->sql, $params), $time);
} catch (DriverException $e) {
Arrays::invoke($this->onQuery, $this, $e);
throw $e;
Expand Down
17 changes: 0 additions & 17 deletions src/Database/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,6 @@ public static function dumpSql(string $sql, ?array $params = null, ?Connection $
}


/** @internal */
public static function normalizeRow(
array $row,
ResultSet $resultSet,
$dateTimeClass = DateTime::class,
): array
{
foreach ($resultSet->resolveColumnConverters() as $key => $converter) {
$value = $row[$key];
$row[$key] = isset($value, $converter)
? $converter($value)
: $value;
}
return $row;
}


/**
* Import SQL dump from file - extremely fast.
* @param ?array<callable(int, ?float): void> $onProgress
Expand Down
19 changes: 19 additions & 0 deletions src/Database/Mapping/Mapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Mapping;


/**
* @internal This is experimental feature
*/
interface Mapper
{
function mapRow(array $row): mixed;
}
63 changes: 35 additions & 28 deletions src/Database/ResultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
*/
class ResultSet implements \Iterator
{
/** @var callable(array, ResultSet): array */
private readonly mixed $normalizer;
private Row|false|null $lastRow = null;
private int $lastRowKey = -1;

Expand All @@ -31,10 +29,8 @@ public function __construct(
private readonly Connection $connection,
private readonly Drivers\Result $result,
private readonly ?SqlLiteral $query = null,
?callable $normalizer = null,
private ?float $time = null,
) {
$this->normalizer = $normalizer;
}


Expand Down Expand Up @@ -76,15 +72,6 @@ public function getTime(): float
}


/** @internal */
public function normalizeRow(array $row): array
{
return $this->normalizer
? ($this->normalizer)($row, $this)
: $row;
}


/********************* misc tools ****************d*g**/


Expand Down Expand Up @@ -136,26 +123,25 @@ public function valid(): bool
}


/********************* fetch ****************d*g**/


/**
* Fetches single row object.
*/
public function fetch(): ?Row
public function fetch(): mixed
{
$data = $this->result->fetch();
if ($data === null) {
$row = $this->result->fetch();
if ($row === null) {
return null;

} elseif ($this->lastRow === null && count($data) !== $this->result->getColumnCount()) {
} elseif ($this->lastRow === null && count($row) !== $this->result->getColumnCount()) {
$duplicates = array_filter(array_count_values(array_column($this->result->getColumnsInfo(), 'name')), fn($val) => $val > 1);
trigger_error("Found duplicate columns in database result set: '" . implode("', '", array_keys($duplicates)) . "'.");
}

$row = new Row;
foreach ($this->normalizeRow($data) as $key => $value) {
if ($key !== '') {
$row->$key = $value;
}
}
$row = $this->convertTypes($row);
$row = $this->connection->getMapper()?->mapRow($row, $this) ?? $this->mapRow($row);

$this->lastRowKey++;
return $this->lastRow = $row;
Expand Down Expand Up @@ -218,12 +204,23 @@ public function fetchAssoc(string $path): array
}


public function resolveColumnConverters(): array
{
if (isset($this->converters)) {
return $this->converters;
/** @internal */
public function convertTypes(array $row): array
{
$converters = $this->converters ??= $this->resolveColumnConverters();
foreach ($row as $key => $value) {
$converter = $converters[$key];
$row[$key] = isset($value, $converter)
? $converter($value)
: $value;
}

return $row;
}


private function resolveColumnConverters(): array
{
$res = [];
$engine = $this->connection->getDatabaseEngine();
$converter = $this->connection->getTypeConverter();
Expand All @@ -232,6 +229,16 @@ public function resolveColumnConverters(): array
? $engine->resolveColumnConverter($meta, $converter)
: null;
}
return $this->converters = $res;
return $res;
}


private function mapRow(?array $row): Row
{
$res = new Row;
foreach ($row as $key => $value) {
$res->$key = $value;
}
return $res;
}
}
2 changes: 1 addition & 1 deletion src/Database/Table/Selection.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ protected function execute(): void
$usedPrimary = true;
$key = 0;
while ($row = $result->fetchAssociative()) {
$row = $this->createRow($result->normalizeRow($row));
$row = $this->createRow($result->convertTypes($row));
$primary = $row->getSignature(false);
$usedPrimary = $usedPrimary && $primary !== '';
$this->rows[$usedPrimary ? $primary : $key] = $row;
Expand Down
5 changes: 3 additions & 2 deletions tests/Database/ResultSet.customNormalizer.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName
$connection->query('UPDATE author SET born=?', new DateTime('2022-01-23'));


// TODO
test('disabled normalization', function () use ($connection) {
$driverName = $GLOBALS['driverName'];

$connection->setRowNormalizer(null);
$connection->getTypeConverter()->convertDateTime = false;
$res = $connection->query('SELECT * FROM author');
$asInt = $driverName === 'pgsql' || ($driverName !== 'sqlsrv' && PHP_VERSION_ID >= 80100);
Assert::same([
Expand All @@ -34,7 +35,7 @@ test('disabled normalization', function () use ($connection) {
test('custom normalization', function () use ($connection) {
$driverName = $GLOBALS['driverName'];

$connection->setRowNormalizer(function (array $row, Nette\Database\ResultSet $resultSet) {
@$connection->setRowNormalizer(function (array $row, Nette\Database\ResultSet $resultSet) { // deprecated
foreach ($row as $key => $value) {
unset($row[$key]);
$row['_' . $key . '_'] = (string) $value;
Expand Down
58 changes: 58 additions & 0 deletions tests/Database/ResultSet.mapper.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

/**
* @dataProvider? databases.ini
*/

use Nette\Database\Mapping;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';

$connection = connectToDB()->getConnection();
Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/files/{$driverName}-nette_test1.sql");

$connection->query('UPDATE author SET born=?', new DateTime('2022-01-23'));


test('disabled normalization', function () use ($connection) {
$driverName = $GLOBALS['driverName'];

$connection->setMapper(null);
$res = $connection->query('SELECT * FROM author');
$asInt = $driverName === 'pgsql' || ($driverName !== 'sqlsrv' && PHP_VERSION_ID >= 80100);
Assert::same([
'id' => $asInt ? 11 : '11',
'name' => 'Jakub Vrana',
'web' => 'http://www.vrana.cz/',
'born' => $driverName === 'sqlite' ? ($asInt ? 1_642_892_400 : '1642892400') : '2022-01-23',
], (array) $res->fetch());
});


class TestMapper implements Mapping\Mapper
{
public function mapRow(array $row): array
{
foreach ($row as $key => $value) {
unset($row[$key]);
$row['_' . $key . '_'] = (string) $value;
}
return $row;
}
}

test('custom normalization', function () use ($connection) {
$driverName = $GLOBALS['driverName'];

$connection->setMapper(new TestMapper);
$res = $connection->query('SELECT * FROM author');
Assert::same([
'_id_' => '11',
'_name_' => 'Jakub Vrana',
'_web_' => 'http://www.vrana.cz/',
'_born_' => $driverName === 'sqlite' ? '1642892400' : '2022-01-23',
], (array) $res->fetch());
});

0 comments on commit 652d293

Please sign in to comment.