Skip to content

Commit 88dc27d

Browse files
committed
wip fix
1 parent 22203ee commit 88dc27d

2 files changed

Lines changed: 84 additions & 4 deletions

File tree

tests/WP_SQLite_Driver_Tests.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11180,4 +11180,11 @@ public function testVersionFunction(): void {
1118011180
$result = $this->engine->query( 'SELECT VERSION()' );
1118111181
$this->assertSame( '8.0.38', $result[0]->{'VERSION()'} );
1118211182
}
11183+
11184+
public function testAsdf(): void {
11185+
$this->assertQuery( 'CREATE TABLE t (key1 INT, key2 INT)' );
11186+
$this->assertQuery( 'CREATE UNIQUE INDEX idx_key1_key2 ON t (key1, key2)' );
11187+
$this->assertQuery( 'INSERT INTO t (key1, key2) VALUES (1, 2) ON DUPLICATE KEY UPDATE key1 = 1, key2 = 2' );
11188+
$this->assertQuery( 'INSERT INTO t (key1, key2) VALUES (1, 2) ON DUPLICATE KEY UPDATE key1 = 1, key2 = 2' );
11189+
}
1118311190
}

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

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,7 +1646,8 @@ private function execute_select_statement( WP_Parser_Node $node ): void {
16461646
* @throws WP_SQLite_Driver_Exception When the query execution fails.
16471647
*/
16481648
private function execute_insert_or_replace_statement( WP_Parser_Node $node ): void {
1649-
$parts = array();
1649+
$parts = array();
1650+
$on_conflict_update_list = null;
16501651
foreach ( $node->get_children() as $child ) {
16511652
$is_token = $child instanceof WP_MySQL_Token;
16521653
$is_node = $child instanceof WP_Parser_Node;
@@ -1678,14 +1679,86 @@ private function execute_insert_or_replace_statement( WP_Parser_Node $node ): vo
16781679
$table_name = $this->unquote_sqlite_identifier( $this->translate( $table_ref ) );
16791680
$parts[] = $this->translate_insert_or_replace_body( $table_name, $child );
16801681
} elseif ( $is_node && 'insertUpdateList' === $child->rule_name ) {
1681-
// Translate "ON DUPLICATE KEY UPDATE" to "ON CONFLICT DO UPDATE SET".
1682-
$parts[] = 'ON CONFLICT DO UPDATE SET ';
1683-
$parts[] = $this->translate_update_list( $table_name, $child );
1682+
/*
1683+
* Translate "ON DUPLICATE KEY UPDATE" to "ON CONFLICT DO UPDATE SET".
1684+
*
1685+
* For SQLite versions older than 3.35.0, we need to handle the
1686+
* ON CONFLICT clause differently, and at this stage, we only
1687+
* save the translated update list to a variable.
1688+
*
1689+
* See bellow at "Handle ON CONFLICT clause for SQLite < 3.35.0".
1690+
*/
1691+
$sqlite_version = $this->get_sqlite_version();
1692+
if ( version_compare( $sqlite_version, '3.35.0', '<' ) ) {
1693+
$on_conflict_update_list = $this->translate_update_list( $table_name, $child );
1694+
} else {
1695+
$parts[] = 'ON CONFLICT DO UPDATE SET ';
1696+
$parts[] = $this->translate_update_list( $table_name, $child );
1697+
}
16841698
} else {
16851699
$parts[] = $this->translate( $child );
16861700
}
16871701
}
1702+
16881703
$query = implode( ' ', $parts );
1704+
1705+
/*
1706+
* Handle ON CONFLICT clause for SQLite < 3.35.0.
1707+
*
1708+
* If and "$on_conflict_update_list" was saved, we are on SQLite version
1709+
* older than 3.35.0 and an ON CONFLICT clause was used in the query.
1710+
*
1711+
* SQLite supports a generic ON CONFLICT clause without an explicit column
1712+
* list only from version 3.35.0.
1713+
*
1714+
* For older versions, we need to work around this limitation:
1715+
* 1. Save the ON CONFLICT update list to a variable.
1716+
* 2. Execute the query without the ON CONFLICT clause.
1717+
* 3. If a constraint violation error occurs, parse the names of the
1718+
* columns that caused the violation from the error message.
1719+
* 4. Execute the query again, appending the ON CONFLICT clause with
1720+
* the column names parsed from the error message.
1721+
*/
1722+
if ( null !== $on_conflict_update_list ) {
1723+
try {
1724+
$this->execute_sqlite_query( $query );
1725+
$this->set_result_from_affected_rows();
1726+
} catch ( PDOException $e ) {
1727+
$unique_key_violation_prefix = 'SQLSTATE[23000]: Integrity constraint violation: 19 UNIQUE constraint failed: ';
1728+
if ( '23000' === $e->getCode() && str_contains( $e->getMessage(), $unique_key_violation_prefix ) ) {
1729+
/*
1730+
* Parse column names from the constraint violation error.
1731+
*
1732+
* The error message is in the following format:
1733+
* <prefix>: <table>.<col1>, <table>.<col2>, ...
1734+
*
1735+
* The table and column names in the message are not quoted.
1736+
* To be on the safe side, we first strip the error message
1737+
* prefix and the "<table>." part for the first column, and
1738+
* then split the rest of the list by ", <table>." sequence.
1739+
*/
1740+
$column_list = substr( $e->getMessage(), strlen( $unique_key_violation_prefix ) + strlen( $table_name ) + 1 );
1741+
$column_names = explode( ", $table_name.", $column_list );
1742+
$quoted_column_names = array_map(
1743+
function ( $column ) {
1744+
return $this->quote_sqlite_identifier( $column );
1745+
},
1746+
$column_names
1747+
);
1748+
$this->execute_sqlite_query(
1749+
$query . sprintf(
1750+
' ON CONFLICT(%s) DO UPDATE SET %s',
1751+
implode( ', ', $quoted_column_names ),
1752+
$on_conflict_update_list
1753+
)
1754+
);
1755+
} else {
1756+
throw $e;
1757+
}
1758+
}
1759+
return;
1760+
}
1761+
16891762
$this->execute_sqlite_query( $query );
16901763
$this->set_result_from_affected_rows();
16911764
}

0 commit comments

Comments
 (0)