@@ -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