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 17, 2024
1 parent cca69a3 commit a308588
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 152 deletions.
6 changes: 0 additions & 6 deletions src/Database/Drivers/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,6 @@ 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 in result set.
* @return array<string, Type::*>
*/
function getColumnTypes(\PDOStatement $statement): array;

/**
* Cheks if driver supports specific property
* @param self::Support* $item
Expand Down
6 changes: 0 additions & 6 deletions src/Database/Drivers/Engines/MSSQLEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
return Nette\Database\Helpers::detectTypes($statement);
}


public function isSupported(string $item): bool
{
return $item === self::SupportSubselect;
Expand Down
30 changes: 10 additions & 20 deletions src/Database/Drivers/Engines/MySQLEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ public function convertException(\PDOException $e): Nette\Database\DriverExcepti
}


public function resolveTypeConverter(TypeConverter $converter, string $sqlType): ?\Closure
{
return match ($sqlType) {
'TIME' => $converter->convertDateTime ? $converter->toInterval(...) : null,
'DATE', 'DATETIME', 'TIMESTAMP' => $converter->convertDateTime ? (fn($value) => str_starts_with($value, '0000-00') ? null : $converter->toDateTime($value)) : null,
default => $converter->resolve($sqlType),
};
}


/********************* SQL ****************d*g**/


Expand Down Expand Up @@ -183,26 +193,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
$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 => Type::Integer,
$meta['native_type'] === 'TINY' && $meta['len'] === 1 && $this->convertBoolean => Type::Boolean,
$meta['native_type'] === 'TIME' => Type::Interval,
default => TypeConverter::detectType($meta['native_type']),
};
}
}

return $types;
}


public function isSupported(string $item): bool
{
// MULTI_COLUMN_AS_OR_COND due to mysql bugs:
Expand Down
6 changes: 0 additions & 6 deletions src/Database/Drivers/Engines/ODBCEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
return [];
}


public function isSupported(string $item): bool
{
return $item === self::SupportSubselect;
Expand Down
6 changes: 0 additions & 6 deletions src/Database/Drivers/Engines/OracleEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
return [];
}


public function isSupported(string $item): bool
{
return $item === self::SupportSequence || $item === self::SupportSubselect;
Expand Down
17 changes: 8 additions & 9 deletions src/Database/Drivers/Engines/PostgreSQLEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ public function convertException(\PDOException $e): Nette\Database\DriverExcepti
}


public function resolveTypeConverter(TypeConverter $converter, string $sqlType): ?\Closure
{
return $sqlType === 'bool'
? fn($value) => ($value && $value !== 'f' && $value !== 'F')
: $converter->resolve($sqlType);
}


/********************* SQL ****************d*g**/


Expand Down Expand Up @@ -232,15 +240,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
static $cache;
$item = &$cache[$statement->queryString];
$item ??= Nette\Database\Helpers::detectTypes($statement);
return $item;
}


public function isSupported(string $item): bool
{
return $item === self::SupportSequence || $item === self::SupportSubselect || $item === self::SupportSchema;
Expand Down
31 changes: 11 additions & 20 deletions src/Database/Drivers/Engines/SQLServerEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ public function convertException(\PDOException $e): Nette\Database\DriverExcepti
}


public function resolveTypeConverter(TypeConverter $converter, string $sqlType): ?\Closure
{
return match ($sqlType) {
'timestamp' => null, // timestamp does not mean time in sqlsrv
'decimal', 'numeric',
'double', 'double precision', 'float', 'real', 'money', 'smallmoney' => fn($value) => (float) (is_string($value) && str_starts_with($value, '.') ? '0' . $value : $value),
default => $converter->resolve($sqlType),
};
}


/********************* SQL ****************d*g**/


Expand Down Expand Up @@ -213,26 +224,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
$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;
}


public function isSupported(string $item): bool
{
return $item === self::SupportSubselect;
Expand Down
28 changes: 9 additions & 19 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 @@ -61,6 +62,14 @@ public function convertException(\PDOException $e): Nette\Database\DriverExcepti
}


public function resolveTypeConverter(TypeConverter $converter, string $sqlType): ?\Closure
{
return $converter->convertDateTime && in_array($sqlType, ['DATE', 'DATETIME'], true)
? (fn($value) => is_int($value) ? (new DateTime)->setTimestamp($value) : new DateTime($value))
: $converter->resolve($sqlType);
}


/********************* SQL ****************d*g**/


Expand Down Expand Up @@ -228,25 +237,6 @@ public function getForeignKeys(string $table): array
}


public function getColumnTypes(\PDOStatement $statement): array
{
$types = [];
$count = $statement->columnCount();
for ($col = 0; $col < $count; $col++) {
$meta = $statement->getColumnMeta($col);
if (isset($meta['sqlite:decl_type'])) {
$types[$meta['name']] = $this->fmtDateTime === '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;
}


public function isSupported(string $item): bool
{
return $item === self::SupportMultiInsertAsSelect || $item === self::SupportSubselect || $item === self::SupportMultiColumnAsOrCond;
Expand Down
18 changes: 18 additions & 0 deletions src/Database/Drivers/PDO/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
namespace Nette\Database\Drivers\PDO;

use Nette\Database\Drivers;
use Nette\Database\TypeConverter;
use PDO;


abstract class Connection implements Drivers\Connection
{
protected readonly PDO $pdo;
protected readonly Drivers\Engine $engine;
protected readonly TypeConverter $typeConverter;


public function __construct(
Expand All @@ -27,6 +29,14 @@ public function __construct(
array $options = [],
) {
$this->engine = $this->getEngine();

$this->typeConverter = new TypeConverter;
foreach (['convertBoolean', 'convertDateTime', 'convertDecimal'] as $opt) {
if (isset($options[$opt])) {
$this->typeConverter->$opt = (bool) $options[$opt];
}
}

$this->pdo = new PDO($dsn, $username, $password, $options);
$this->initialize($options);
}
Expand Down Expand Up @@ -91,4 +101,12 @@ public function getNativeConnection(): PDO
{
return $this->pdo;
}


public function resolveColumnConverter(array $meta): ?\Closure
{
return isset($meta['native_type'])
? $this->typeConverter->resolve($meta['native_type'])
: null;
}
}
14 changes: 13 additions & 1 deletion src/Database/Drivers/PDO/MySQL/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,19 @@ protected function initialize(array $options): void
}

if (isset($options['convertBoolean'])) {
$this->engine->convertBoolean = (bool) $options['convertBoolean'];
$this->typeConverter->convertBoolean = $this->engine->convertBoolean = (bool) $options['convertBoolean'];
}
}


public function resolveColumnConverter(array $meta): ?\Closure
{
$type = $meta['native_type'] ?? null;
return match (true) {
!$type => null,
$type === 'NEWDECIMAL' && $meta['precision'] === 0 => fn($value) => is_float($tmp = $value * 1) ? $value : $tmp,
$type === 'TINY' && $meta['len'] === 1 && $this->typeConverter->convertBoolean => fn($value) => (bool) $value,
default => $this->engine->resolveTypeConverter($this->typeConverter, $type),
};
}
}
8 changes: 8 additions & 0 deletions src/Database/Drivers/PDO/PgSQL/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ public function getEngine(): Drivers\Engines\PostgreSQLEngine
{
return new Drivers\Engines\PostgreSQLEngine($this);
}


public function resolveColumnConverter(array $meta): ?\Closure
{
return isset($meta['native_type'])
? $this->engine->resolveTypeConverter($this->typeConverter, $meta['native_type'])
: null;
}
}
8 changes: 8 additions & 0 deletions src/Database/Drivers/PDO/SQLSrv/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ public function getEngine(): Drivers\Engines\SQLServerEngine
{
return new Drivers\Engines\SQLServerEngine($this);
}


public function resolveColumnConverter(array $meta): ?\Closure
{
return isset($meta['sqlsrv:decl_type'])
? $this->engine->resolveTypeConverter($this->typeConverter, $meta['sqlsrv:decl_type'])
: null;
}
}
8 changes: 8 additions & 0 deletions src/Database/Drivers/PDO/SQLite/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ protected function initialize(array $options): void
$this->engine->formatDateTime = $options['formatDateTime'];
}
}


public function resolveColumnConverter(array $meta): ?\Closure
{
return isset($meta['sqlite:decl_type'])
? $this->engine->resolveTypeConverter($this->typeConverter, $meta['sqlite:decl_type'])
: null;
}
}
56 changes: 4 additions & 52 deletions src/Database/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,63 +151,15 @@ public static function dumpSql(string $sql, ?array $params = null, ?Connection $
}


/**
* Common column type detection.
* @return array<Type::*>
*/
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): array
{
foreach ($resultSet->getColumnTypes() as $key => $type) {
foreach ($resultSet->getColumnTypes() as $key => $converter) {
$value = $row[$key];
if ($value === null || $value === false || $type === Type::Text) {
// do nothing
} elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;

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

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

} elseif ($type === Type::DateTime || $type === Type::Date) {
$row[$key] = str_starts_with($value, '0000-00')
? null
: new DateTime($value);

} elseif ($type === Type::Time) {
$row[$key] = (new DateTime($value))->setDate(1, 1, 1);

} elseif ($type === Type::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 DateTime)->setTimestamp($value);
}
$row[$key] = isset($value, $converter)
? $converter($value)
: $value;
}

return $row;
}

Expand Down
Loading

0 comments on commit a308588

Please sign in to comment.