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

Default value as expression #232

Merged
merged 13 commits into from
Feb 18, 2023
41 changes: 41 additions & 0 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Helper\ArrayHelper;
use Yiisoft\Db\Schema\AbstractColumnSchemaBuilder;
use Yiisoft\Db\Schema\AbstractSchema;
use Yiisoft\Db\Schema\ColumnSchemaInterface;
use Yiisoft\Db\Schema\ColumnSchemaBuilderInterface;
Expand Down Expand Up @@ -67,6 +68,7 @@
* key: string,
* default: string|null,
* extra: string,
* extra_default_value: string|null,
* privileges: string,
* comment: string
* }
Expand Down Expand Up @@ -193,6 +195,26 @@ protected function findColumns(TableSchemaInterface $table): bool

try {
$columns = $this->db->createCommand($sql)->queryAll();
// Chapter 1: cruthes for MariaDB. {@see https://github.com/yiisoft/yii2/issues/19747}
$columnsExtra = [];
if (str_contains($this->db->getServerVersion(), 'MariaDB')) {
/** @psalm-var array[] $columnsExtra */
$columnsExtra = $this->db->createCommand(
<<<SQL
SELECT `COLUMN_NAME` as name,`COLUMN_DEFAULT` as default_value
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = COALESCE(:schemaName, DATABASE()) AND TABLE_NAME = :tableName
SQL ,
[
':schemaName' => $table->getSchemaName(),
':tableName' => $table->getName(),
]
)->queryAll();
/** @psalm-var array<string, string> $cols */
foreach ($columnsExtra as $cols) {
$columnsExtra[$cols['name']] = $cols['default_value'];
}
}
} catch (Exception $e) {
$previous = $e->getPrevious();

Expand All @@ -214,6 +236,8 @@ protected function findColumns(TableSchemaInterface $table): bool
foreach ($columns as $info) {
$info = $this->normalizeRowKeyCase($info, false);

$info['extra_default_value'] = $columnsExtra[(string) $info['field']] ?? '';

if (in_array($info['field'], $jsonColumns, true)) {
$info['type'] = self::TYPE_JSON;
}
Expand Down Expand Up @@ -510,6 +534,21 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface
$column->phpType($this->getColumnPhpType($column));

if (!$column->isPrimaryKey()) {
// Chapter 2: cruthes for MariaDB {@see https://github.com/yiisoft/yii2/issues/19747}
/** @var string $columnCategory */
$columnCategory = $this->createColumnSchemaBuilder(
$column->getType(),
$column->getSize()
)->getCategoryMap()[$column->getType()] ?? '';
$defaultValue = $info['extra_default_value'] ?? '';
if (
empty($info['extra']) &&
!empty($defaultValue) &&
$columnCategory === AbstractColumnSchemaBuilder::CATEGORY_STRING
&& !str_starts_with($defaultValue, '\'')
) {
$info['extra'] = 'DEFAULT_GENERATED';
}
/**
* When displayed in the INFORMATION_SCHEMA.COLUMNS table, a default CURRENT TIMESTAMP is displayed
* as CURRENT_TIMESTAMP up until MariaDB 10.2.2, and as current_timestamp() from MariaDB 10.2.3.
Expand All @@ -522,6 +561,8 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface
) {
$column->defaultValue(new Expression('CURRENT_TIMESTAMP' . (!empty($matches[1])
? '(' . $matches[1] . ')' : '')));
} elseif (!empty($info['extra']) && !empty($info['default'])) {
$column->defaultValue(new Expression($info['default']));
} elseif (isset($type) && $type === 'bit' && $column->getType() !== self::TYPE_BOOLEAN) {
$column->defaultValue(bindec(trim((string) $info['default'], 'b\'')));
} else {
Expand Down
58 changes: 48 additions & 10 deletions tests/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,40 @@ public function testDefaultValueDatetimeColumn(): void
{
$db = $this->getConnection();

if ($db->getTableSchema('{{%datetime_test}}', true) !== null) {
$db->createCommand('DROP TABLE `datetime_test`')->execute();
}

$command = $db->createCommand();
$schema = $db->getSchema();

$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `datetime_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
SQL;
$oldMySQL = !(
version_compare($db->getServerVersion(), '8.0.0', '>') &&
!str_contains($db->getServerVersion(), 'MariaDB')
);

if ($oldMySQL) {
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `datetime_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`simple_col` varchar(40) DEFAULT 'uuid()',
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
SQL;
} else {
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `datetime_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`simple_col` varchar(40) DEFAULT 'uuid()',
`uuid_col` varchar(40) DEFAULT (uuid()),
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
SQL;
}

$command->setSql($sql)->execute();

Expand All @@ -177,11 +200,26 @@ public function testDefaultValueDatetimeColumn(): void
$this->assertNotNull($schema);

$dt = $schema->getColumn('dt');

$this->assertNotNull($dt);

$this->assertInstanceOf(Expression::class, $dt->getDefaultValue());
$this->assertEquals('CURRENT_TIMESTAMP', (string) $dt->getDefaultValue());

if (!$oldMySQL) {
$uuid = $schema->getColumn('uuid_col');
$this->assertNotNull($uuid);
$this->assertInstanceOf(Expression::class, $uuid->getDefaultValue());
$this->assertEquals('uuid()', (string)$uuid->getDefaultValue());
}

$simple = $schema->getColumn('simple_col');
$this->assertNotNull($simple);
$this->assertNotInstanceOf(Expression::class, $simple->getDefaultValue());
$this->assertEquals('uuid()', (string) $simple->getDefaultValue());

$ts = $schema->getColumn('ts');
$this->assertNotNull($ts);
$this->assertInstanceOf(Expression::class, $ts->getDefaultValue());
$this->assertEquals('CURRENT_TIMESTAMP', (string) $ts->getDefaultValue());
}

/**
Expand Down