Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php
Original file line number Diff line number Diff line change
Expand Up @@ -5779,6 +5779,27 @@ private function recreate_table_from_information_schema(
)
);

// Preserve the AUTO_INCREMENT counter from the original table.
$schema = $table_is_temporary ? 'temp' : 'main';
$has_sequence_table = (bool) $this->execute_sqlite_query(
sprintf( "SELECT 1 FROM %s.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'", $schema )
)->fetchColumn();
if ( $has_sequence_table ) {
$this->execute_sqlite_query(
Comment thread
coderabbitai[bot] marked this conversation as resolved.
sprintf(
'UPDATE %s.sqlite_sequence
SET seq = MAX(seq, COALESCE((
SELECT seq FROM %s.sqlite_sequence
WHERE name = ? COLLATE NOCASE -- $table_name: original table
), 0))
WHERE name = ? -- $tmp_table_name: update only the replacement\'s row.',
$schema,
$schema
),
array( $table_name, $tmp_table_name )
);
}

// 4. Drop the original table.
$this->execute_sqlite_query( sprintf( 'DROP TABLE %s', $quoted_table_name ) );

Expand Down
143 changes: 143 additions & 0 deletions packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Metadata_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,149 @@ public function testAlterTableSetAutoIncrement(): void {
$this->assertSame( null, $result[0]->Auto_increment );
}

/**
* @dataProvider tableRebuildsWithAutoIncrement
*/
public function testTableRebuildPreservesAutoIncrement( string $temporary, string $rebuild, array $setup, string $expected_id ): void {
$this->assertQuery( "CREATE $temporary TABLE t (id BIGINT AUTO_INCREMENT PRIMARY KEY, name TEXT)" );
foreach ( $setup as $query ) {
$this->assertQuery( $query );
}
$rows = $this->assertQuery( 'SELECT * FROM t ORDER BY id' );

$result = $this->assertQuery( $rebuild );
if ( is_array( $result ) ) {
$this->assertSame( 'OK', $result[0]->Msg_text );
}
$this->assertEquals( $rows, $this->assertQuery( 'SELECT id, name FROM t ORDER BY id' ) );
if ( '' === $temporary ) {
$this->assertSame( $expected_id, $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" )[0]->Auto_increment );
$this->assertSame(
$expected_id,
$this->assertQuery( "SELECT `AUTO_INCREMENT` FROM information_schema.tables WHERE table_name = 't'" )[0]->AUTO_INCREMENT
);
}

$this->assertQuery( "INSERT INTO t (name) VALUES ('next')" );
$this->assertSame( $expected_id, $this->assertQuery( "SELECT id FROM t WHERE name = 'next'" )[0]->id );
}

public static function tableRebuildsWithAutoIncrement(): array {
$states = array(
'deleted highest ID' => array(
array( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')", 'DELETE FROM t WHERE id = 3' ),
'4',
),
'deleted all rows' => array(
array( "INSERT INTO t (name) VALUES ('a'), ('b'), ('c')", 'DELETE FROM t' ),
'4',
),
'empty with seed' => array( array( 'ALTER TABLE t AUTO_INCREMENT = 1000' ), '1000' ),
'empty without seed' => array( array(), '1' ),
'large sequence' => array(
array( "INSERT INTO t (id, name) VALUES (9007199254740993, 'a')", 'DELETE FROM t' ),
'9007199254740994',
),
'updated highest ID' => array(
array( "INSERT INTO t (name) VALUES ('a')", 'UPDATE t SET id = 100' ),
'101',
),
);
$cases = array();
foreach ( array( '', 'TEMPORARY' ) as $temporary ) {
foreach ( array( 'ALTER TABLE t ADD COLUMN note TEXT', 'ALTER TABLE t MODIFY COLUMN name VARCHAR(100)', 'OPTIMIZE TABLE t', 'REPAIR TABLE t' ) as $rebuild ) {
foreach ( $states as $state => list( $setup, $expected_id ) ) {
$cases[ "$temporary $rebuild: $state" ] = array( $temporary, $rebuild, $setup, $expected_id );
}
}
}
return $cases;
}

public function testTableRebuildPreservesCreateTableAutoIncrementOption(): void {
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT = 1000' );
$this->assertQuery( 'ALTER TABLE t ADD COLUMN note TEXT' );
$this->assertQuery( 'OPTIMIZE TABLE t' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('next')" );
$this->assertSame( '1000', $this->assertQuery( 'SELECT id FROM t' )[0]->id );
}

public function testTableRebuildAppliesExplicitAutoIncrementOption(): void {
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT = 1000' );
$this->assertQuery( 'ALTER TABLE t ADD COLUMN a INT, AUTO_INCREMENT = 500' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('a')" );
$this->assertSame( '500', $this->assertQuery( "SELECT id FROM t WHERE name = 'a'" )[0]->id );

$this->assertQuery( 'ALTER TABLE t ADD COLUMN b INT, AUTO_INCREMENT = 1' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('b')" );
$this->assertSame( '501', $this->assertQuery( "SELECT id FROM t WHERE name = 'b'" )[0]->id );

$this->assertQuery( 'ALTER TABLE t ADD COLUMN c INT, AUTO_INCREMENT = 2000' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('c')" );
$this->assertSame( '2000', $this->assertQuery( "SELECT id FROM t WHERE name = 'c'" )[0]->id );
}

public function testTableRebuildPreservesAutoIncrementWithQuotedNames(): void {
$this->assertQuery( "CREATE TABLE `a``b'c` (`i``d` INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT = 1000" );
$this->assertQuery( "ALTER TABLE `a``b'c` CHANGE COLUMN `i``d` `new``id` BIGINT AUTO_INCREMENT" );
$this->assertQuery( "INSERT INTO `a``b'c` (name) VALUES ('next')" );
$this->assertSame( '1000', $this->assertQuery( "SELECT `new``id` AS id FROM `a``b'c`" )[0]->id );
}

public function testTableRebuildPreservesAutoIncrementWithDifferentTableNameCase(): void {
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT = 1000' );
$this->assertQuery( 'ALTER TABLE T ADD COLUMN note TEXT' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('next')" );
$this->assertSame( '1000', $this->assertQuery( 'SELECT id FROM t' )[0]->id );
}

public function testTableRebuildKeepsTemporaryAndPersistentSequencesSeparate(): void {
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT = 1000' );
$this->assertQuery( 'CREATE TEMPORARY TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT) AUTO_INCREMENT = 500' );
$this->assertQuery( 'ALTER TABLE t ADD COLUMN note TEXT' );
$this->assertQuery( 'OPTIMIZE TABLE t' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('temporary')" );
$this->assertSame( '500', $this->assertQuery( 'SELECT id FROM t' )[0]->id );

$this->assertQuery( 'DROP TEMPORARY TABLE t' );
$this->assertQuery( 'ALTER TABLE t ADD COLUMN note TEXT' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('persistent')" );
$this->assertSame( '1000', $this->assertQuery( 'SELECT id FROM t' )[0]->id );
}

public function testTableRebuildWithoutAutoIncrement(): void {
$this->assertQuery( 'CREATE TABLE t (id INT PRIMARY KEY, name TEXT)' );
$this->assertQuery( "INSERT INTO t VALUES (1, 'a')" );
$this->assertQuery( 'ALTER TABLE t ADD COLUMN note TEXT' );
$this->assertQuery( 'OPTIMIZE TABLE t' );
$this->assertSame( 'a', $this->assertQuery( 'SELECT name FROM t' )[0]->name );
$this->assertFalse( $this->sqlite->query( "SELECT 1 FROM sqlite_master WHERE name = 'sqlite_sequence'" )->fetchColumn() );

$this->assertQuery( 'ALTER TABLE t MODIFY COLUMN id INT AUTO_INCREMENT' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('b')" );
$this->assertSame( '2', $this->assertQuery( "SELECT id FROM t WHERE name = 'b'" )[0]->id );

$this->assertQuery( 'ALTER TABLE t AUTO_INCREMENT = 1000' );
$this->assertQuery( 'ALTER TABLE t MODIFY COLUMN id INT NOT NULL' );
$this->assertFalse( $this->sqlite->query( "SELECT seq FROM sqlite_sequence WHERE name = 't'" )->fetchColumn() );
$this->assertSame( null, $this->assertQuery( "SHOW TABLE STATUS LIKE 't'" )[0]->Auto_increment );
}

public function testFailedTableRebuildPreservesAutoIncrement(): void {
$this->assertQuery( 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)' );
$this->assertQuery( "INSERT INTO t (name) VALUES ('a'), ('a'), ('b')" );
$this->assertQuery( 'DELETE FROM t WHERE id = 3' );
try {
$this->assertQuery( 'ALTER TABLE t ADD UNIQUE KEY unique_name (name)' );
$this->fail( 'Adding a unique key on duplicate values must fail.' );
} catch ( WP_MySQL_On_SQLite_Exception $e ) {
$this->assertSame( '23000', $e->getCode() );
}
$this->assertQuery( "INSERT INTO t (name) VALUES ('next')" );
$this->assertSame( '4', $this->assertQuery( "SELECT id FROM t WHERE name = 'next'" )[0]->id );
$this->assertSame( array( 't' ), $this->sqlite->query( 'SELECT name FROM sqlite_sequence' )->fetchAll( PDO::FETCH_COLUMN ) );
}

public function testTemporaryTableAutoIncrement(): void {
// Persistent and temporary tables must keep independent sequences.

Expand Down
18 changes: 18 additions & 0 deletions packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -6753,6 +6753,24 @@ public function testConfigurationNormalizesStoredTemporalZeroDefaults(): void {
$this->assertEquals( $expected_rows, $this->last_result );
}

public function testConfigurationPreservesAutoIncrementWhenNormalizingTemporalZeroDefaults(): void {
$this->assertQuery( 'CREATE TABLE _tmp_table (id INT AUTO_INCREMENT PRIMARY KEY, created_at DATETIME NOT NULL DEFAULT 0)' );
$this->assertQuery( 'CREATE TABLE _tmp_empty (id INT AUTO_INCREMENT PRIMARY KEY, created_at DATETIME NOT NULL DEFAULT 0)' );
$this->sqlite->exec( "UPDATE _wp_sqlite_mysql_information_schema_columns SET column_default = '0' WHERE table_name IN ('_tmp_table', '_tmp_empty') AND column_name = 'created_at'" );
$this->assertQuery( 'OPTIMIZE TABLE _tmp_table, _tmp_empty' );
$this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (1), (2), (3)' );
$this->assertQuery( 'DELETE FROM _tmp_table WHERE id = 3' );
$this->assertQuery( 'ALTER TABLE _tmp_empty AUTO_INCREMENT = 1000' );

$this->reconnectAfterDriverUpgrade();

$this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (NULL)' );
$this->assertSame( '4', $this->assertQuery( 'SELECT MAX(id) AS id FROM _tmp_table' )[0]->id );
$this->assertQuery( 'INSERT INTO _tmp_empty (id) VALUES (NULL)' );
$this->assertSame( '1000', $this->assertQuery( 'SELECT id FROM _tmp_empty' )[0]->id );
$this->assertSame( '0000-00-00 00:00:00', $this->assertQuery( 'SELECT created_at FROM _tmp_empty' )[0]->created_at );
}

public function testConfigurationNormalizesStoredTemporalZeroValuesWithoutRunningTriggers(): void {
$this->assertQuery(
'CREATE TABLE _tmp_table (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ public function testAlterTableAddColumn( bool $stringify_fetches ): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER, `a` INTEGER )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -934,6 +935,7 @@ public function testAlterTableAddColumnWithNotNull(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER, `a` INTEGER NOT NULL )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -965,6 +967,7 @@ public function testAlterTableAddColumnWithDefault(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER, `a` INTEGER DEFAULT \'0\' )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -996,6 +999,7 @@ public function testAlterTableAddColumnWithNotNullAndDefault(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER, `a` INTEGER NOT NULL DEFAULT \'0\' )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -1027,6 +1031,7 @@ public function testAlterTableAddMultipleColumns(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER, `a` INTEGER, `b` TEXT COLLATE NOCASE, `c` INTEGER )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -1064,6 +1069,7 @@ public function testAlterTableDropColumn(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -1096,6 +1102,7 @@ public function testAlterTableDropMultipleColumns(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `id` INTEGER )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`, `id`) SELECT `rowid`, `id` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -1134,6 +1141,7 @@ public function testAlterTableAddAndDropColumns(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `b` INTEGER )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`) SELECT `rowid` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down Expand Up @@ -1170,6 +1178,7 @@ public function testAlterTableDropAndAddSingleColumn(): void {
'PRAGMA foreign_keys = OFF',
'CREATE TABLE `<tmp-table>` ( `a` INTEGER )' . $this->strict_suffix,
'INSERT INTO `<tmp-table>` (`rowid`) SELECT `rowid` FROM `t`',
"SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'",
'DROP TABLE `t`',
'ALTER TABLE `<tmp-table>` RENAME TO `t`',
'PRAGMA foreign_key_check',
Expand Down
Loading