Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ColumnDefinitionBuilder #883

Merged
merged 18 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/Connection/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Yiisoft\Db\Query\BatchQueryResultInterface;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
use Yiisoft\Db\Schema\Column\ColumnFactoryInterface;
use Yiisoft\Db\Schema\QuoterInterface;
use Yiisoft\Db\Schema\SchemaInterface;
use Yiisoft\Db\Schema\TableSchemaInterface;
Expand Down Expand Up @@ -86,11 +85,6 @@ public function createTransaction(): TransactionInterface;
*/
public function close(): void;

/**
* Returns the column factory for creating column instances.
*/
public function getColumnFactory(): ColumnFactoryInterface;

/**
* Returns the name of the DB driver for the current `dsn`.
*
Expand Down
6 changes: 0 additions & 6 deletions src/Debug/ConnectionInterfaceProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Yiisoft\Db\Query\BatchQueryResultInterface;
use Yiisoft\Db\Query\QueryInterface;
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
use Yiisoft\Db\Schema\Column\ColumnFactoryInterface;
use Yiisoft\Db\Schema\QuoterInterface;
use Yiisoft\Db\Schema\SchemaInterface;
use Yiisoft\Db\Schema\TableSchemaInterface;
Expand Down Expand Up @@ -63,11 +62,6 @@ public function close(): void
$this->connection->close();
}

public function getColumnFactory(): ColumnFactoryInterface
{
return $this->connection->getColumnFactory();
}

public function getLastInsertID(string $sequenceName = null): string
{
return $this->connection->getLastInsertID($sequenceName);
Expand Down
328 changes: 328 additions & 0 deletions src/QueryBuilder/AbstractColumnDefinitionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\QueryBuilder;

use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;

use function gettype;
use function in_array;
use function strtolower;

/**
* Builds column definition from {@see ColumnSchemaInterface} object. Column definition is a string that represents
* the column type and all constraints associated with the column. For example: `VARCHAR(128) NOT NULL DEFAULT 'foo'`.
*/
abstract class AbstractColumnDefinitionBuilder implements ColumnDefinitionBuilderInterface
{
/**
* @var string The keyword used to specify that a column is auto incremented.
*/
protected const AUTO_INCREMENT_KEYWORD = '';

/**
* @var string The expression used to generate a UUID value.
*/
protected const GENERATE_UUID_EXPRESSION = '';

/**
* @var string[] The list of database column types (in lower case) that allow size specification.
*/
protected const TYPES_WITH_SIZE = [];

/**
* @var string[] The list of database column types (in lower case) that allow scale specification.
*/
protected const TYPES_WITH_SCALE = [];

/**
* Get the database column type for the given column.
*
* @param ColumnSchemaInterface $column The column object.
*
* @return string The database column type.
*/
abstract protected function getDbType(ColumnSchemaInterface $column): string;

public function __construct(
protected QueryBuilderInterface $queryBuilder,
) {
}

public function build(ColumnSchemaInterface $column): string
{
return $this->buildType($column)
. $this->buildUnsigned($column)
. $this->buildNotNull($column)
. $this->buildPrimaryKey($column)
. $this->buildAutoIncrement($column)
. $this->buildUnique($column)
. $this->buildDefault($column)
. $this->buildComment($column)
. $this->buildCheck($column)
. $this->buildReferences($column)
. $this->buildExtra($column);
}

/**
* Builds the auto increment clause for column.
Tigrov marked this conversation as resolved.
Show resolved Hide resolved
*
* @return string A string containing the {@see AUTO_INCREMENT_KEYWORD} keyword.
*/
protected function buildAutoIncrement(ColumnSchemaInterface $column): string
{
if (empty(static::AUTO_INCREMENT_KEYWORD) || !$column->isAutoIncrement()) {
return '';
}

return match ($column->getType()) {
ColumnType::TINYINT,
ColumnType::SMALLINT,
ColumnType::INTEGER,
ColumnType::BIGINT => ' ' . static::AUTO_INCREMENT_KEYWORD,
default => '',
};
}

/**
* Builds the check constraint for the column.
*
* @return string A string containing the CHECK constraint.
*/
protected function buildCheck(ColumnSchemaInterface $column): string
{
$check = $column->getCheck();

return !empty($check) ? " CHECK ($check)" : '';
}

/**
* 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(ColumnSchemaInterface $column): string

Check warning on line 108 in src/QueryBuilder/AbstractColumnDefinitionBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/QueryBuilder/AbstractColumnDefinitionBuilder.php#L108

Added line #L108 was not covered by tests
{
return '';

Check warning on line 110 in src/QueryBuilder/AbstractColumnDefinitionBuilder.php

View check run for this annotation

Codecov / codecov/patch

src/QueryBuilder/AbstractColumnDefinitionBuilder.php#L110

Added line #L110 was not covered by tests
}

/**
* Builds the default value specification for the column.
*
* @return string A string containing the DEFAULT keyword and the default value.
*/
protected function buildDefault(ColumnSchemaInterface $column): string
{
if (!empty(static::GENERATE_UUID_EXPRESSION)
&& $column->getType() === ColumnType::UUID
&& $column->isAutoIncrement()
&& $column->getDefaultValue() === null
) {
return ' DEFAULT ' . static::GENERATE_UUID_EXPRESSION;
}

if ($column->isAutoIncrement() && $column->getType() !== ColumnType::UUID
|| $column->getDefaultValue() === null
) {
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(ColumnSchemaInterface $column): string|null
{
$value = $column->dbTypecast($column->getDefaultValue());

if ($value === null) {
return null;
}

if ($value instanceof ExpressionInterface) {
return $this->queryBuilder->buildExpression($value);
}

/** @var string */
return match (gettype($value)) {
GettypeResult::INTEGER => (string) $value,
GettypeResult::DOUBLE => (string) $value,
GettypeResult::BOOLEAN => $value ? 'TRUE' : 'FALSE',
default => $this->queryBuilder->quoter()->quoteValue((string) $value),
};
}

/**
* 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(ColumnSchemaInterface $column): string
{
$extra = $column->getExtra();

return !empty($extra) ? " $extra" : '';
}

/**
* Builds the not null constraint for the column.
*
* @return string A string 'NOT NULL' if {@see ColumnSchemaInterface::isNotNull()} is `true` or an empty string
* otherwise.
*/
protected function buildNotNull(ColumnSchemaInterface $column): string
{
return $column->isNotNull() ? ' NOT NULL' : '';
}

/**
* Builds the primary key clause for column.
*
* @return string A string containing the PRIMARY KEY keyword.
*/
protected function buildPrimaryKey(ColumnSchemaInterface $column): string
{
return $column->isPrimaryKey() ? ' PRIMARY KEY' : '';
}

/**
* Builds the references clause for the column.
*/
protected function buildReferences(ColumnSchemaInterface $column): string
{
$reference = $this->buildReferenceDefinition($column);

if ($reference === null) {
return '';
}

return " REFERENCES $reference";
}

/**
* Builds the reference definition for the column.
*/
protected function buildReferenceDefinition(ColumnSchemaInterface $column): string|null
{
$reference = $column->getReference();
$table = $reference?->getForeignTableName();

if ($table === null) {
return null;
}

$quoter = $this->queryBuilder->quoter();
$schema = $reference?->getForeignSchemaName();

if ($schema !== null) {
$sql = $quoter->quoteTableName($schema) . '.' . $quoter->quoteTableName($table);
} else {
$sql = $quoter->quoteTableName($table);
}
Tigrov marked this conversation as resolved.
Show resolved Hide resolved

$columns = $reference?->getForeignColumnNames();

if (!empty($columns)) {
$sql .= ' (' . $this->queryBuilder->buildColumns($columns) . ')';
}

if (null !== $onDelete = $reference?->getOnDelete()) {
$sql .= $this->buildOnDelete($onDelete);
}

if (null !== $onUpdate = $reference?->getOnUpdate()) {
$sql .= $this->buildOnUpdate($onUpdate);
}

return $sql;
}

/**
* Builds the ON DELETE clause for the column reference.
*/
protected function buildOnDelete(string $onDelete): string
{
return " ON DELETE $onDelete";
}

/**
* Builds the ON UPDATE clause for the column reference.
*/
protected function buildOnUpdate(string $onUpdate): string
{
return " ON UPDATE $onUpdate";
}

/**
* Builds the type definition for the column. For example: `varchar(128)` or `decimal(10,2)`.
*
* @return string A string containing the column type definition.
*/
protected function buildType(ColumnSchemaInterface $column): string
{
$dbType = $column->getDbType();

if ($dbType === null) {
$dbType = $this->getDbType($column);
}

if (empty($dbType)
|| $dbType[-1] === ')'
|| !in_array(strtolower($dbType), static::TYPES_WITH_SIZE, true)
) {
return $dbType;
}

$size = $column->getSize();

if ($size === null) {
return $dbType;
}

$scale = $column->getScale();

if ($scale === null || !in_array(strtolower($dbType), static::TYPES_WITH_SCALE, true)) {
return "$dbType($size)";
}

return "$dbType($size,$scale)";
}

/**
* 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(ColumnSchemaInterface $column): string
{
if ($column->isPrimaryKey()) {
return '';
}

return $column->isUnique() ? ' UNIQUE' : '';
}

/**
* Builds the unsigned string for column. Default is empty string.
*
* @return string A string containing the UNSIGNED keyword.
*/
protected function buildUnsigned(ColumnSchemaInterface $column): string
{
return $column->isUnsigned() ? ' UNSIGNED' : '';
}
}
3 changes: 3 additions & 0 deletions src/QueryBuilder/AbstractDDLQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function addCheck(string $table, string $name, string $expression): strin

public function addColumn(string $table, string $column, ColumnInterface|string $type): string
{
/** @psalm-suppress DeprecatedMethod */
return 'ALTER TABLE '
. $this->quoter->quoteTableName($table)
. ' ADD '
Expand Down Expand Up @@ -138,6 +139,7 @@ public function alterColumn(
string $column,
ColumnInterface|string $type
): string {
/** @psalm-suppress DeprecatedMethod */
return 'ALTER TABLE '
. $this->quoter->quoteTableName($table)
. ' CHANGE '
Expand Down Expand Up @@ -171,6 +173,7 @@ public function createTable(string $table, array $columns, string $options = nul

foreach ($columns as $name => $type) {
if (is_string($name)) {
/** @psalm-suppress DeprecatedMethod */
$cols[] = "\t"
. $this->quoter->quoteColumnName($name)
. ' '
Expand Down
Loading
Loading