Skip to content

Commit 00db965

Browse files
committed
Implement OPTIMIZE TABLE and REPAIR TABLE statements
1 parent b38106e commit 00db965

1 file changed

Lines changed: 105 additions & 65 deletions

File tree

wp-includes/sqlite-ast/class-wp-sqlite-driver.php

Lines changed: 105 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,11 +1122,6 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
11221122
array( $this->db_name, $table_name )
11231123
)->fetchAll( PDO::FETCH_COLUMN );
11241124

1125-
// Preserve ROWIDs.
1126-
// This also addresses a special case when all original columns are dropped
1127-
// and there is nothing to copy. We'll always have at least the ROWID column.
1128-
array_unshift( $column_names, 'rowid' );
1129-
11301125
// Track column renames and removals.
11311126
$column_map = array_combine( $column_names, $column_names );
11321127
foreach ( $node->get_descendant_nodes( 'alterListItem' ) as $action ) {
@@ -1167,66 +1162,7 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
11671162
}
11681163

11691164
$this->information_schema_builder->record_alter_table( $node );
1170-
1171-
/*
1172-
* See:
1173-
* https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
1174-
*/
1175-
1176-
// 1. If foreign key constraints are enabled, disable them.
1177-
$pragma_foreign_keys = $this->execute_sqlite_query( 'PRAGMA foreign_keys' )->fetchColumn();
1178-
$this->execute_sqlite_query( 'PRAGMA foreign_keys = OFF' );
1179-
1180-
// 2. Create a new table with the new schema.
1181-
$tmp_table_name = self::RESERVED_PREFIX . "tmp_{$table_name}_" . uniqid();
1182-
$quoted_table_name = $this->quote_sqlite_identifier( $table_name );
1183-
$quoted_tmp_table_name = $this->quote_sqlite_identifier( $tmp_table_name );
1184-
$queries = $this->get_sqlite_create_table_statement( $table_name, $tmp_table_name );
1185-
$create_table_query = $queries[0];
1186-
$constraint_queries = array_slice( $queries, 1 );
1187-
$this->execute_sqlite_query( $create_table_query );
1188-
1189-
// 3. Copy data from the original table to the new table.
1190-
$this->execute_sqlite_query(
1191-
sprintf(
1192-
'INSERT INTO %s (%s) SELECT %s FROM %s',
1193-
$quoted_tmp_table_name,
1194-
implode(
1195-
', ',
1196-
array_map( array( $this, 'quote_sqlite_identifier' ), $column_map )
1197-
),
1198-
implode(
1199-
', ',
1200-
array_map( array( $this, 'quote_sqlite_identifier' ), array_keys( $column_map ) )
1201-
),
1202-
$quoted_table_name
1203-
)
1204-
);
1205-
1206-
// 4. Drop the original table.
1207-
$this->execute_sqlite_query( sprintf( 'DROP TABLE %s', $quoted_table_name ) );
1208-
1209-
// 5. Rename the new table to the original table name.
1210-
$this->execute_sqlite_query(
1211-
sprintf(
1212-
'ALTER TABLE %s RENAME TO %s',
1213-
$quoted_tmp_table_name,
1214-
$quoted_table_name
1215-
)
1216-
);
1217-
1218-
// 6. Reconstruct indexes, triggers, and views.
1219-
foreach ( $constraint_queries as $query ) {
1220-
$this->execute_sqlite_query( $query );
1221-
}
1222-
1223-
// 7. If foreign key constraints were enabled, verify and enable them.
1224-
if ( '1' === $pragma_foreign_keys ) {
1225-
$this->execute_sqlite_query( 'PRAGMA foreign_key_check' );
1226-
$this->execute_sqlite_query( 'PRAGMA foreign_keys = ON' );
1227-
}
1228-
1229-
// @TODO: Triggers and views.
1165+
$this->recreate_table_from_information_schema( $table_name, $column_map );
12301166

12311167
// @TODO: Consider using a "fast path" for ALTER TABLE statements that
12321168
// consist only of operations that SQLite's ALTER TABLE supports.
@@ -1666,6 +1602,8 @@ private function execute_use_statement( WP_Parser_Node $node ): void {
16661602
* This emulates the following MySQL statements:
16671603
* - ANALYZE TABLE
16681604
* - CHECK TABLE
1605+
* - OPTIMIZE TABLE
1606+
* - REPAIR TABLE
16691607
*
16701608
* @param WP_Parser_Node $node A "tableAdministrationStatement" AST node.
16711609
* @throws WP_SQLite_Driver_Exception When the query execution fails.
@@ -1690,6 +1628,17 @@ private function execute_administration_statement( WP_Parser_Node $node ): void
16901628
array_shift( $errors );
16911629
}
16921630
break;
1631+
case WP_MySQL_Lexer::OPTIMIZE_SYMBOL:
1632+
case WP_MySQL_Lexer::REPAIR_SYMBOL:
1633+
/*
1634+
* SQLite doesn't support OPTIMIZE and REPAIR TABLE commands.
1635+
* We will recreate the table and copy the data instead.
1636+
* This corresponds to older MySQL OPTIMIZE TABLE behavior
1637+
* and still applies to some storage engines in some cases.
1638+
*/
1639+
$this->recreate_table_from_information_schema( $table_name );
1640+
$errors = array();
1641+
break;
16931642
default:
16941643
throw $this->new_not_supported_exception(
16951644
sprintf(
@@ -2407,6 +2356,97 @@ private function translate_datetime_literal( string $value ): string {
24072356
return $value;
24082357
}
24092358

2359+
/**
2360+
* Recreate an existing table using data in the information schema.
2361+
*
2362+
* This is used for a generic support of ALTER TABLE queries, as well as
2363+
* for some other statements like OPTIMIZE TABLE and REPAIR TABLE.
2364+
*
2365+
* See:
2366+
* https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
2367+
*
2368+
* @param string $table_name The name of the table to recreate.
2369+
* @param array $column_map Optional. A map of column names (old name -> new name)
2370+
* to use when copying data from the original table.
2371+
* When not provided, all columns are copied without renaming.
2372+
* @throws WP_SQLite_Driver_Exception
2373+
*/
2374+
private function recreate_table_from_information_schema( string $table_name, array $column_map = null ): void {
2375+
if ( null === $column_map ) {
2376+
$columns_table = $this->information_schema_builder->get_table_name( 'columns' );
2377+
$column_names = $this->execute_sqlite_query(
2378+
"SELECT COLUMN_NAME FROM $columns_table WHERE table_schema = ? AND table_name = ?",
2379+
array( $this->db_name, $table_name )
2380+
)->fetchAll( PDO::FETCH_COLUMN );
2381+
$column_map = array_combine( $column_names, $column_names );
2382+
}
2383+
2384+
// Preserve ROWIDs.
2385+
// This also addresses a special case when all original columns are dropped
2386+
// and there is nothing to copy. We'll always have at least the ROWID column.
2387+
$column_map = array( 'rowid' => 'rowid' ) + $column_map;
2388+
2389+
/*
2390+
* See:
2391+
* https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
2392+
*/
2393+
2394+
// 1. If foreign key constraints are enabled, disable them.
2395+
$pragma_foreign_keys = $this->execute_sqlite_query( 'PRAGMA foreign_keys' )->fetchColumn();
2396+
$this->execute_sqlite_query( 'PRAGMA foreign_keys = OFF' );
2397+
2398+
// 2. Create a new table with the new schema.
2399+
$tmp_table_name = self::RESERVED_PREFIX . "tmp_{$table_name}_" . uniqid();
2400+
$quoted_table_name = $this->quote_sqlite_identifier( $table_name );
2401+
$quoted_tmp_table_name = $this->quote_sqlite_identifier( $tmp_table_name );
2402+
$queries = $this->get_sqlite_create_table_statement( $table_name, $tmp_table_name );
2403+
$create_table_query = $queries[0];
2404+
$constraint_queries = array_slice( $queries, 1 );
2405+
$this->execute_sqlite_query( $create_table_query );
2406+
2407+
// 3. Copy data from the original table to the new table.
2408+
$this->execute_sqlite_query(
2409+
sprintf(
2410+
'INSERT INTO %s (%s) SELECT %s FROM %s',
2411+
$quoted_tmp_table_name,
2412+
implode(
2413+
', ',
2414+
array_map( array( $this, 'quote_sqlite_identifier' ), $column_map )
2415+
),
2416+
implode(
2417+
', ',
2418+
array_map( array( $this, 'quote_sqlite_identifier' ), array_keys( $column_map ) )
2419+
),
2420+
$quoted_table_name
2421+
)
2422+
);
2423+
2424+
// 4. Drop the original table.
2425+
$this->execute_sqlite_query( sprintf( 'DROP TABLE %s', $quoted_table_name ) );
2426+
2427+
// 5. Rename the new table to the original table name.
2428+
$this->execute_sqlite_query(
2429+
sprintf(
2430+
'ALTER TABLE %s RENAME TO %s',
2431+
$quoted_tmp_table_name,
2432+
$quoted_table_name
2433+
)
2434+
);
2435+
2436+
// 6. Reconstruct indexes, triggers, and views.
2437+
foreach ( $constraint_queries as $query ) {
2438+
$this->execute_sqlite_query( $query );
2439+
}
2440+
2441+
// 7. If foreign key constraints were enabled, verify and enable them.
2442+
if ( '1' === $pragma_foreign_keys ) {
2443+
$this->execute_sqlite_query( 'PRAGMA foreign_key_check' );
2444+
$this->execute_sqlite_query( 'PRAGMA foreign_keys = ON' );
2445+
}
2446+
2447+
// @TODO: Triggers and views.
2448+
}
2449+
24102450
/**
24112451
* Translate a MySQL SHOW LIKE ... or SHOW WHERE ... condition to SQLite.
24122452
*

0 commit comments

Comments
 (0)