From d9cb00770cc88e7ce5262130fd4e473b3a811446 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 6 Jan 2026 16:30:07 +0100 Subject: [PATCH 1/3] Simplify SQLite table constraint generation --- .../class-wp-pdo-mysql-on-sqlite.php | 57 +++++++------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php index 285fb7f0..e1b43bd4 100644 --- a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php +++ b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php @@ -5605,27 +5605,29 @@ private function get_sqlite_create_table_statement( ksort( $constraint ); $info = $constraint[1]; + $column_list = array_map( + function ( $column ) { + $fragment = $this->quote_sqlite_identifier( $column['COLUMN_NAME'] ); + if ( 'D' === $column['COLLATION'] ) { + $fragment .= ' DESC'; + } + return $fragment; + }, + $constraint + ); + if ( 'PRIMARY' === $info['INDEX_NAME'] ) { if ( $has_autoincrement ) { - if ( count( $constraint ) > 1 ) { - throw $this->new_driver_exception( - 'Cannot combine AUTOINCREMENT and multiple primary keys in SQLite' - ); + if ( $has_autoincrement ) { + if ( count( $constraint ) > 1 ) { + throw $this->new_driver_exception( + 'Cannot combine AUTOINCREMENT and multiple primary keys in SQLite' + ); + } } continue; } - $query = ' PRIMARY KEY ('; - $query .= implode( - ', ', - array_map( - function ( $column ) { - return $this->quote_sqlite_identifier( $column['COLUMN_NAME'] ); - }, - $constraint - ) - ); - $query .= ')'; - $rows[] = $query; + $rows[] = sprintf( ' PRIMARY KEY (%s)', implode( ', ', $column_list ) ); } else { $is_unique = '0' === $info['NON_UNIQUE']; @@ -5633,28 +5635,13 @@ function ( $column ) { // This is to avoid conflicting index names in SQLite. $sqlite_index_name = $this->get_sqlite_index_name( $table_name, $info['INDEX_NAME'] ); - $query = sprintf( - 'CREATE %sINDEX %s ON %s (', + $create_index_queries[] = sprintf( + 'CREATE %sINDEX %s ON %s (%s)', $is_unique ? 'UNIQUE ' : '', $this->quote_sqlite_identifier( $sqlite_index_name ), - $this->quote_sqlite_identifier( $table_name ) - ); - $query .= implode( - ', ', - array_map( - function ( $column ) { - $fragment = $this->quote_sqlite_identifier( $column['COLUMN_NAME'] ); - if ( 'D' === $column['COLLATION'] ) { - $fragment .= ' DESC'; - } - return $fragment; - }, - $constraint - ) + $this->quote_sqlite_identifier( $table_name ), + implode( ', ', $column_list ) ); - $query .= ')'; - - $create_index_queries[] = $query; } } From c6074c6e1434cfba6d522d8b816fad3e6d68a83e Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 6 Jan 2026 16:35:34 +0100 Subject: [PATCH 2/3] Allow AUTOINCREMENT with compound PRIMARY KEY --- tests/WP_SQLite_Driver_Tests.php | 59 +++++++++++++++++-- .../class-wp-pdo-mysql-on-sqlite.php | 32 ++++++++-- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index f84fe906..b0ce3266 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -4125,11 +4125,62 @@ public function testTimestampColumnNamedTimestamp() { ); } - public function testCompoundPrimaryKeyAndAutoincrementNotSupported(): void { - $this->expectException( WP_SQLite_Driver_Exception::class ); - $this->expectExceptionMessage( 'Cannot combine AUTOINCREMENT and multiple primary keys in SQLite' ); + public function testCompoundPrimaryKeyWithAutoincrement(): void { $this->assertQuery( - 'CREATE TABLE t1 (id1 INT AUTO_INCREMENT, id2 INT, PRIMARY KEY(id1, id2))' + 'CREATE TABLE t1 (id INT AUTO_INCREMENT, name VARCHAR(32), PRIMARY KEY(id, name))' + ); + + // Ensure auto-increment is working. + $this->assertQuery( "INSERT INTO t1 (name) VALUES ('A'), ('B'), ('C')" ); + $results = $this->assertQuery( 'SELECT * FROM t1' ); + $this->assertEquals( + array( + (object) array( + 'id' => 1, + 'name' => 'A', + ), + (object) array( + 'id' => 2, + 'name' => 'B', + ), + (object) array( + 'id' => 3, + 'name' => 'C', + ), + ), + $results + ); + + // Verify the table schema. + $results = $this->assertQuery( 'SHOW CREATE TABLE t1' ); + $this->assertEquals( + implode( + "\n", + array( + 'CREATE TABLE `t1` (', + ' `id` int NOT NULL AUTO_INCREMENT,', + ' `name` varchar(32) NOT NULL,', + ' PRIMARY KEY (`id`, `name`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci', + ) + ), + $results[0]->{'Create Table'} + ); + + // Ensure an SQLite index was created for the compound key columns. + $result = $this->engine + ->execute_sqlite_query( "SELECT * FROM pragma_index_list('t1')" ) + ->fetchAll( PDO::FETCH_ASSOC ); + $this->assertCount( 1, $result ); + $this->assertEquals( + array( + 'seq' => '0', + 'name' => '_wp_sqlite_t1__primary', + 'unique' => '1', + 'origin' => 'c', + 'partial' => '0', + ), + $result[0] ); } diff --git a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php index e1b43bd4..03e98e98 100644 --- a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php +++ b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php @@ -5618,13 +5618,33 @@ function ( $column ) { if ( 'PRIMARY' === $info['INDEX_NAME'] ) { if ( $has_autoincrement ) { - if ( $has_autoincrement ) { - if ( count( $constraint ) > 1 ) { - throw $this->new_driver_exception( - 'Cannot combine AUTOINCREMENT and multiple primary keys in SQLite' - ); - } + /* + * In MySQL, a compound PRIMARY KEY can have an AUTOINCREMENT + * column, when it is the first column in the key. + * + * SQLite doesn't support this, but we can emulate it as follows: + * 1. Keep only the first column as a PRIMARY KEY. + * Since this is the column that also has AUTOINCREMENT, + * it reasonable to assume that its values are unique. + * 2. Create a UNIQUE key for all the PRIMARY KEY columns. + * This is to preserve the index of the compound key. + */ + if ( count( $constraint ) > 1 ) { + $sqlite_index_name = $this->get_sqlite_index_name( $table_name, 'primary' ); + $create_index_queries[] = sprintf( + 'CREATE UNIQUE INDEX %s ON %s (%s)', + self::RESERVED_PREFIX . $sqlite_index_name, + $this->quote_sqlite_identifier( $table_name ), + implode( ', ', $column_list ) + ); } + + /* + * The PRIMARY KEY was already generated with AUTOINCREMENT, + * as required by SQLite column constraint syntax. + * + * @see https://www.sqlite.org/syntax/column-constraint.html + */ continue; } $rows[] = sprintf( ' PRIMARY KEY (%s)', implode( ', ', $column_list ) ); From f4c275901c2d5523a76a37c2d9a8740e48703974 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 8 Jan 2026 09:50:59 +0100 Subject: [PATCH 3/3] Improve auto increment spelling in docs --- wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php index 03e98e98..26c9ce5e 100644 --- a/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php +++ b/wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php @@ -5619,12 +5619,12 @@ function ( $column ) { if ( 'PRIMARY' === $info['INDEX_NAME'] ) { if ( $has_autoincrement ) { /* - * In MySQL, a compound PRIMARY KEY can have an AUTOINCREMENT + * In MySQL, a compound PRIMARY KEY can have an AUTO_INCREMENT * column, when it is the first column in the key. * * SQLite doesn't support this, but we can emulate it as follows: * 1. Keep only the first column as a PRIMARY KEY. - * Since this is the column that also has AUTOINCREMENT, + * Since this is the column that also has AUTO_INCREMENT, * it reasonable to assume that its values are unique. * 2. Create a UNIQUE key for all the PRIMARY KEY columns. * This is to preserve the index of the compound key.