Skip to content

Commit 2d8d399

Browse files
authored
Allow AUTOINCREMENT with compound PRIMARY KEY (#301)
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. 3. Create a `UNIQUE` key for all the `PRIMARY KEY` columns. This is to preserve the index of the compound key. The actual fix is best viewed in [the second commit](c6074c6).
1 parent 22203ee commit 2d8d399

2 files changed

Lines changed: 95 additions & 37 deletions

File tree

tests/WP_SQLite_Driver_Tests.php

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4125,11 +4125,62 @@ public function testTimestampColumnNamedTimestamp() {
41254125
);
41264126
}
41274127

4128-
public function testCompoundPrimaryKeyAndAutoincrementNotSupported(): void {
4129-
$this->expectException( WP_SQLite_Driver_Exception::class );
4130-
$this->expectExceptionMessage( 'Cannot combine AUTOINCREMENT and multiple primary keys in SQLite' );
4128+
public function testCompoundPrimaryKeyWithAutoincrement(): void {
41314129
$this->assertQuery(
4132-
'CREATE TABLE t1 (id1 INT AUTO_INCREMENT, id2 INT, PRIMARY KEY(id1, id2))'
4130+
'CREATE TABLE t1 (id INT AUTO_INCREMENT, name VARCHAR(32), PRIMARY KEY(id, name))'
4131+
);
4132+
4133+
// Ensure auto-increment is working.
4134+
$this->assertQuery( "INSERT INTO t1 (name) VALUES ('A'), ('B'), ('C')" );
4135+
$results = $this->assertQuery( 'SELECT * FROM t1' );
4136+
$this->assertEquals(
4137+
array(
4138+
(object) array(
4139+
'id' => 1,
4140+
'name' => 'A',
4141+
),
4142+
(object) array(
4143+
'id' => 2,
4144+
'name' => 'B',
4145+
),
4146+
(object) array(
4147+
'id' => 3,
4148+
'name' => 'C',
4149+
),
4150+
),
4151+
$results
4152+
);
4153+
4154+
// Verify the table schema.
4155+
$results = $this->assertQuery( 'SHOW CREATE TABLE t1' );
4156+
$this->assertEquals(
4157+
implode(
4158+
"\n",
4159+
array(
4160+
'CREATE TABLE `t1` (',
4161+
' `id` int NOT NULL AUTO_INCREMENT,',
4162+
' `name` varchar(32) NOT NULL,',
4163+
' PRIMARY KEY (`id`, `name`)',
4164+
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
4165+
)
4166+
),
4167+
$results[0]->{'Create Table'}
4168+
);
4169+
4170+
// Ensure an SQLite index was created for the compound key columns.
4171+
$result = $this->engine
4172+
->execute_sqlite_query( "SELECT * FROM pragma_index_list('t1')" )
4173+
->fetchAll( PDO::FETCH_ASSOC );
4174+
$this->assertCount( 1, $result );
4175+
$this->assertEquals(
4176+
array(
4177+
'seq' => '0',
4178+
'name' => '_wp_sqlite_t1__primary',
4179+
'unique' => '1',
4180+
'origin' => 'c',
4181+
'partial' => '0',
4182+
),
4183+
$result[0]
41334184
);
41344185
}
41354186

wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5605,56 +5605,63 @@ private function get_sqlite_create_table_statement(
56055605
ksort( $constraint );
56065606
$info = $constraint[1];
56075607

5608+
$column_list = array_map(
5609+
function ( $column ) {
5610+
$fragment = $this->quote_sqlite_identifier( $column['COLUMN_NAME'] );
5611+
if ( 'D' === $column['COLLATION'] ) {
5612+
$fragment .= ' DESC';
5613+
}
5614+
return $fragment;
5615+
},
5616+
$constraint
5617+
);
5618+
56085619
if ( 'PRIMARY' === $info['INDEX_NAME'] ) {
56095620
if ( $has_autoincrement ) {
5621+
/*
5622+
* In MySQL, a compound PRIMARY KEY can have an AUTO_INCREMENT
5623+
* column, when it is the first column in the key.
5624+
*
5625+
* SQLite doesn't support this, but we can emulate it as follows:
5626+
* 1. Keep only the first column as a PRIMARY KEY.
5627+
* Since this is the column that also has AUTO_INCREMENT,
5628+
* it reasonable to assume that its values are unique.
5629+
* 2. Create a UNIQUE key for all the PRIMARY KEY columns.
5630+
* This is to preserve the index of the compound key.
5631+
*/
56105632
if ( count( $constraint ) > 1 ) {
5611-
throw $this->new_driver_exception(
5612-
'Cannot combine AUTOINCREMENT and multiple primary keys in SQLite'
5633+
$sqlite_index_name = $this->get_sqlite_index_name( $table_name, 'primary' );
5634+
$create_index_queries[] = sprintf(
5635+
'CREATE UNIQUE INDEX %s ON %s (%s)',
5636+
self::RESERVED_PREFIX . $sqlite_index_name,
5637+
$this->quote_sqlite_identifier( $table_name ),
5638+
implode( ', ', $column_list )
56135639
);
56145640
}
5641+
5642+
/*
5643+
* The PRIMARY KEY was already generated with AUTOINCREMENT,
5644+
* as required by SQLite column constraint syntax.
5645+
*
5646+
* @see https://www.sqlite.org/syntax/column-constraint.html
5647+
*/
56155648
continue;
56165649
}
5617-
$query = ' PRIMARY KEY (';
5618-
$query .= implode(
5619-
', ',
5620-
array_map(
5621-
function ( $column ) {
5622-
return $this->quote_sqlite_identifier( $column['COLUMN_NAME'] );
5623-
},
5624-
$constraint
5625-
)
5626-
);
5627-
$query .= ')';
5628-
$rows[] = $query;
5650+
$rows[] = sprintf( ' PRIMARY KEY (%s)', implode( ', ', $column_list ) );
56295651
} else {
56305652
$is_unique = '0' === $info['NON_UNIQUE'];
56315653

56325654
// Prefix the original index name with the table name.
56335655
// This is to avoid conflicting index names in SQLite.
56345656
$sqlite_index_name = $this->get_sqlite_index_name( $table_name, $info['INDEX_NAME'] );
56355657

5636-
$query = sprintf(
5637-
'CREATE %sINDEX %s ON %s (',
5658+
$create_index_queries[] = sprintf(
5659+
'CREATE %sINDEX %s ON %s (%s)',
56385660
$is_unique ? 'UNIQUE ' : '',
56395661
$this->quote_sqlite_identifier( $sqlite_index_name ),
5640-
$this->quote_sqlite_identifier( $table_name )
5662+
$this->quote_sqlite_identifier( $table_name ),
5663+
implode( ', ', $column_list )
56415664
);
5642-
$query .= implode(
5643-
', ',
5644-
array_map(
5645-
function ( $column ) {
5646-
$fragment = $this->quote_sqlite_identifier( $column['COLUMN_NAME'] );
5647-
if ( 'D' === $column['COLLATION'] ) {
5648-
$fragment .= ' DESC';
5649-
}
5650-
return $fragment;
5651-
},
5652-
$constraint
5653-
)
5654-
);
5655-
$query .= ')';
5656-
5657-
$create_index_queries[] = $query;
56585665
}
56595666
}
56605667

0 commit comments

Comments
 (0)