diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc2faabf..1e75f42c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,12 @@ - Bug #746: Typecast values in `AbstractDMLQueryBuilder::batchInsert()` if column names with table name and brackets (@Tigrov) - Bug #746, #61: Typecast values in `AbstractDMLQueryBuilder::batchInsert()` if values with string keys (@Tigrov) - Bug #751: Fix collected debug actions (@xepozz) +- Enh #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns + for type casting performance (@Tigrov) +- Chg #755: Deprecate `TableSchemaInterface::compositeForeignKey()` (@Tigrov) +- Enh #756: Refactor `Quoter` (@Tigrov) +- Bug #756: Fix `Quoter::quoteSql()` for SQL containing table with prefix (@Tigrov) +- Bug #756: Fix `Quoter::getTableNameParts()` for cases when different quotes for tables and columns (@Tigrov) - Bug #756: Fix `Quoter::quoteTableName()` for sub-query with alias (@Tigrov) - Bug #761: Quote aliases of CTE in `WITH` queries (@Tigrov) - Bug #769, #61: Fix `AbstractDMLQueryBuilder::batchInsert()` for values as associative arrays (@Tigrov) diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php index 574395b63..164ab26fb 100644 --- a/src/Command/AbstractCommand.php +++ b/src/Command/AbstractCommand.php @@ -11,7 +11,7 @@ use Yiisoft\Db\Query\Data\DataReaderInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnInterface; use function explode; use function get_resource_type; diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php index 945d122d4..b88842fc4 100644 --- a/src/Command/CommandInterface.php +++ b/src/Command/CommandInterface.php @@ -15,7 +15,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Query\Data\DataReaderInterface; use Yiisoft\Db\Query\QueryInterface; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnInterface; /** * This interface represents a database command, such as a `SELECT`, `INSERT`, `UPDATE`, or `DELETE` statement. diff --git a/src/Driver/Pdo/AbstractPdoConnection.php b/src/Driver/Pdo/AbstractPdoConnection.php index f6895545d..07e0f9ec0 100644 --- a/src/Driver/Pdo/AbstractPdoConnection.php +++ b/src/Driver/Pdo/AbstractPdoConnection.php @@ -19,8 +19,8 @@ use Yiisoft\Db\Profiler\ProfilerAwareInterface; use Yiisoft\Db\Profiler\ProfilerAwareTrait; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use Yiisoft\Db\Schema\QuoterInterface; -use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Transaction\TransactionInterface; use function array_keys; @@ -47,10 +47,12 @@ abstract class AbstractPdoConnection extends AbstractConnection implements PdoCo protected bool|null $emulatePrepare = null; protected QueryBuilderInterface|null $queryBuilder = null; protected QuoterInterface|null $quoter = null; - protected SchemaInterface|null $schema = null; - public function __construct(protected PdoDriverInterface $driver, protected SchemaCache $schemaCache) - { + public function __construct( + protected PdoDriverInterface $driver, + protected SchemaCache $schemaCache, + protected ColumnFactoryInterface|null $columnFactory = null, + ) { } /** diff --git a/src/QueryBuilder/AbstractDDLQueryBuilder.php b/src/QueryBuilder/AbstractDDLQueryBuilder.php index ce387a8ae..82b1bcfdf 100644 --- a/src/QueryBuilder/AbstractDDLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDDLQueryBuilder.php @@ -6,7 +6,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Query\QueryInterface; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; @@ -46,7 +46,7 @@ public function addColumn(string $table, string $column, ColumnInterface|string . ' ADD ' . $this->quoter->quoteColumnName($column) . ' ' - . $this->queryBuilder->getColumnType($type); + . $this->queryBuilder->buildColumnDefinition($type); } public function addCommentOnColumn(string $table, string $column, string $comment): string @@ -144,7 +144,7 @@ public function alterColumn( . $this->quoter->quoteColumnName($column) . ' ' . $this->quoter->quoteColumnName($column) . ' ' - . $this->queryBuilder->getColumnType($type); + . $this->queryBuilder->buildColumnDefinition($type); } public function checkIntegrity(string $schema = '', string $table = '', bool $check = true): string @@ -174,9 +174,8 @@ public function createTable(string $table, array $columns, string $options = nul $cols[] = "\t" . $this->quoter->quoteColumnName($name) . ' ' - . $this->queryBuilder->getColumnType($type); + . $this->queryBuilder->buildColumnDefinition($type); } else { - /** @psalm-var string $type */ $cols[] = "\t" . $type; } } diff --git a/src/QueryBuilder/AbstractDMLQueryBuilder.php b/src/QueryBuilder/AbstractDMLQueryBuilder.php index 302153c6b..cc26736ce 100644 --- a/src/QueryBuilder/AbstractDMLQueryBuilder.php +++ b/src/QueryBuilder/AbstractDMLQueryBuilder.php @@ -14,7 +14,7 @@ use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Query\QueryInterface; -use Yiisoft\Db\Schema\ColumnSchemaInterface; +use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; @@ -470,7 +470,7 @@ static function (Constraint $constraint) use ($quoter, $columns, &$columnNames): * * @deprecated will be removed in version 2.0.0 */ - protected function getTypecastValue(mixed $value, ColumnSchemaInterface $columnSchema = null): mixed + protected function getTypecastValue(mixed $value, ColumnInterface $columnSchema = null): mixed { if ($columnSchema) { return $columnSchema->dbTypecast($value); diff --git a/src/QueryBuilder/AbstractQueryBuilder.php b/src/QueryBuilder/AbstractQueryBuilder.php index 20e0ededa..3ac8fbb15 100644 --- a/src/QueryBuilder/AbstractQueryBuilder.php +++ b/src/QueryBuilder/AbstractQueryBuilder.php @@ -8,13 +8,11 @@ use Yiisoft\Db\Expression\ExpressionInterface; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\Condition\Interface\ConditionInterface; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; use function count; -use function preg_match; -use function preg_replace; /** * Builds a SELECT SQL statement based on the specification given as a {@see QueryInterface} object. @@ -44,7 +42,8 @@ public function __construct( private SchemaInterface $schema, private AbstractDDLQueryBuilder $ddlBuilder, private AbstractDMLQueryBuilder $dmlBuilder, - private AbstractDQLQueryBuilder $dqlBuilder + private AbstractDQLQueryBuilder $dqlBuilder, + private ColumnDefinitionBuilder $columnDefinitionBuilder, ) { } @@ -305,31 +304,10 @@ public function dropView(string $viewName): string return $this->ddlBuilder->dropView($viewName); } + /** @deprecated Use {@see buildColumnDefinition()}. Will be removed in version 3.0.0. */ public function getColumnType(ColumnInterface|string $type): string { - if ($type instanceof ColumnInterface) { - $type = $type->asString(); - } - - if (isset($this->typeMap[$type])) { - return $this->typeMap[$type]; - } - - if (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) { - if (isset($this->typeMap[$matches[1]])) { - return preg_replace( - '/\(.+\)/', - '(' . $matches[2] . ')', - $this->typeMap[$matches[1]] - ) . $matches[3]; - } - } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) { - if (isset($this->typeMap[$matches[1]])) { - return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type); - } - } - - return $type; + return $this->buildColumnDefinition($type); } public function getExpressionBuilder(ExpressionInterface $expression): object @@ -405,4 +383,13 @@ public function upsert( ): string { return $this->dmlBuilder->upsert($table, $insertColumns, $updateColumns, $params); } + + public function buildColumnDefinition(ColumnInterface|string $column): string + { + if (!$column instanceof ColumnInterface) { + $column = $this->schema->getColumnFactory()->fromDefinition($column); + } + + return $this->columnDefinitionBuilder->build($column); + } } diff --git a/src/QueryBuilder/ColumnDefinitionBuilder.php b/src/QueryBuilder/ColumnDefinitionBuilder.php new file mode 100644 index 000000000..e2592af79 --- /dev/null +++ b/src/QueryBuilder/ColumnDefinitionBuilder.php @@ -0,0 +1,302 @@ +clauses as $clause) { + $result .= match ($clause) { + 'type' => $this->buildType($column), + 'unsigned' => $this->buildUnsigned($column), + 'null' => $this->buildNull($column), + 'primary_key' => $this->buildPrimaryKey($column), + 'auto_increment' => $this->buildAutoIncrement($column), + 'unique' => $this->buildUnique($column), + 'default' => $this->buildDefault($column), + 'comment' => $this->buildComment($column), + 'check' => $this->buildCheck($column), + 'references' => $this->buildReferences($column), + 'extra' => $this->buildExtra($column), + default => '', + }; + } + + return $result; + } + + protected function buildType(ColumnInterface $column): string + { + if ($column->getDbType() === null) { + $column = clone $column; + $column->dbType($this->getDbType($column->getType())); + } + + return (string) $column->getFullDbType(); + } + + /** + * Builds the null or not null constraint for the column. + * + * @return string A string 'NOT NULL' if {@see ColumnInterface::allowNull} is false, + * 'NULL' if {@see ColumnInterface::allowNull} is true or an empty string otherwise. + */ + protected function buildNull(ColumnInterface $column): string + { + if ($column->isPrimaryKey()) { + return ''; + } + + return match ($column->isAllowNull()) { + true => ' NULL', + false => ' NOT NULL', + default => '', + }; + } + + /** + * Builds the primary key clause for column. + * + * @return string A string containing the PRIMARY KEY keyword. + */ + public function buildPrimaryKey(ColumnInterface $column): string + { + return $column->isPrimaryKey() ? ' PRIMARY KEY' : ''; + } + + /** + * Builds the auto increment clause for column. Default is empty string. + * + * @return string A string containing the AUTOINCREMENT keyword. + */ + public function buildAutoIncrement(ColumnInterface $column): string + { + return ''; + } + + /** + * Builds the unique constraint for the column. + * + * @return string A string 'UNIQUE' if {@see isUnique} is true, otherwise it returns an empty string. + */ + protected function buildUnique(ColumnInterface $column): string + { + if ($column->isPrimaryKey()) { + return ''; + } + + return $column->isUnique() ? ' UNIQUE' : ''; + } + + /** + * Builds the default value specification for the column. + * + * @return string A string containing the DEFAULT keyword and the default value. + */ + protected function buildDefault(ColumnInterface $column): string + { + if ($column->isAutoIncrement()) { + return ''; + } + + $defaultValue = $this->buildDefaultValue($column); + + if ($defaultValue === null) { + return ''; + } + + return " DEFAULT $defaultValue"; + } + + /** + * Return the default value for the column. + * + * @return string|null string with default value of column. + */ + protected function buildDefaultValue(ColumnInterface $column): string|null + { + $value = $column->dbTypecast($column->getDefaultValue()); + + if ($value === null) { + return $column->isAllowNull() === true ? 'NULL' : null; + } + + if ($value instanceof ExpressionInterface) { + return $this->queryBuilder->buildExpression($value); + } + + /** @var string */ + return match (gettype($value)) { + 'integer', 'double' => (string) $value, + 'boolean' => $value ? 'TRUE' : 'FALSE', + default => $this->queryBuilder->quoter()->quoteValue((string) $value), + }; + } + + /** + * Builds the check constraint for the column. + * + * @return string A string containing the CHECK constraint. + */ + protected function buildCheck(ColumnInterface $column): string + { + $check = $column->getCheck(); + + return !empty($check) ? " CHECK ($check)" : ''; + } + + /** + * Builds the unsigned string for column. Default is empty string. + * + * @return string A string containing the UNSIGNED keyword. + */ + protected function buildUnsigned(ColumnInterface $column): string + { + return $column->isUnsigned() ? ' UNSIGNED' : ''; + } + + /** + * Builds the custom string that's appended to column definition. + * + * @return string A string containing the custom SQL fragment appended to column definition. + */ + protected function buildExtra(ColumnInterface $column): string + { + $extra = $column->getExtra(); + + return !empty($extra) ? " $extra" : ''; + } + + /** + * Builds the comment clause for the column. Default is empty string. + * + * @return string A string containing the COMMENT keyword and the comment itself. + */ + protected function buildComment(ColumnInterface $column): string + { + return ''; + } + + /** + * Builds the references clause for the column. + */ + private function buildReferences(ColumnInterface $column): string + { + $reference = $this->buildReferenceDefinition($column); + + if ($reference === null) { + return ''; + } + + return "REFERENCES $reference"; + } + + /** + * Builds the reference definition for the column. + */ + protected function buildReferenceDefinition(ColumnInterface $column): string|null + { + /** @var ForeignKeyConstraint|null $reference */ + $reference = $column->getReference(); + $table = $reference?->getForeignTableName(); + + if ($table === null) { + return null; + } + + $quoter = $this->queryBuilder->quoter(); + + if (null !== $schema = $reference->getForeignSchemaName()) { + $sql = $quoter->quoteTableName($schema) . '.' . $quoter->quoteTableName($table); + } else { + $sql = $quoter->quoteTableName($table); + } + + $columns = $reference->getForeignColumnNames(); + + if (!empty($columns)) { + $sql .= ' (' . $this->queryBuilder->buildColumns($columns) . ')'; + } + + if (null !== $onDelete = $reference->getOnDelete()) { + $sql .= ' ON DELETE ' . $onDelete; + } + + if (null !== $onUpdate = $reference->getOnUpdate()) { + $sql .= ' ON UPDATE ' . $onUpdate; + } + + return $sql; + } + + /** + * Get the database column type from an abstract database type. + * + * @param string $type The abstract database type. + * + * @return string The database column type. + */ + protected function getDbType(string $type): string + { + return match ($type) { + SchemaInterface::TYPE_UUID => 'uuid', + SchemaInterface::TYPE_CHAR => 'char', + SchemaInterface::TYPE_STRING => 'varchar', + SchemaInterface::TYPE_TEXT => 'text', + SchemaInterface::TYPE_BINARY => 'binary', + SchemaInterface::TYPE_BOOLEAN => 'boolean', + SchemaInterface::TYPE_TINYINT => 'tinyint', + SchemaInterface::TYPE_SMALLINT => 'smallint', + SchemaInterface::TYPE_INTEGER => 'integer', + SchemaInterface::TYPE_BIGINT => 'bigint', + SchemaInterface::TYPE_FLOAT => 'float', + SchemaInterface::TYPE_DOUBLE => 'double', + SchemaInterface::TYPE_DECIMAL => 'decimal', + SchemaInterface::TYPE_MONEY => 'money', + SchemaInterface::TYPE_DATETIME => 'datetime', + SchemaInterface::TYPE_TIMESTAMP => 'timestamp', + SchemaInterface::TYPE_TIME => 'time', + SchemaInterface::TYPE_DATE => 'date', + SchemaInterface::TYPE_JSON => 'json', + default => 'varchar', + }; + } +} diff --git a/src/QueryBuilder/ColumnDefinitionBuilderInterface.php b/src/QueryBuilder/ColumnDefinitionBuilderInterface.php new file mode 100644 index 000000000..1e3d90219 --- /dev/null +++ b/src/QueryBuilder/ColumnDefinitionBuilderInterface.php @@ -0,0 +1,19 @@ +name('id'); - * $column->allowNull(false); - * $column->dbType('int(11)'); - * $column->phpType('integer'); - * $column->type('integer'); - * $column->defaultValue(0); - * $column->autoIncrement(true); - * $column->primaryKey(true); - * `` - */ -abstract class AbstractColumnSchema implements ColumnSchemaInterface -{ - private bool $allowNull = false; - private bool $autoIncrement = false; - private string|null $comment = null; - private bool $computed = false; - private string|null $dbType = null; - private mixed $defaultValue = null; - private array|null $enumValues = null; - private string|null $extra = null; - private bool $isPrimaryKey = false; - private string|null $phpType = 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 allowNull(bool $value): void - { - $this->allowNull = $value; - } - - public function autoIncrement(bool $value): void - { - $this->autoIncrement = $value; - } - - public function comment(string|null $value): void - { - $this->comment = $value; - } - - public function computed(bool $value): void - { - $this->computed = $value; - } - - 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; - } - - public function enumValues(array|null $value): void - { - $this->enumValues = $value; - } - - public function extra(string|null $value): void - { - $this->extra = $value; - } - - public function getComment(): string|null - { - return $this->comment; - } - - public function getDbType(): string|null - { - return $this->dbType; - } - - public function getDefaultValue(): mixed - { - return $this->defaultValue; - } - - public function getEnumValues(): array|null - { - return $this->enumValues; - } - - public function getExtra(): string|null - { - return $this->extra; - } - - public function getName(): string - { - return $this->name; - } - - public function getPrecision(): int|null - { - return $this->precision; - } - - public function getPhpType(): string|null - { - return $this->phpType; - } - - public function getScale(): int|null - { - return $this->scale; - } - - public function getSize(): int|null - { - return $this->size; - } - - public function getType(): string - { - return $this->type; - } - - public function isAllowNull(): bool - { - return $this->allowNull; - } - - public function isAutoIncrement(): bool - { - return $this->autoIncrement; - } - - public function isComputed(): bool - { - return $this->computed; - } - - public function isPrimaryKey(): bool - { - return $this->isPrimaryKey; - } - - public function isUnsigned(): bool - { - return $this->unsigned; - } - - public function phpType(string|null $value): void - { - $this->phpType = $value; - } - - public function phpTypecast(mixed $value): mixed - { - return $this->typecast($value); - } - - public function precision(int|null $value): void - { - $this->precision = $value; - } - - public function primaryKey(bool $value): void - { - $this->isPrimaryKey = $value; - } - - public function scale(int|null $value): void - { - $this->scale = $value; - } - - public function size(int|null $value): void - { - $this->size = $value; - } - - public function type(string $value): void - { - $this->type = $value; - } - - 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/AbstractSchema.php b/src/Schema/AbstractSchema.php index 7e13c88f0..95756ae9e 100644 --- a/src/Schema/AbstractSchema.php +++ b/src/Schema/AbstractSchema.php @@ -12,6 +12,8 @@ use Yiisoft\Db\Constraint\Constraint; use Yiisoft\Db\Constraint\IndexConstraint; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Schema\Column\ColumnFactory; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; use function array_change_key_case; use function array_map; @@ -46,8 +48,11 @@ abstract class AbstractSchema implements SchemaInterface private array $tableNames = []; private array $tableMetadata = []; - public function __construct(protected ConnectionInterface $db, private SchemaCache $schemaCache) - { + public function __construct( + protected ConnectionInterface $db, + private SchemaCache $schemaCache, + private ColumnFactoryInterface|null $columnFactory = null, + ) { } /** @@ -127,6 +132,11 @@ abstract protected function loadTableUniques(string $tableName): array; */ abstract protected function loadTableSchema(string $name): TableSchemaInterface|null; + public function getColumnFactory(): ColumnFactoryInterface + { + return $this->columnFactory ??= new ColumnFactory(); + } + public function getDefaultSchema(): string|null { return $this->defaultSchema; @@ -391,35 +401,6 @@ protected function findTableNames(string $schema): array throw new NotSupportedException(static::class . ' does not support fetching all table names.'); } - /** - * 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 match ($column->getType()) { - // 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::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, - SchemaInterface::TYPE_DOUBLE => SchemaInterface::PHP_TYPE_DOUBLE, - SchemaInterface::TYPE_BINARY => SchemaInterface::PHP_TYPE_RESOURCE, - SchemaInterface::TYPE_JSON => SchemaInterface::PHP_TYPE_ARRAY, - default => SchemaInterface::PHP_TYPE_STRING, - }; - } - /** * 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 e5b6ada95..17764bddd 100644 --- a/src/Schema/AbstractTableSchema.php +++ b/src/Schema/AbstractTableSchema.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Schema; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Schema\Column\ColumnInterface; use function array_keys; @@ -13,22 +14,43 @@ */ abstract class AbstractTableSchema implements TableSchemaInterface { - private string|null $schemaName = null; + private string $schemaName = ''; private string $name = ''; - private string|null $fullName = null; private string|null $comment = null; private string|null $sequenceName = null; /** @psalm-var string[] */ private array $primaryKey = []; - /** @psalm-var array */ - private array $columns = []; /** @psalm-var array */ protected array $foreignKeys = []; protected string|null $createSql = null; private string|null $catalogName = null; private string|null $serverName = null; - public function getColumn(string $name): ColumnSchemaInterface|null + /** + * @param ColumnInterface[] $columns + * + * @psalm-param array $columns + */ + public function __construct( + private string $fullName = '', + private array $columns = [], + ) { + $values = explode('.', $this->fullName, 2); + + if (count($values) === 2) { + [$this->schemaName, $this->name] = $values; + } else { + $this->name = $this->fullName; + } + + foreach ($columns as $columnName => $column) { + if ($column->isPrimaryKey()) { + $this->primaryKey[] = $columnName; + } + } + } + + public function getColumn(string $name): ColumnInterface|null { return $this->columns[$name] ?? null; } @@ -103,7 +125,7 @@ public function primaryKey(string $value): void $this->primaryKey[] = $value; } - public function column(string $name, ColumnSchemaInterface $value): void + public function column(string $name, ColumnInterface $value): void { $this->columns[$name] = $value; } diff --git a/src/Schema/Builder/AbstractColumn.php b/src/Schema/Builder/AbstractColumn.php index b778a8d8e..0ca087c3b 100644 --- a/src/Schema/Builder/AbstractColumn.php +++ b/src/Schema/Builder/AbstractColumn.php @@ -6,6 +6,7 @@ use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Helper\DbStringHelper; +use Yiisoft\Db\Schema\Column\Column; use Yiisoft\Db\Schema\SchemaInterface; use function gettype; @@ -26,6 +27,8 @@ * * Provides a fluent interface, which means that the methods can be chained together to create a column schema with * many properties in a single line of code. + * + * @deprecated Use {@see Column}. Will be removed in version 3.0.0. */ abstract class AbstractColumn implements ColumnInterface { diff --git a/src/Schema/Builder/ColumnInterface.php b/src/Schema/Builder/ColumnInterface.php index 220c6bdb9..270800838 100644 --- a/src/Schema/Builder/ColumnInterface.php +++ b/src/Schema/Builder/ColumnInterface.php @@ -4,11 +4,15 @@ namespace Yiisoft\Db\Schema\Builder; +use Yiisoft\Db\Schema\Column\ColumnInterface; + /** * This interface defines the methods that must be implemented by classes that build the schema of a database column. * * It provides methods for setting the column name, type, length, precision, scale, default value, and other properties * of the column, as well as methods for adding constraints, such as a primary key, unique, and not null. + * + * @deprecated Use {@see ColumnInterface}. Will be removed in version 3.0.0. */ interface ColumnInterface { diff --git a/src/Schema/Column/BigIntColumn.php b/src/Schema/Column/BigIntColumn.php new file mode 100644 index 000000000..9c442fdc4 --- /dev/null +++ b/src/Schema/Column/BigIntColumn.php @@ -0,0 +1,47 @@ + null, + false => 0, + true => 1, + default => match (true) { + PHP_INT_MIN <= $value && $value <= PHP_INT_MAX => (int) $value, + default => (string) $value, + }, + }; + } + + public function phpTypecast(mixed $value): int|string|null + { + return match (true) { + $value === null => null, + PHP_INT_MIN <= $value && $value <= PHP_INT_MAX => (int) $value, + default => (string) $value, + }; + } +} diff --git a/src/Schema/Column/BinaryColumn.php b/src/Schema/Column/BinaryColumn.php new file mode 100644 index 000000000..c721b9e0a --- /dev/null +++ b/src/Schema/Column/BinaryColumn.php @@ -0,0 +1,37 @@ + new Param($value, PDO::PARAM_LOB), + 'resource' => $value, + 'NULL' => null, + 'boolean' => $value ? '1' : '0', + default => (string) $value, + }; + } +} diff --git a/src/Schema/Column/BitColumn.php b/src/Schema/Column/BitColumn.php new file mode 100644 index 000000000..f2bd83d11 --- /dev/null +++ b/src/Schema/Column/BitColumn.php @@ -0,0 +1,57 @@ + null, + default => (int) $value, + }; + } + + public function phpTypecast(mixed $value): int|null + { + if ($value === null) { + return null; + } + + return (int) $value; + } + + public function normalizeDefaultValue(string|null $value): int|ExpressionInterface|null + { + if ($value === null || $this->isComputed() || preg_match("/^\(?NULL\b/i", $value) === 1) { + return null; + } + + if (preg_match("/^[Bb]?'([01]+)'/", $value, $matches) === 1) { + /** @var int */ + return bindec($matches[1]); + } + + return new Expression($value); + } +} diff --git a/src/Schema/Column/BooleanColumn.php b/src/Schema/Column/BooleanColumn.php new file mode 100644 index 000000000..f3cf395ef --- /dev/null +++ b/src/Schema/Column/BooleanColumn.php @@ -0,0 +1,39 @@ + null, + default => (bool) $value, + }; + } + + public function phpTypecast(mixed $value): bool|null + { + if ($value === null) { + return null; + } + + return $value && $value !== "\0"; + } +} diff --git a/src/Schema/Column/Column.php b/src/Schema/Column/Column.php new file mode 100644 index 000000000..29a47d796 --- /dev/null +++ b/src/Schema/Column/Column.php @@ -0,0 +1,320 @@ +name('id'); + * $column->allowNull(false); + * $column->dbType('int(11)'); + * $column->phpType('integer'); + * $column->type('integer'); + * $column->defaultValue(0); + * $column->autoIncrement(); + * $column->primaryKey(); + * ``` + */ +abstract class Column implements ColumnInterface +{ + private bool|null $allowNull = null; + private bool $autoIncrement = false; + private string|ExpressionInterface|null $check = null; + private string|null $comment = null; + private bool $computed = false; + private string|null $dbType = null; + private mixed $defaultValue = null; + private string|null $extra = null; + private bool $primaryKey = false; + private ForeignKeyConstraint|null $reference = null; + private int|null $scale = null; + private int|null $size = null; + private bool $unique = false; + private bool $unsigned = false; + private array $values = []; + + public function __construct( + private string|null $type = null, + private string|null $phpType = null, + ) { + } + + public function allowNull(bool|null $value = true): static + { + $this->allowNull = $value; + return $this; + } + + public function autoIncrement(bool $value = true): static + { + $this->autoIncrement = $value; + return $this; + } + + public function check(string|ExpressionInterface|null $value): static + { + $this->check = $value; + return $this; + } + + public function comment(string|null $value): static + { + $this->comment = $value; + return $this; + } + + public function computed(bool $value = true): static + { + $this->computed = $value; + return $this; + } + + public function dbType(string|null $value): static + { + $this->dbType = $value; + return $this; + } + + public function dbTypecast(mixed $value): mixed + { + return $value; + } + + public function defaultValue(mixed $value): static + { + $this->defaultValue = $value; + return $this; + } + + public function extra(string|null $value): static + { + $this->extra = $value; + return $this; + } + + public function getCheck(): string|ExpressionInterface|null + { + return $this->check; + } + + public function getComment(): string|null + { + return $this->comment; + } + + public function getDbType(): string|null + { + return $this->dbType; + } + + public function getDefaultValue(): mixed + { + return $this->defaultValue; + } + + public function getExtra(): string|null + { + return $this->extra; + } + + public function getFullDbType(): string|null + { + if ($this->dbType === null) { + return null; + } + + if ($this->size === null) { + return $this->dbType; + } + + if ($this->scale === null) { + return "$this->dbType($this->size)"; + } + + return "$this->dbType($this->size,$this->scale)"; + } + + public function getPhpType(): string|null + { + return $this->phpType; + } + + public function getReference(): ForeignKeyConstraint|null + { + return $this->reference; + } + + public function getScale(): int|null + { + return $this->scale; + } + + public function getSize(): int|null + { + return $this->size; + } + + public function getType(): string + { + return $this->type; + } + + public function getValues(): array + { + return $this->values; + } + + public function isAllowNull(): bool|null + { + return $this->allowNull; + } + + public function isAutoIncrement(): bool + { + return $this->autoIncrement; + } + + public function isComputed(): bool + { + return $this->computed; + } + + public function isPrimaryKey(): bool + { + return $this->primaryKey; + } + + public function isUnique(): bool + { + return $this->unique; + } + + public function isUnsigned(): bool + { + return $this->unsigned; + } + + public function load(array $info): static + { + foreach ($info as $key => $value) { + match ($key) { + 'allow_null' => $this->allowNull($value !== null ? (bool) $value : null), + 'auto_increment' => $this->autoIncrement((bool) $value), + 'comment' => $this->comment($value !== null ? (string) $value : null), + 'computed' => $this->computed((bool) $value), + 'db_type' => $this->dbType($value !== null ? (string) $value : null), + 'default_value' => $this->defaultValue($value), + 'extra' => $this->extra($value !== null ? (string) $value : null), + 'primary_key' => $this->primaryKey((bool) $value), + 'php_type' => $this->phpType($value !== null ? (string) $value : null), + 'scale' => $this->scale($value !== null ? (int) $value : null), + 'size' => $this->size($value !== null ? (int) $value : null), + 'type' => $this->type($value !== null ? (string) $value : null), + 'unsigned' => $this->unsigned((bool) $value), + 'values' => $this->values(is_array($value) ? $value : null), + default => null, + }; + } + + if (array_key_exists('default_value_raw', $info)) { + $this->defaultValue($this->normalizeDefaultValue($info['default_value_raw'])); + } + + return $this; + } + + public function normalizeDefaultValue(string|null $value): mixed + { + if ($value === null || $this->computed || preg_match("/^\(?NULL\b/i", $value) === 1) { + return null; + } + + if (preg_match("/^'(.*)'|^\(([^()]*)\)/s", $value, $matches) === 1) { + return $this->phpTypecast($matches[2] ?? str_replace("''", "'", $matches[1])); + } + + return new Expression($value); + } + + public function phpType(string|null $value): static + { + $this->phpType = $value; + return $this; + } + + public function phpTypecast(mixed $value): mixed + { + return $value; + } + + public function primaryKey(bool $value = true): static + { + $this->primaryKey = $value; + return $this; + } + + public function reference(?ForeignKeyConstraint $value): static + { + $this->reference = $value; + return $this; + } + + public function scale(int|null $value): static + { + $this->scale = $value; + return $this; + } + + public function size(int|null $value): static + { + $this->size = $value; + return $this; + } + + public function type(string|null $value): static + { + $this->type = $value; + return $this; + } + + public function unique(bool $value = true): static + { + $this->unique = $value; + return $this; + } + + public function unsigned(bool $value = true): static + { + $this->unsigned = $value; + return $this; + } + + public function values(array $value): static + { + $this->values = $value; + return $this; + } +} diff --git a/src/Schema/Column/ColumnBuilder.php b/src/Schema/Column/ColumnBuilder.php new file mode 100644 index 000000000..fa9526cbf --- /dev/null +++ b/src/Schema/Column/ColumnBuilder.php @@ -0,0 +1,185 @@ +primaryKey() + ->autoIncrement($autoIncrement); + } + + public static function upk(bool $autoIncrement = true): ColumnInterface + { + return static::pk()->unsigned(); + } + + public static function bigpk(bool $autoIncrement = true): ColumnInterface + { + return static::bigint() + ->primaryKey() + ->autoIncrement($autoIncrement); + } + + public static function ubigpk(bool $autoIncrement = true): ColumnInterface + { + return static::bigpk()->unsigned(); + } + + public static function uuidpk(bool $autoIncrement = false): ColumnInterface + { + return static::uuid() + ->primaryKey() + ->autoIncrement($autoIncrement); + } + + public static function uuidpkseq(): ColumnInterface + { + return static::uuidpk(true); + } + + // Abstract type column builders + public static function uuid(): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_UUID); + } + + public static function char(int|null $size = 1): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_CHAR) + ->size($size); + } + + public static function string(int|null $size = 255): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_STRING) + ->size($size); + } + + public static function text(): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_TEXT); + } + + public static function binary(int|null $size = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_BINARY) + ->size($size); + } + + public static function boolean(): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_BOOLEAN); + } + + public static function tinyint(int|null $size = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_TINYINT) + ->size($size); + } + + public static function smallint(int|null $size = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_SMALLINT) + ->size($size); + } + + public static function integer(int|null $size = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_INTEGER) + ->size($size); + } + + public static function bigint(int|null $size = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_BIGINT) + ->size($size); + } + + public static function float(int|null $size = null, int|null $scale = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_FLOAT) + ->size($size) + ->scale($scale); + } + + public static function double(int|null $size = null, int|null $scale = null): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_DOUBLE) + ->size($size) + ->scale($scale); + } + + public static function decimal(int|null $size = 10, int|null $scale = 0): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_DECIMAL) + ->size($size) + ->scale($scale); + } + + public static function money(int|null $size = 19, int|null $scale = 4): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_MONEY) + ->size($size) + ->scale($scale); + } + + public static function datetime(int|null $size = 0): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_DATETIME) + ->size($size); + } + + public static function timestamp(int|null $size = 0): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_TIMESTAMP) + ->size($size); + } + + public static function time(int|null $size = 0): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_TIME) + ->size($size); + } + + public static function date(): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_DATE); + } + + public static function json(): ColumnInterface + { + return static::columnFactory() + ->fromType(SchemaInterface::TYPE_JSON); + } + + protected static function columnFactory(): ColumnFactory + { + return new ColumnFactory(); + } +} diff --git a/src/Schema/Column/ColumnBuilderInterface.php b/src/Schema/Column/ColumnBuilderInterface.php new file mode 100644 index 000000000..4aa9d5131 --- /dev/null +++ b/src/Schema/Column/ColumnBuilderInterface.php @@ -0,0 +1,58 @@ +> $fromDbType + * @psalm-param array> $fromType + */ +class ColumnFactory implements ColumnFactoryInterface +{ + private const TYPE_MAP = [ + 'uuid' => SchemaInterface::TYPE_UUID, + 'char' => SchemaInterface::TYPE_CHAR, + 'varchar' => SchemaInterface::TYPE_STRING, + 'text' => SchemaInterface::TYPE_TEXT, + 'binary' => SchemaInterface::TYPE_BINARY, + 'boolean' => SchemaInterface::TYPE_BOOLEAN, + 'tinyint' => SchemaInterface::TYPE_TINYINT, + 'smallint' => SchemaInterface::TYPE_SMALLINT, + 'integer' => SchemaInterface::TYPE_INTEGER, + 'bigint' => SchemaInterface::TYPE_BIGINT, + 'float' => SchemaInterface::TYPE_FLOAT, + 'double' => SchemaInterface::TYPE_DOUBLE, + 'decimal' => SchemaInterface::TYPE_DECIMAL, + 'money' => SchemaInterface::TYPE_MONEY, + 'datetime' => SchemaInterface::TYPE_DATETIME, + 'timestamp' => SchemaInterface::TYPE_TIMESTAMP, + 'time' => SchemaInterface::TYPE_TIME, + 'date' => SchemaInterface::TYPE_DATE, + 'json' => SchemaInterface::TYPE_JSON, + ]; + + public function __construct( + private string $columnBuilderClass = ColumnBuilder::class, + private array $fromDbType = [], + private array $fromType = [], + ) { + } + + public function fromDbType(string $dbType, array $info = []): ColumnInterface + { + $info['db_type'] = $dbType; + $type = $info['type'] ?? $this->getTypeFromDb($dbType); + + if (isset($this->fromDbType[$dbType])) { + $phpType = $info['php_type'] ?? $this->getPhpType($type); + return (new $this->fromDbType[$dbType]($type, $phpType))->load($info); + } + + return $this->fromType($type, $info); + } + + public function fromDefinition(string $definition, array $info = []): ColumnInterface + { + preg_match('/^(\w*)(?:\(([^)]+)\))?\s*/', $definition, $matches); + + $dbType = strtolower($matches[1]); + + if (isset($matches[2])) { + if ($dbType === 'enum') { + preg_match_all("/'([^']*)'/", $matches[2], $values); + + $info['values'] = $values[1]; + } else { + $values = explode(',', $matches[2]); + $info['size'] = (int) $values[0]; + + if (isset($values[1])) { + $info['scale'] = (int) $values[1]; + } + } + } + + $extra = substr($definition, strlen($matches[0])); + + if (!empty($extra) && str_contains(strtolower($extra), 'unsigned')) { + $info['unsigned'] = true; + $extra = trim(str_ireplace('unsigned', '', $extra)); + } + + if (!empty($extra)) { + if (empty($info['extra'])) { + $info['extra'] = $extra; + } else { + $info['extra'] = $extra . ' ' . $info['extra']; + } + } + + if ($this->isDbType($dbType)) { + return $this->fromDbType($dbType, $info); + } + + if ($this->isBuilder($dbType)) { + return $this->columnBuilderClass::$dbType()->load($info); + } + + return $this->fromDbType($dbType, $info); + } + + public function fromPhpType(string $phpType, array $info = []): ColumnInterface + { + $type = $info['type'] ?? $this->getTypeFromPhp($phpType); + + $column = match ($phpType) { + SchemaInterface::PHP_TYPE_INTEGER => new IntegerColumn($type, $phpType), + SchemaInterface::PHP_TYPE_DOUBLE => new DoubleColumn($type, $phpType), + SchemaInterface::PHP_TYPE_BOOLEAN => new BooleanColumn($type, $phpType), + SchemaInterface::PHP_TYPE_RESOURCE => new BinaryColumn($type, $phpType), + SchemaInterface::PHP_TYPE_ARRAY => new JsonColumn($type, $phpType), + default => new StringColumn($type, $phpType), + }; + + $column->load($info); + + return $column; + } + + public function fromType(string $type, array $info = []): ColumnInterface + { + $info['type'] = $type; + $phpType = $info['php_type'] ?? $this->getPhpType($type); + + if (isset($this->fromType[$type])) { + return (new $this->fromType[$type]($type, $phpType))->load($info); + } + + $isUnsigned = !empty($info['unsigned']); + + if ( + PHP_INT_SIZE !== 8 && $isUnsigned && $type === SchemaInterface::TYPE_INTEGER + || (PHP_INT_SIZE !== 8 || $isUnsigned) && $type === SchemaInterface::TYPE_BIGINT + ) { + return (new BigIntColumn($type, $phpType))->load($info); + } + + return $this->fromPhpType($phpType, $info); + } + + /** + * Get the PHP type from an abstract database type. + * + * @param string $type The abstract database type. + * + * @return string The PHP type name. + */ + protected function getPhpType(string $type): string + { + return match ($type) { + // abstract type => php type + SchemaInterface::TYPE_BOOLEAN => SchemaInterface::PHP_TYPE_BOOLEAN, + SchemaInterface::TYPE_TINYINT => SchemaInterface::PHP_TYPE_INTEGER, + SchemaInterface::TYPE_SMALLINT => SchemaInterface::PHP_TYPE_INTEGER, + SchemaInterface::TYPE_INTEGER => SchemaInterface::PHP_TYPE_INTEGER, + SchemaInterface::TYPE_BIGINT => SchemaInterface::PHP_TYPE_INTEGER, + SchemaInterface::TYPE_DECIMAL => SchemaInterface::PHP_TYPE_DOUBLE, + SchemaInterface::TYPE_FLOAT => SchemaInterface::PHP_TYPE_DOUBLE, + SchemaInterface::TYPE_DOUBLE => SchemaInterface::PHP_TYPE_DOUBLE, + SchemaInterface::TYPE_BINARY => SchemaInterface::PHP_TYPE_RESOURCE, + SchemaInterface::TYPE_JSON => SchemaInterface::PHP_TYPE_ARRAY, + default => SchemaInterface::PHP_TYPE_STRING, + }; + } + + /** + * Get the abstract database type from a database column type. + * + * @param string $dbType The database column type. + * + * @return string The abstract database type. + */ + protected function getTypeFromDb(string $dbType): string + { + return self::TYPE_MAP[$dbType] ?? SchemaInterface::TYPE_STRING; + } + + protected function getTypeFromPhp(string $phpType): string + { + return match ($phpType) { + // php type => abstract type + SchemaInterface::PHP_TYPE_INTEGER => SchemaInterface::TYPE_INTEGER, + SchemaInterface::PHP_TYPE_BOOLEAN => SchemaInterface::TYPE_BOOLEAN, + SchemaInterface::PHP_TYPE_DOUBLE => SchemaInterface::TYPE_DOUBLE, + SchemaInterface::PHP_TYPE_RESOURCE => SchemaInterface::TYPE_BINARY, + SchemaInterface::PHP_TYPE_ARRAY => SchemaInterface::TYPE_JSON, + default => SchemaInterface::TYPE_STRING, + }; + } + + protected function isBuilder(string $dbType): bool + { + return match ($dbType) { + 'pk', + 'upk', + 'bigpk', + 'ubigpk', + 'uuidpk', + 'uuidpkseq' => true, + default => $this->isType($dbType), + }; + } + + protected function isDbType(string $dbType): bool + { + return isset(self::TYPE_MAP[$dbType]); + } + + protected function isType(string $dbType): bool + { + return match ($dbType) { + SchemaInterface::TYPE_UUID, + SchemaInterface::TYPE_CHAR, + SchemaInterface::TYPE_STRING, + SchemaInterface::TYPE_TEXT, + SchemaInterface::TYPE_BINARY, + SchemaInterface::TYPE_BOOLEAN, + SchemaInterface::TYPE_TINYINT, + SchemaInterface::TYPE_SMALLINT, + SchemaInterface::TYPE_INTEGER, + SchemaInterface::TYPE_BIGINT, + SchemaInterface::TYPE_FLOAT, + SchemaInterface::TYPE_DOUBLE, + SchemaInterface::TYPE_DECIMAL, + SchemaInterface::TYPE_MONEY, + SchemaInterface::TYPE_DATETIME, + SchemaInterface::TYPE_TIMESTAMP, + SchemaInterface::TYPE_TIME, + SchemaInterface::TYPE_DATE, + SchemaInterface::TYPE_JSON => true, + default => false, + }; + } +} diff --git a/src/Schema/Column/ColumnFactoryInterface.php b/src/Schema/Column/ColumnFactoryInterface.php new file mode 100644 index 000000000..9837b73d9 --- /dev/null +++ b/src/Schema/Column/ColumnFactoryInterface.php @@ -0,0 +1,54 @@ + $this->text()->allowNull(true), + * 'description' => $this->text()->allowNull(false), * ]; * ``` */ - public function allowNull(bool $value): void; + public function allowNull(bool|null $value = true): static; /** * The database assigns auto incremented column a unique value automatically whenever you insert a new row into @@ -32,11 +53,13 @@ public function allowNull(bool $value): void; * * ```php * $columns = [ - * 'id' => $this->primaryKey()->autoIncrement(true), + * 'id' => $this->primaryKey()->autoIncrement(), * ]; * ``` */ - public function autoIncrement(bool $value): void; + public function autoIncrement(bool $value = true): static; + + public function check(string|ExpressionInterface|null $value): static; /** * The comment for a column in a database table. @@ -49,7 +72,7 @@ public function autoIncrement(bool $value): void; * ]; * ``` */ - public function comment(string|null $value): void; + public function comment(string|null $value): static; /** * A computed column is a virtual column that computes its values from an expression. @@ -62,7 +85,7 @@ public function comment(string|null $value): void; * ]; * ``` */ - public function computed(bool $value): void; + public function computed(bool $value = true): static; /** * The database data-type of column. @@ -77,7 +100,7 @@ public function computed(bool $value): void; * ]; * ``` */ - public function dbType(string|null $value): void; + public function dbType(string|null $value): static; /** * Convert a value from its PHP representation to a database-specific representation. @@ -100,18 +123,7 @@ public function dbTypecast(mixed $value): mixed; * ]; * ``` */ - public function defaultValue(mixed $value): void; - - /** - * The list of possible values for the `ENUM` column. - * - * ```php - * $columns = [ - * 'status' => $this->string(16)->enumValues(['active', 'inactive']), - * ]; - * ``` - */ - public function enumValues(array|null $value): void; + public function defaultValue(mixed $value): static; /** * Extra SQL to append to the generated SQL for a column. @@ -125,7 +137,9 @@ public function enumValues(array|null $value): void; * ]; * ``` */ - public function extra(string|null $value): void; + public function extra(string|null $value): static; + + public function getCheck(): string|ExpressionInterface|null; /** * @return string|null The comment of the column. @@ -138,7 +152,7 @@ public function getComment(): string|null; * @return string|null The database type of the column. * Null means the column has no type in the database. * - * Note that the type includes size for columns supporting it, e.g. `varchar(128)`. The size can be obtained + * Note that the type is not including size for columns supporting it, e.g. `varchar(128)`. The size can be obtained * separately via {@see getSize()}. * * @see dbType() @@ -152,13 +166,6 @@ public function getDbType(): string|null; */ public function getDefaultValue(): mixed; - /** - * @return array|null The enum values of the column. - * - * @see enumValues() - */ - public function getEnumValues(): array|null; - /** * @return string|null The extra SQL for the column. * @@ -167,16 +174,11 @@ public function getEnumValues(): array|null; public function getExtra(): string|null; /** - * @return string The name of the column. - */ - public function getName(): string; - - /** - * @return int|null The precision of the column. + * @return string|null The database type of the column including size, precision and scale. * - * @see precision() + * @see getDbType(), getSize(), getScale() for more details. */ - public function getPrecision(): int|null; + public function getFullDbType(): string|null; /** * @return string|null The PHP type of the column. @@ -185,6 +187,8 @@ public function getPrecision(): int|null; */ public function getPhpType(): string|null; + public function getReference(): ForeignKeyConstraint|null; + /** * @return int|null The scale of the column. * @@ -206,12 +210,19 @@ public function getSize(): int|null; */ public function getType(): string; + /** + * @return array|null The `ENUM`, `SET` or other values of the column. + * + * @see values() + */ + public function getValues(): array|null; + /** * Whether this column is nullable. * * @see allowNull() */ - public function isAllowNull(): bool; + public function isAllowNull(): bool|null; /** * Whether this column is auto incremental. @@ -230,20 +241,43 @@ public function isAutoIncrement(): bool; public function isComputed(): bool; /** - * Whether this column is a primary key. + * Whether this column is a part of primary key. * * @see primaryKey() */ public function isPrimaryKey(): bool; /** - * Whether this column is unsigned. This is only meaningful when {@see type} is `smallint`, `integer` + * Whether this column has a unique index. + * + * @see unique() + */ + public function isUnique(): bool; + + /** + * Whether this column is unsigned. This is only meaningful when {@see type} is `tinyint`, `smallint`, `integer` * or `bigint`. * * @see unsigned() */ public function isUnsigned(): bool; + /** + * Loads the column's schema information from an array. + * + * @psalm-param ColumnInfo $info + */ + public function load(array $info): static; + + /** + * Converts column's default value according to {@see ColumnSchema::phpType} after retrieval from the database. + * + * @param string|null $value The default value retrieved from the database. + * + * @return mixed The normalized default value. + */ + public function normalizeDefaultValue(string|null $value): mixed; + /** * 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. @@ -259,7 +293,7 @@ public function isUnsigned(): bool; * ]; * ``` */ - public function phpType(string|null $value): void; + public function phpType(string|null $value): static; /** * Converts the input value according to {@see phpType} after retrieval from the database. @@ -269,26 +303,30 @@ public function phpType(string|null $value): void; public function phpTypecast(mixed $value): mixed; /** - * The precision is the total number of digits that represent the value. - * This is only meaningful when {@see type} is `decimal`. + * The primary key is a column or set of columns that uniquely identifies each row in a table. * * ```php * $columns = [ - * 'price' => $this->decimal(10, 2)->precision(10), + * 'id' => $this->primaryKey(true), * ]; + * ``` */ - public function precision(int|null $value): void; + public function primaryKey(bool $value = true): static; /** - * The primary key is a column or set of columns that uniquely identifies each row in a table. + * The reference to the foreign key constraint. * * ```php + * $reference = new ForeignKeyConstraint(); + * $reference->foreignTableName('user'); + * $reference->foreignColumnNames(['id']); + * * $columns = [ - * 'id' => $this->primaryKey(true), + * 'user_id' => $this->reference($reference), * ]; * ``` */ - public function primaryKey(bool $value): void; + public function reference(ForeignKeyConstraint|null $value): static; /** * The scale is the number of digits to the right of the decimal point and is only meaningful when {@see type} is @@ -296,16 +334,16 @@ public function primaryKey(bool $value): void; * * ```php * $columns = [ - * 'price' => $this->decimal(10, 2)->scale(2), + * 'price' => $this->decimal(10, 2), * ]; * ``` */ - public function scale(int|null $value): void; + public function scale(int|null $value): static; /** * The size refers to the number of characters or digits allowed in a column of a database table. The size is - * typically used for character or numeric data types, such as `VARCHAR` or `INT`, to specify the maximum length or - * precision of the data in the column. + * typically used for character or numeric data types, such as `VARCHAR`, `INT` or DECIMAL, to specify the maximum + * length or precision of the data in the column. * * ```php * $columns = [ @@ -313,7 +351,7 @@ public function scale(int|null $value): void; * ]; * ``` */ - public function size(int|null $value): void; + public function size(int|null $value): static; /** * The database type of the column. @@ -323,7 +361,12 @@ public function size(int|null $value): void; * 'description' => $this->text()->type('text'), * ]; */ - public function type(string $value): void; + public function type(string|null $value): static; + + /** + * Whether the column has a unique index. + */ + public function unique(bool $value = true): static; /** * Whether the column type is an unsigned integer. @@ -335,5 +378,16 @@ public function type(string $value): void; * ]; * ``` */ - public function unsigned(bool $value): void; + public function unsigned(bool $value = true): static; + + /** + * The list of possible values for the `ENUM`, `SET` or other column. + * + * ```php + * $columns = [ + * 'status' => $this->string(16)->values(['active', 'inactive']), + * ]; + * ``` + */ + public function values(array $value): static; } diff --git a/src/Schema/Column/DoubleColumn.php b/src/Schema/Column/DoubleColumn.php new file mode 100644 index 000000000..b6aa324aa --- /dev/null +++ b/src/Schema/Column/DoubleColumn.php @@ -0,0 +1,39 @@ + null, + default => (float) $value, + }; + } + + public function phpTypecast(mixed $value): float|null + { + if ($value === null) { + return null; + } + + return (float) $value; + } +} diff --git a/src/Schema/Column/IntegerColumn.php b/src/Schema/Column/IntegerColumn.php new file mode 100644 index 000000000..9e1d7e7f0 --- /dev/null +++ b/src/Schema/Column/IntegerColumn.php @@ -0,0 +1,39 @@ + null, + default => (int) $value, + }; + } + + public function phpTypecast(mixed $value): int|null + { + if ($value === null) { + return null; + } + + return (int) $value; + } +} diff --git a/src/Schema/Column/JsonColumn.php b/src/Schema/Column/JsonColumn.php new file mode 100644 index 000000000..81aa1f934 --- /dev/null +++ b/src/Schema/Column/JsonColumn.php @@ -0,0 +1,45 @@ +dbType('json'); + } + + public function dbTypecast(mixed $value): ExpressionInterface|null + { + if ($value === null || $value instanceof ExpressionInterface) { + return $value; + } + + return new JsonExpression($value, $this->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/StringColumn.php b/src/Schema/Column/StringColumn.php new file mode 100644 index 000000000..7d6db60e0 --- /dev/null +++ b/src/Schema/Column/StringColumn.php @@ -0,0 +1,34 @@ + $value, + 'NULL' => null, + 'boolean' => $value ? '1' : '0', + default => (string) $value, + }; + } +} diff --git a/src/Schema/SchemaInterface.php b/src/Schema/SchemaInterface.php index 744ebc555..db0a4611f 100644 --- a/src/Schema/SchemaInterface.php +++ b/src/Schema/SchemaInterface.php @@ -10,7 +10,8 @@ use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; +use Yiisoft\Db\Schema\Column\ColumnFactoryInterface; /** * Represents the schema for a database table. @@ -120,26 +121,38 @@ interface SchemaInterface extends ConstraintSchemaInterface public const INDEX_BITMAP = 'BITMAP'; /** * Define the abstract column type as a primary key. + * + * @deprecated Use {@see ColumnBuilder::pk()}. Will be removed in version 3.0.0. */ public const TYPE_PK = 'pk'; /** * Define the abstract column type as an `unsigned` primary key. + * + * @deprecated Use {@see ColumnBuilder::upk()}. Will be removed in version 3.0.0. */ public const TYPE_UPK = 'upk'; /** * Define the abstract column type as big primary key. + * + * @deprecated Use {@see ColumnBuilder::bigpk()}. Will be removed in version 3.0.0. */ public const TYPE_BIGPK = 'bigpk'; /** * Define the abstract column type as `unsigned` big primary key. + * + * @deprecated Use {@see ColumnBuilder::ubigpk()}. Will be removed in version 3.0.0. */ public const TYPE_UBIGPK = 'ubigpk'; /** * Define the abstract column type as an `uuid` primary key. + * + * @deprecated Use {@see ColumnBuilder::uuidpk()}. Will be removed in version 3.0.0. */ public const TYPE_UUID_PK = 'uuid_pk'; /** * Define the abstract column type as an`uuid` primary key with a sequence. + * + * @deprecated Use {@see ColumnBuilder::uuidpk(true)}. Will be removed in version 3.0.0. */ public const TYPE_UUID_PK_SEQ = 'uuid_pk_seq'; /** @@ -158,6 +171,14 @@ interface SchemaInterface extends ConstraintSchemaInterface * Define the abstract column type as `text`. */ public const TYPE_TEXT = 'text'; + /** + * Define the abstract column type as `binary`. + */ + public const TYPE_BINARY = 'binary'; + /** + * Define the abstract column type as `boolean`. + */ + public const TYPE_BOOLEAN = 'boolean'; /** * Define the abstract column type as `tinyint`. */ @@ -186,6 +207,10 @@ interface SchemaInterface extends ConstraintSchemaInterface * Define the abstract column type as `decimal`. */ public const TYPE_DECIMAL = 'decimal'; + /** + * Define the abstract column type as `money`. + */ + public const TYPE_MONEY = 'money'; /** * Define the abstract column type as `datetime`. */ @@ -202,29 +227,10 @@ interface SchemaInterface extends ConstraintSchemaInterface * Define the abstract column type as `date`. */ public const TYPE_DATE = 'date'; - /** - * Define the abstract column type as `binary`. - */ - public const TYPE_BINARY = 'binary'; - /** - * Define the abstract column type as `boolean`. - */ - public const TYPE_BOOLEAN = 'boolean'; - /** - * Define the abstract column type as `money`. - */ - public const TYPE_MONEY = 'money'; /** * Define the abstract column type as `json`. */ public const TYPE_JSON = 'json'; - /** - * Define the abstract column type as `jsonb`. - * - * @deprecated will be removed in version 2.0.0. Use `SchemaInterface::TYPE_JSON` instead. - */ - public const TYPE_JSONB = 'jsonb'; - /** * Define the php type as `integer` for cast to php value. */ @@ -255,9 +261,11 @@ interface SchemaInterface extends ConstraintSchemaInterface public const PHP_TYPE_NULL = 'NULL'; /** - * @psalm-param string[]|int[]|int|string|null $length + * Returns the column schema factory for the database. + * + * @return ColumnFactoryInterface The column schema factory for the database. */ - public function createColumn(string $type, array|int|string $length = null): ColumnInterface; + public function getColumnFactory(): ColumnFactoryInterface; /** * @return string|null The default schema name. diff --git a/src/Schema/TableSchemaInterface.php b/src/Schema/TableSchemaInterface.php index 47fe0c107..315c87848 100644 --- a/src/Schema/TableSchemaInterface.php +++ b/src/Schema/TableSchemaInterface.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Schema; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Schema\Column\ColumnInterface; /** * Represents the metadata of a database table. @@ -22,9 +23,9 @@ interface TableSchemaInterface * * @param string $name The column name. * - * @return ColumnSchemaInterface|null The named column metadata. Null if the named column doesn't exist. + * @return ColumnInterface|null The named column metadata. Null if the named column doesn't exist. */ - public function getColumn(string $name): ColumnSchemaInterface|null; + public function getColumn(string $name): ColumnInterface|null; /** * @return array The names of all columns in this table. @@ -66,10 +67,10 @@ public function getSequenceName(): string|null; public function getPrimaryKey(): array; /** - * @return ColumnSchemaInterface[] The column metadata of this table. - * Array of {@see ColumnSchemaInterface} objects indexed by column names. + * @return ColumnInterface[] The column metadata of this table. + * Array of {@see ColumnInterface} objects indexed by column names. * - * @psalm-return array + * @psalm-return array */ public function getColumns(): array; @@ -125,7 +126,7 @@ public function primaryKey(string $value): void; * * @param string $name The column name. */ - public function column(string $name, ColumnSchemaInterface $value): void; + public function column(string $name, ColumnInterface $value): void; /** * @return string|null The name of the catalog (database) that this table belongs to. Defaults to null, meaning no diff --git a/tests/AbstractColumnDefinitionBuilderTest.php b/tests/AbstractColumnDefinitionBuilderTest.php new file mode 100644 index 000000000..4631e1632 --- /dev/null +++ b/tests/AbstractColumnDefinitionBuilderTest.php @@ -0,0 +1,25 @@ +getConnection(); + + $builder = new ColumnDefinitionBuilder($db->getQueryBuilder()); + + $this->assertSame($expected, $builder->build($column)); + } +} diff --git a/tests/AbstractQueryBuilderTest.php b/tests/AbstractQueryBuilderTest.php index 35cf22c43..68b01d1a1 100644 --- a/tests/AbstractQueryBuilderTest.php +++ b/tests/AbstractQueryBuilderTest.php @@ -21,7 +21,7 @@ use Yiisoft\Db\Query\Query; use Yiisoft\Db\Query\QueryInterface; use Yiisoft\Db\QueryBuilder\Condition\SimpleCondition; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\QuoterInterface; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\Support\Assert; @@ -65,7 +65,7 @@ public function testAddColumn(ColumnInterface|string $type): void DbHelper::replaceQuotes( <<getColumnType($type), + SQL . ' ' . $qb->buildColumnDefinition($type), $db->getDriverName(), ), $sql, @@ -1788,34 +1788,13 @@ public function testDropView(): void ); } - public function testGetColumnType(): void + /** @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::buildColumnDefinition */ + public function testBuildColumnDefinition($type, $expected): void { $db = $this->getConnection(); - $qb = $db->getQueryBuilder(); - $this->assertSame('pk', $qb->getColumnType(SchemaInterface::TYPE_PK)); - $this->assertSame('upk', $qb->getColumnType(SchemaInterface::TYPE_UPK)); - $this->assertSame('bigpk', $qb->getColumnType(SchemaInterface::TYPE_BIGPK)); - $this->assertSame('ubigpk', $qb->getColumnType(SchemaInterface::TYPE_UBIGPK)); - $this->assertSame('char', $qb->getColumnType(SchemaInterface::TYPE_CHAR)); - $this->assertSame('string', $qb->getColumnType(SchemaInterface::TYPE_STRING)); - $this->assertSame('text', $qb->getColumnType(SchemaInterface::TYPE_TEXT)); - $this->assertSame('tinyint', $qb->getColumnType(SchemaInterface::TYPE_TINYINT)); - $this->assertSame('smallint', $qb->getColumnType(SchemaInterface::TYPE_SMALLINT)); - $this->assertSame('integer', $qb->getColumnType(SchemaInterface::TYPE_INTEGER)); - $this->assertSame('bigint', $qb->getColumnType(SchemaInterface::TYPE_BIGINT)); - $this->assertSame('float', $qb->getColumnType(SchemaInterface::TYPE_FLOAT)); - $this->assertSame('double', $qb->getColumnType(SchemaInterface::TYPE_DOUBLE)); - $this->assertSame('decimal', $qb->getColumnType(SchemaInterface::TYPE_DECIMAL)); - $this->assertSame('datetime', $qb->getColumnType(SchemaInterface::TYPE_DATETIME)); - $this->assertSame('timestamp', $qb->getColumnType(SchemaInterface::TYPE_TIMESTAMP)); - $this->assertSame('time', $qb->getColumnType(SchemaInterface::TYPE_TIME)); - $this->assertSame('date', $qb->getColumnType(SchemaInterface::TYPE_DATE)); - $this->assertSame('binary', $qb->getColumnType(SchemaInterface::TYPE_BINARY)); - $this->assertSame('boolean', $qb->getColumnType(SchemaInterface::TYPE_BOOLEAN)); - $this->assertSame('money', $qb->getColumnType(SchemaInterface::TYPE_MONEY)); - $this->assertSame('json', $qb->getColumnType(SchemaInterface::TYPE_JSON)); + $this->assertSame($expected, $qb->buildColumnDefinition($type)); } /** diff --git a/tests/AbstractSchemaTest.php b/tests/AbstractSchemaTest.php index 3059e606f..4430c9cd4 100644 --- a/tests/AbstractSchemaTest.php +++ b/tests/AbstractSchemaTest.php @@ -6,10 +6,7 @@ use PHPUnit\Framework\TestCase; use Yiisoft\Db\Command\DataType; -use Yiisoft\Db\Schema\Builder\ColumnInterface; -use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\Support\Assert; -use Yiisoft\Db\Tests\Support\Stub\ColumnSchema; use Yiisoft\Db\Tests\Support\TestTrait; use function fclose; @@ -20,22 +17,6 @@ abstract class AbstractSchemaTest extends TestCase { use TestTrait; - public function testCreateColumnSchemaBuilder(): void - { - $columnSchemaBuilder = $this->getConnection()->getSchema()->createColumn('string'); - - $this->assertInstanceOf(ColumnInterface::class, $columnSchemaBuilder); - $this->assertSame('string', $columnSchemaBuilder->getType()); - } - - public function testColumnSchemaDbTypecastWithEmptyCharType(): void - { - $columnSchema = new ColumnSchema('new'); - $columnSchema->type(SchemaInterface::TYPE_CHAR); - - $this->assertSame('', $columnSchema->dbTypecast('')); - } - public function testGetDefaultSchema(): void { $db = $this->getConnection(); diff --git a/tests/AbstractTableSchemaTest.php b/tests/AbstractTableSchemaTest.php index a22446710..01f97d8cd 100644 --- a/tests/AbstractTableSchemaTest.php +++ b/tests/AbstractTableSchemaTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Tests\Support\Stub\ColumnSchema; +use Yiisoft\Db\Schema\Column\StringColumn; use Yiisoft\Db\Tests\Support\Stub\TableSchema; use Yiisoft\Db\Tests\Support\TestTrait; @@ -49,7 +49,7 @@ public function testGetComment(): void public function testGetColumn(): void { // Defined column schema. - $columnSchema = new ColumnSchema('id'); + $columnSchema = new StringColumn(); // Create table schema. $tableSchema = new TableSchema(); @@ -64,7 +64,7 @@ public function testGetColumn(): void public function testGetColumns(): void { // Defined column schema. - $columnSchema = new ColumnSchema('id'); + $columnSchema = new StringColumn(); // Create table schema. $tableSchema = new TableSchema(); @@ -79,7 +79,7 @@ public function testGetColumns(): void public function testGetColumnName(): void { // Defined column schema. - $columnSchema = new ColumnSchema('id'); + $columnSchema = new StringColumn(); // Create table schema. $tableSchema = new TableSchema(); @@ -192,7 +192,7 @@ public function testGetSchemaName(): void { $tableSchema = new TableSchema(); - $this->assertNull($tableSchema->getSchemaName()); + $this->assertSame('', $tableSchema->getSchemaName()); $tableSchema->schemaName('test'); diff --git a/tests/Common/CommonColumnSchemaTest.php b/tests/Common/CommonColumnSchemaTest.php new file mode 100644 index 000000000..bfabc24a1 --- /dev/null +++ b/tests/Common/CommonColumnSchemaTest.php @@ -0,0 +1,46 @@ +assertSame('column_name', $column->getName()); + $this->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('column_name'); + + 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/CommonCommandTest.php b/tests/Common/CommonCommandTest.php index 924a94800..004052668 100644 --- a/tests/Common/CommonCommandTest.php +++ b/tests/Common/CommonCommandTest.php @@ -21,10 +21,10 @@ use Yiisoft\Db\Query\Data\DataReaderInterface; use Yiisoft\Db\Query\Query; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\AbstractCommandTest; use Yiisoft\Db\Tests\Support\Assert; -use Yiisoft\Db\Tests\Support\Stub\Column; use Yiisoft\Db\Transaction\TransactionInterface; use function call_user_func_array; @@ -546,9 +546,9 @@ public function testCreateTable(): void $command->createTable( '{{testCreateTable}}', [ - '[[id]]' => SchemaInterface::TYPE_PK, + '[[id]]' => ColumnBuilder::pk(), '[[bar]]' => SchemaInterface::TYPE_INTEGER, - '[[name]]' => (new Column('string(100)'))->notNull(), + '[[name]]' => ColumnBuilder::string(100)->allowNull(false), ], )->execute(); $command->insert('{{testCreateTable}}', ['[[bar]]' => 1, '[[name]]' => 'Lilo'])->execute(); diff --git a/tests/Common/CommonSchemaTest.php b/tests/Common/CommonSchemaTest.php index c91b4e9ff..e0af4bb9d 100644 --- a/tests/Common/CommonSchemaTest.php +++ b/tests/Common/CommonSchemaTest.php @@ -898,6 +898,14 @@ protected function columnSchema(array $columns, string $table): void ); } + if (isset($expected['unsigned'])) { + $this->assertSame( + $expected['unsigned'], + $column->isUnsigned(), + "unsigned of column $name does not match" + ); + } + /* Pgsql only */ if (isset($expected['dimension'])) { /** @psalm-suppress UndefinedMethod */ diff --git a/tests/Db/Command/CommandTest.php b/tests/Db/Command/CommandTest.php index 4c77527dc..f622b728e 100644 --- a/tests/Db/Command/CommandTest.php +++ b/tests/Db/Command/CommandTest.php @@ -5,7 +5,8 @@ namespace Yiisoft\Db\Tests\Db\Command; use Yiisoft\Db\Exception\NotSupportedException; -use Yiisoft\Db\Schema\Builder\ColumnInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; +use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\AbstractCommandTest; use Yiisoft\Db\Tests\Support\Assert; @@ -48,12 +49,12 @@ public function testAddColumn(ColumnInterface|string $type): void $command = $db->createCommand(); $sql = $command->addColumn('table', 'column', $type)->getSql(); - $columnType = $db->getQueryBuilder()->getColumnType($type); + $columnType = $db->getQueryBuilder()->buildColumnDefinition($type); $this->assertSame( DbHelper::replaceQuotes( <<getDriverName(), ), @@ -231,19 +232,20 @@ public function testCreateTable(): void $command = $db->createCommand(); $expected = << SchemaInterface::TYPE_PK, + 'id' => ColumnBuilder::pk(), 'name' => SchemaInterface::TYPE_STRING . '(255) NOT NULL', 'email' => SchemaInterface::TYPE_STRING . '(255) NOT NULL', 'address' => SchemaInterface::TYPE_STRING . '(255) NOT NULL', diff --git a/tests/Db/QueryBuilder/ColumnDefinitionBuilderTest.php b/tests/Db/QueryBuilder/ColumnDefinitionBuilderTest.php new file mode 100644 index 000000000..36cdb0a7a --- /dev/null +++ b/tests/Db/QueryBuilder/ColumnDefinitionBuilderTest.php @@ -0,0 +1,11 @@ + 'pk', 'name' => 'string(255) NOT NULL', - 'email' => (new Column('string(255)'))->notNull(), + 'email' => ColumnBuilder::string(255)->allowNull(false), 'status' => 'integer NOT NULL', 'created_at' => 'datetime NOT NULL', 'UNIQUE test_email_unique (email)', diff --git a/tests/Db/Schema/ColumnSchemaBuilderTest.php b/tests/Db/Schema/ColumnSchemaBuilderTest.php deleted file mode 100644 index 8b0a4457c..000000000 --- a/tests/Db/Schema/ColumnSchemaBuilderTest.php +++ /dev/null @@ -1,280 +0,0 @@ -assertSame('string', $column->asString()); - $this->assertSame('string bar', $column->append('bar')->asString()); - $this->assertSame('string foo', $column->append('foo')->asString()); - } - - public function testAppendWithEmptyString(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string', $column->append('')->asString()); - } - - public function testCheck(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string CHECK (value > 5)', $column->check('value > 5')->asString()); - } - - public function testCheckWithEmptyString(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string', $column->check('')->asString()); - } - - public function testCheckWithNull(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string', $column->check(null)->asString()); - } - - public function testComment(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string', $column->comment('comment')->asString()); - } - - public function testCommentWithEmptyString(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string', $column->comment('')->asString()); - } - - public function testCommentWithNull(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string', $column->comment(null)->asString()); - } - - public function testDefaultExpression(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame("string DEFAULT 'expression'", $column->defaultExpression("'expression'")->asString()); - } - - public function testDefaultExpressionWithEmptyString(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string DEFAULT ', $column->defaultExpression('')->asString()); - } - - public function testDefaultValue(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame("string DEFAULT ''value''", $column->defaultValue("'value'")->asString()); - } - - public function testDefaultValueWithEmptyString(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame("string DEFAULT ''", $column->defaultValue('')->asString()); - } - - public function testDefaultValueWithNull(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string NULL DEFAULT NULL', $column->defaultValue(null)->asString()); - } - - public function testGetAppend(): void - { - $column = new Column('string'); - - $this->assertNull($column->getAppend()); - $this->assertSame('bar', $column->append('bar')->getAppend()); - $this->assertSame('bar', $column->getAppend()); - } - - public function testGetCategoryMap(): void - { - $column = new Column('string'); - - $this->assertSame( - [ - 'pk' => 'pk', - 'upk' => 'pk', - 'bigpk' => 'pk', - 'ubigpk' => 'pk', - 'char' => 'string', - 'string' => 'string', - 'text' => 'string', - 'tinyint' => 'numeric', - 'smallint' => 'numeric', - 'integer' => 'numeric', - 'bigint' => 'numeric', - 'float' => 'numeric', - 'double' => 'numeric', - 'decimal' => 'numeric', - 'datetime' => 'time', - 'timestamp' => 'time', - 'time' => 'time', - 'date' => 'time', - 'binary' => 'other', - 'boolean' => 'numeric', - 'money' => 'numeric', - 'uuid' => 'uuid', - 'uuid_pk' => 'uuid_pk', - ], - $column->getCategoryMap(), - ); - } - - public function testGetCheck(): void - { - $column = new Column('string'); - - $this->assertNull($column->getCheck()); - $this->assertSame('value > 5', $column->check('value > 5')->getCheck()); - $this->assertSame('value > 5', $column->getCheck()); - } - - public function testGetComment(): void - { - $column = new Column('string'); - - $this->assertNull($column->getComment()); - $this->assertSame('comment', $column->comment('comment')->getComment()); - $this->assertSame('comment', $column->getComment()); - } - - public function testGetDefault(): void - { - $column = new Column('string'); - - $this->assertNull($column->getDefault()); - $this->assertSame("'value'", $column->defaultValue("'value'")->getDefault()); - $this->assertSame("'value'", $column->getDefault()); - } - - public function testGetDefaultExpression(): void - { - $column = new Column('string'); - - $this->assertNull($column->getDefault()); - $this->assertInstanceOf(Expression::class, $column->defaultExpression("'expression'")->getDefault()); - $this->assertInstanceOf(Expression::class, $column->getDefault()); - } - - public function testGetLength(): void - { - $column = new Column('string', 10); - - $this->assertSame(10, $column->getLength()); - } - - public function testIsNotNull(): void - { - $column = new Column('string'); - - $this->assertNull($column->isNotNull()); - $this->assertTrue($column->notNull()->isNotNull()); - } - - public function testIsUnique(): void - { - $column = new Column('string'); - - $this->assertFalse($column->isUnique()); - $this->assertTrue($column->unique()->isUnique()); - } - - public function testIsUnsigned(): void - { - $column = new Column('pk'); - - $this->assertFalse($column->isUnsigned()); - $this->assertTrue($column->unsigned()->isUnsigned()); - } - - public function testLengthWithArray(): void - { - $column = new Column('integer', [10, 2]); - - $this->assertSame('integer(10,2)', $column->asString()); - } - - public function testNotnull(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string NOT NULL', $column->notNull()->asString()); - } - - public function testNull(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string NULL DEFAULT NULL', $column->null()->asString()); - } - - public function testUnique(): void - { - $column = new Column('string'); - - $this->assertSame('string', $column->asString()); - $this->assertSame('string UNIQUE', $column->unique()->asString()); - } - - public function testUnsignedTypePk(): void - { - $column = new Column(SchemaInterface::TYPE_PK); - - $this->assertSame('pk', $column->asString()); - $this->assertSame('upk', $column->unsigned()->asString()); - } - - public function testUnsignedTypeUbigPk(): void - { - $column = new Column(SchemaInterface::TYPE_BIGPK); - - $this->assertSame('bigpk', $column->asString()); - $this->assertSame('ubigpk', $column->unsigned()->asString()); - } -} diff --git a/tests/Db/Schema/ColumnSchemaTest.php b/tests/Db/Schema/ColumnSchemaTest.php index b2b1a2838..280311f84 100644 --- a/tests/Db/Schema/ColumnSchemaTest.php +++ b/tests/Db/Schema/ColumnSchemaTest.php @@ -5,8 +5,11 @@ namespace Yiisoft\Db\Tests\Db\Schema; use PHPUnit\Framework\TestCase; +use Yiisoft\Db\Schema\Column\BooleanColumn; +use Yiisoft\Db\Schema\Column\DoubleColumn; +use Yiisoft\Db\Schema\Column\IntegerColumn; +use Yiisoft\Db\Schema\Column\StringColumn; use Yiisoft\Db\Schema\SchemaInterface; -use Yiisoft\Db\Tests\Support\Stub\ColumnSchema; /** * @group db @@ -17,11 +20,11 @@ final class ColumnSchemaTest extends TestCase { public function testAllowNull(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); - $this->assertFalse($column->isAllowNull()); + $this->assertNull($column->isAllowNull()); - $column->allowNull(true); + $column->allowNull(); $this->assertTrue($column->isAllowNull()); @@ -32,11 +35,11 @@ public function testAllowNull(): void public function testAutoIncrement(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertFalse($column->isAutoIncrement()); - $column->autoIncrement(true); + $column->autoIncrement(); $this->assertTrue($column->isAutoIncrement()); @@ -47,7 +50,7 @@ public function testAutoIncrement(): void public function testComment(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertNull($column->getComment()); @@ -62,11 +65,11 @@ public function testComment(): void public function testComputed(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertFalse($column->isComputed()); - $column->computed(true); + $column->computed(); $this->assertTrue($column->isComputed()); @@ -77,7 +80,7 @@ public function testComputed(): void public function testDbType(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertNull($column->getDbType()); @@ -92,14 +95,14 @@ public function testDbType(): void public function testDbTypecast(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); - $this->assertNull($column->dbTypecast('')); + $this->assertSame('', $column->dbTypecast('')); } public function testDefaultValue(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertNull($column->getDefaultValue()); @@ -114,22 +117,22 @@ public function testDefaultValue(): void public function testEnumValues(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); - $this->assertNull($column->getEnumValues()); + $this->assertSame([], $column->getValues()); - $column->enumValues(['positive', 'negative']); + $column->values(['positive', 'negative']); - $this->assertSame(['positive', 'negative'], $column->getEnumValues()); + $this->assertSame(['positive', 'negative'], $column->getValues()); - $column->enumValues([]); + $column->values([]); - $this->assertSame([], $column->getEnumValues()); + $this->assertSame([], $column->getValues()); } public function testExtra(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertNull($column->getExtra()); @@ -142,30 +145,11 @@ public function testExtra(): void $this->assertSame('', $column->getExtra()); } - /** - * @link https://github.com/yiisoft/db/issues/718 - */ - public function testTypecastIssue718(): void - { - $column = new ColumnSchema('new'); - - $param = [1, 2]; - $result = $column->dbTypecast($param); - $this->assertSame([1, 2], $result); - } - - public function testName(): void - { - $column = new ColumnSchema('test'); - - $this->assertSame('test', $column->getName()); - } - public function testPhpType(): void { - $column = new ColumnSchema('new'); + $column = new IntegerColumn(); - $this->assertNull($column->getPhpType()); + $this->assertSame(SchemaInterface::PHP_TYPE_INTEGER, $column->getPhpType()); $column->phpType(SchemaInterface::PHP_TYPE_STRING); @@ -178,107 +162,80 @@ public function testPhpType(): void public function testPhpTypecast(): void { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); + $column = new StringColumn(); $this->assertSame('test', $column->phpTypecast('test')); } public function testPhpTypecastWithBoolean(): void { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_BOOLEAN); + $column = new BooleanColumn(); $this->assertTrue($column->phpTypecast(1)); } public function testPhpTypecastWithDouble(): void { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_DOUBLE); + $column = new DoubleColumn(); $this->assertSame(1.2, $column->phpTypecast('1.2')); } public function testPhpTypecastWithInteger(): void { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_INTEGER); + $column = new IntegerColumn(); $this->assertSame(1, $column->phpTypecast('1')); } public function testPhpTypecastWithStringBooleanValue(): void { - $column = new ColumnSchema('new'); + self::markTestSkipped('Wrong test: database does not return bool value for string type'); - $column->phpType(SchemaInterface::PHP_TYPE_STRING); + $column = new StringColumn(); $this->assertSame('1', $column->phpTypecast(true)); } public function testPhpTypecastWithStringFloatValue(): void { - $column = new ColumnSchema('new'); + self::markTestSkipped('Wrong test: database does not return double value for string type'); - $column->phpType(SchemaInterface::PHP_TYPE_STRING); + $column = new StringColumn(); $this->assertSame('1.1', $column->phpTypecast(1.1)); } public function testPhpTypecastWithStringIntegerValue(): void { - $column = new ColumnSchema('new'); + self::markTestSkipped('Wrong test: database does not return int value for string type'); - $column->phpType(SchemaInterface::PHP_TYPE_STRING); + $column = new StringColumn(); $this->assertSame('1', $column->phpTypecast(1)); } public function testPhpTypecastWithStringNullValue(): void { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); + $column = new StringColumn(); $this->assertNull($column->phpTypecast(null)); } public function testPhpTypecastWithStringResourceValue(): void { - $column = new ColumnSchema('new'); - - $column->phpType(SchemaInterface::PHP_TYPE_STRING); + $column = new StringColumn(); $this->assertIsResource($column->phpTypecast(fopen('php://memory', 'rb'))); } - public function testPrecision(): void - { - $column = new ColumnSchema('new'); - - $this->assertNull($column->getPrecision()); - - $column->precision(10); - - $this->assertSame(10, $column->getPrecision()); - - $column->precision(0); - - $this->assertSame(0, $column->getPrecision()); - } - public function testPrimaryKey(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertFalse($column->isPrimaryKey()); - $column->primaryKey(true); + $column->primaryKey(); $this->assertTrue($column->isPrimaryKey()); @@ -289,7 +246,7 @@ public function testPrimaryKey(): void public function testScale(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertNull($column->getScale()); @@ -304,7 +261,7 @@ public function testScale(): void public function testSize(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertNull($column->getSize()); @@ -319,9 +276,9 @@ public function testSize(): void public function testType(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); - $this->assertSame('', $column->getType()); + $this->assertSame(SchemaInterface::PHP_TYPE_STRING, $column->getType()); $column->type('test'); @@ -334,11 +291,11 @@ public function testType(): void public function testUnsigned(): void { - $column = new ColumnSchema('new'); + $column = new StringColumn(); $this->assertFalse($column->isUnsigned()); - $column->unsigned(true); + $column->unsigned(); $this->assertTrue($column->isUnsigned()); diff --git a/tests/Db/Schema/SchemaTest.php b/tests/Db/Schema/SchemaTest.php index 7db253b30..6d989ac85 100644 --- a/tests/Db/Schema/SchemaTest.php +++ b/tests/Db/Schema/SchemaTest.php @@ -11,11 +11,11 @@ use Yiisoft\Db\Constraint\ForeignKeyConstraint; use Yiisoft\Db\Constraint\IndexConstraint; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Schema\TableSchemaInterface; use Yiisoft\Db\Tests\AbstractSchemaTest; use Yiisoft\Db\Tests\Support\Assert; use Yiisoft\Db\Tests\Support\DbHelper; -use Yiisoft\Db\Tests\Support\Stub\ColumnSchema; use Yiisoft\Db\Tests\Support\Stub\Schema; use Yiisoft\Db\Tests\Support\Stub\TableSchema; use Yiisoft\Db\Tests\Support\TestTrait; @@ -56,45 +56,6 @@ public function testFindViewNames(): void $this->assertSame([], Assert::invokeMethod($schema, 'findViewNames', ['dbo'])); } - /** - * @throws ReflectionException - */ - public function testGetColumnPhpType(): void - { - $db = $this->getConnection(); - - $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]), - ); - $this->assertSame( - 'boolean', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnBoolean]), - ); - $this->assertSame( - 'integer', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnInteger]), - ); - $this->assertSame( - 'string', - Assert::invokeMethod($schema, 'getColumnPhpType', [$columnString]), - ); - } - /** * @throws NotSupportedException */ @@ -436,49 +397,15 @@ public function testSetTableMetadata(): void private function createTableSchemaStub(): TableSchemaInterface { - // defined column C_id - $columnCid = new ColumnSchema('C_id'); - $columnCid->autoIncrement(true); - $columnCid->dbType('int'); - $columnCid->primaryKey(true); - $columnCid->phpType('integer'); - $columnCid->type('integer'); - - // defined column C_not_null - $columnCNotNull = new ColumnSchema('C_not_null'); - $columnCNotNull->dbType('int'); - $columnCNotNull->phpType('int'); - $columnCNotNull->type('int'); - - // defined column C_check - $columnCCheck = new ColumnSchema('C_check'); - $columnCCheck->dbType('varchar(255)'); - $columnCCheck->phpType('string'); - $columnCCheck->type('string'); - - // defined column C_default - $columnCDefault = new ColumnSchema('C_default'); - $columnCDefault->dbType('int'); - $columnCDefault->phpType('integer'); - $columnCDefault->type('integer'); - - // defined column C_unique - $columnCUnique = new ColumnSchema('C_unique'); - $columnCUnique->dbType('int'); - $columnCUnique->phpType('integer'); - $columnCUnique->type('integer'); - // defined table T_constraints_1 - $tableSchema = new TableSchema(); - $tableSchema->column('C_id', $columnCid); - $tableSchema->column('C_not_null', $columnCNotNull); - $tableSchema->column('C_check', $columnCCheck); - $tableSchema->column('C_default', $columnCDefault); - $tableSchema->column('C_unique', $columnCUnique); - $tableSchema->fullName('T_constraints_1'); - $tableSchema->name('T_constraints_1'); + $tableSchema = new TableSchema('dbo.T_constraints_1', [ + 'C_id' => ColumnBuilder::pk(), + 'C_not_null' => ColumnBuilder::integer(), + 'C_check' => ColumnBuilder::string(), + 'C_default' => ColumnBuilder::integer(), + 'C_unique' => ColumnBuilder::integer(), + ]); $tableSchema->primaryKey('C_id'); - $tableSchema->schemaName('dbo'); return $tableSchema; } diff --git a/tests/Provider/ColumnDefinitionBuilderProvider.php b/tests/Provider/ColumnDefinitionBuilderProvider.php new file mode 100644 index 000000000..c2208f12a --- /dev/null +++ b/tests/Provider/ColumnDefinitionBuilderProvider.php @@ -0,0 +1,94 @@ +extra('bar')]; + $result[] = ['varchar foo', ($column = clone $column)->extra('foo')]; + $result[] = ['varchar', (clone $column)->extra('')]; + + $column = new StringColumn(); + $result[] = ['varchar CHECK (value > 5)', $column->check('value > 5')]; + $result[] = ['varchar', ($column = clone $column)->check('')]; + $result[] = ['varchar', (clone $column)->check(null)]; + + $column = new StringColumn(); + $result[] = ['varchar', $column->comment('comment')]; + $result[] = ['varchar', ($column = clone $column)->comment('')]; + $result[] = ['varchar', (clone $column)->comment(null)]; + + $column = new StringColumn(); + $result[] = ["varchar DEFAULT 'value'", $column->defaultValue('value')]; + $result[] = ["varchar DEFAULT ''", ($column = clone $column)->defaultValue('')]; + $result[] = ['varchar', ($column = clone $column)->defaultValue(null)]; + $result[] = ["varchar DEFAULT 'expression'", ($column = clone $column)->defaultValue(new Expression("'expression'"))]; + $result[] = ['varchar DEFAULT ', ($column = clone $column)->defaultValue(new Expression(''))]; + $result[] = ['varchar NULL DEFAULT NULL', (clone $column)->allowNull()->defaultValue(null)]; + + $column = new StringColumn(); + $result[] = ['varchar NULL DEFAULT NULL', $column->allowNull()]; + $result[] = ['varchar NOT NULL', (clone $column)->allowNull(false)]; + + $result[] = ['varchar UNIQUE', (new StringColumn())->unique()]; + + $column = ColumnBuilder::pk(); + $result[] = ['integer PRIMARY KEY', $column]; + $result[] = ['integer UNSIGNED PRIMARY KEY', (clone $column)->unsigned()]; + + $column = ColumnBuilder::bigpk(); + $result[] = ['bigint PRIMARY KEY', $column]; + $result[] = ['bigint UNSIGNED PRIMARY KEY', (clone $column)->unsigned()]; + + return $result; + } + /* + public static function buildColumnDefinition(): array + { + return [ + // Primary key columns + 'pk' => [ColumnBuilder::pk(), 'integer PRIMARY KEY'], + 'upk' => [ColumnBuilder::upk(), 'integer UNSIGNED PRIMARY KEY'], + 'bigpk' => [ColumnBuilder::bigpk(), 'bigint PRIMARY KEY'], + 'ubigpk' => [ColumnBuilder::ubigpk(), 'bigint UNSIGNED PRIMARY KEY'], + 'uuidpk' => [ColumnBuilder::uuidpk(), 'uuid PRIMARY KEY'], + 'uuidpkseq' => [ColumnBuilder::uuidpkseq(), 'uuid PRIMARY KEY'], + // Abstract types + SchemaInterface::TYPE_UUID => [SchemaInterface::TYPE_UUID, 'char(36)'], + SchemaInterface::TYPE_CHAR => [SchemaInterface::TYPE_CHAR, 'char(1)'], + SchemaInterface::TYPE_STRING => [SchemaInterface::TYPE_STRING, 'varchar(255)'], + SchemaInterface::TYPE_TEXT => [SchemaInterface::TYPE_TEXT, 'text'], + SchemaInterface::TYPE_BINARY => [SchemaInterface::TYPE_BINARY, 'binary(255)'], + SchemaInterface::TYPE_BOOLEAN => [SchemaInterface::TYPE_BOOLEAN, 'boolean'], + SchemaInterface::TYPE_TINYINT => [SchemaInterface::TYPE_TINYINT, 'tinyint'], + SchemaInterface::TYPE_SMALLINT => [SchemaInterface::TYPE_SMALLINT, 'smallint'], + SchemaInterface::TYPE_INTEGER => [SchemaInterface::TYPE_INTEGER, 'integer'], + SchemaInterface::TYPE_BIGINT => [SchemaInterface::TYPE_BIGINT, 'bigint'], + SchemaInterface::TYPE_FLOAT => [SchemaInterface::TYPE_FLOAT, 'float'], + SchemaInterface::TYPE_DOUBLE => [SchemaInterface::TYPE_DOUBLE, 'double'], + SchemaInterface::TYPE_DECIMAL => [SchemaInterface::TYPE_DECIMAL, 'decimal(10,0)'], + SchemaInterface::TYPE_MONEY => [SchemaInterface::TYPE_MONEY, 'money(19,4)'], + SchemaInterface::TYPE_DATETIME => [SchemaInterface::TYPE_DATETIME, 'datetime(0)'], + SchemaInterface::TYPE_TIMESTAMP => [SchemaInterface::TYPE_TIMESTAMP, 'timestamp(0)'], + SchemaInterface::TYPE_TIME => [SchemaInterface::TYPE_TIME, 'time(0)'], + SchemaInterface::TYPE_DATE => [SchemaInterface::TYPE_DATE, 'date'], + SchemaInterface::TYPE_JSON => [SchemaInterface::TYPE_JSON, 'json'], + ]; + } + */ +} diff --git a/tests/Provider/ColumnSchemaProvider.php b/tests/Provider/ColumnSchemaProvider.php new file mode 100644 index 000000000..27a86e06e --- /dev/null +++ b/tests/Provider/ColumnSchemaProvider.php @@ -0,0 +1,218 @@ + [IntegerColumn::class, SchemaInterface::TYPE_INTEGER, SchemaInterface::PHP_TYPE_INTEGER], + 'bigint' => [BigIntColumn::class, SchemaInterface::TYPE_BIGINT, SchemaInterface::PHP_TYPE_INTEGER], + 'double' => [DoubleColumn::class, SchemaInterface::TYPE_DOUBLE, SchemaInterface::PHP_TYPE_DOUBLE], + 'string' => [StringColumn::class, SchemaInterface::TYPE_STRING, SchemaInterface::PHP_TYPE_STRING], + 'binary' => [BinaryColumn::class, SchemaInterface::TYPE_BINARY, SchemaInterface::PHP_TYPE_RESOURCE], + 'boolean' => [BooleanColumn::class, SchemaInterface::TYPE_BOOLEAN, SchemaInterface::PHP_TYPE_BOOLEAN], + 'json' => [JsonColumn::class, SchemaInterface::TYPE_JSON, SchemaInterface::PHP_TYPE_ARRAY], + ]; + } + + public static function dbTypecastColumns(): array + { + return [ + 'integer' => [ + IntegerColumn::class, + [ + // [expected, typecast value] + [null, null], + [null, ''], + [1, 1], + [1, 1.0], + [1, '1'], + [1, true], + [0, false], + [$expression = new Expression('1'), $expression], + ], + ], + 'bigint' => [ + BigIntColumn::class, + [ + [null, null], + [null, ''], + [1, 1], + [1, 1.0], + [1, '1'], + [1, true], + [0, false], + ['12345678901234567890', '12345678901234567890'], + [$expression = new Expression('1'), $expression], + ], + ], + 'double' => [ + DoubleColumn::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' => [ + StringColumn::class, + [ + [null, null], + ['', ''], + ['1', 1], + ['1', true], + ['0', false], + ['string', 'string'], + [$resource = fopen('php://memory', 'rb'), $resource], + [$expression = new Expression('expression'), $expression], + ], + ], + 'binary' => [ + BinaryColumn::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' => [ + BooleanColumn::class, + [ + [null, null], + [null, ''], + [true, true], + [true, 1], + [true, 1.0], + [true, '1'], + [false, false], + [false, 0], + [false, 0.0], + [false, '0'], + [false, "\0"], + [$expression = new Expression('expression'), $expression], + ], + ], + 'json' => [ + JsonColumn::class, + [ + [null, null], + [new JsonExpression('', 'json'), ''], + [new JsonExpression(1, 'json'), 1], + [new JsonExpression(true, 'json'), true], + [new JsonExpression(false, 'json'), false], + [new JsonExpression('string', 'json'), 'string'], + [new JsonExpression([1, 2, 3], 'json'), [1, 2, 3]], + [new JsonExpression(['key' => 'value'], 'json'), ['key' => 'value']], + [new JsonExpression(new stdClass(), 'json'), new stdClass()], + [$expression = new JsonExpression([1, 2, 3]), $expression], + ], + ], + ]; + } + + public static function phpTypecastColumns(): array + { + return [ + 'integer' => [ + IntegerColumn::class, + [ + // [expected, typecast value] + [null, null], + [1, 1], + [1, '1'], + ], + ], + 'bigint' => [ + BigIntColumn::class, + [ + [null, null], + [1, 1], + [1, '1'], + ['12345678901234567890', '12345678901234567890'], + ], + ], + 'double' => [ + DoubleColumn::class, + [ + [null, null], + [1.0, 1.0], + [1.0, '1.0'], + ], + ], + 'string' => [ + StringColumn::class, + [ + [null, null], + ['', ''], + ['string', 'string'], + [$resource = fopen('php://memory', 'rb'), $resource], + ], + ], + 'binary' => [ + BinaryColumn::class, + [ + [null, null], + ['', ''], + ["\x10\x11\x12", "\x10\x11\x12"], + [$resource = fopen('php://memory', 'rb'), $resource], + ], + ], + 'boolean' => [ + BooleanColumn::class, + [ + [null, null], + [true, true], + [true, '1'], + [false, false], + [false, '0'], + [false, "\0"], + ], + ], + 'json' => [ + JsonColumn::class, + [ + [null, null], + ['', '""'], + [1.0, '1.0'], + [1, '1'], + [true, 'true'], + [false, 'false'], + ['string', '"string"'], + [[1, 2, 3], '[1,2,3]'], + [['key' => 'value'], '{"key":"value"}'], + ], + ], + ]; + } +} diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index e552165b6..572e5aec5 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -9,10 +9,10 @@ use Yiisoft\Db\Command\Param; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Query\Query; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\Support\DbHelper; use Yiisoft\Db\Tests\Support\Stringable; -use Yiisoft\Db\Tests\Support\Stub\Column; use Yiisoft\Db\Tests\Support\TestTrait; class CommandProvider @@ -886,7 +886,7 @@ public static function columnTypes(): array { return [ [SchemaInterface::TYPE_INTEGER], - [new Column('string(100)')], + [ColumnBuilder::string(100)], ]; } } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index f8f0b5356..465350c50 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -12,9 +12,9 @@ use Yiisoft\Db\QueryBuilder\Condition\InCondition; use Yiisoft\Db\QueryBuilder\Condition\LikeCondition; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; +use Yiisoft\Db\Schema\Column\ColumnBuilder; use Yiisoft\Db\Schema\SchemaInterface; use Yiisoft\Db\Tests\Support\DbHelper; -use Yiisoft\Db\Tests\Support\Stub\Column; use Yiisoft\Db\Tests\Support\TestTrait; use Yiisoft\Db\Tests\Support\TraversableObject; @@ -1531,7 +1531,40 @@ public static function columnTypes(): array { return [ [SchemaInterface::TYPE_STRING], - [new Column('string(100)')], + [ColumnBuilder::string(100)], + ]; + } + + public static function buildColumnDefinition(): array + { + return [ + // Primary key columns + 'pk' => [ColumnBuilder::pk(), 'integer PRIMARY KEY'], + 'upk' => [ColumnBuilder::upk(), 'integer UNSIGNED PRIMARY KEY'], + 'bigpk' => [ColumnBuilder::bigpk(), 'bigint PRIMARY KEY'], + 'ubigpk' => [ColumnBuilder::ubigpk(), 'bigint UNSIGNED PRIMARY KEY'], + 'uuidpk' => [ColumnBuilder::uuidpk(), 'uuid PRIMARY KEY'], + 'uuidpkseq' => [ColumnBuilder::uuidpkseq(), 'uuid PRIMARY KEY'], + // Abstract types + SchemaInterface::TYPE_UUID => [SchemaInterface::TYPE_UUID, 'uuid'], + SchemaInterface::TYPE_CHAR => [SchemaInterface::TYPE_CHAR, 'char'], + SchemaInterface::TYPE_STRING => [SchemaInterface::TYPE_STRING, 'varchar(255)'], + SchemaInterface::TYPE_TEXT => [SchemaInterface::TYPE_TEXT, 'text'], + SchemaInterface::TYPE_BINARY => [SchemaInterface::TYPE_BINARY, 'binary'], + SchemaInterface::TYPE_BOOLEAN => [SchemaInterface::TYPE_BOOLEAN, 'boolean'], + SchemaInterface::TYPE_TINYINT => [SchemaInterface::TYPE_TINYINT, 'tinyint'], + SchemaInterface::TYPE_SMALLINT => [SchemaInterface::TYPE_SMALLINT, 'smallint'], + SchemaInterface::TYPE_INTEGER => [SchemaInterface::TYPE_INTEGER, 'integer'], + SchemaInterface::TYPE_BIGINT => [SchemaInterface::TYPE_BIGINT, 'bigint'], + SchemaInterface::TYPE_FLOAT => [SchemaInterface::TYPE_FLOAT, 'float'], + SchemaInterface::TYPE_DOUBLE => [SchemaInterface::TYPE_DOUBLE, 'double'], + SchemaInterface::TYPE_DECIMAL => [SchemaInterface::TYPE_DECIMAL, 'decimal'], + SchemaInterface::TYPE_MONEY => [SchemaInterface::TYPE_MONEY, 'money'], + SchemaInterface::TYPE_DATETIME => [SchemaInterface::TYPE_DATETIME, 'datetime'], + SchemaInterface::TYPE_TIMESTAMP => [SchemaInterface::TYPE_TIMESTAMP, 'timestamp'], + SchemaInterface::TYPE_TIME => [SchemaInterface::TYPE_TIME, 'time'], + SchemaInterface::TYPE_DATE => [SchemaInterface::TYPE_DATE, 'date'], + SchemaInterface::TYPE_JSON => [SchemaInterface::TYPE_JSON, 'json'], ]; } } diff --git a/tests/Support/Stub/Column.php b/tests/Support/Stub/Column.php deleted file mode 100644 index f8b2f0dfe..000000000 --- a/tests/Support/Stub/Column.php +++ /dev/null @@ -1,11 +0,0 @@ -