Skip to content

Commit 3a204b1

Browse files
committed
Add support for DEFAULT (expression) in table column definitions
1 parent 65aaca3 commit 3a204b1

3 files changed

Lines changed: 79 additions & 18 deletions

File tree

tests/WP_SQLite_Driver_Tests.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11391,5 +11391,58 @@ public function testCreateTableWithDefaultNowFunction(): void {
1139111391

1139211392
// Verify the updated timestamp was set (should match YYYY-MM-DD HH:MM:SS format)
1139311393
$this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->updated );
11394+
11395+
// SHOW CREATE TABLE
11396+
$this->assertQuery( 'SHOW CREATE TABLE test_now_default' );
11397+
$results = $this->engine->get_query_results();
11398+
$this->assertEquals(
11399+
implode(
11400+
"\n",
11401+
array(
11402+
'CREATE TABLE `test_now_default` (',
11403+
' `id` int NOT NULL,',
11404+
' `updated` timestamp NOT NULL DEFAULT ( now( ) ) ON UPDATE CURRENT_TIMESTAMP',
11405+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci',
11406+
)
11407+
),
11408+
$results[0]->{'Create Table'}
11409+
);
11410+
}
11411+
11412+
public function testCreateTableWithDefaultExpressions(): void {
11413+
$this->assertQuery(
11414+
'CREATE TABLE t (
11415+
id int NOT NULL,
11416+
col1 int NOT NULL DEFAULT (1 + 2),
11417+
col2 datetime NOT NULL DEFAULT (DATE_ADD(NOW(), INTERVAL 1 YEAR)),
11418+
col3 varchar(255) NOT NULL DEFAULT (CONCAT(\'a\', \'b\'))
11419+
)'
11420+
);
11421+
11422+
// Insert a row and verify the default values
11423+
$this->assertQuery( 'INSERT INTO t (id) VALUES (1)' );
11424+
$this->assertQuery( 'SELECT * FROM t WHERE id = 1' );
11425+
$results = $this->engine->get_query_results();
11426+
$this->assertEquals( 3, $results[0]->col1 );
11427+
$this->assertStringStartsWith( ( gmdate( 'Y' ) + 1 ) . '-', $results[0]->col2 );
11428+
$this->assertEquals( 'ab', $results[0]->col3 );
11429+
11430+
// SHOW CREATE TABLE
11431+
$this->assertQuery( 'SHOW CREATE TABLE t' );
11432+
$results = $this->engine->get_query_results();
11433+
$this->assertEquals(
11434+
implode(
11435+
"\n",
11436+
array(
11437+
'CREATE TABLE `t` (',
11438+
' `id` int NOT NULL,',
11439+
' `col1` int NOT NULL DEFAULT ( 1 + 2 ),',
11440+
' `col2` datetime NOT NULL DEFAULT ( DATE_ADD( NOW( ) , INTERVAL 1 YEAR ) ),',
11441+
" `col3` varchar(255) NOT NULL DEFAULT ( CONCAT( 'a' , 'b' ) )",
11442+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
11443+
)
11444+
),
11445+
$results[0]->{'Create Table'}
11446+
);
1139411447
}
1139511448
}

wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5908,15 +5908,20 @@ private function get_sqlite_create_table_statement(
59085908
$query .= ' PRIMARY KEY AUTOINCREMENT';
59095909
}
59105910
if ( null !== $column['COLUMN_DEFAULT'] ) {
5911-
// @TODO: Handle defaults with expression values (DEFAULT_GENERATED).
5912-
59135911
// Handle DEFAULT CURRENT_TIMESTAMP. This works only with timestamp
59145912
// and datetime columns. For other column types, it's just a string.
59155913
if (
59165914
'CURRENT_TIMESTAMP' === $column['COLUMN_DEFAULT']
59175915
&& ( 'timestamp' === $column['DATA_TYPE'] || 'datetime' === $column['DATA_TYPE'] )
59185916
) {
59195917
$query .= ' DEFAULT CURRENT_TIMESTAMP';
5918+
} elseif ( str_contains( $column['EXTRA'], 'DEFAULT_GENERATED' ) ) {
5919+
// Handle DEFAULT values with expressions (DEFAULT_GENERATED).
5920+
// Translate the default clause from MySQL to SQLite.
5921+
$ast = $this->create_parser( 'SELECT ' . $column['COLUMN_DEFAULT'] )->parse();
5922+
$expr = $ast->get_first_descendant_node( 'selectItem' )->get_first_child_node();
5923+
$default_clause = $this->translate( $expr );
5924+
$query .= ' DEFAULT ' . $default_clause;
59205925
} else {
59215926
$query .= ' DEFAULT ' . $this->quote_sqlite_value( $column['COLUMN_DEFAULT'] );
59225927
}
@@ -6220,7 +6225,11 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str
62206225
) {
62216226
$sql .= ' DEFAULT CURRENT_TIMESTAMP';
62226227
} elseif ( null !== $column['COLUMN_DEFAULT'] ) {
6223-
$sql .= ' DEFAULT ' . $this->quote_mysql_utf8_string_literal( $column['COLUMN_DEFAULT'] );
6228+
if ( str_contains( $column['EXTRA'], 'DEFAULT_GENERATED' ) ) {
6229+
$sql .= ' DEFAULT ' . $column['COLUMN_DEFAULT'];
6230+
} else {
6231+
$sql .= ' DEFAULT ' . $this->quote_mysql_utf8_string_literal( $column['COLUMN_DEFAULT'] );
6232+
}
62246233
} elseif ( 'YES' === $column['IS_NULLABLE'] ) {
62256234
$sql .= ' DEFAULT NULL';
62266235
}

wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,23 +2096,22 @@ private function get_column_default( WP_Parser_Node $node ): ?string {
20962096
// DEFAULT (expression) - MySQL 8.0.13+ supports exprWithParentheses
20972097
$expr_with_parens = $default_attr->get_first_child_node( 'exprWithParentheses' );
20982098
if ( $expr_with_parens ) {
2099-
// For now, only support simple function calls like (now()), (CURRENT_TIMESTAMP)
2100-
// Check if it's (now()) or (NOW())
2101-
$now_tokens = $expr_with_parens->get_descendant_tokens( WP_MySQL_Lexer::NOW_SYMBOL );
2102-
if ( ! empty( $now_tokens ) ) {
2103-
return 'CURRENT_TIMESTAMP';
2104-
}
2105-
2106-
// Check if it's (CURRENT_TIMESTAMP) or (CURRENT_TIMESTAMP())
2107-
$current_ts_tokens = $expr_with_parens->get_descendant_tokens( WP_MySQL_Lexer::CURRENT_TIMESTAMP_SYMBOL );
2108-
if ( ! empty( $current_ts_tokens ) ) {
2109-
return 'CURRENT_TIMESTAMP';
2099+
$default_clause = '';
2100+
foreach ( $expr_with_parens->get_descendant_tokens() as $i => $token ) {
2101+
if ( WP_MySQL_Lexer::OPEN_PAR_SYMBOL === $token->id ) {
2102+
// TODO: This is just a quick fix to avoid inserting whitespace
2103+
// before '(', which would break function call expressions.
2104+
// The proper fix is to implement a "$node->get_bytes()" API.
2105+
// This same applies to the CHECK (expression) case as well.
2106+
$default_clause .= $token->get_bytes();
2107+
} else {
2108+
$default_clause .= ( $i > 0 ? ' ' : '' ) . $token->get_bytes();
2109+
}
21102110
}
2111-
2112-
// For any other complex expressions, throw an exception
2113-
throw new Exception( 'DEFAULT values with complex expressions are not yet supported. Only (now()) and (CURRENT_TIMESTAMP) are currently supported.' );
2111+
return $default_clause;
21142112
}
2115-
throw new Exception( 'DEFAULT values with expressions are not yet supported.' );
2113+
2114+
throw new Exception( 'DEFAULT value of this type is not supported.' );
21162115
}
21172116

21182117
/**

0 commit comments

Comments
 (0)