Skip to content

Commit

Permalink
Result::getColumnTypes() redesigned, uses TypeConverter
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Aug 19, 2024
1 parent c5617a0 commit 1307ef8
Show file tree
Hide file tree
Showing 17 changed files with 191 additions and 153 deletions.
11 changes: 10 additions & 1 deletion src/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Connection
private ?Drivers\Connection $connection = null;
private ?Drivers\Engine $engine;
private ?SqlPreprocessor $preprocessor;
private TypeConverter $typeConverter;

/** @var callable(array, ResultSet): array */
private $rowNormalizer = [Helpers::class, 'normalizeRow'];
Expand All @@ -49,7 +50,9 @@ public function __construct(
$lazy = $options['lazy'] ?? false;
unset($options['newDateTime'], $options['lazy']);

$this->driver = (new Factory)->createDriverFromDsn($dsn, $user, $password, $options);
$factory = new Factory;
$this->typeConverter = $factory->createTypeConverter($options);
$this->driver = $factory->createDriverFromDsn($dsn, $user, $password, $options);
if (!$lazy) {
$this->connect();
}
Expand Down Expand Up @@ -134,6 +137,12 @@ public function getReflection(): Reflection
}


public function getTypeConverter(): TypeConverter
{
return $this->typeConverter;
}


public function setRowNormalizer(?callable $normalizer): static
{
$this->rowNormalizer = $normalizer;
Expand Down
7 changes: 2 additions & 5 deletions src/Database/Drivers/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Nette\Database\Drivers;

use Nette\Database;
use Nette\Database\TypeConverter;


/**
Expand Down Expand Up @@ -69,11 +70,7 @@ function getIndexes(string $table): array;
/** @return list<array{name: string, local: string, table: string, foreign: string}> */
function getForeignKeys(string $table): array;

/**
* Returns associative array of detected types (IStructure::FIELD_*) in result set.
* @return array<string, string>
*/
function getColumnTypes(\PDOStatement $statement): array;
function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure;

/**
* Cheks if driver supports specific property
Expand Down
5 changes: 3 additions & 2 deletions src/Database/Drivers/Engines/MSSQLEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Nette;
use Nette\Database\Drivers\Connection;
use Nette\Database\Drivers\Engine;
use Nette\Database\TypeConverter;


/**
Expand Down Expand Up @@ -213,9 +214,9 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
return Nette\Database\Helpers::detectTypes($statement);
return $converter->resolve($meta['nativeType']);
}


Expand Down
28 changes: 9 additions & 19 deletions src/Database/Drivers/Engines/MySQLEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Nette\Database\Drivers\Engines;

use Nette;
use Nette\Database\DateTime;
use Nette\Database\Drivers\Connection;
use Nette\Database\Drivers\Engine;
use Nette\Database\TypeConverter;
Expand All @@ -20,9 +21,6 @@
*/
class MySQLEngine implements Engine
{
public bool $convertBoolean = true;


public function __construct(
private readonly Connection $connection,
) {
Expand Down Expand Up @@ -176,23 +174,15 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
$types = [];
$count = $statement->columnCount();
for ($col = 0; $col < $count; $col++) {
$meta = $statement->getColumnMeta($col);
if (isset($meta['native_type'])) {
$types[$meta['name']] = match (true) {
$meta['native_type'] === 'NEWDECIMAL' && $meta['precision'] === 0 => Nette\Database\IStructure::FIELD_INTEGER,
$meta['native_type'] === 'TINY' && $meta['len'] === 1 && $this->convertBoolean => Nette\Database\IStructure::FIELD_BOOL,
$meta['native_type'] === 'TIME' => Nette\Database\IStructure::FIELD_TIME_INTERVAL,
default => TypeConverter::detectType($meta['native_type']),
};
}
}

return $types;
return match ($meta['nativeType']) {
'NEWDECIMAL' => $meta['precision'] === 0 ? $converter->toInt(...) : $converter->toFloat(...), // precision in PDOStatement::getColumnMeta() means scale
'TINY' => $meta['length'] === 1 && $converter->convertBoolean ? $converter->toBool(...) : $converter->toInt(...),
'TIME' => $converter->toInterval(...),
'DATE', 'DATETIME', 'TIMESTAMP' => fn($value): ?DateTime => str_starts_with($value, '0000-00') ? null : $converter->toDateTime($value),
default => $converter->resolve($meta['nativeType']),
};
}


Expand Down
5 changes: 3 additions & 2 deletions src/Database/Drivers/Engines/ODBCEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Nette;
use Nette\Database\Drivers\Engine;
use Nette\Database\TypeConverter;


/**
Expand Down Expand Up @@ -96,9 +97,9 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
return [];
return $converter->resolve($meta['nativeType']);
}


Expand Down
5 changes: 3 additions & 2 deletions src/Database/Drivers/Engines/OracleEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Nette;
use Nette\Database\Drivers\Connection;
use Nette\Database\Drivers\Engine;
use Nette\Database\TypeConverter;


/**
Expand Down Expand Up @@ -130,9 +131,9 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
return [];
return $converter->resolve($meta['nativeType']);
}


Expand Down
10 changes: 5 additions & 5 deletions src/Database/Drivers/Engines/PostgreSQLEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Nette;
use Nette\Database\Drivers\Connection;
use Nette\Database\Drivers\Engine;
use Nette\Database\TypeConverter;


/**
Expand Down Expand Up @@ -238,12 +239,11 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
static $cache;
$item = &$cache[$statement->queryString];
$item ??= Nette\Database\Helpers::detectTypes($statement);
return $item;
return $meta['nativeType'] === 'bool'
? fn($value): bool => ($value && $value !== 'f' && $value !== 'F')
: $converter->resolve($meta['nativeType']);
}


Expand Down
23 changes: 7 additions & 16 deletions src/Database/Drivers/Engines/SQLServerEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,23 +218,14 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
$types = [];
$count = $statement->columnCount();
for ($col = 0; $col < $count; $col++) {
$meta = $statement->getColumnMeta($col);
if (
isset($meta['sqlsrv:decl_type'])
&& $meta['sqlsrv:decl_type'] !== 'timestamp'
) { // timestamp does not mean time in sqlsrv
$types[$meta['name']] = TypeConverter::detectType($meta['sqlsrv:decl_type']);
} elseif (isset($meta['native_type'])) {
$types[$meta['name']] = TypeConverter::detectType($meta['native_type']);
}
}

return $types;
return match ($meta['nativeType']) {
'timestamp' => null, // timestamp does not mean time in sqlsrv
'decimal', 'numeric',
'double', 'double precision', 'float', 'real', 'money', 'smallmoney' => fn($value): float => (float) (is_string($value) && str_starts_with($value, '.') ? '0' . $value : $value),
default => $converter->resolve($meta['nativeType']),
};
}


Expand Down
20 changes: 5 additions & 15 deletions src/Database/Drivers/Engines/SQLiteEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Nette\Database\Drivers\Engines;

use Nette;
use Nette\Database\DateTime;
use Nette\Database\Drivers\Connection;
use Nette\Database\Drivers\Engine;
use Nette\Database\TypeConverter;
Expand Down Expand Up @@ -228,22 +229,11 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
public function resolveColumnConverter(array $meta, TypeConverter $converter): ?\Closure
{
$types = [];
$count = $statement->columnCount();
for ($col = 0; $col < $count; $col++) {
$meta = $statement->getColumnMeta($col);
if (isset($meta['sqlite:decl_type'])) {
$types[$meta['name']] = $this->formatDateTime === 'U' && in_array($meta['sqlite:decl_type'], ['DATE', 'DATETIME'], strict: true)
? Nette\Database\IStructure::FIELD_UNIX_TIMESTAMP
: TypeConverter::detectType($meta['sqlite:decl_type']);
} elseif (isset($meta['native_type'])) {
$types[$meta['name']] = TypeConverter::detectType($meta['native_type']);
}
}

return $types;
return in_array($meta['nativeType'], ['DATE', 'DATETIME'], true)
? (fn($value): DateTime => is_int($value) ? (new DateTime)->setTimestamp($value) : new DateTime($value))
: $converter->resolve($meta['nativeType']);
}


Expand Down
3 changes: 2 additions & 1 deletion src/Database/Drivers/PDO/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

class Connection implements Drivers\Connection
{
protected readonly PDO $pdo;
public readonly PDO $pdo;
public string $metaTypeKey = 'native_type';


public function __construct(
Expand Down
7 changes: 1 addition & 6 deletions src/Database/Drivers/PDO/MySQL/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ public function connect(): Drivers\Connection

public function createDatabaseEngine(Drivers\Connection $connection): Drivers\Engine
{
$engine = new (self::EngineClass)($connection);
$options = $this->params['options'];
if (isset($options['convertBoolean'])) {
$engine->convertBoolean = (bool) $options['convertBoolean'];
}
return $engine;
return new (self::EngineClass)($connection);
}
}
4 changes: 3 additions & 1 deletion src/Database/Drivers/PDO/SQLSrv/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public function __construct(

public function connect(): Drivers\Connection
{
return new Drivers\PDO\Connection(...$this->params);
$connection = new Drivers\PDO\Connection(...$this->params);
$connection->metaTypeKey = 'sqlsrv:decl_type';
return $connection;
}


Expand Down
4 changes: 3 additions & 1 deletion src/Database/Drivers/PDO/SQLite/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public function __construct(

public function connect(): Drivers\Connection
{
return new Drivers\PDO\Connection(...$this->params);
$connection = new Drivers\PDO\Connection(...$this->params);
$connection->metaTypeKey = 'sqlite:decl_type';
return $connection;
}


Expand Down
13 changes: 13 additions & 0 deletions src/Database/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ public function createDriverFromDsn(

return new $class(['dsn' => $dsn, 'username' => $username, 'password' => $password, 'options' => $options]);
}


public function createTypeConverter(array &$options): TypeConverter
{
$converter = new TypeConverter;
foreach (['convertBoolean'] as $opt) {
if (isset($options[$opt])) {
$converter->$opt = (bool) $options[$opt];
unset($options[$opt]);
}
}
return $converter;
}
}
55 changes: 4 additions & 51 deletions src/Database/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,66 +151,19 @@ public static function dumpSql(string $sql, ?array $params = null, ?Connection $
}


/**
* Common column type detection.
*/
public static function detectTypes(\PDOStatement $statement): array
{
$types = [];
$count = $statement->columnCount(); // driver must be meta-aware, see PHP bugs #53782, #54695
for ($col = 0; $col < $count; $col++) {
$meta = $statement->getColumnMeta($col);
if (isset($meta['native_type'])) {
$types[$meta['name']] = TypeConverter::detectType($meta['native_type']);
}
}

return $types;
}


/** @internal */
public static function normalizeRow(
array $row,
ResultSet $resultSet,
$dateTimeClass = DateTime::class,
): array
{
foreach ($resultSet->getColumnTypes() as $key => $type) {
foreach ($resultSet->resolveColumnConverters() as $key => $converter) {
$value = $row[$key];
if ($value === null || $value === false || $type === IStructure::FIELD_TEXT) {
// do nothing
} elseif ($type === IStructure::FIELD_INTEGER) {
$row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;

} elseif ($type === IStructure::FIELD_FLOAT || $type === IStructure::FIELD_DECIMAL) {
if (is_string($value) && str_starts_with($value, '.')) {
$value = '0' . $value;
}
$row[$key] = (float) $value;

} elseif ($type === IStructure::FIELD_BOOL) {
$row[$key] = $value && $value !== 'f' && $value !== 'F';

} elseif ($type === IStructure::FIELD_DATETIME || $type === IStructure::FIELD_DATE) {
$row[$key] = str_starts_with($value, '0000-00')
? null
: new $dateTimeClass($value);

} elseif ($type === IStructure::FIELD_TIME) {
$row[$key] = (new $dateTimeClass($value))->setDate(1, 1, 1);

} elseif ($type === IStructure::FIELD_TIME_INTERVAL) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)(\.\d+)?$#D', $value, $m);
$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$row[$key]->f = isset($m[5]) ? (float) $m[5] : 0.0;
$row[$key]->invert = (int) (bool) $m[1];

} elseif ($type === IStructure::FIELD_UNIX_TIMESTAMP) {
$row[$key] = (new $dateTimeClass)->setTimestamp($value);
}
$row[$key] = isset($value, $converter)
? $converter($value)
: $value;
}

return $row;
}

Expand Down
Loading

0 comments on commit 1307ef8

Please sign in to comment.