@@ -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