Skip to content

Commit 47325ab

Browse files
committed
Report AUTO_INCREMENT values in metadata commands
Compute the MySQL Auto_increment value live from SQLite's sqlite_sequence so SHOW TABLE STATUS, SHOW CREATE TABLE, and queries against INFORMATION_SCHEMA.TABLES report the next AUTO_INCREMENT value instead of always returning NULL. The counter is preserved past DELETE and reset by TRUNCATE, matching MySQL InnoDB semantics. Retires the first half of patchWp15AdminPostAutoIncrement in WordPress, which existed to work around WP 1.5's wp-admin/post.php reading the SHOW TABLE STATUS Auto_increment column.
1 parent 3a3baf7 commit 47325ab

3 files changed

Lines changed: 262 additions & 37 deletions

File tree

packages/mysql-on-sqlite/src/sqlite/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 108 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2958,41 +2958,64 @@ private function execute_show_table_status_statement( WP_Parser_Node $node ): vo
29582958
// LIKE and WHERE clauses.
29592959
$like_or_where = $node->get_first_child_node( 'likeOrWhere' );
29602960
if ( null !== $like_or_where ) {
2961-
$condition = $this->translate_show_like_or_where_condition( $like_or_where, 'table_name' );
2961+
$condition = $this->translate_show_like_or_where_condition( $like_or_where, 'Name' );
29622962
}
29632963

2964-
// Fetch table information.
2965-
$tables_tables = $this->information_schema_builder->get_table_name(
2966-
false, // SHOW TABLE STATUS lists only non-temporary tables.
2967-
'tables'
2964+
// SHOW TABLE STATUS lists only non-temporary tables.
2965+
$tables_table = $this->information_schema_builder->get_table_name( false, 'tables' );
2966+
$columns_table = $this->information_schema_builder->get_table_name( false, 'columns' );
2967+
2968+
// Compose a subquery to compute auto-increment values.
2969+
$has_sequence_table = (bool) $this->execute_sqlite_query(
2970+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'"
2971+
)->fetchColumn();
2972+
2973+
$auto_increment_subquery = sprintf(
2974+
"(
2975+
SELECT COALESCE(s.seq + 1, 1)
2976+
FROM %s AS c
2977+
%s
2978+
WHERE c.extra = 'auto_increment'
2979+
AND c.table_schema = t.table_schema
2980+
AND c.table_name = t.table_name
2981+
)",
2982+
$this->quote_sqlite_identifier( $columns_table ),
2983+
$has_sequence_table
2984+
? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name'
2985+
: 'LEFT JOIN (SELECT 0 AS seq) AS s'
29682986
);
2969-
$query = sprintf(
2970-
'SELECT
2971-
table_name AS `Name`,
2972-
engine AS `Engine`,
2973-
version AS `Version`,
2974-
row_format AS `Row_format`,
2975-
table_rows AS `Rows`,
2976-
avg_row_length AS `Avg_row_length`,
2977-
data_length AS `Data_length`,
2978-
max_data_length AS `Max_data_length`,
2979-
index_length AS `Index_length`,
2980-
data_free AS `Data_free`,
2981-
auto_increment AS `Auto_increment`,
2982-
create_time AS `Create_time`,
2983-
update_time AS `Update_time`,
2984-
check_time AS `Check_time`,
2985-
table_collation AS `Collation`,
2986-
checksum AS `Checksum`,
2987-
create_options AS `Create_options`,
2988-
table_comment AS `Comment`
2989-
FROM %s
2990-
WHERE table_schema = ? %s
2991-
ORDER BY table_name',
2992-
$this->quote_sqlite_identifier( $tables_tables ),
2987+
2988+
$query = sprintf(
2989+
'SELECT * FROM (
2990+
SELECT
2991+
table_name AS `Name`,
2992+
engine AS `Engine`,
2993+
version AS `Version`,
2994+
row_format AS `Row_format`,
2995+
table_rows AS `Rows`,
2996+
avg_row_length AS `Avg_row_length`,
2997+
data_length AS `Data_length`,
2998+
max_data_length AS `Max_data_length`,
2999+
index_length AS `Index_length`,
3000+
data_free AS `Data_free`,
3001+
%s AS `Auto_increment`,
3002+
create_time AS `Create_time`,
3003+
update_time AS `Update_time`,
3004+
check_time AS `Check_time`,
3005+
table_collation AS `Collation`,
3006+
checksum AS `Checksum`,
3007+
create_options AS `Create_options`,
3008+
table_comment AS `Comment`
3009+
FROM %s AS t
3010+
WHERE table_schema = ?
3011+
)
3012+
WHERE 1 %s
3013+
ORDER BY `Name`',
3014+
$auto_increment_subquery,
3015+
$this->quote_sqlite_identifier( $tables_table ),
29933016
$condition ?? ''
29943017
);
2995-
$params = array(
3018+
$params = array(
29963019
$this->get_saved_db_name( $database ),
29973020
);
29983021

@@ -4697,7 +4720,7 @@ public function translate_table_ref( WP_Parser_Node $node ): string {
46974720
$table_name = $this->unquote_sqlite_identifier( $this->translate( $table ) );
46984721

46994722
// When the table reference targets an information schema table,
4700-
// we need to inject the configured database name dynamically.
4723+
// we need to inject some additional values dynamically.
47014724
if (
47024725
( null === $schema_name && 'information_schema' === $this->db_name )
47034726
|| ( null !== $schema_name && 'information_schema' === strtolower( $schema_name ) )
@@ -4743,14 +4766,40 @@ public function translate_table_ref( WP_Parser_Node $node ): string {
47434766
$expanded_list = array();
47444767
foreach ( $columns as $column ) {
47454768
$quoted_column = $this->quote_sqlite_identifier( $column );
4746-
if ( isset( $information_schema_db_column_map[ strtoupper( $column ) ] ) ) {
4769+
if ( isset( $information_schema_db_column_map[ $column ] ) ) {
4770+
// Replace the database name with the configured database name.
47474771
$expanded_list[] = sprintf(
47484772
"CASE WHEN %s = 'information_schema' THEN %s ELSE %s END AS %s",
47494773
$quoted_column,
47504774
$quoted_column,
47514775
$this->quote_sqlite_value( $this->main_db_name ),
4752-
strtoupper( $quoted_column )
4776+
$quoted_column
4777+
);
4778+
} elseif ( 'tables' === $table_name && 'AUTO_INCREMENT' === $column ) {
4779+
// Inject the auto-increment values.
4780+
$columns_table = $this->information_schema_builder->get_table_name( false, 'columns' );
4781+
$has_sequence_table = (bool) $this->execute_sqlite_query(
4782+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'"
4783+
)->fetchColumn();
4784+
4785+
$auto_increment_subquery = sprintf(
4786+
"(
4787+
SELECT COALESCE(s.seq + 1, 1)
4788+
FROM %s AS c
4789+
%s
4790+
WHERE c.extra = 'auto_increment'
4791+
AND c.table_schema = %s.table_schema
4792+
AND c.table_name = %s.table_name
4793+
)",
4794+
$this->quote_sqlite_identifier( $columns_table ),
4795+
$has_sequence_table
4796+
? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name'
4797+
: 'LEFT JOIN (SELECT 0 AS seq) AS s',
4798+
$this->quote_sqlite_identifier( $table_name ),
4799+
$this->quote_sqlite_identifier( $table_name )
47534800
);
4801+
4802+
$expanded_list[] = sprintf( '%s AS %s', $auto_increment_subquery, $quoted_column );
47544803
} else {
47554804
$expanded_list[] = $quoted_column;
47564805
}
@@ -6303,7 +6352,8 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str
63036352
)->fetchAll( PDO::FETCH_ASSOC );
63046353

63056354
// 6. Generate CREATE TABLE statement columns.
6306-
$rows = array();
6355+
$rows = array();
6356+
$has_auto_increment = false;
63076357
foreach ( $column_info as $column ) {
63086358
$sql = ' ';
63096359
$sql .= $this->quote_mysql_identifier( $column['COLUMN_NAME'] );
@@ -6315,7 +6365,8 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str
63156365
$sql .= ' NULL';
63166366
}
63176367
if ( 'auto_increment' === $column['EXTRA'] ) {
6318-
$sql .= ' AUTO_INCREMENT';
6368+
$has_auto_increment = true;
6369+
$sql .= ' AUTO_INCREMENT';
63196370
}
63206371

63216372
// Handle DEFAULT CURRENT_TIMESTAMP. This works only with timestamp
@@ -6454,6 +6505,28 @@ function ( $column ) {
64546505
$sql .= implode( ",\n", $rows );
64556506
$sql .= "\n)";
64566507
$sql .= sprintf( ' ENGINE=%s', $table_info['ENGINE'] );
6508+
6509+
// Add "AUTO_INCREMENT=N" if a sequence exists and has been advanced.
6510+
if ( $has_auto_increment ) {
6511+
try {
6512+
$seq = (int) $this->execute_sqlite_query(
6513+
sprintf(
6514+
'SELECT seq FROM %s.sqlite_sequence WHERE name = ?',
6515+
$table_is_temporary ? 'temp' : 'main'
6516+
),
6517+
array( $table_name )
6518+
)->fetchColumn();
6519+
} catch ( PDOException $e ) {
6520+
if ( ! str_contains( $e->getMessage(), 'no such table' ) ) {
6521+
throw $e;
6522+
}
6523+
$seq = 0;
6524+
}
6525+
if ( $seq > 0 ) {
6526+
$sql .= sprintf( ' AUTO_INCREMENT=%d', $seq + 1 );
6527+
}
6528+
}
6529+
64576530
$sql .= sprintf( ' DEFAULT CHARSET=%s', $charset );
64586531
$sql .= sprintf( ' COLLATE=%s', $collation );
64596532
if ( '' !== $table_info['TABLE_COMMENT'] ) {

packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Metadata_Tests.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,158 @@ public function testShowTableStatus() {
628628
);
629629
}
630630

631+
public function testInformationSchemaTablesAutoIncrement(): void {
632+
// A non-AUTO_INCREMENT table reports NULL.
633+
$this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' );
634+
$result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 'plain'" );
635+
$this->assertSame( null, $result[0]->AUTO_INCREMENT );
636+
637+
// A fresh AUTO_INCREMENT table reports 1.
638+
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
639+
$result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" );
640+
$this->assertSame( '1', $result[0]->AUTO_INCREMENT );
641+
642+
// After inserts, the stored value advances, and a backtick-quoted
643+
// projection returns the same result as SELECT *.
644+
$this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')" );
645+
$result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" );
646+
$this->assertSame( '4', $result[0]->AUTO_INCREMENT );
647+
648+
$result = $this->assertQuery(
649+
"SELECT table_name, `auto_increment` FROM information_schema.tables WHERE table_name = 't'"
650+
);
651+
$this->assertSame( '4', $result[0]->AUTO_INCREMENT );
652+
653+
// DELETE preserves the counter.
654+
$this->assertQuery( 'DELETE FROM t' );
655+
$result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" );
656+
$this->assertSame( '4', $result[0]->AUTO_INCREMENT );
657+
658+
// TRUNCATE resets the counter.
659+
$this->assertQuery( 'TRUNCATE TABLE t' );
660+
$result = $this->assertQuery( "SELECT * FROM information_schema.tables WHERE table_name = 't'" );
661+
$this->assertSame( '1', $result[0]->AUTO_INCREMENT );
662+
}
663+
664+
public function testShowTableStatusAutoIncrement() {
665+
// A non-AUTO_INCREMENT table reports NULL.
666+
$this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' );
667+
$this->assertSame( null, $this->getAutoIncrement( 'plain' ) );
668+
669+
// A fresh AUTO_INCREMENT table reports 1.
670+
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
671+
$this->assertSame( '1', $this->getAutoIncrement( 't' ) );
672+
673+
// After inserts, Auto_increment is the next value.
674+
$this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')" );
675+
$this->assertSame( '4', $this->getAutoIncrement( 't' ) );
676+
677+
// DELETE must preserve the counter, matching InnoDB behavior.
678+
$this->assertQuery( 'DELETE FROM t WHERE id = 3' );
679+
$this->assertSame( '4', $this->getAutoIncrement( 't' ) );
680+
681+
// DELETE of all rows still preserves the counter.
682+
$this->assertQuery( 'DELETE FROM t' );
683+
$this->assertSame( '4', $this->getAutoIncrement( 't' ) );
684+
685+
// TRUNCATE resets the counter.
686+
$this->assertQuery( 'TRUNCATE TABLE t' );
687+
$this->assertSame( '1', $this->getAutoIncrement( 't' ) );
688+
}
689+
690+
public function testShowTableStatusFilterByAutoIncrement() {
691+
$this->assertQuery( 'CREATE TABLE low (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
692+
$this->assertQuery( 'CREATE TABLE high (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
693+
$this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' );
694+
$this->assertQuery( "INSERT INTO low (name) VALUES ('a')" );
695+
$this->assertQuery( "INSERT INTO high (name) VALUES ('a'), ('b'), ('c'), ('d'), ('e')" );
696+
697+
// WHERE must filter on the computed Auto_increment alias, not on the
698+
// always-NULL underlying info_schema column.
699+
$result = $this->assertQuery( 'SHOW TABLE STATUS WHERE `Auto_increment` > 3' );
700+
$this->assertCount( 1, $result );
701+
$this->assertSame( 'high', $result[0]->Name );
702+
703+
$result = $this->assertQuery( 'SHOW TABLE STATUS WHERE `Auto_increment` IS NULL' );
704+
$this->assertCount( 1, $result );
705+
$this->assertSame( 'plain', $result[0]->Name );
706+
}
707+
708+
public function testShowCreateTableAutoIncrement(): void {
709+
// No AUTO_INCREMENT=N on a non-AI table.
710+
$this->assertQuery( 'CREATE TABLE plain (id INT, name TEXT)' );
711+
$this->assertStringNotContainsString(
712+
'AUTO_INCREMENT=',
713+
$this->getCreateTable( 'plain' )
714+
);
715+
716+
// No AUTO_INCREMENT=N on a fresh AI table (counter has not advanced).
717+
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
718+
$this->assertStringNotContainsString(
719+
'AUTO_INCREMENT=',
720+
$this->getCreateTable( 't' )
721+
);
722+
723+
// After inserts, AUTO_INCREMENT=<next value> is emitted.
724+
$this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')" );
725+
$this->assertStringContainsString(
726+
'AUTO_INCREMENT=4',
727+
$this->getCreateTable( 't' )
728+
);
729+
730+
// DELETE preserves the counter.
731+
$this->assertQuery( 'DELETE FROM t' );
732+
$this->assertStringContainsString(
733+
'AUTO_INCREMENT=4',
734+
$this->getCreateTable( 't' )
735+
);
736+
737+
// TRUNCATE resets the counter; AUTO_INCREMENT=N is omitted again.
738+
$this->assertQuery( 'TRUNCATE TABLE t' );
739+
$this->assertStringNotContainsString(
740+
'AUTO_INCREMENT=',
741+
$this->getCreateTable( 't' )
742+
);
743+
}
744+
745+
public function testShowCreateTableAutoIncrementOnTemporaryTable(): void {
746+
// Temporary tables live in SQLite's `temp` schema with their own
747+
// `sqlite_sequence`. Make sure the counter is read from the right one
748+
// even when a persistent AI table in `main` might shadow it.
749+
$this->assertQuery( 'CREATE TABLE main_tbl (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
750+
$this->assertQuery( "INSERT INTO main_tbl (name) VALUES ('m')" );
751+
752+
$this->assertQuery( 'CREATE TEMPORARY TABLE tmp (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
753+
$this->assertStringNotContainsString(
754+
'AUTO_INCREMENT=',
755+
$this->getCreateTable( 'tmp' )
756+
);
757+
758+
$this->assertQuery( "INSERT INTO tmp (name) VALUES ('a'), ('b')" );
759+
$this->assertStringContainsString(
760+
'AUTO_INCREMENT=3',
761+
$this->getCreateTable( 'tmp' )
762+
);
763+
764+
// The persistent table's counter must remain independent.
765+
$this->assertStringContainsString(
766+
'AUTO_INCREMENT=2',
767+
$this->getCreateTable( 'main_tbl' )
768+
);
769+
}
770+
771+
private function getAutoIncrement( string $table_name ): ?string {
772+
$result = $this->assertQuery(
773+
sprintf( "SHOW TABLE STATUS LIKE '%s'", $table_name )
774+
);
775+
return $result[0]->Auto_increment;
776+
}
777+
778+
private function getCreateTable( string $table_name ): string {
779+
$result = $this->assertQuery( sprintf( 'SHOW CREATE TABLE %s', $table_name ) );
780+
return $result[0]->{'Create Table'};
781+
}
782+
631783
public function testShowFullColumns(): void {
632784
$this->assertQuery( "CREATE TABLE t (id INT COMMENT 'Comment ID', name TEXT COMMENT 'Comment Name')" );
633785
$result = $this->assertQuery( 'SHOW FULL COLUMNS FROM t' );

packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Tests.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ public function testShowTableStatusWhere() {
889889
);
890890

891891
$this->assertQuery(
892-
"SHOW TABLE STATUS WHERE SUBSTR(table_name, 11, 1) = '1'"
892+
"SHOW TABLE STATUS WHERE SUBSTR(Name, 11, 1) = '1'"
893893
);
894894
$this->assertCount(
895895
1,
@@ -4562,7 +4562,7 @@ public function testCompoundPrimaryKeyWithAutoincrement(): void {
45624562
' `id` int NOT NULL AUTO_INCREMENT,',
45634563
' `name` varchar(32) NOT NULL,',
45644564
' PRIMARY KEY (`id`, `name`)',
4565-
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
4565+
') ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
45664566
)
45674567
),
45684568
$results[0]->{'Create Table'}

0 commit comments

Comments
 (0)