From 4b678463a28fcc74399aae68023df7baa9a85847 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 28 Jun 2023 01:17:18 +0700 Subject: [PATCH 01/22] Fix bit type --- src/ColumnSchema.php | 12 ++++++++++++ src/Schema.php | 12 ++++-------- tests/ColumnSchemaTest.php | 6 ++++++ tests/Provider/SchemaProvider.php | 17 +++++++++++++++-- tests/Support/Fixture/pgsql.sql | 3 ++- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 19cdcb05b..52ccd2e8e 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -14,10 +14,14 @@ use Yiisoft\Db\Schema\SchemaInterface; use function array_walk_recursive; +use function bindec; +use function decbin; use function in_array; use function is_array; +use function is_int; use function is_string; use function json_decode; +use function str_pad; use function strtolower; /** @@ -89,6 +93,12 @@ public function dbTypecast(mixed $value): mixed return new Param($value, PDO::PARAM_LOB); } + if (is_int($value) && $this->getType() === SchemaInterface::TYPE_BIT) { + return $this->getSize() !== null + ? str_pad(decbin($value), $this->getSize(), '0', STR_PAD_LEFT) + : decbin($value); + } + return $this->typecast($value); } @@ -137,6 +147,8 @@ protected function phpTypecastValue(mixed $value): mixed } switch ($this->getType()) { + case Schema::TYPE_BIT: + return is_string($value) ? bindec($value) : $value; case SchemaInterface::TYPE_BOOLEAN: /** @psalm-var mixed $value */ $value = is_string($value) ? strtolower($value) : $value; diff --git a/src/Schema.php b/src/Schema.php index f765bb3c3..86bfcfe04 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -90,9 +90,9 @@ final class Schema extends AbstractPdoSchema * @psalm-var string[] */ private array $typeMap = [ - 'bit' => self::TYPE_INTEGER, - 'bit varying' => self::TYPE_INTEGER, - 'varbit' => self::TYPE_INTEGER, + 'bit' => self::TYPE_BIT, + 'bit varying' => self::TYPE_BIT, + 'varbit' => self::TYPE_BIT, 'bool' => self::TYPE_BOOLEAN, 'boolean' => self::TYPE_BOOLEAN, 'box' => self::TYPE_STRING, @@ -790,11 +790,7 @@ protected function findColumns(TableSchemaInterface $table): bool $loadColumnSchema->defaultValue(new Expression($defaultValue)); } elseif ($loadColumnSchema->getType() === 'boolean') { $loadColumnSchema->defaultValue($defaultValue === 'true'); - } elseif (is_string($defaultValue) && preg_match("/^B'(.*?)'::/", $defaultValue, $matches)) { - $loadColumnSchema->defaultValue(bindec($matches[1])); - } elseif (is_string($defaultValue) && preg_match("/^'(\d+)'::\"bit\"$/", $defaultValue, $matches)) { - $loadColumnSchema->defaultValue(bindec($matches[1])); - } elseif (is_string($defaultValue) && preg_match("/^'(.*?)'::/", $defaultValue, $matches)) { + } elseif (is_string($defaultValue) && preg_match("/^B?'(.*?)'::/", $defaultValue, $matches)) { if ($loadColumnSchema->getType() === 'binary' && str_starts_with($matches[1], '\\x')) { $loadColumnSchema->defaultValue(hex2bin(substr($matches[1], 2))); } else { diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index 6536d1fbc..cb6470825 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -48,6 +48,8 @@ public function testPhpTypeCast(): void 'float_col' => 1.234, 'blob_col' => "\x10\x11\x12", 'bool_col' => false, + 'bit_col' => 0b0110_0100, // 100 + 'varbit_col' => 0b1_1100_1000, // 456 'bigint_col' => 9_223_372_036_854_775_806, 'intarray_col' => [1, -2, null, '42'], 'numericarray_col' => [1.2, -2.2, null], @@ -68,6 +70,8 @@ public function testPhpTypeCast(): void $floatColPhpTypeCast = $tableSchema->getColumn('float_col')?->phpTypecast($query['float_col']); $blobColPhpTypeCast = $tableSchema->getColumn('blob_col')?->phpTypecast($query['blob_col']); $boolColPhpTypeCast = $tableSchema->getColumn('bool_col')?->phpTypecast($query['bool_col']); + $bitColPhpTypeCast = $tableSchema->getColumn('bit_col')?->phpTypecast($query['bit_col']); + $varbitColPhpTypeCast = $tableSchema->getColumn('varbit_col')?->phpTypecast($query['varbit_col']); $numericColPhpTypeCast = $tableSchema->getColumn('numeric_col')?->phpTypecast($query['numeric_col']); $intArrayColPhpType = $tableSchema->getColumn('intarray_col')?->phpTypecast($query['intarray_col']); $numericArrayColPhpTypeCast = $tableSchema->getColumn('numericarray_col')?->phpTypecast($query['numericarray_col']); @@ -82,6 +86,8 @@ public function testPhpTypeCast(): void $this->assertSame(1.234, $floatColPhpTypeCast); $this->assertSame("\x10\x11\x12", stream_get_contents($blobColPhpTypeCast)); $this->assertFalse($boolColPhpTypeCast); + $this->assertSame(0b0110_0100, $bitColPhpTypeCast); + $this->assertSame(0b1_1100_1000, $varbitColPhpTypeCast); $this->assertSame(33.22, $numericColPhpTypeCast); $this->assertSame([1, -2, null, 42], $intArrayColPhpType); $this->assertSame([1.2, -2.2, null], $numericArrayColPhpTypeCast); diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index c82583196..07eef00c3 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -209,7 +209,7 @@ public static function columns(): array 'defaultValue' => new Expression('now()'), ], 'bit_col' => [ - 'type' => 'integer', + 'type' => 'bit', 'dbType' => 'bit', 'phpType' => 'integer', 'primaryKey' => false, @@ -219,7 +219,20 @@ public static function columns(): array 'size' => 8, 'precision' => null, 'scale' => null, - 'defaultValue' => 130, //b '10000010' + 'defaultValue' => 0b1000_0010, // 130 + ], + 'varbit_col' => [ + 'type' => 'bit', + 'dbType' => 'varbit', + 'phpType' => 'integer', + 'primaryKey' => false, + 'allowNull' => false, + 'autoIncrement' => false, + 'enumValues' => null, + 'size' => null, + 'precision' => null, + 'scale' => null, + 'defaultValue' => 0b100, // 4 ], 'bigint_col' => [ 'type' => 'bigint', diff --git a/tests/Support/Fixture/pgsql.sql b/tests/Support/Fixture/pgsql.sql index 2f4d5e1da..c23647abb 100644 --- a/tests/Support/Fixture/pgsql.sql +++ b/tests/Support/Fixture/pgsql.sql @@ -152,7 +152,8 @@ CREATE TABLE "type" ( bool_col boolean NOT NULL, bool_col2 boolean DEFAULT TRUE, ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - bit_col BIT(8) NOT NULL DEFAULT B'10000010', + bit_col BIT(8) NOT NULL DEFAULT B'10000010', -- 130 + varbit_col VARBIT NOT NULL DEFAULT B'100', -- 4 bigint_col BIGINT, intarray_col integer[], numericarray_col numeric(5,2)[], From 03d6955283b107894da16f88b3f58b0735cdd421 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 28 Jun 2023 01:48:26 +0700 Subject: [PATCH 02/22] Update --- src/ColumnSchema.php | 6 ++---- src/Schema.php | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 52ccd2e8e..0abfc06ab 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -94,9 +94,7 @@ public function dbTypecast(mixed $value): mixed } if (is_int($value) && $this->getType() === SchemaInterface::TYPE_BIT) { - return $this->getSize() !== null - ? str_pad(decbin($value), $this->getSize(), '0', STR_PAD_LEFT) - : decbin($value); + return str_pad(decbin($value), $this->getSize() ?? 0, '0', STR_PAD_LEFT); } return $this->typecast($value); @@ -147,7 +145,7 @@ protected function phpTypecastValue(mixed $value): mixed } switch ($this->getType()) { - case Schema::TYPE_BIT: + case SchemaInterface::TYPE_BIT: return is_string($value) ? bindec($value) : $value; case SchemaInterface::TYPE_BOOLEAN: /** @psalm-var mixed $value */ diff --git a/src/Schema.php b/src/Schema.php index 86bfcfe04..4a0744b5b 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -24,7 +24,6 @@ use function array_merge; use function array_unique; use function array_values; -use function bindec; use function explode; use function hex2bin; use function is_string; From b7e96974748c45a6de48e6630cc93cc6c6530391 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 4 Jul 2023 23:18:57 +0700 Subject: [PATCH 03/22] Typecast refactoring --- .gitignore | 6 +-- src/ColumnSchema.php | 75 +++++++++++++---------------- src/Schema.php | 112 +++++++++++++++++++++---------------------- 3 files changed, 91 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index 3ff596fea..3d694002b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,11 +32,9 @@ composer.phar # phpunit itself is not needed phpunit.phar -# PHPUnit cache -/.phpunit.result.cache - -# local phpunit config +# local phpunit config and cache /phpunit.xml +/.phpunit.result.cache # NPM packages /node_modules diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 0abfc06ab..65cf49b52 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -72,32 +72,28 @@ final class ColumnSchema extends AbstractColumnSchema */ public function dbTypecast(mixed $value): mixed { - if ($value === null) { - return null; - } - - if ($value instanceof ExpressionInterface) { - return $value; - } - - if ($this->dimension > 0) { - return new ArrayExpression($value, $this->getDbType(), $this->dimension); - } - - if (in_array($this->getDbType(), [SchemaInterface::TYPE_JSON, SchemaInterface::TYPE_JSONB], true)) { - return new JsonExpression($value, $this->getDbType()); - } - - if (is_string($value) && $this->getType() === SchemaInterface::TYPE_BINARY) { - /** explicitly setup PDO param type for binary column */ - return new Param($value, PDO::PARAM_LOB); - } - - if (is_int($value) && $this->getType() === SchemaInterface::TYPE_BIT) { - return str_pad(decbin($value), $this->getSize() ?? 0, '0', STR_PAD_LEFT); - } - - return $this->typecast($value); + return match (true) { + $value === null, + $value instanceof ExpressionInterface + => $value, + $this->dimension > 0 + => new ArrayExpression($value, $this->getDbType(), $this->dimension), + default + => match ($this->getType()) { + SchemaInterface::TYPE_JSON + => new JsonExpression($value, $this->getDbType()), + SchemaInterface::TYPE_BINARY + => is_string($value) + ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column + : $this->typecast($value), + SchemaInterface::TYPE_BIT + => is_int($value) + ? str_pad(decbin($value), $this->getSize() ?? 0, '0', STR_PAD_LEFT) + : $this->typecast($value), + default + => $this->typecast($value), + }, + }; } /** @@ -144,23 +140,20 @@ protected function phpTypecastValue(mixed $value): mixed return null; } - switch ($this->getType()) { - case SchemaInterface::TYPE_BIT: - return is_string($value) ? bindec($value) : $value; - case SchemaInterface::TYPE_BOOLEAN: - /** @psalm-var mixed $value */ - $value = is_string($value) ? strtolower($value) : $value; - - return match ($value) { + return match ($this->getType()) { + SchemaInterface::TYPE_BIT + => is_string($value) ? bindec($value) : $value, + SchemaInterface::TYPE_BOOLEAN + => match (is_string($value) ? strtolower($value) : $value) { 't', 'true' => true, 'f', 'false' => false, - default => (bool)$value, - }; - case SchemaInterface::TYPE_JSON: - return json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR); - } - - return parent::phpTypecast($value); + default => (bool) $value, + }, + SchemaInterface::TYPE_JSON + => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), + default + => parent::phpTypecast($value), + }; } /** diff --git a/src/Schema.php b/src/Schema.php index 4a0744b5b..6652b479c 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -756,56 +756,16 @@ protected function findColumns(TableSchemaInterface $table): bool /** @psalm-var ColumnArray $column */ $column = $this->normalizeRowKeyCase($column, false); - /** @psalm-var ColumnSchema $loadColumnSchema */ - $loadColumnSchema = $this->loadColumnSchema($column); + /** @psalm-var ColumnSchema $columnSchema */ + $columnSchema = $this->loadColumnSchema($column); - $table->column($loadColumnSchema->getName(), $loadColumnSchema); + $table->column($columnSchema->getName(), $columnSchema); - /** @psalm-var mixed $defaultValue */ - $defaultValue = $loadColumnSchema->getDefaultValue(); - - if ($loadColumnSchema->isPrimaryKey()) { - $table->primaryKey($loadColumnSchema->getName()); + if ($columnSchema->isPrimaryKey()) { + $table->primaryKey($columnSchema->getName()); if ($table->getSequenceName() === null) { - $table->sequenceName($loadColumnSchema->getSequenceName()); - } - - $loadColumnSchema->defaultValue(null); - } elseif ($defaultValue) { - if ( - is_string($defaultValue) && - in_array( - $loadColumnSchema->getType(), - [self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME], - true - ) && - in_array( - strtoupper($defaultValue), - ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], - true - ) - ) { - $loadColumnSchema->defaultValue(new Expression($defaultValue)); - } elseif ($loadColumnSchema->getType() === 'boolean') { - $loadColumnSchema->defaultValue($defaultValue === 'true'); - } elseif (is_string($defaultValue) && preg_match("/^B?'(.*?)'::/", $defaultValue, $matches)) { - if ($loadColumnSchema->getType() === 'binary' && str_starts_with($matches[1], '\\x')) { - $loadColumnSchema->defaultValue(hex2bin(substr($matches[1], 2))); - } else { - $loadColumnSchema->defaultValue($loadColumnSchema->phpTypecast($matches[1])); - } - } elseif ( - is_string($defaultValue) && - preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) - ) { - if ($matches[2] === 'NULL') { - $loadColumnSchema->defaultValue(null); - } else { - $loadColumnSchema->defaultValue($loadColumnSchema->phpTypecast($matches[2])); - } - } else { - $loadColumnSchema->defaultValue($loadColumnSchema->phpTypecast($defaultValue)); + $table->sequenceName($columnSchema->getSequenceName()); } } } @@ -847,16 +807,15 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface $column->autoIncrement($info['is_autoinc']); $column->comment($info['column_comment']); - if (!in_array($info['type_scheme'], [$this->defaultSchema, 'pg_catalog'], true) - ) { + if (!in_array($info['type_scheme'], [$this->defaultSchema, 'pg_catalog'], true)) { $column->dbType($info['type_scheme'] . '.' . $info['data_type']); } else { $column->dbType($info['data_type']); } - $column->defaultValue($info['column_default']); - $column->enumValues(($info['enum_values'] !== null) - ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) : null); + $column->enumValues($info['enum_values'] !== null + ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) + : null); $column->unsigned(false); // has no meaning in PG $column->primaryKey((bool) $info['is_pkey']); $column->precision($info['numeric_precision']); @@ -866,18 +825,16 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface /** * pg_get_serial_sequence() doesn't track DEFAULT value change. - * * GENERATED BY IDENTITY columns always have a null default value. * - * @psalm-var mixed $defaultValue + * @psalm-var string|null $defaultValue */ - $defaultValue = $column->getDefaultValue(); + $defaultValue = $info['column_default']; $sequenceName = $info['sequence_name'] ?? null; if ( - isset($defaultValue) && - is_string($defaultValue) && - preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $defaultValue) === 1 + is_string($defaultValue) + && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $defaultValue) === 1 ) { $column->sequenceName(preg_replace( ['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], @@ -895,10 +852,51 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface } $column->phpType($this->getColumnPhpType($column)); + $column->defaultValue($this->normalizeDefaultValue($defaultValue, $column)); return $column; } + /** + * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. + * + * @param string|null $defaultValue The default value retrieved from the database. + * @param ColumnSchemaInterface $columnSchema The column schema object. + * + * @return mixed The normalized default value. + */ + private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $columnSchema): mixed + { + return match (true) { + $defaultValue === null, + $columnSchema->isPrimaryKey() + => null, + in_array($columnSchema->getType(), [ + self::TYPE_TIMESTAMP, + self::TYPE_DATE, + self::TYPE_TIME + ], true) + && in_array(strtoupper($defaultValue), [ + 'NOW()', + 'CURRENT_TIMESTAMP', + 'CURRENT_DATE', + 'CURRENT_TIME' + ],true) + => new Expression($defaultValue), + preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1 + => $columnSchema->getType() === self::TYPE_BINARY + && str_starts_with($matches[1], '\\x') + ? hex2bin(substr($matches[1], 2)) + : $columnSchema->phpTypecast($matches[1]), + preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) === 1 + => $matches[2] !== 'NULL' + ? $columnSchema->phpTypecast($matches[2]) + : null, + default + => $columnSchema->phpTypecast($defaultValue), + }; + } + /** * Loads multiple types of constraints and returns the specified ones. * From e0a81c944b292acfbb8822e4c7637d337b72e8dd Mon Sep 17 00:00:00 2001 From: Tigrov Date: Wed, 5 Jul 2023 00:37:10 +0700 Subject: [PATCH 04/22] Start from master --- tests/ColumnSchemaTest.php | 6 ------ tests/Provider/SchemaProvider.php | 17 ++--------------- tests/Support/Fixture/pgsql.sql | 3 +-- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index cb6470825..6536d1fbc 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -48,8 +48,6 @@ public function testPhpTypeCast(): void 'float_col' => 1.234, 'blob_col' => "\x10\x11\x12", 'bool_col' => false, - 'bit_col' => 0b0110_0100, // 100 - 'varbit_col' => 0b1_1100_1000, // 456 'bigint_col' => 9_223_372_036_854_775_806, 'intarray_col' => [1, -2, null, '42'], 'numericarray_col' => [1.2, -2.2, null], @@ -70,8 +68,6 @@ public function testPhpTypeCast(): void $floatColPhpTypeCast = $tableSchema->getColumn('float_col')?->phpTypecast($query['float_col']); $blobColPhpTypeCast = $tableSchema->getColumn('blob_col')?->phpTypecast($query['blob_col']); $boolColPhpTypeCast = $tableSchema->getColumn('bool_col')?->phpTypecast($query['bool_col']); - $bitColPhpTypeCast = $tableSchema->getColumn('bit_col')?->phpTypecast($query['bit_col']); - $varbitColPhpTypeCast = $tableSchema->getColumn('varbit_col')?->phpTypecast($query['varbit_col']); $numericColPhpTypeCast = $tableSchema->getColumn('numeric_col')?->phpTypecast($query['numeric_col']); $intArrayColPhpType = $tableSchema->getColumn('intarray_col')?->phpTypecast($query['intarray_col']); $numericArrayColPhpTypeCast = $tableSchema->getColumn('numericarray_col')?->phpTypecast($query['numericarray_col']); @@ -86,8 +82,6 @@ public function testPhpTypeCast(): void $this->assertSame(1.234, $floatColPhpTypeCast); $this->assertSame("\x10\x11\x12", stream_get_contents($blobColPhpTypeCast)); $this->assertFalse($boolColPhpTypeCast); - $this->assertSame(0b0110_0100, $bitColPhpTypeCast); - $this->assertSame(0b1_1100_1000, $varbitColPhpTypeCast); $this->assertSame(33.22, $numericColPhpTypeCast); $this->assertSame([1, -2, null, 42], $intArrayColPhpType); $this->assertSame([1.2, -2.2, null], $numericArrayColPhpTypeCast); diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index 07eef00c3..c82583196 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -209,7 +209,7 @@ public static function columns(): array 'defaultValue' => new Expression('now()'), ], 'bit_col' => [ - 'type' => 'bit', + 'type' => 'integer', 'dbType' => 'bit', 'phpType' => 'integer', 'primaryKey' => false, @@ -219,20 +219,7 @@ public static function columns(): array 'size' => 8, 'precision' => null, 'scale' => null, - 'defaultValue' => 0b1000_0010, // 130 - ], - 'varbit_col' => [ - 'type' => 'bit', - 'dbType' => 'varbit', - 'phpType' => 'integer', - 'primaryKey' => false, - 'allowNull' => false, - 'autoIncrement' => false, - 'enumValues' => null, - 'size' => null, - 'precision' => null, - 'scale' => null, - 'defaultValue' => 0b100, // 4 + 'defaultValue' => 130, //b '10000010' ], 'bigint_col' => [ 'type' => 'bigint', diff --git a/tests/Support/Fixture/pgsql.sql b/tests/Support/Fixture/pgsql.sql index c23647abb..2f4d5e1da 100644 --- a/tests/Support/Fixture/pgsql.sql +++ b/tests/Support/Fixture/pgsql.sql @@ -152,8 +152,7 @@ CREATE TABLE "type" ( bool_col boolean NOT NULL, bool_col2 boolean DEFAULT TRUE, ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - bit_col BIT(8) NOT NULL DEFAULT B'10000010', -- 130 - varbit_col VARBIT NOT NULL DEFAULT B'100', -- 4 + bit_col BIT(8) NOT NULL DEFAULT B'10000010', bigint_col BIGINT, intarray_col integer[], numericarray_col numeric(5,2)[], From ed98ff124fd2c8b94c1cc6ed11b51823868e8ec3 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 6 Jul 2023 13:13:54 +0700 Subject: [PATCH 05/22] Fix test issues --- src/ColumnSchema.php | 29 ++++++++++++++--------------- src/Schema.php | 17 ++++++++++------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 65cf49b52..846f31a84 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -16,7 +16,6 @@ use function array_walk_recursive; use function bindec; use function decbin; -use function in_array; use function is_array; use function is_int; use function is_string; @@ -79,19 +78,19 @@ public function dbTypecast(mixed $value): mixed $this->dimension > 0 => new ArrayExpression($value, $this->getDbType(), $this->dimension), default - => match ($this->getType()) { - SchemaInterface::TYPE_JSON - => new JsonExpression($value, $this->getDbType()), - SchemaInterface::TYPE_BINARY - => is_string($value) - ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column - : $this->typecast($value), - SchemaInterface::TYPE_BIT - => is_int($value) - ? str_pad(decbin($value), $this->getSize() ?? 0, '0', STR_PAD_LEFT) - : $this->typecast($value), - default - => $this->typecast($value), + => match ($this->getType()) { + SchemaInterface::TYPE_JSON + => new JsonExpression($value, $this->getDbType()), + SchemaInterface::TYPE_BINARY + => is_string($value) + ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column + : $this->typecast($value), + SchemaInterface::TYPE_BIT + => is_int($value) + ? str_pad(decbin($value), $this->getSize() ?? 0, '0', STR_PAD_LEFT) + : $this->typecast($value), + default + => $this->typecast($value), }, }; } @@ -152,7 +151,7 @@ protected function phpTypecastValue(mixed $value): mixed SchemaInterface::TYPE_JSON => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), default - => parent::phpTypecast($value), + => parent::phpTypecast($value), }; } diff --git a/src/Schema.php b/src/Schema.php index 6652b479c..5b279d615 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -864,6 +864,8 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface * @param ColumnSchemaInterface $columnSchema The column schema object. * * @return mixed The normalized default value. + * + * @psalm-suppress PossiblyNullArgument */ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $columnSchema): mixed { @@ -871,17 +873,18 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf $defaultValue === null, $columnSchema->isPrimaryKey() => null, + /** @psalm-var string $defaultValue */ in_array($columnSchema->getType(), [ - self::TYPE_TIMESTAMP, - self::TYPE_DATE, - self::TYPE_TIME - ], true) + self::TYPE_TIMESTAMP, + self::TYPE_DATE, + self::TYPE_TIME, + ], true) && in_array(strtoupper($defaultValue), [ 'NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', - 'CURRENT_TIME' - ],true) + 'CURRENT_TIME', + ], true) => new Expression($defaultValue), preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1 => $columnSchema->getType() === self::TYPE_BINARY @@ -893,7 +896,7 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf ? $columnSchema->phpTypecast($matches[2]) : null, default - => $columnSchema->phpTypecast($defaultValue), + => $columnSchema->phpTypecast($defaultValue), }; } From d5ec4ae991a90bd596fef76fddafeed31728bb3a Mon Sep 17 00:00:00 2001 From: Tigrov Date: Thu, 6 Jul 2023 13:28:27 +0700 Subject: [PATCH 06/22] Remove extra @psalm declarations --- src/Schema.php | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 5b279d615..9064969ef 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -47,7 +47,7 @@ * column_comment: string|null, * modifier: int, * is_nullable: bool, - * column_default: mixed, + * column_default: string|null, * is_autoinc: bool, * sequence_name: string|null, * enum_values: array|string|null, @@ -776,27 +776,7 @@ protected function findColumns(TableSchemaInterface $table): bool /** * Loads the column information into a {@see ColumnSchemaInterface} object. * - * @psalm-param array{ - * table_schema: string, - * table_name: string, - * column_name: string, - * data_type: string, - * type_type: string|null, - * type_scheme: string|null, - * character_maximum_length: int, - * column_comment: string|null, - * modifier: int, - * is_nullable: bool, - * column_default: mixed, - * is_autoinc: bool, - * sequence_name: string|null, - * enum_values: array|string|null, - * numeric_precision: int|null, - * numeric_scale: int|null, - * size: string|null, - * is_pkey: bool|null, - * dimension: int - * } $info Column information. + * @psalm-param ColumnArray $info Column information. * * @return ColumnSchemaInterface The column schema object. */ @@ -826,8 +806,6 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface /** * pg_get_serial_sequence() doesn't track DEFAULT value change. * GENERATED BY IDENTITY columns always have a null default value. - * - * @psalm-var string|null $defaultValue */ $defaultValue = $info['column_default']; $sequenceName = $info['sequence_name'] ?? null; From 91fe8be8b531d37ef2d95cd245fd707a297ad457 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 7 Jul 2023 19:37:24 +0700 Subject: [PATCH 07/22] Update --- src/Schema.php | 50 ++++++++++++++------------------------------------ 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 9064969ef..46e1f83f6 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -527,30 +527,14 @@ protected function findConstraints(TableSchemaInterface $table): void /** @psalm-var array{array{tableName: string, columns: array}} $constraints */ $constraints = []; - /** - * @psalm-var array< - * array{ - * constraint_name: string, - * column_name: string, - * foreign_table_name: string, - * foreign_table_schema: string, - * foreign_column_name: string, - * } - * > $rows - */ + /** @psalm-var array $rows */ $rows = $this->db->createCommand($sql, [ ':schemaName' => $table->getSchemaName(), ':tableName' => $table->getName(), ])->queryAll(); foreach ($rows as $constraint) { - /** @psalm-var array{ - * constraint_name: string, - * column_name: string, - * foreign_table_name: string, - * foreign_table_schema: string, - * foreign_column_name: string, - * } $constraint */ + /** @psalm-var FindConstraintArray $constraint */ $constraint = $this->normalizeRowKeyCase($constraint, false); if ($constraint['foreign_table_schema'] !== $this->defaultSchema) { @@ -808,10 +792,9 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface * GENERATED BY IDENTITY columns always have a null default value. */ $defaultValue = $info['column_default']; - $sequenceName = $info['sequence_name'] ?? null; if ( - is_string($defaultValue) + $defaultValue !== null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $defaultValue) === 1 ) { $column->sequenceName(preg_replace( @@ -819,16 +802,11 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface '', $defaultValue )); - } elseif ($sequenceName !== null) { - $column->sequenceName($this->resolveTableName($sequenceName)->getFullName()); - } - - if (isset($this->typeMap[$column->getDbType() ?? ''])) { - $column->type($this->typeMap[$column->getDbType() ?? '']); - } else { - $column->type(self::TYPE_STRING); + } elseif ($info['sequence_name'] !== null) { + $column->sequenceName($this->resolveTableName($info['sequence_name'])->getFullName()); } + $column->type($this->typeMap[(string) $column->getDbType()] ?? self::TYPE_STRING); $column->phpType($this->getColumnPhpType($column)); $column->defaultValue($this->normalizeDefaultValue($defaultValue, $column)); @@ -839,20 +817,20 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. * * @param string|null $defaultValue The default value retrieved from the database. - * @param ColumnSchemaInterface $columnSchema The column schema object. + * @param ColumnSchemaInterface $column The column schema object. * * @return mixed The normalized default value. * * @psalm-suppress PossiblyNullArgument */ - private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $columnSchema): mixed + private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed { return match (true) { $defaultValue === null, - $columnSchema->isPrimaryKey() + $column->isPrimaryKey() => null, /** @psalm-var string $defaultValue */ - in_array($columnSchema->getType(), [ + in_array($column->getType(), [ self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME, @@ -865,16 +843,16 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf ], true) => new Expression($defaultValue), preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1 - => $columnSchema->getType() === self::TYPE_BINARY + => $column->getType() === self::TYPE_BINARY && str_starts_with($matches[1], '\\x') ? hex2bin(substr($matches[1], 2)) - : $columnSchema->phpTypecast($matches[1]), + : $column->phpTypecast($matches[1]), preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) === 1 => $matches[2] !== 'NULL' - ? $columnSchema->phpTypecast($matches[2]) + ? $column->phpTypecast($matches[2]) : null, default - => $columnSchema->phpTypecast($defaultValue), + => $column->phpTypecast($defaultValue), }; } From 425c069f98356ef4d813e89ea8e6d5d5fc73ab76 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 8 Jul 2023 01:33:08 +0700 Subject: [PATCH 08/22] Update --- src/ColumnSchema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 846f31a84..962e0059f 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -87,7 +87,7 @@ public function dbTypecast(mixed $value): mixed : $this->typecast($value), SchemaInterface::TYPE_BIT => is_int($value) - ? str_pad(decbin($value), $this->getSize() ?? 0, '0', STR_PAD_LEFT) + ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) : $this->typecast($value), default => $this->typecast($value), From df1d86962a26c05030e69b65b75e6c7f4fbeaa58 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 9 Jul 2023 09:33:43 +0700 Subject: [PATCH 09/22] Update --- src/ColumnSchema.php | 4 ++-- src/Schema.php | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 962e0059f..df99c93ff 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -85,7 +85,7 @@ public function dbTypecast(mixed $value): mixed => is_string($value) ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column : $this->typecast($value), - SchemaInterface::TYPE_BIT + Schema::TYPE_BIT => is_int($value) ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) : $this->typecast($value), @@ -140,7 +140,7 @@ protected function phpTypecastValue(mixed $value): mixed } return match ($this->getType()) { - SchemaInterface::TYPE_BIT + Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value, SchemaInterface::TYPE_BOOLEAN => match (is_string($value) ? strtolower($value) : $value) { diff --git a/src/Schema.php b/src/Schema.php index 46e1f83f6..e74119aaf 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -81,6 +81,11 @@ */ final class Schema extends AbstractPdoSchema { + /** + * Define the abstract column type as `bit`. + */ + public const TYPE_BIT = 'bit'; + /** * @var array The mapping from physical column types (keys) to abstract column types (values). * @@ -813,6 +818,20 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface return $column; } + /** + * Extracts the PHP type from an abstract DB type. + * + * @param ColumnSchemaInterface $column The column schema information. + * + * @return string The PHP type name. + */ + protected function getColumnPhpType(ColumnSchemaInterface $column): string + { + return $column->getType() === self::TYPE_BIT + ? self::PHP_TYPE_INTEGER + : parent::getColumnPhpType($column); + } + /** * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. * From 7099e25bc78731f8fcb0c8526fb32e4a57889d05 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 10 Jul 2023 10:17:26 +0700 Subject: [PATCH 10/22] Update --- CHANGELOG.md | 1 + src/ColumnSchema.php | 43 +++++++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83aa915c3..4338f31de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Enh #282: Support `numeric` arrays, improve support of domain types and `int` and `varchar` array types (@Tigrov) - Enh #284: Add tests for `binary` type and fix casting of default value (@Tigrov) +- Chg #288: Typecast refactoring (@Tigrov) ## 1.0.0 April 12, 2023 diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index df99c93ff..3d6d13ce4 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -71,27 +71,30 @@ final class ColumnSchema extends AbstractColumnSchema */ public function dbTypecast(mixed $value): mixed { - return match (true) { - $value === null, - $value instanceof ExpressionInterface - => $value, - $this->dimension > 0 - => new ArrayExpression($value, $this->getDbType(), $this->dimension), + if ( + $value === null + || $value instanceof ExpressionInterface + ) { + return $value; + } + + if ($this->dimension > 0) { + return new ArrayExpression($value, $this->getDbType(), $this->dimension); + } + + return match ($this->getType()) { + SchemaInterface::TYPE_JSON + => new JsonExpression($value, $this->getDbType()), + SchemaInterface::TYPE_BINARY + => is_string($value) + ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column + : $this->typecast($value), + Schema::TYPE_BIT + => is_int($value) + ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) + : $this->typecast($value), default - => match ($this->getType()) { - SchemaInterface::TYPE_JSON - => new JsonExpression($value, $this->getDbType()), - SchemaInterface::TYPE_BINARY - => is_string($value) - ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column - : $this->typecast($value), - Schema::TYPE_BIT - => is_int($value) - ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) - : $this->typecast($value), - default - => $this->typecast($value), - }, + => $this->typecast($value), }; } From 2d5b025acbbca92df54e750d01317470b4018555 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 10 Jul 2023 10:38:56 +0700 Subject: [PATCH 11/22] Update --- src/ColumnSchema.php | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 3d6d13ce4..351dfec57 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -83,18 +83,14 @@ public function dbTypecast(mixed $value): mixed } return match ($this->getType()) { - SchemaInterface::TYPE_JSON - => new JsonExpression($value, $this->getDbType()), - SchemaInterface::TYPE_BINARY - => is_string($value) - ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column - : $this->typecast($value), - Schema::TYPE_BIT - => is_int($value) - ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) - : $this->typecast($value), - default - => $this->typecast($value), + SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()), + SchemaInterface::TYPE_BINARY => is_string($value) + ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column + : $this->typecast($value), + Schema::TYPE_BIT => is_int($value) + ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) + : $this->typecast($value), + default => $this->typecast($value), }; } @@ -143,8 +139,7 @@ protected function phpTypecastValue(mixed $value): mixed } return match ($this->getType()) { - Schema::TYPE_BIT - => is_string($value) ? bindec($value) : $value, + Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value, SchemaInterface::TYPE_BOOLEAN => match (is_string($value) ? strtolower($value) : $value) { 't', 'true' => true, @@ -153,8 +148,7 @@ protected function phpTypecastValue(mixed $value): mixed }, SchemaInterface::TYPE_JSON => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), - default - => parent::phpTypecast($value), + default => parent::phpTypecast($value), }; } From 774ab9f4d15fc77e9225c0a082b230f458953790 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 10 Jul 2023 15:00:30 +0700 Subject: [PATCH 12/22] Update --- src/ColumnSchema.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 351dfec57..8d2b67280 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -71,14 +71,9 @@ final class ColumnSchema extends AbstractColumnSchema */ public function dbTypecast(mixed $value): mixed { - if ( - $value === null - || $value instanceof ExpressionInterface - ) { + if ($value === null || $value instanceof ExpressionInterface) { return $value; - } - - if ($this->dimension > 0) { + } elseif ($this->dimension > 0) { return new ArrayExpression($value, $this->getDbType(), $this->dimension); } From ea2a165b3bcebd3508883ede01ad3a5fba7ca051 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Mon, 10 Jul 2023 15:02:52 +0700 Subject: [PATCH 13/22] Update --- src/ColumnSchema.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 8d2b67280..a9af96bfa 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -73,7 +73,9 @@ public function dbTypecast(mixed $value): mixed { if ($value === null || $value instanceof ExpressionInterface) { return $value; - } elseif ($this->dimension > 0) { + } + + if ($this->dimension > 0) { return new ArrayExpression($value, $this->getDbType(), $this->dimension); } From ac2037bd261c67cf6ed923e16e8da1d019cc5010 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Tue, 11 Jul 2023 12:11:36 +0700 Subject: [PATCH 14/22] Rename $columnSchema to $column --- src/Schema.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index f5a751119..03254fc73 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -740,21 +740,21 @@ protected function findColumns(TableSchemaInterface $table): bool return false; } - /** @psalm-var array $column */ - foreach ($columns as $column) { - /** @psalm-var ColumnArray $column */ - $column = $this->normalizeRowKeyCase($column, false); + /** @psalm-var ColumnArray $info */ + foreach ($columns as $info) { + /** @psalm-var ColumnArray $info */ + $info = $this->normalizeRowKeyCase($info, false); - /** @psalm-var ColumnSchema $columnSchema */ - $columnSchema = $this->loadColumnSchema($column); + /** @psalm-var ColumnSchema $column */ + $column = $this->loadColumnSchema($info); - $table->column($columnSchema->getName(), $columnSchema); + $table->column($column->getName(), $column); - if ($columnSchema->isPrimaryKey()) { - $table->primaryKey($columnSchema->getName()); + if ($column->isPrimaryKey()) { + $table->primaryKey($column->getName()); if ($table->getSequenceName() === null) { - $table->sequenceName($columnSchema->getSequenceName()); + $table->sequenceName($column->getSequenceName()); } } } From e250c96418a27fb79ae34e6f21e129035bb6d272 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 14 Jul 2023 16:23:59 +0700 Subject: [PATCH 15/22] Add tests --- src/Schema.php | 27 +++++---------- tests/ColumnSchemaTest.php | 61 +++++++++++++++++++++++++++++++++ tests/Support/Fixture/pgsql.sql | 13 ++++++- 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 03254fc73..baccb1115 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -850,30 +850,19 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf $defaultValue === null, $column->isPrimaryKey() => null, - /** @psalm-var string $defaultValue */ - in_array($column->getType(), [ - self::TYPE_TIMESTAMP, - self::TYPE_DATE, - self::TYPE_TIME, - ], true) - && in_array(strtoupper($defaultValue), [ - 'NOW()', - 'CURRENT_TIMESTAMP', - 'CURRENT_DATE', - 'CURRENT_TIME', - ], true) - => new Expression($defaultValue), + /** @var string $defaultValue */ + in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME], true) + && in_array(strtoupper($defaultValue), ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true) + => new Expression($defaultValue), preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1 - => $column->getType() === self::TYPE_BINARY - && str_starts_with($matches[1], '\\x') - ? hex2bin(substr($matches[1], 2)) - : $column->phpTypecast($matches[1]), + => $column->getType() === self::TYPE_BINARY && str_starts_with($matches[1], '\\x') + ? hex2bin(substr($matches[1], 2)) + : $column->phpTypecast($matches[1]), preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) === 1 => $matches[2] !== 'NULL' ? $column->phpTypecast($matches[2]) : null, - default - => $column->phpTypecast($defaultValue), + default => $column->phpTypecast($defaultValue), }; } diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index cb6470825..e143162c5 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Expression\ArrayExpression; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\JsonExpression; use Yiisoft\Db\Pgsql\ColumnSchema; use Yiisoft\Db\Pgsql\Tests\Support\TestTrait; @@ -112,4 +113,64 @@ public function testPhpTypeCastBool(): void $this->assertFalse($columnSchema->phpTypeCast('false')); $this->assertTrue($columnSchema->phpTypeCast('true')); } + + public function testDbTypeCastJson(): void + { + $db = $this->getConnection(true); + $schema = $db->getSchema(); + $tableSchema = $schema->getTableSchema('type'); + + $this->assertEquals(new JsonExpression('' , 'json'), $tableSchema->getColumn('json_col')->dbTypecast('')); + $this->assertEquals(new JsonExpression('', 'jsonb'), $tableSchema->getColumn('jsonb_col')->dbTypecast('')); + } + + public function testBoolDefault(): void + { + $db = $this->getConnection(true); + + $command = $db->createCommand(); + $schema = $db->getSchema(); + $tableSchema = $schema->getTableSchema('bool_values'); + $command->insert('bool_values', ['id' => new Expression('DEFAULT')]); + $command->execute(); + $query = (new Query($db))->from('bool_values')->one(); + + $this->assertNull($query['bool_col']); + $this->assertTrue($query['default_true']); + $this->assertTrue($query['default_qtrueq']); + $this->assertTrue($query['default_t']); + $this->assertTrue($query['default_yes']); + $this->assertTrue($query['default_on']); + $this->assertTrue($query['default_1']); + $this->assertFalse($query['default_false']); + $this->assertFalse($query['default_qfalseq']); + $this->assertFalse($query['default_f']); + $this->assertFalse($query['default_no']); + $this->assertFalse($query['default_off']); + $this->assertFalse($query['default_0']); + $this->assertSame( + [null, true, true, true, true, true, true, false, false, false, false, false, false], + $tableSchema->getColumn('default_array')->phpTypecast($query['default_array']) + ); + + $this->assertNull($tableSchema->getColumn('bool_col')->getDefaultValue()); + $this->assertTrue($tableSchema->getColumn('default_true')->getDefaultValue()); + $this->assertTrue($tableSchema->getColumn('default_qtrueq')->getDefaultValue()); + $this->assertTrue($tableSchema->getColumn('default_t')->getDefaultValue()); + $this->assertTrue($tableSchema->getColumn('default_yes')->getDefaultValue()); + $this->assertTrue($tableSchema->getColumn('default_on')->getDefaultValue()); + $this->assertTrue($tableSchema->getColumn('default_1')->getDefaultValue()); + $this->assertFalse($tableSchema->getColumn('default_false')->getDefaultValue()); + $this->assertFalse($tableSchema->getColumn('default_qfalseq')->getDefaultValue()); + $this->assertFalse($tableSchema->getColumn('default_f')->getDefaultValue()); + $this->assertFalse($tableSchema->getColumn('default_no')->getDefaultValue()); + $this->assertFalse($tableSchema->getColumn('default_off')->getDefaultValue()); + $this->assertFalse($tableSchema->getColumn('default_0')->getDefaultValue()); + $this->assertSame( + [null, true, true, true, true, true, true, false, false, false, false, false, false], + $tableSchema->getColumn('default_array')->getDefaultValue() + ); + + $db->close(); + } } diff --git a/tests/Support/Fixture/pgsql.sql b/tests/Support/Fixture/pgsql.sql index 9f15b613d..bf21829d1 100644 --- a/tests/Support/Fixture/pgsql.sql +++ b/tests/Support/Fixture/pgsql.sql @@ -168,7 +168,18 @@ CREATE TABLE "bool_values" ( id serial not null primary key, bool_col bool, default_true bool not null default true, - default_false boolean not null default false + default_qtrueq boolean not null default 'true', + default_t boolean not null default 't', + default_yes boolean not null default 'yes', + default_on boolean not null default 'on', + default_1 boolean not null default '1', + default_false boolean not null default false, + default_qfalseq boolean not null default 'false', + default_f boolean not null default 'f', + default_no boolean not null default 'no', + default_off boolean not null default 'off', + default_0 boolean not null default '0', + default_array boolean[] not null default '{null,true,"true",t,yes,on,1,false,"false",f,no,off,0}' ); CREATE TABLE "negative_default_values" ( From ee6439393fbd039eedcd342b0a52fc8c6240586f Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 14 Jul 2023 17:00:06 +0700 Subject: [PATCH 16/22] Fix test issues --- CHANGELOG.md | 2 +- tests/ColumnSchemaTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 263d9335b..353c169cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ - Enh #282: Support `numeric` arrays, improve support of domain types and `int` and `varchar` array types (@Tigrov) - Enh #284: Add tests for `binary` type and fix casting of default value (@Tigrov) -- Chg #288: Typecast refactoring (@Tigrov) - Bug #287: Fix `bit` type (@Tigrov) - Enh #289: Array parser refactoring (@Tigrov) +- Chg #288: Typecast refactoring (@Tigrov) ## 1.0.0 April 12, 2023 diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index 8e3bb8e7d..fab9578b1 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -120,7 +120,7 @@ public function testDbTypeCastJson(): void $schema = $db->getSchema(); $tableSchema = $schema->getTableSchema('type'); - $this->assertEquals(new JsonExpression('' , 'json'), $tableSchema->getColumn('json_col')->dbTypecast('')); + $this->assertEquals(new JsonExpression('', 'json'), $tableSchema->getColumn('json_col')->dbTypecast('')); $this->assertEquals(new JsonExpression('', 'jsonb'), $tableSchema->getColumn('jsonb_col')->dbTypecast('')); } From d10b41c662dc5388b5cb4d7b9aabb42d49432d38 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 14 Jul 2023 17:32:58 +0700 Subject: [PATCH 17/22] Remove `strtolower($value)` PortgreSQL always returns bool values in lower case --- src/ColumnSchema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index a9af96bfa..7148406bd 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -138,7 +138,7 @@ protected function phpTypecastValue(mixed $value): mixed return match ($this->getType()) { Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value, SchemaInterface::TYPE_BOOLEAN - => match (is_string($value) ? strtolower($value) : $value) { + => match ($value) { 't', 'true' => true, 'f', 'false' => false, default => (bool) $value, From 04646db0f9ce927087e7df50843d085e9256679e Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 14 Jul 2023 17:41:04 +0700 Subject: [PATCH 18/22] Update bool tests --- src/ColumnSchema.php | 1 - tests/Support/Fixture/pgsql.sql | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 7148406bd..270b3443e 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -21,7 +21,6 @@ use function is_string; use function json_decode; use function str_pad; -use function strtolower; /** * Represents the metadata of a column in a database table for PostgreSQL Server. diff --git a/tests/Support/Fixture/pgsql.sql b/tests/Support/Fixture/pgsql.sql index bf21829d1..dd990765f 100644 --- a/tests/Support/Fixture/pgsql.sql +++ b/tests/Support/Fixture/pgsql.sql @@ -167,19 +167,19 @@ CREATE TABLE "type" ( CREATE TABLE "bool_values" ( id serial not null primary key, bool_col bool, - default_true bool not null default true, - default_qtrueq boolean not null default 'true', - default_t boolean not null default 't', + default_true bool not null default TRUE, + default_qtrueq boolean not null default 'TRUE', + default_t boolean not null default 'T', default_yes boolean not null default 'yes', default_on boolean not null default 'on', default_1 boolean not null default '1', - default_false boolean not null default false, - default_qfalseq boolean not null default 'false', - default_f boolean not null default 'f', + default_false boolean not null default FALSE, + default_qfalseq boolean not null default 'FALSE', + default_f boolean not null default 'F', default_no boolean not null default 'no', default_off boolean not null default 'off', default_0 boolean not null default '0', - default_array boolean[] not null default '{null,true,"true",t,yes,on,1,false,"false",f,no,off,0}' + default_array boolean[] not null default '{null,TRUE,"TRUE",T,yes,on,1,FALSE,"FALSE",F,no,off,0}' ); CREATE TABLE "negative_default_values" ( From 477bdab988f2f3de183b4fe4f64ff4f5b759e5f3 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 14 Jul 2023 18:19:09 +0700 Subject: [PATCH 19/22] Cases 'true' and 'false' for default values only --- src/ColumnSchema.php | 4 ++-- src/Schema.php | 5 +++++ tests/ColumnSchemaTest.php | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 270b3443e..1824e7b77 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -138,8 +138,8 @@ protected function phpTypecastValue(mixed $value): mixed Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value, SchemaInterface::TYPE_BOOLEAN => match ($value) { - 't', 'true' => true, - 'f', 'false' => false, + 't' => true, + 'f' => false, default => (bool) $value, }, SchemaInterface::TYPE_JSON diff --git a/src/Schema.php b/src/Schema.php index baccb1115..8cf8c1e82 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -851,6 +851,11 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf $column->isPrimaryKey() => null, /** @var string $defaultValue */ + $column->getType() === self::TYPE_BOOLEAN && in_array($defaultValue, ['true', 'false'], true) + => match ($defaultValue) { + 'true' => true, + 'false' => false, + }, in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME], true) && in_array(strtoupper($defaultValue), ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true) => new Expression($defaultValue), diff --git a/tests/ColumnSchemaTest.php b/tests/ColumnSchemaTest.php index fab9578b1..de2fdab73 100644 --- a/tests/ColumnSchemaTest.php +++ b/tests/ColumnSchemaTest.php @@ -110,8 +110,8 @@ public function testPhpTypeCastBool(): void $columnSchema->type('boolean'); - $this->assertFalse($columnSchema->phpTypeCast('false')); - $this->assertTrue($columnSchema->phpTypeCast('true')); + $this->assertFalse($columnSchema->phpTypeCast('f')); + $this->assertTrue($columnSchema->phpTypeCast('t')); } public function testDbTypeCastJson(): void From beb2e904e61edd21e53000f71393e79c80183b30 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Fri, 14 Jul 2023 18:33:06 +0700 Subject: [PATCH 20/22] Update --- src/Schema.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 8cf8c1e82..22744d7e2 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -852,10 +852,7 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf => null, /** @var string $defaultValue */ $column->getType() === self::TYPE_BOOLEAN && in_array($defaultValue, ['true', 'false'], true) - => match ($defaultValue) { - 'true' => true, - 'false' => false, - }, + => $defaultValue === 'true', in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME], true) && in_array(strtoupper($defaultValue), ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true) => new Expression($defaultValue), From 552f62a60b5d8f5ee044f08d9b0652c2ef73d5d2 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 11:11:19 +0700 Subject: [PATCH 21/22] Add EOLs --- src/ColumnSchema.php | 6 ++++++ src/Schema.php | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ColumnSchema.php b/src/ColumnSchema.php index 1824e7b77..f816b5004 100644 --- a/src/ColumnSchema.php +++ b/src/ColumnSchema.php @@ -80,12 +80,15 @@ public function dbTypecast(mixed $value): mixed return match ($this->getType()) { SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()), + SchemaInterface::TYPE_BINARY => is_string($value) ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column : $this->typecast($value), + Schema::TYPE_BIT => is_int($value) ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT) : $this->typecast($value), + default => $this->typecast($value), }; } @@ -136,14 +139,17 @@ protected function phpTypecastValue(mixed $value): mixed return match ($this->getType()) { Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value, + SchemaInterface::TYPE_BOOLEAN => match ($value) { 't' => true, 'f' => false, default => (bool) $value, }, + SchemaInterface::TYPE_JSON => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR), + default => parent::phpTypecast($value), }; } diff --git a/src/Schema.php b/src/Schema.php index 22744d7e2..431d9858b 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -847,23 +847,26 @@ protected function getColumnPhpType(ColumnSchemaInterface $column): string private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed { return match (true) { - $defaultValue === null, - $column->isPrimaryKey() - => null, + $defaultValue === null, $column->isPrimaryKey() => null, + /** @var string $defaultValue */ $column->getType() === self::TYPE_BOOLEAN && in_array($defaultValue, ['true', 'false'], true) => $defaultValue === 'true', + in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME], true) && in_array(strtoupper($defaultValue), ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true) => new Expression($defaultValue), + preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1 => $column->getType() === self::TYPE_BINARY && str_starts_with($matches[1], '\\x') ? hex2bin(substr($matches[1], 2)) : $column->phpTypecast($matches[1]), + preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) === 1 => $matches[2] !== 'NULL' ? $column->phpTypecast($matches[2]) : null, + default => $column->phpTypecast($defaultValue), }; } From add4688a5711df85976fd48b8e1cf14d659fbb26 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sat, 15 Jul 2023 22:02:38 +0700 Subject: [PATCH 22/22] Split `match (true)` in `if` --- src/Schema.php | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Schema.php b/src/Schema.php index 431d9858b..3c8932b0c 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -841,34 +841,37 @@ protected function getColumnPhpType(ColumnSchemaInterface $column): string * @param ColumnSchemaInterface $column The column schema object. * * @return mixed The normalized default value. - * - * @psalm-suppress PossiblyNullArgument */ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed { - return match (true) { - $defaultValue === null, $column->isPrimaryKey() => null, + if ($defaultValue === null || $column->isPrimaryKey()) { + return null; + } - /** @var string $defaultValue */ - $column->getType() === self::TYPE_BOOLEAN && in_array($defaultValue, ['true', 'false'], true) - => $defaultValue === 'true', + if ($column->getType() === self::TYPE_BOOLEAN && in_array($defaultValue, ['true', 'false'], true)) { + return $defaultValue === 'true'; + } + if ( in_array($column->getType(), [self::TYPE_TIMESTAMP, self::TYPE_DATE, self::TYPE_TIME], true) && in_array(strtoupper($defaultValue), ['NOW()', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME'], true) - => new Expression($defaultValue), + ) { + return new Expression($defaultValue); + } - preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1 - => $column->getType() === self::TYPE_BINARY && str_starts_with($matches[1], '\\x') - ? hex2bin(substr($matches[1], 2)) - : $column->phpTypecast($matches[1]), + if (preg_match("/^B?'(.*?)'::/", $defaultValue, $matches) === 1) { + return $column->getType() === self::TYPE_BINARY && str_starts_with($matches[1], '\\x') + ? hex2bin(substr($matches[1], 2)) + : $column->phpTypecast($matches[1]); + } - preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) === 1 - => $matches[2] !== 'NULL' - ? $column->phpTypecast($matches[2]) - : null, + if (preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $defaultValue, $matches) === 1) { + return $matches[2] !== 'NULL' + ? $column->phpTypecast($matches[2]) + : null; + } - default => $column->phpTypecast($defaultValue), - }; + return $column->phpTypecast($defaultValue); } /**