@@ -2410,6 +2410,9 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void {
24102410 foreach ( $ constraint_queries as $ query ) {
24112411 $ this ->execute_sqlite_query ( $ query );
24122412 }
2413+
2414+ // Apply AUTO_INCREMENT = N table option, if any.
2415+ $ this ->apply_auto_increment_table_option ( $ table_is_temporary , $ table_name , $ node );
24132416 }
24142417
24152418 /**
@@ -2483,8 +2486,18 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
24832486 }
24842487 }
24852488
2486- $ this ->information_schema_builder ->record_alter_table ( $ node );
2487- $ this ->recreate_table_from_information_schema ( $ table_is_temporary , $ table_name , $ column_map );
2489+ /*
2490+ * Skip the expensive table rebuild when the statement only carries
2491+ * table options (e.g. ALTER TABLE t AUTO_INCREMENT = N). These don't
2492+ * change the schema, so the recreate would be a pointless full copy.
2493+ */
2494+ if ( count ( $ node ->get_descendant_nodes ( 'alterListItem ' ) ) > 0 ) {
2495+ $ this ->information_schema_builder ->record_alter_table ( $ node );
2496+ $ this ->recreate_table_from_information_schema ( $ table_is_temporary , $ table_name , $ column_map );
2497+ }
2498+
2499+ // Apply AUTO_INCREMENT = N table option, if any.
2500+ $ this ->apply_auto_increment_table_option ( $ table_is_temporary , $ table_name , $ node );
24882501
24892502 // @TODO: Consider using a "fast path" for ALTER TABLE statements that
24902503 // consist only of operations that SQLite's ALTER TABLE supports.
@@ -2990,41 +3003,64 @@ private function execute_show_table_status_statement( WP_Parser_Node $node ): vo
29903003 // LIKE and WHERE clauses.
29913004 $ like_or_where = $ node ->get_first_child_node ( 'likeOrWhere ' );
29923005 if ( null !== $ like_or_where ) {
2993- $ condition = $ this ->translate_show_like_or_where_condition ( $ like_or_where , 'table_name ' );
3006+ $ condition = $ this ->translate_show_like_or_where_condition ( $ like_or_where , 'Name ' );
29943007 }
29953008
2996- // Fetch table information.
2997- $ tables_tables = $ this ->information_schema_builder ->get_table_name (
2998- false , // SHOW TABLE STATUS lists only non-temporary tables.
2999- 'tables '
3009+ // SHOW TABLE STATUS lists only non-temporary tables.
3010+ $ tables_table = $ this ->information_schema_builder ->get_table_name ( false , 'tables ' );
3011+ $ columns_table = $ this ->information_schema_builder ->get_table_name ( false , 'columns ' );
3012+
3013+ // Compose a subquery to compute auto-increment values.
3014+ $ has_sequence_table = (bool ) $ this ->execute_sqlite_query (
3015+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence' "
3016+ )->fetchColumn ();
3017+
3018+ $ auto_increment_subquery = sprintf (
3019+ "(
3020+ SELECT COALESCE(s.seq + 1, 1)
3021+ FROM %s AS c
3022+ %s
3023+ WHERE c.extra = 'auto_increment'
3024+ AND c.table_schema = t.table_schema
3025+ AND c.table_name = t.table_name
3026+ ) " ,
3027+ $ this ->quote_sqlite_identifier ( $ columns_table ),
3028+ $ has_sequence_table
3029+ ? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name '
3030+ : 'LEFT JOIN (SELECT 0 AS seq) AS s '
30003031 );
3001- $ query = sprintf (
3002- 'SELECT
3003- table_name AS `Name`,
3004- engine AS `Engine`,
3005- version AS `Version`,
3006- row_format AS `Row_format`,
3007- table_rows AS `Rows`,
3008- avg_row_length AS `Avg_row_length`,
3009- data_length AS `Data_length`,
3010- max_data_length AS `Max_data_length`,
3011- index_length AS `Index_length`,
3012- data_free AS `Data_free`,
3013- auto_increment AS `Auto_increment`,
3014- create_time AS `Create_time`,
3015- update_time AS `Update_time`,
3016- check_time AS `Check_time`,
3017- table_collation AS `Collation`,
3018- checksum AS `Checksum`,
3019- create_options AS `Create_options`,
3020- table_comment AS `Comment`
3021- FROM %s
3022- WHERE table_schema = ? %s
3023- ORDER BY table_name ' ,
3024- $ this ->quote_sqlite_identifier ( $ tables_tables ),
3032+
3033+ $ query = sprintf (
3034+ 'SELECT * FROM (
3035+ SELECT
3036+ table_name AS `Name`,
3037+ engine AS `Engine`,
3038+ version AS `Version`,
3039+ row_format AS `Row_format`,
3040+ table_rows AS `Rows`,
3041+ avg_row_length AS `Avg_row_length`,
3042+ data_length AS `Data_length`,
3043+ max_data_length AS `Max_data_length`,
3044+ index_length AS `Index_length`,
3045+ data_free AS `Data_free`,
3046+ %s AS `Auto_increment`,
3047+ create_time AS `Create_time`,
3048+ update_time AS `Update_time`,
3049+ check_time AS `Check_time`,
3050+ table_collation AS `Collation`,
3051+ checksum AS `Checksum`,
3052+ create_options AS `Create_options`,
3053+ table_comment AS `Comment`
3054+ FROM %s AS t
3055+ WHERE table_schema = ?
3056+ )
3057+ WHERE 1 %s
3058+ ORDER BY `Name` ' ,
3059+ $ auto_increment_subquery ,
3060+ $ this ->quote_sqlite_identifier ( $ tables_table ),
30253061 $ condition ?? ''
30263062 );
3027- $ params = array (
3063+ $ params = array (
30283064 $ this ->get_saved_db_name ( $ database ),
30293065 );
30303066
@@ -4729,7 +4765,7 @@ public function translate_table_ref( WP_Parser_Node $node ): string {
47294765 $ table_name = $ this ->unquote_sqlite_identifier ( $ this ->translate ( $ table ) );
47304766
47314767 // When the table reference targets an information schema table,
4732- // we need to inject the configured database name dynamically.
4768+ // we need to inject some additional values dynamically.
47334769 if (
47344770 ( null === $ schema_name && 'information_schema ' === $ this ->db_name )
47354771 || ( null !== $ schema_name && 'information_schema ' === strtolower ( $ schema_name ) )
@@ -4775,14 +4811,40 @@ public function translate_table_ref( WP_Parser_Node $node ): string {
47754811 $ expanded_list = array ();
47764812 foreach ( $ columns as $ column ) {
47774813 $ quoted_column = $ this ->quote_sqlite_identifier ( $ column );
4778- if ( isset ( $ information_schema_db_column_map [ strtoupper ( $ column ) ] ) ) {
4814+ if ( isset ( $ information_schema_db_column_map [ $ column ] ) ) {
4815+ // Replace the database name with the configured database name.
47794816 $ expanded_list [] = sprintf (
47804817 "CASE WHEN %s = 'information_schema' THEN %s ELSE %s END AS %s " ,
47814818 $ quoted_column ,
47824819 $ quoted_column ,
47834820 $ this ->quote_sqlite_value ( $ this ->main_db_name ),
4784- strtoupper ( $ quoted_column )
4821+ $ quoted_column
47854822 );
4823+ } elseif ( 'tables ' === $ table_name && 'AUTO_INCREMENT ' === $ column ) {
4824+ // Inject the auto-increment values.
4825+ $ columns_table = $ this ->information_schema_builder ->get_table_name ( false , 'columns ' );
4826+ $ has_sequence_table = (bool ) $ this ->execute_sqlite_query (
4827+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence' "
4828+ )->fetchColumn ();
4829+
4830+ $ auto_increment_subquery = sprintf (
4831+ "(
4832+ SELECT COALESCE(s.seq + 1, 1)
4833+ FROM %s AS c
4834+ %s
4835+ WHERE c.extra = 'auto_increment'
4836+ AND c.table_schema = %s.table_schema
4837+ AND c.table_name = %s.table_name
4838+ ) " ,
4839+ $ this ->quote_sqlite_identifier ( $ columns_table ),
4840+ $ has_sequence_table
4841+ ? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name '
4842+ : 'LEFT JOIN (SELECT 0 AS seq) AS s ' ,
4843+ $ this ->quote_sqlite_identifier ( $ table_name ),
4844+ $ this ->quote_sqlite_identifier ( $ table_name )
4845+ );
4846+
4847+ $ expanded_list [] = sprintf ( '%s AS %s ' , $ auto_increment_subquery , $ quoted_column );
47864848 } else {
47874849 $ expanded_list [] = $ quoted_column ;
47884850 }
@@ -4899,6 +4961,83 @@ private function recreate_table_from_information_schema(
48994961 // @TODO: Triggers and views.
49004962 }
49014963
4964+ /**
4965+ * Apply the AUTO_INCREMENT table option from a CREATE TABLE or ALTER TABLE
4966+ * statement by adjusting the row in SQLite's "sqlite_sequence" table.
4967+ *
4968+ * @param bool $table_is_temporary Whether the table is temporary.
4969+ * @param string $table_name The table name.
4970+ * @param WP_Parser_Node $node The "createStatement" or "alterStatement" AST node.
4971+ */
4972+ private function apply_auto_increment_table_option (
4973+ bool $ table_is_temporary ,
4974+ string $ table_name ,
4975+ WP_Parser_Node $ node
4976+ ): void {
4977+ // Find the last AUTO_INCREMENT = N option (MySQL uses the last one).
4978+ $ value = null ;
4979+ foreach ( $ node ->get_descendant_nodes ( 'createTableOption ' ) as $ option ) {
4980+ if ( ! $ option ->has_child_token ( WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL ) ) {
4981+ continue ;
4982+ }
4983+ $ number_node = $ option ->get_first_child_node ( 'ulonglong_number ' );
4984+ if ( null === $ number_node ) {
4985+ continue ;
4986+ }
4987+ $ value = (int ) $ number_node ->get_first_descendant_token ()->get_value ();
4988+ }
4989+ if ( null === $ value ) {
4990+ return ;
4991+ }
4992+
4993+ // Find the AUTO_INCREMENT column.
4994+ $ columns_table = $ this ->information_schema_builder ->get_table_name ( $ table_is_temporary , 'columns ' );
4995+ $ auto_column = $ this ->execute_sqlite_query (
4996+ sprintf (
4997+ "SELECT column_name FROM %s
4998+ WHERE table_schema = ?
4999+ AND table_name = ?
5000+ AND extra = 'auto_increment' " ,
5001+ $ this ->quote_sqlite_identifier ( $ columns_table )
5002+ ),
5003+ array ( $ this ->get_saved_db_name (), $ table_name )
5004+ )->fetchColumn ();
5005+ if ( false === $ auto_column ) {
5006+ return ;
5007+ }
5008+
5009+ /*
5010+ * Prepare an expression for the sequence value.
5011+ * 1. Use N - 1. MySQL stores the next value, SQLite the last one.
5012+ * 2. Clamp to MAX(col) like MySQL (we can't go below existing values).
5013+ *
5014+ * The value is inlined as an integer literal because PDO binds PHP ints
5015+ * as TEXT, and SQLite's type affinity ranks TEXT above INTEGER in MAX().
5016+ */
5017+ $ schema = $ table_is_temporary ? 'temp ' : 'main ' ;
5018+ $ seq_expr = sprintf (
5019+ 'MAX(%d, COALESCE((SELECT MAX(%s) FROM %s), 0)) ' ,
5020+ $ value - 1 ,
5021+ $ this ->quote_sqlite_identifier ( $ auto_column ),
5022+ $ this ->quote_sqlite_identifier ( $ table_name )
5023+ );
5024+
5025+ // Update the value in the "sqlite_sequence" table.
5026+ $ updated = $ this ->execute_sqlite_query (
5027+ sprintf ( 'UPDATE %s.sqlite_sequence SET seq = %s WHERE name = ? ' , $ schema , $ seq_expr ),
5028+ array ( $ table_name )
5029+ )->rowCount ();
5030+
5031+ // If the sequence value does not exist yet, insert a new row.
5032+ // SQLite reports matched (not affected) rows, so the 0 check is safe.
5033+ if ( 0 === $ updated ) {
5034+ $ this ->execute_sqlite_query (
5035+ sprintf ( 'INSERT INTO %s.sqlite_sequence (name, seq) VALUES (?, %s) ' , $ schema , $ seq_expr ),
5036+ array ( $ table_name )
5037+ );
5038+ }
5039+ }
5040+
49025041 /**
49035042 * Translate a MySQL SHOW LIKE ... or SHOW WHERE ... condition to SQLite.
49045043 *
@@ -6335,7 +6474,8 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str
63356474 )->fetchAll ( PDO ::FETCH_ASSOC );
63366475
63376476 // 6. Generate CREATE TABLE statement columns.
6338- $ rows = array ();
6477+ $ rows = array ();
6478+ $ has_auto_increment = false ;
63396479 foreach ( $ column_info as $ column ) {
63406480 $ sql = ' ' ;
63416481 $ sql .= $ this ->quote_mysql_identifier ( $ column ['COLUMN_NAME ' ] );
@@ -6347,7 +6487,8 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str
63476487 $ sql .= ' NULL ' ;
63486488 }
63496489 if ( 'auto_increment ' === $ column ['EXTRA ' ] ) {
6350- $ sql .= ' AUTO_INCREMENT ' ;
6490+ $ has_auto_increment = true ;
6491+ $ sql .= ' AUTO_INCREMENT ' ;
63516492 }
63526493
63536494 // Handle DEFAULT CURRENT_TIMESTAMP. This works only with timestamp
@@ -6486,6 +6627,28 @@ function ( $column ) {
64866627 $ sql .= implode ( ", \n" , $ rows );
64876628 $ sql .= "\n) " ;
64886629 $ sql .= sprintf ( ' ENGINE=%s ' , $ table_info ['ENGINE ' ] );
6630+
6631+ // Add "AUTO_INCREMENT=N" if a sequence exists and has been advanced.
6632+ if ( $ has_auto_increment ) {
6633+ try {
6634+ $ seq = (int ) $ this ->execute_sqlite_query (
6635+ sprintf (
6636+ 'SELECT seq FROM %s.sqlite_sequence WHERE name = ? ' ,
6637+ $ table_is_temporary ? 'temp ' : 'main '
6638+ ),
6639+ array ( $ table_name )
6640+ )->fetchColumn ();
6641+ } catch ( PDOException $ e ) {
6642+ if ( ! str_contains ( $ e ->getMessage (), 'no such table ' ) ) {
6643+ throw $ e ;
6644+ }
6645+ $ seq = 0 ;
6646+ }
6647+ if ( $ seq > 0 ) {
6648+ $ sql .= sprintf ( ' AUTO_INCREMENT=%d ' , $ seq + 1 );
6649+ }
6650+ }
6651+
64896652 $ sql .= sprintf ( ' DEFAULT CHARSET=%s ' , $ charset );
64906653 $ sql .= sprintf ( ' COLLATE=%s ' , $ collation );
64916654 if ( '' !== $ table_info ['TABLE_COMMENT ' ] ) {
0 commit comments