Skip to content

Commit

Permalink
Add basic ALTER TABLE support
Browse files Browse the repository at this point in the history
  • Loading branch information
JanJakes committed Dec 2, 2024
1 parent 797c3b7 commit 647eaae
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
49 changes: 49 additions & 0 deletions tests/WP_SQLite_Driver_Translation_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,55 @@ public function testCreateTable(): void {
);
}

public function testAlterTable(): void {
// Prepare a real table, so we can test multi-operation alter statements.
// Otherwise, we'd hit and exception and rollback after the first query.
$this->assertQuery(
'CREATE TABLE "t" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )',
'CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT)'
);

// ADD COLUMN.
$this->assertQuery(
'ALTER TABLE "t" ADD COLUMN "a" INTEGER',
'ALTER TABLE t ADD a INT'
);

// ADD COLUMN with multiple columns.
$this->assertQuery(
array(
'ALTER TABLE "t" ADD COLUMN "b" INTEGER',
'ALTER TABLE "t" ADD COLUMN "c" TEXT',
'ALTER TABLE "t" ADD COLUMN "d" INTEGER',
),
'ALTER TABLE t ADD b INT, ADD c TEXT, ADD d BOOL'
);

// DROP COLUMN.
$this->assertQuery(
'ALTER TABLE "t" DROP COLUMN "a"',
'ALTER TABLE t DROP a'
);

// DROP COLUMN with multiple columns.
$this->assertQuery(
array(
'ALTER TABLE "t" DROP COLUMN "b"',
'ALTER TABLE "t" DROP COLUMN "c"',
),
'ALTER TABLE t DROP b, DROP c'
);

// ADD COLUMN and DROP COLUMN combined.
$this->assertQuery(
array(
'ALTER TABLE "t" ADD COLUMN "a" INTEGER',
'ALTER TABLE "t" DROP COLUMN "d"',
),
'ALTER TABLE t ADD a INT, DROP d'
);
}

public function testDataTypes(): void {
// Numeric data types.
$this->assertQuery(
Expand Down
92 changes: 92 additions & 0 deletions wp-includes/sqlite-ast/class-wp-sqlite-driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,36 @@ private function execute_mysql_query( WP_Parser_Node $ast ) {
);
}
break;
case 'alterStatement':
$this->query_type = 'ALTER';
$subtree = $ast->get_child_node();
switch ( $subtree->rule_name ) {
case 'alterTable':
$this->execute_alter_table_statement( $ast );
break;
default:
throw new Exception(
sprintf(
'Unsupported statement type: "%s" > "%s"',
$ast->rule_name,
$subtree->rule_name
)
);
}
break;
case 'dropStatement':
$this->query_type = 'DROP';
$query = $this->translate( $ast );
$this->execute_sqlite_query( $query );
$this->set_result_from_affected_rows();
break;
case 'setStatement':
/*
* It would be lovely to support at least SET autocommit,
* but I don't think that is even possible with SQLite.
*/
$this->results = 0;
break;
default:
throw new Exception( sprintf( 'Unsupported statement type: "%s"', $ast->rule_name ) );
}
Expand Down Expand Up @@ -934,6 +964,68 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void {
$this->set_result_from_affected_rows();
}

private function execute_alter_table_statement( WP_Parser_Node $node ): void {
$table_name = $this->translate( $node->get_descendant_node( 'tableRef' ) );
$actions = $node->get_descendant_nodes( 'alterListItem' );

/*
* SQLite supports only a small subset of MySQL ALTER TABLE statement.
* We need to handle some differences and emulate some operations:
*
* 1. Multiple operations in a single ALTER TABLE statement.
*
* SQLite doesn't support multiple operations in a single ALTER TABLE
* statement. We need to execute each operation as a separate query.
*
* 2. ADD COLUMN in SQLite doesn't support some valid MySQL constructs:
*
* - Adding a column with PRIMARY KEY or UNIQUE constraint.
* - Adding a column with AUTOINCREMENT.
* - Adding a column with CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP,
* or an expression in parentheses as a default value.
* - Adding a NOT NULL column without a default value when the table is
* not empty. In MySQL, this depends on the data type and SQL mode.
*
* @TODO: Address these nuances.
*/
foreach ( $actions as $action ) {
$token = $action->get_child_token();

// ADD column/constraint.
if ( WP_MySQL_Lexer::ADD_SYMBOL === $token->id ) {
// ADD COLUMN.
$field_definition = $action->get_descendant_node( 'fieldDefinition' );
if ( null !== $field_definition ) {
$field_name = $this->translate( $action->get_child_node( 'identifier' ) );
$field = $this->translate( $field_definition );
$this->execute_sqlite_query(
sprintf( 'ALTER TABLE %s ADD COLUMN %s %s', $table_name, $field_name, $field )
);
}

// ADD CONSTRAINT.
$constraint = $action->get_descendant_node( 'tableConstraintDef' );
if ( null !== $constraint ) {
$constraint_name = $this->translate( $constraint->get_child_node( 'identifier' ) );
$constraint = $this->translate( $constraint );
$this->execute_sqlite_query(
sprintf( 'ALTER TABLE %s ADD CONSTRAINT %s %s', $table_name, $constraint_name, $constraint )
);
}
} elseif ( WP_MySQL_Lexer::DROP_SYMBOL === $token->id ) {
// DROP COLUMN.
$field_name = $this->translate( $action->get_child_node( 'columnInternalRef' ) );
if ( null !== $field_name ) {
$this->execute_sqlite_query(
sprintf( 'ALTER TABLE %s DROP COLUMN %s', $table_name, $field_name )
);
}
}
}

$this->set_result_from_affected_rows();
}

private function translate( $ast ) {
if ( null === $ast ) {
return null;
Expand Down

0 comments on commit 647eaae

Please sign in to comment.