Skip to content

Commit 04753f9

Browse files
committed
Add support for DEFAULT (expression) in table column definitions
1 parent 50bf5bb commit 04753f9

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
@@ -11316,5 +11316,58 @@ public function testCreateTableWithDefaultNowFunction(): void {
1131611316

1131711317
// Verify the updated timestamp was set (should match YYYY-MM-DD HH:MM:SS format)
1131811318
$this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->updated );
11319+
11320+
// SHOW CREATE TABLE
11321+
$this->assertQuery( 'SHOW CREATE TABLE test_now_default' );
11322+
$results = $this->engine->get_query_results();
11323+
$this->assertEquals(
11324+
implode(
11325+
"\n",
11326+
array(
11327+
'CREATE TABLE `test_now_default` (',
11328+
' `id` int NOT NULL,',
11329+
' `updated` timestamp NOT NULL DEFAULT ( now( ) ) ON UPDATE CURRENT_TIMESTAMP',
11330+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci',
11331+
)
11332+
),
11333+
$results[0]->{'Create Table'}
11334+
);
11335+
}
11336+
11337+
public function testCreateTableWithDefaultExpressions(): void {
11338+
$this->assertQuery(
11339+
'CREATE TABLE t (
11340+
id int NOT NULL,
11341+
col1 int NOT NULL DEFAULT (1 + 2),
11342+
col2 datetime NOT NULL DEFAULT (DATE_ADD(NOW(), INTERVAL 1 YEAR)),
11343+
col3 varchar(255) NOT NULL DEFAULT (CONCAT(\'a\', \'b\'))
11344+
)'
11345+
);
11346+
11347+
// Insert a row and verify the default values
11348+
$this->assertQuery( 'INSERT INTO t (id) VALUES (1)' );
11349+
$this->assertQuery( 'SELECT * FROM t WHERE id = 1' );
11350+
$results = $this->engine->get_query_results();
11351+
$this->assertEquals( 3, $results[0]->col1 );
11352+
$this->assertStringStartsWith( ( gmdate( 'Y' ) + 1 ) . '-', $results[0]->col2 );
11353+
$this->assertEquals( 'ab', $results[0]->col3 );
11354+
11355+
// SHOW CREATE TABLE
11356+
$this->assertQuery( 'SHOW CREATE TABLE t' );
11357+
$results = $this->engine->get_query_results();
11358+
$this->assertEquals(
11359+
implode(
11360+
"\n",
11361+
array(
11362+
'CREATE TABLE `t` (',
11363+
' `id` int NOT NULL,',
11364+
' `col1` int NOT NULL DEFAULT ( 1 + 2 ),',
11365+
' `col2` datetime NOT NULL DEFAULT ( DATE_ADD( NOW( ) , INTERVAL 1 YEAR ) ),',
11366+
" `col3` varchar(255) NOT NULL DEFAULT ( CONCAT( 'a' , 'b' ) )",
11367+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
11368+
)
11369+
),
11370+
$results[0]->{'Create Table'}
11371+
);
1131911372
}
1132011373
}

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
@@ -5694,15 +5694,20 @@ private function get_sqlite_create_table_statement(
56945694
$query .= ' PRIMARY KEY AUTOINCREMENT';
56955695
}
56965696
if ( null !== $column['COLUMN_DEFAULT'] ) {
5697-
// @TODO: Handle defaults with expression values (DEFAULT_GENERATED).
5698-
56995697
// Handle DEFAULT CURRENT_TIMESTAMP. This works only with timestamp
57005698
// and datetime columns. For other column types, it's just a string.
57015699
if (
57025700
'CURRENT_TIMESTAMP' === $column['COLUMN_DEFAULT']
57035701
&& ( 'timestamp' === $column['DATA_TYPE'] || 'datetime' === $column['DATA_TYPE'] )
57045702
) {
57055703
$query .= ' DEFAULT CURRENT_TIMESTAMP';
5704+
} elseif ( str_contains( $column['EXTRA'], 'DEFAULT_GENERATED' ) ) {
5705+
// Handle DEFAULT values with expressions (DEFAULT_GENERATED).
5706+
// Translate the default clause from MySQL to SQLite.
5707+
$ast = $this->create_parser( 'SELECT ' . $column['COLUMN_DEFAULT'] )->parse();
5708+
$expr = $ast->get_first_descendant_node( 'selectItem' )->get_first_child_node();
5709+
$default_clause = $this->translate( $expr );
5710+
$query .= ' DEFAULT ' . $default_clause;
57065711
} else {
57075712
$query .= ' DEFAULT ' . $this->connection->quote( $column['COLUMN_DEFAULT'] );
57085713
}
@@ -6006,7 +6011,11 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str
60066011
) {
60076012
$sql .= ' DEFAULT CURRENT_TIMESTAMP';
60086013
} elseif ( null !== $column['COLUMN_DEFAULT'] ) {
6009-
$sql .= ' DEFAULT ' . $this->quote_mysql_utf8_string_literal( $column['COLUMN_DEFAULT'] );
6014+
if ( str_contains( $column['EXTRA'], 'DEFAULT_GENERATED' ) ) {
6015+
$sql .= ' DEFAULT ' . $column['COLUMN_DEFAULT'];
6016+
} else {
6017+
$sql .= ' DEFAULT ' . $this->quote_mysql_utf8_string_literal( $column['COLUMN_DEFAULT'] );
6018+
}
60106019
} elseif ( 'YES' === $column['IS_NULLABLE'] ) {
60116020
$sql .= ' DEFAULT NULL';
60126021
}

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)