Skip to content

Commit b0da0a5

Browse files
committed
Apply type casting and implicit defaults to ON DUPLICATE KEY UPDATE clause
1 parent d7c3309 commit b0da0a5

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

tests/WP_SQLite_Driver_Tests.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10522,6 +10522,25 @@ public function testCastValuesOnInsertInNonStrictMode(): void {
1052210522
$this->assertQuery( 'DROP TABLE t' );
1052310523
}
1052410524

10525+
public function testCastValuesOnDuplicateKeyUpdate(): void {
10526+
$this->assertQuery( 'CREATE TABLE t (value TEXT UNIQUE)' );
10527+
$this->assertQuery( "INSERT INTO t VALUES ('test')" );
10528+
10529+
// Ensure that type casting is applied to ON DUPLICATE KEY UPDATE clause.
10530+
$this->assertQuery( "INSERT INTO t VALUES ('test') ON DUPLICATE KEY UPDATE value = 0x61" );
10531+
$this->assertSame( 'a', $this->assertQuery( 'SELECT * FROM t' )[0]->value );
10532+
}
10533+
10534+
public function testCastValuesOnDuplicateKeyUpdateInNonStrictMode(): void {
10535+
$this->assertQuery( "SET SESSION sql_mode = ''" );
10536+
$this->assertQuery( 'CREATE TABLE t (value INT UNIQUE)' );
10537+
$this->assertQuery( 'INSERT INTO t VALUES (123)' );
10538+
10539+
// Ensure that type casting is applied to ON DUPLICATE KEY UPDATE clause.
10540+
$this->assertQuery( "INSERT INTO t VALUES (123) ON DUPLICATE KEY UPDATE value = 'test'" );
10541+
$this->assertSame( '0', $this->assertQuery( 'SELECT * FROM t' )[0]->value );
10542+
}
10543+
1052510544
public function testCastValuesOnUpdate(): void {
1052610545
// INTEGER
1052710546
$this->assertQuery( 'CREATE TABLE t (value INT)' );

wp-includes/sqlite-ast/class-wp-sqlite-driver.php

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,10 @@ private function execute_insert_or_replace_statement( WP_Parser_Node $node ): vo
15311531
$table_ref = $node->get_first_child_node( 'tableRef' );
15321532
$table_name = $this->unquote_sqlite_identifier( $this->translate( $table_ref ) );
15331533
$parts[] = $this->translate_insert_or_replace_body( $table_name, $child );
1534+
} elseif ( $is_node && 'insertUpdateList' === $child->rule_name ) {
1535+
// Translate "ON DUPLICATE KEY UPDATE" to "ON CONFLICT DO UPDATE SET".
1536+
$parts[] = 'ON CONFLICT DO UPDATE SET ';
1537+
$parts[] = $this->translate_update_list( $table_name, $child );
15341538
} else {
15351539
$parts[] = $this->translate( $child );
15361540
}
@@ -1737,7 +1741,7 @@ private function execute_update_statement( WP_Parser_Node $node ): void {
17371741
}
17381742

17391743
// Translate UPDATE list, applying relevant type casting and IMPLICIT DEFAULT values.
1740-
$update_list = $this->translate_update_list( $update_target_table, $update_list_node );
1744+
$update_list = $this->translate_update_list( $update_target_table, $node );
17411745

17421746
// Translate WHERE, ORDER BY, and LIMIT clauses.
17431747
if ( $where_subquery ) {
@@ -3241,12 +3245,6 @@ private function translate( $node ): ?string {
32413245
return null;
32423246
}
32433247
return $this->translate_sequence( $node->get_children() );
3244-
case 'insertUpdateList':
3245-
// Translate "ON DUPLICATE KEY UPDATE" to "ON CONFLICT DO UPDATE SET".
3246-
return sprintf(
3247-
'ON CONFLICT DO UPDATE SET %s',
3248-
$this->translate( $node->get_first_child_node( 'updateList' ) )
3249-
);
32503248
case 'simpleExpr':
32513249
return $this->translate_simple_expr( $node );
32523250
case 'predicateOperations':
@@ -4649,11 +4647,13 @@ function ( $column ) use ( $is_strict_mode, $insert_map ) {
46494647
* For more information about IMPLICIT DEFAULT values in MySQL, see:
46504648
* https://dev.mysql.com/doc/refman/8.4/en/data-type-defaults.html#data-type-defaults-implicit
46514649
*
4652-
* @param string $table_name The name of the target table.
4653-
* @param WP_Parser_Node $node The "updateList" AST node.
4654-
* @return string The translated UPDATE list.
4650+
* @param string $table_name The name of the target table.
4651+
* @param WP_Parser_Node $parent_node The "updateList" AST node parent node.
4652+
* @return string The translated UPDATE list.
46554653
*/
4656-
private function translate_update_list( string $table_name, WP_Parser_Node $node ): string {
4654+
private function translate_update_list( string $table_name, WP_Parser_Node $parent_node ): string {
4655+
$node = $parent_node->get_first_child_node( 'updateList' );
4656+
46574657
// This method is always used with the main database.
46584658
$database = $this->get_saved_db_name( $this->main_db_name );
46594659

@@ -4724,11 +4724,17 @@ private function translate_update_list( string $table_name, WP_Parser_Node $node
47244724
// Apply type casting.
47254725
$value = $this->cast_value_for_saving( $data_type, $value );
47264726

4727-
// In MySQL non-STRICT mode, when a column is declared as NOT NULL,
4728-
// updating to a NULL value saves an IMPLICIT DEFAULT value instead.
4729-
$implicit_default = self::DATA_TYPE_IMPLICIT_DEFAULT_MAP[ $data_type ] ?? null;
4730-
if ( ! $is_strict_mode && ! $is_nullable && null !== $implicit_default ) {
4731-
$value = sprintf( 'COALESCE(%s, %s)', $value, $this->connection->quote( $implicit_default ) );
4727+
/*
4728+
* In MySQL non-STRICT mode, when a column is declared as NOT NULL,
4729+
* updating to a NULL value saves an IMPLICIT DEFAULT value instead.
4730+
* This behavior does not apply to ON DUPLICATE KEY UPDATE clauses.
4731+
*/
4732+
$is_on_duplicate_key_update = 'insertUpdateList' === $parent_node->rule_name;
4733+
if ( ! $is_strict_mode && ! $is_nullable && ! $is_on_duplicate_key_update ) {
4734+
$implicit_default = self::DATA_TYPE_IMPLICIT_DEFAULT_MAP[ $data_type ] ?? null;
4735+
if ( null !== $implicit_default ) {
4736+
$value = sprintf( 'COALESCE(%s, %s)', $value, $this->connection->quote( $implicit_default ) );
4737+
}
47324738
}
47334739

47344740
// Compose the UPDATE list item.

0 commit comments

Comments
 (0)