diff --git a/CHANGELOG.md b/CHANGELOG.md index 567a7d2e4..380b9d10a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - Enh #806: Build `Expression` instances inside `Expression::$params` when build a query using `QueryBuilder` (@Tigrov) - Enh #766: Allow `ColumnInterface` as column type. (@Tigrov) - Bug #828: Fix `float` type when use `AbstractCommand::getRawSql()` method (@Tigrov) +- Enh #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns + for type casting performance (@Tigrov) - Enh #829: Rename `batchInsert()` to `insertBatch()` in `DMLQueryBuilderInterface` and `CommandInterface` and change parameters from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov) - Enh #834: Refactor `AbstractCommand::insertBatch()`, add `Quoter::getRawTableName()` to `QuoterInterface` (@Tigrov) diff --git a/UPGRADE.md b/UPGRADE.md index 099a5694c..6c45653a8 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -35,7 +35,7 @@ Add support any scalar values for `$columns` parameter of these methods in your `QueryBuilderInterface` parameter in the constructor. Each DBMS driver should implement its own expression builder. `Expression::$params` can contain: -- non-unique placeholder names, they will be replaced with unique names. +- non-unique placeholder names, they will be replaced with unique names; - `Expression` instances, they will be built when building a query using `QueryBuilder`. ### Rename `batchInsert()` to `insertBatch()` @@ -57,6 +57,27 @@ $values = [ $db->createCommand()->insertBatch('user', $values)->execute(); ``` +### `ColumnSchemaInterface` changes + +The interface and the abstract implementation `AbstractColumnSchema` were moved to `Yiisoft\Db\Schema\Column` namespace +and the following changes were made: + +- `getName()` method can return `string` or `null`; +- `name(string|null $name)` method is added; +- constructor of `AbstractColumnSchema` class is changed to `__construct(string $type, string|null $phpType = null)`. + +### New classes for table columns + +Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace according to the data type: + +- `BooleanColumnSchema` for columns with boolean type; +- `IntegerColumnSchema` for columns with integer type (tinyint, smallint, integer, bigint); +- `BigIntColumnSchema` for columns with integer type with range outside `PHP_INT_MIN` and `PHP_INT_MAX`; +- `DoubleColumnSchema` for columns with fractional number type (float, double, decimal, money); +- `StringColumnSchema` for columns with string or datetime type (char, string, text, datetime, timestamp, date, time); +- `BinaryColumnSchema` for columns with binary type; +- `JsonColumnSchema` for columns with json type. + ### New methods in `QuoterInterface` - `QuoterInterface::getRawTableName()` - returns the raw table name without quotes. diff --git a/src/Schema/AbstractSchema.php b/src/Schema/AbstractSchema.php index 3a7e9697c..2b6abea37 100644 --- a/src/Schema/AbstractSchema.php +++ b/src/Schema/AbstractSchema.php @@ -12,6 +12,14 @@ use Yiisoft\Db\Constraint\Constraint; use Yiisoft\Db\Constraint\IndexConstraint; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Schema\Column\BinaryColumnSchema; +use Yiisoft\Db\Schema\Column\BooleanColumnSchema; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; +use Yiisoft\Db\Schema\Column\DoubleColumnSchema; +use Yiisoft\Db\Schema\Column\IntegerColumnSchema; +use Yiisoft\Db\Schema\Column\JsonColumnSchema; +use Yiisoft\Db\Schema\Column\StringColumnSchema; +use Yiisoft\Db\Schema\Column\BigIntColumnSchema; use function gettype; use function is_array; @@ -365,24 +373,46 @@ protected function findTableNames(string $schema): array } /** - * Extracts the PHP type from an abstract DB type. + * Creates a column schema for the database. * - * @param ColumnSchemaInterface $column The column schema information. + * This method may be overridden by child classes to create a DBMS-specific column schema. + * + * @param string $type The abstract data type. + * @param mixed ...$info The column information. + * @psalm-param array{unsigned?: bool} $info The set of parameters may be different for a specific DBMS. + * + * @return ColumnSchemaInterface + */ + protected function createColumnSchema(string $type, mixed ...$info): ColumnSchemaInterface + { + $isUnsigned = !empty($info['unsigned']); + $phpType = $this->getColumnPhpType($type, $isUnsigned); + + $column = $this->createColumnSchemaFromPhpType($phpType, $type); + $column->unsigned($isUnsigned); + + return $column; + } + + /** + * Get the PHP type from an abstract database type. + * + * @param string $type The abstract database type. * * @return string The PHP type name. */ - protected function getColumnPhpType(ColumnSchemaInterface $column): string + protected function getColumnPhpType(string $type, bool $isUnsigned = false): string { - return match ($column->getType()) { + return match ($type) { // abstract type => php type SchemaInterface::TYPE_TINYINT => SchemaInterface::PHP_TYPE_INTEGER, SchemaInterface::TYPE_SMALLINT => SchemaInterface::PHP_TYPE_INTEGER, - SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE === 4 && $column->isUnsigned() + SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE !== 8 && $isUnsigned + ? SchemaInterface::PHP_TYPE_STRING + : SchemaInterface::PHP_TYPE_INTEGER, + SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE !== 8 || $isUnsigned ? SchemaInterface::PHP_TYPE_STRING : SchemaInterface::PHP_TYPE_INTEGER, - SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE === 8 && !$column->isUnsigned() - ? SchemaInterface::PHP_TYPE_INTEGER - : SchemaInterface::PHP_TYPE_STRING, SchemaInterface::TYPE_BOOLEAN => SchemaInterface::PHP_TYPE_BOOLEAN, SchemaInterface::TYPE_DECIMAL => SchemaInterface::PHP_TYPE_DOUBLE, SchemaInterface::TYPE_FLOAT => SchemaInterface::PHP_TYPE_DOUBLE, @@ -393,6 +423,23 @@ protected function getColumnPhpType(ColumnSchemaInterface $column): string }; } + protected function createColumnSchemaFromPhpType(string $phpType, string $type): ColumnSchemaInterface + { + return match ($phpType) { + SchemaInterface::PHP_TYPE_STRING => match ($type) { + SchemaInterface::TYPE_INTEGER => new BigIntColumnSchema($type, $phpType), + SchemaInterface::TYPE_BIGINT => new BigIntColumnSchema($type, $phpType), + default => new StringColumnSchema($type, $phpType), + }, + SchemaInterface::PHP_TYPE_INTEGER => new IntegerColumnSchema($type, $phpType), + SchemaInterface::PHP_TYPE_DOUBLE => new DoubleColumnSchema($type, $phpType), + SchemaInterface::PHP_TYPE_BOOLEAN => new BooleanColumnSchema($type, $phpType), + SchemaInterface::PHP_TYPE_RESOURCE => new BinaryColumnSchema($type, $phpType), + SchemaInterface::PHP_TYPE_ARRAY => new JsonColumnSchema($type, $phpType), + default => new StringColumnSchema($type, $phpType), + }; + } + /** * Returns the metadata of the given type for all tables in the given schema. * diff --git a/src/Schema/AbstractTableSchema.php b/src/Schema/AbstractTableSchema.php index bc72ad989..d4740f2a9 100644 --- a/src/Schema/AbstractTableSchema.php +++ b/src/Schema/AbstractTableSchema.php @@ -4,6 +4,8 @@ namespace Yiisoft\Db\Schema; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; + use function array_keys; /** diff --git a/src/Schema/AbstractColumnSchema.php b/src/Schema/Column/AbstractColumnSchema.php similarity index 62% rename from src/Schema/AbstractColumnSchema.php rename to src/Schema/Column/AbstractColumnSchema.php index 235f2823c..4e2f2a517 100644 --- a/src/Schema/AbstractColumnSchema.php +++ b/src/Schema/Column/AbstractColumnSchema.php @@ -2,16 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Db\Schema; - -use Yiisoft\Db\Expression\ExpressionInterface; -use Yiisoft\Db\Helper\DbStringHelper; - -use function gettype; -use function in_array; -use function is_bool; -use function is_float; -use function is_resource; +namespace Yiisoft\Db\Schema\Column; /** * Represents the metadata of a column in a database table. @@ -50,15 +41,16 @@ abstract class AbstractColumnSchema implements ColumnSchemaInterface private array|null $enumValues = null; private string|null $extra = null; private bool $isPrimaryKey = false; - private string|null $phpType = null; + private string|null $name = null; private int|null $precision = null; private int|null $scale = null; private int|null $size = null; - private string $type = ''; private bool $unsigned = false; - public function __construct(private string $name) - { + public function __construct( + private string $type, + private string|null $phpType = null, + ) { } public function allowNull(bool $value): void @@ -86,15 +78,6 @@ public function dbType(string|null $value): void $this->dbType = $value; } - public function dbTypecast(mixed $value): mixed - { - /** - * The default implementation does the same as casting for PHP, but it should be possible to override this with - * annotation of an explicit PDO type. - */ - return $this->typecast($value); - } - public function defaultValue(mixed $value): void { $this->defaultValue = $value; @@ -135,7 +118,7 @@ public function getExtra(): string|null return $this->extra; } - public function getName(): string + public function getName(): string|null { return $this->name; } @@ -190,14 +173,14 @@ public function isUnsigned(): bool return $this->unsigned; } - public function phpType(string|null $value): void + public function name(string|null $name): void { - $this->phpType = $value; + $this->name = $name; } - public function phpTypecast(mixed $value): mixed + public function phpType(string|null $value): void { - return $this->typecast($value); + $this->phpType = $value; } public function precision(int|null $value): void @@ -229,50 +212,4 @@ public function unsigned(bool $value): void { $this->unsigned = $value; } - - /** - * Converts the input value according to {@see phpType} after retrieval from the database. - * - * If the value is null or an {@see Expression}, it won't be converted. - * - * @param mixed $value The value to be converted. - * - * @return mixed The converted value. - */ - protected function typecast(mixed $value): mixed - { - if ( - $value === null - || $value === '' && !in_array($this->type, [ - SchemaInterface::TYPE_TEXT, - SchemaInterface::TYPE_STRING, - SchemaInterface::TYPE_BINARY, - SchemaInterface::TYPE_CHAR, - ], true) - ) { - return null; - } - - if ($value instanceof ExpressionInterface) { - return $value; - } - - return match ($this->phpType) { - gettype($value) => $value, - SchemaInterface::PHP_TYPE_RESOURCE, - SchemaInterface::PHP_TYPE_STRING - => match (true) { - is_resource($value) => $value, - /** ensure type cast always has . as decimal separator in all locales */ - is_float($value) => DbStringHelper::normalizeFloat($value), - is_bool($value) => $value ? '1' : '0', - default => (string) $value, - }, - SchemaInterface::PHP_TYPE_INTEGER => (int) $value, - /** Treating a 0-bit value as false too (@link https://github.com/yiisoft/yii2/issues/9006) */ - SchemaInterface::PHP_TYPE_BOOLEAN => $value && $value !== "\0", - SchemaInterface::PHP_TYPE_DOUBLE => (float) $value, - default => $value, - }; - } } diff --git a/src/Schema/Column/BigIntColumnSchema.php b/src/Schema/Column/BigIntColumnSchema.php new file mode 100644 index 000000000..2dc2c341b --- /dev/null +++ b/src/Schema/Column/BigIntColumnSchema.php @@ -0,0 +1,49 @@ + $value === '' ? null : ( + $value <= PHP_INT_MAX && $value >= PHP_INT_MIN + ? (int) $value + : $value + ), + 'NULL' => null, + 'integer' => $value, + 'boolean' => $value ? 1 : 0, + default => $value instanceof ExpressionInterface ? $value : ( + ($val = (string) $value) <= PHP_INT_MAX && $val >= PHP_INT_MIN + ? (int) $val + : $val + ), + }; + } + + public function phpTypecast(mixed $value): string|null + { + if ($value === null) { + return null; + } + + return (string) $value; + } +} diff --git a/src/Schema/Column/BinaryColumnSchema.php b/src/Schema/Column/BinaryColumnSchema.php new file mode 100644 index 000000000..b38eac3a4 --- /dev/null +++ b/src/Schema/Column/BinaryColumnSchema.php @@ -0,0 +1,38 @@ + new Param($value, PDO::PARAM_LOB), + 'resource' => $value, + 'NULL' => null, + 'boolean' => $value ? '1' : '0', + default => $value instanceof ExpressionInterface ? $value : (string) $value, + }; + } + + public function phpTypecast(mixed $value): mixed + { + return $value; + } +} diff --git a/src/Schema/Column/BooleanColumnSchema.php b/src/Schema/Column/BooleanColumnSchema.php new file mode 100644 index 000000000..935f23840 --- /dev/null +++ b/src/Schema/Column/BooleanColumnSchema.php @@ -0,0 +1,37 @@ + true, + false => false, + null, '' => null, + default => $value instanceof ExpressionInterface ? $value : (bool) $value, + }; + } + + public function phpTypecast(mixed $value): bool|null + { + if ($value === null) { + return null; + } + + return $value && $value !== "\0"; + } +} diff --git a/src/Schema/ColumnSchemaInterface.php b/src/Schema/Column/ColumnSchemaInterface.php similarity index 96% rename from src/Schema/ColumnSchemaInterface.php rename to src/Schema/Column/ColumnSchemaInterface.php index 65778a652..82927ae26 100644 --- a/src/Schema/ColumnSchemaInterface.php +++ b/src/Schema/Column/ColumnSchemaInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Db\Schema; +namespace Yiisoft\Db\Schema\Column; /** * This interface defines a set of methods that must be implemented by a class that represents the column schema of a @@ -167,9 +167,9 @@ public function getEnumValues(): array|null; public function getExtra(): string|null; /** - * @return string The name of the column. + * @return string|null The name of the column. */ - public function getName(): string; + public function getName(): string|null; /** * @return int|null The precision of the column. @@ -244,6 +244,17 @@ public function isPrimaryKey(): bool; */ public function isUnsigned(): bool; + /** + * Sets the name of the column. + * + * ```php + * $columns = [ + * 'id' => $this->primaryKey()->name('id'), + * ]; + * ``` + */ + public function name(string|null $name): void; + /** * The PHP data type for representing the data stored in the column. * It's determined based on the data type of the column as defined in the database schema. diff --git a/src/Schema/Column/DoubleColumnSchema.php b/src/Schema/Column/DoubleColumnSchema.php new file mode 100644 index 000000000..31a9f9563 --- /dev/null +++ b/src/Schema/Column/DoubleColumnSchema.php @@ -0,0 +1,41 @@ + null, + default => $value instanceof ExpressionInterface ? $value : (float) $value, + }; + } + + public function phpTypecast(mixed $value): float|null + { + if ($value === null) { + return null; + } + + return (float) $value; + } +} diff --git a/src/Schema/Column/IntegerColumnSchema.php b/src/Schema/Column/IntegerColumnSchema.php new file mode 100644 index 000000000..19a82c592 --- /dev/null +++ b/src/Schema/Column/IntegerColumnSchema.php @@ -0,0 +1,41 @@ + null, + default => $value instanceof ExpressionInterface ? $value : (int) $value, + }; + } + + public function phpTypecast(mixed $value): int|null + { + if ($value === null) { + return null; + } + + return (int) $value; + } +} diff --git a/src/Schema/Column/JsonColumnSchema.php b/src/Schema/Column/JsonColumnSchema.php new file mode 100644 index 000000000..861335f5c --- /dev/null +++ b/src/Schema/Column/JsonColumnSchema.php @@ -0,0 +1,43 @@ +getDbType()); + } + + /** + * @throws \JsonException + */ + public function phpTypecast(mixed $value): mixed + { + if (is_string($value)) { + return json_decode($value, true, 512, JSON_THROW_ON_ERROR); + } + + return $value; + } +} diff --git a/src/Schema/Column/StringColumnSchema.php b/src/Schema/Column/StringColumnSchema.php new file mode 100644 index 000000000..daeac2ced --- /dev/null +++ b/src/Schema/Column/StringColumnSchema.php @@ -0,0 +1,35 @@ + $value, + 'NULL' => null, + 'boolean' => $value ? '1' : '0', + default => $value instanceof ExpressionInterface ? $value : (string) $value, + }; + } + + public function phpTypecast(mixed $value): mixed + { + return $value; + } +} diff --git a/src/Schema/TableSchemaInterface.php b/src/Schema/TableSchemaInterface.php index db1ddd1de..7b2431b5c 100644 --- a/src/Schema/TableSchemaInterface.php +++ b/src/Schema/TableSchemaInterface.php @@ -4,6 +4,8 @@ namespace Yiisoft\Db\Schema; +use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; + /** * Represents the metadata of a database table. * diff --git a/tests/Common/CommonColumnSchemaTest.php b/tests/Common/CommonColumnSchemaTest.php new file mode 100644 index 000000000..c9ffc454c --- /dev/null +++ b/tests/Common/CommonColumnSchemaTest.php @@ -0,0 +1,45 @@ +assertSame($type, $column->getType()); + $this->assertSame($phpType, $column->getPhpType()); + } + + /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnSchemaProvider::dbTypecastColumns */ + public function testDbTypecastColumns(string $className, array $values) + { + $column = new $className(); + + foreach ($values as [$expected, $value]) { + if (is_object($expected) && !(is_object($value) && $expected::class === $value::class)) { + $this->assertEquals($expected, $column->dbTypecast($value)); + } else { + $this->assertSame($expected, $column->dbTypecast($value)); + } + } + } + + /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnSchemaProvider::phpTypecastColumns */ + public function testPhpTypecastColumns(string $className, array $values) + { + $column = new $className('column_name'); + + foreach ($values as [$expected, $value]) { + $this->assertSame($expected, $column->phpTypecast($value)); + } + } +} diff --git a/tests/Common/CommonSchemaTest.php b/tests/Common/CommonSchemaTest.php index c91b4e9ff..52bdb1ef1 100644 --- a/tests/Common/CommonSchemaTest.php +++ b/tests/Common/CommonSchemaTest.php @@ -898,7 +898,15 @@ protected function columnSchema(array $columns, string $table): void ); } - /* Pgsql only */ + if (isset($expected['unsigned'])) { + $this->assertSame( + $expected['unsigned'], + $column->isUnsigned(), + "unsigned of column $name does not match" + ); + } + + /* For array types */ if (isset($expected['dimension'])) { /** @psalm-suppress UndefinedMethod */ $this->assertSame( @@ -907,6 +915,25 @@ protected function columnSchema(array $columns, string $table): void "dimension of column $name does not match" ); } + + if (isset($expected['column'])) { + /** @psalm-suppress UndefinedMethod */ + $arrayColumn = $column->getColumn(); + + $this->assertSame( + $expected['column'], + [ + 'type' => $arrayColumn->getType(), + 'dbType' => $arrayColumn->getDbType(), + 'phpType' => $arrayColumn->getPhpType(), + 'enumValues' => $arrayColumn->getEnumValues(), + 'size' => $arrayColumn->getSize(), + 'precision' => $arrayColumn->getPrecision(), + 'scale' => $arrayColumn->getScale(), + ], + "array column of column $name does not match" + ); + } } $db->close(); diff --git a/tests/Db/Schema/ColumnSchemaTest.php b/tests/Db/Schema/ColumnSchemaTest.php index b2b1a2838..8af577b81 100644 --- a/tests/Db/Schema/ColumnSchemaTest.php +++ b/tests/Db/Schema/ColumnSchemaTest.php @@ -17,7 +17,7 @@ final class ColumnSchemaTest extends TestCase { public function testAllowNull(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertFalse($column->isAllowNull()); @@ -32,7 +32,7 @@ public function testAllowNull(): void public function testAutoIncrement(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertFalse($column->isAutoIncrement()); @@ -47,7 +47,7 @@ public function testAutoIncrement(): void public function testComment(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getComment()); @@ -62,7 +62,7 @@ public function testComment(): void public function testComputed(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertFalse($column->isComputed()); @@ -77,7 +77,7 @@ public function testComputed(): void public function testDbType(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getDbType()); @@ -90,16 +90,9 @@ public function testDbType(): void $this->assertNull($column->getDbType()); } - public function testDbTypecast(): void - { - $column = new ColumnSchema('new'); - - $this->assertNull($column->dbTypecast('')); - } - public function testDefaultValue(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getDefaultValue()); @@ -114,7 +107,7 @@ public function testDefaultValue(): void public function testEnumValues(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getEnumValues()); @@ -129,7 +122,7 @@ public function testEnumValues(): void public function testExtra(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getExtra()); @@ -142,28 +135,24 @@ public function testExtra(): void $this->assertSame('', $column->getExtra()); } - /** - * @link https://github.com/yiisoft/db/issues/718 - */ - public function testTypecastIssue718(): void + public function testName(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); - $param = [1, 2]; - $result = $column->dbTypecast($param); - $this->assertSame([1, 2], $result); - } + $this->assertNull($column->getName()); - public function testName(): void - { - $column = new ColumnSchema('test'); + $column->name('test'); $this->assertSame('test', $column->getName()); + + $column->name(''); + + $this->assertSame('', $column->getName()); } public function testPhpType(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getPhpType()); @@ -176,90 +165,9 @@ public function testPhpType(): void $this->assertNull($column->getPhpType()); } - public function testPhpTypecast(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); - - $this->assertSame('test', $column->phpTypecast('test')); - } - - public function testPhpTypecastWithBoolean(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_BOOLEAN); - - $this->assertTrue($column->phpTypecast(1)); - } - - public function testPhpTypecastWithDouble(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_DOUBLE); - - $this->assertSame(1.2, $column->phpTypecast('1.2')); - } - - public function testPhpTypecastWithInteger(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_INTEGER); - - $this->assertSame(1, $column->phpTypecast('1')); - } - - public function testPhpTypecastWithStringBooleanValue(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); - - $this->assertSame('1', $column->phpTypecast(true)); - } - - public function testPhpTypecastWithStringFloatValue(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); - - $this->assertSame('1.1', $column->phpTypecast(1.1)); - } - - public function testPhpTypecastWithStringIntegerValue(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); - - $this->assertSame('1', $column->phpTypecast(1)); - } - - public function testPhpTypecastWithStringNullValue(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); - - $this->assertNull($column->phpTypecast(null)); - } - - public function testPhpTypecastWithStringResourceValue(): void - { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); - - $this->assertIsResource($column->phpTypecast(fopen('php://memory', 'rb'))); - } - public function testPrecision(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getPrecision()); @@ -274,7 +182,7 @@ public function testPrecision(): void public function testPrimaryKey(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertFalse($column->isPrimaryKey()); @@ -289,7 +197,7 @@ public function testPrimaryKey(): void public function testScale(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getScale()); @@ -304,7 +212,7 @@ public function testScale(): void public function testSize(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertNull($column->getSize()); @@ -319,7 +227,7 @@ public function testSize(): void public function testType(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertSame('', $column->getType()); @@ -334,7 +242,7 @@ public function testType(): void public function testUnsigned(): void { - $column = new ColumnSchema('new'); + $column = new ColumnSchema(); $this->assertFalse($column->isUnsigned()); diff --git a/tests/Db/Schema/SchemaTest.php b/tests/Db/Schema/SchemaTest.php index cfd480b8f..1d3810a63 100644 --- a/tests/Db/Schema/SchemaTest.php +++ b/tests/Db/Schema/SchemaTest.php @@ -65,33 +65,21 @@ public function testGetColumnPhpType(): void $schema = $db->getSchema(); - $columnBigInt = new ColumnSchema('bigint'); - $columnBigInt->type('bigint'); - - $columnBoolean = new ColumnSchema('boolean'); - $columnBoolean->type('boolean'); - - $columnInteger = new ColumnSchema('integer'); - $columnInteger->type('integer'); - - $columnString = new ColumnSchema('string'); - $columnString->type('string'); - $this->assertSame( 'integer', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnBigInt]), + Assert::invokeMethod($schema, 'getColumnPhpType', ['bigint']), ); $this->assertSame( 'boolean', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnBoolean]), + Assert::invokeMethod($schema, 'getColumnPhpType', ['boolean']), ); $this->assertSame( 'integer', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnInteger]), + Assert::invokeMethod($schema, 'getColumnPhpType', ['integer']), ); $this->assertSame( 'string', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnString]), + Assert::invokeMethod($schema, 'getColumnPhpType', ['string']), ); } diff --git a/tests/Provider/ColumnSchemaProvider.php b/tests/Provider/ColumnSchemaProvider.php new file mode 100644 index 000000000..5c90ca91f --- /dev/null +++ b/tests/Provider/ColumnSchemaProvider.php @@ -0,0 +1,219 @@ + [IntegerColumnSchema::class, SchemaInterface::TYPE_INTEGER, SchemaInterface::PHP_TYPE_INTEGER], + 'bigint' => [BigIntColumnSchema::class, SchemaInterface::TYPE_BIGINT, SchemaInterface::PHP_TYPE_STRING], + 'double' => [DoubleColumnSchema::class, SchemaInterface::TYPE_DOUBLE, SchemaInterface::PHP_TYPE_DOUBLE], + 'string' => [StringColumnSchema::class, SchemaInterface::TYPE_STRING, SchemaInterface::PHP_TYPE_STRING], + 'binary' => [BinaryColumnSchema::class, SchemaInterface::TYPE_BINARY, SchemaInterface::PHP_TYPE_RESOURCE], + 'boolean' => [BooleanColumnSchema::class, SchemaInterface::TYPE_BOOLEAN, SchemaInterface::PHP_TYPE_BOOLEAN], + 'json' => [JsonColumnSchema::class, SchemaInterface::TYPE_JSON, SchemaInterface::PHP_TYPE_ARRAY], + ]; + } + + public static function dbTypecastColumns(): array + { + return [ + 'integer' => [ + IntegerColumnSchema::class, + [ + // [expected, typecast value] + [null, null], + [null, ''], + [1, 1], + [1, 1.0], + [1, '1'], + [1, true], + [0, false], + [$expression = new Expression('1'), $expression], + ], + ], + 'bigint' => [ + BigIntColumnSchema::class, + [ + [null, null], + [null, ''], + [1, 1], + [1, 1.0], + [1, '1'], + [1, true], + [0, false], + ['12345678901234567890', '12345678901234567890'], + [$expression = new Expression('1'), $expression], + ], + ], + 'double' => [ + DoubleColumnSchema::class, + [ + [null, null], + [null, ''], + [1.0, 1.0], + [1.0, 1], + [1.0, '1'], + [1.0, true], + [0.0, false], + [$expression = new Expression('1'), $expression], + ], + ], + 'string' => [ + StringColumnSchema::class, + [ + [null, null], + ['', ''], + ['1', 1], + ['1', true], + ['0', false], + ['string', 'string'], + [$resource = fopen('php://memory', 'rb'), $resource], + [$expression = new Expression('expression'), $expression], + ], + ], + 'binary' => [ + BinaryColumnSchema::class, + [ + [null, null], + ['1', 1], + ['1', true], + ['0', false], + [new Param("\x10\x11\x12", PDO::PARAM_LOB), "\x10\x11\x12"], + [$resource = fopen('php://memory', 'rb'), $resource], + [$expression = new Expression('expression'), $expression], + ], + ], + 'boolean' => [ + BooleanColumnSchema::class, + [ + [null, null], + [null, ''], + [true, true], + [true, 1], + [true, 1.0], + [true, '1'], + [false, false], + [false, 0], + [false, 0.0], + [false, '0'], + [$expression = new Expression('expression'), $expression], + ], + ], + 'json' => [ + JsonColumnSchema::class, + [ + [null, null], + [new JsonExpression(''), ''], + [new JsonExpression(1), 1], + [new JsonExpression(true), true], + [new JsonExpression(false), false], + [new JsonExpression('string'), 'string'], + [new JsonExpression([1, 2, 3]), [1, 2, 3]], + [new JsonExpression(['key' => 'value']), ['key' => 'value']], + [new JsonExpression(['a' => 1]), ['a' => 1]], + [new JsonExpression(new stdClass()), new stdClass()], + [$expression = new JsonExpression([1, 2, 3]), $expression], + ], + ], + ]; + } + + public static function phpTypecastColumns(): array + { + return [ + 'integer' => [ + IntegerColumnSchema::class, + [ + // [expected, typecast value] + [null, null], + [1, 1], + [1, '1'], + ], + ], + 'bigint' => [ + BigIntColumnSchema::class, + [ + [null, null], + ['1', 1], + ['1', '1'], + ['12345678901234567890', '12345678901234567890'], + ], + ], + 'double' => [ + DoubleColumnSchema::class, + [ + [null, null], + [1.0, 1.0], + [1.0, '1.0'], + ], + ], + 'string' => [ + StringColumnSchema::class, + [ + [null, null], + ['', ''], + ['string', 'string'], + [$resource = fopen('php://memory', 'rb'), $resource], + ], + ], + 'binary' => [ + BinaryColumnSchema::class, + [ + [null, null], + ['', ''], + ["\x10\x11\x12", "\x10\x11\x12"], + [$resource = fopen('php://memory', 'rb'), $resource], + ], + ], + 'boolean' => [ + BooleanColumnSchema::class, + [ + [null, null], + [true, true], + [true, '1'], + [false, false], + [false, '0'], + [false, "\0"], + ], + ], + 'json' => [ + JsonColumnSchema::class, + [ + [null, null], + [null, 'null'], + ['', '""'], + [1.0, '1.0'], + [1, '1'], + [true, 'true'], + [false, 'false'], + ['string', '"string"'], + [[1, 2, 3], '[1,2,3]'], + [['key' => 'value'], '{"key":"value"}'], + [['a' => 1], '{"a":1}'], + ], + ], + ]; + } +} diff --git a/tests/Support/Stub/ColumnSchema.php b/tests/Support/Stub/ColumnSchema.php index 61809cfba..eb11395d6 100644 --- a/tests/Support/Stub/ColumnSchema.php +++ b/tests/Support/Stub/ColumnSchema.php @@ -4,8 +4,24 @@ namespace Yiisoft\Db\Tests\Support\Stub; -use Yiisoft\Db\Schema\AbstractColumnSchema; +use Yiisoft\Db\Schema\Column\AbstractColumnSchema; final class ColumnSchema extends AbstractColumnSchema { + public function __construct( + private string $type = '', + private string|null $phpType = null, + ) { + parent::__construct($type, $phpType); + } + + public function dbTypecast(mixed $value): mixed + { + return $value; + } + + public function phpTypecast(mixed $value): mixed + { + return $value; + } }