Skip to content

Commit 6df418e

Browse files
committed
Fix INSERT ... SELECT ... edge case in non-strict mode
1 parent 88b8679 commit 6df418e

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

tests/WP_SQLite_Driver_Tests.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10526,6 +10526,42 @@ public function testCastValuesOnInsertInNonStrictMode(): void {
1052610526
$this->assertQuery( 'DROP TABLE t' );
1052710527
}
1052810528

10529+
public function testCastNotNullValuesOnInsert(): void {
10530+
$this->assertQuery( 'CREATE TABLE t (value INT NOT NULL)' );
10531+
10532+
// Strict mode:
10533+
$this->assertQueryError( 'INSERT INTO t VALUES (NULL)', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10534+
$this->assertQueryError( 'INSERT INTO t SET value = NULL', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10535+
$this->assertQueryError( 'INSERT INTO t SELECT NULL', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10536+
$this->assertQueryError( 'INSERT INTO t VALUES ((SELECT NULL))', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10537+
10538+
// Non-strict mode:
10539+
$this->assertQuery( "SET SESSION sql_mode = ''" );
10540+
$this->assertQueryError( 'INSERT INTO t VALUES (NULL)', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10541+
$this->assertQueryError( 'INSERT INTO t SET value = NULL', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10542+
$this->assertQuery( 'INSERT INTO t SELECT NULL' );
10543+
$this->assertSame( '0', $this->assertQuery( 'SELECT * FROM t' )[0]->value );
10544+
$this->assertQueryError( 'INSERT INTO t VALUES ((SELECT NULL))', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10545+
}
10546+
10547+
public function testCastNotNullValuesOnUpdate(): void {
10548+
$this->assertQuery( 'CREATE TABLE t (value INT NOT NULL)' );
10549+
$this->assertQuery( 'INSERT INTO t VALUES (1)' );
10550+
10551+
// Strict mode:
10552+
$this->assertQueryError( 'UPDATE t SET value = NULL', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10553+
$this->assertQueryError( 'UPDATE t SET value = (SELECT NULL)', 'SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: t.value' );
10554+
10555+
// Non-strict mode:
10556+
$this->assertQuery( "SET SESSION sql_mode = ''" );
10557+
$this->assertQuery( 'UPDATE t SET value = NULL' );
10558+
$this->assertSame( '0', $this->assertQuery( 'SELECT * FROM t' )[0]->value );
10559+
10560+
$this->assertQuery( 'UPDATE t SET value = (SELECT NULL)' );
10561+
$this->assertSame( '0', $this->assertQuery( 'SELECT * FROM t' )[0]->value );
10562+
$this->assertQuery( 'DROP TABLE t' );
10563+
}
10564+
1052910565
public function testCastValuesOnDuplicateKeyUpdate(): void {
1053010566
$this->assertQuery( 'CREATE TABLE t (value TEXT UNIQUE)' );
1053110567
$this->assertQuery( "INSERT INTO t VALUES ('test')" );

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4580,7 +4580,23 @@ function ( $column ) use ( $is_strict_mode, $insert_map ) {
45804580
// When a column value is included, we need to apply type casting.
45814581
$position = array_search( $column['COLUMN_NAME'], $insert_list, true );
45824582
$identifier = $this->quote_sqlite_identifier( $select_list[ $position ] );
4583-
$fragment .= $this->cast_value_for_saving( $column['DATA_TYPE'], $identifier );
4583+
$value = $this->cast_value_for_saving( $column['DATA_TYPE'], $identifier );
4584+
4585+
/*
4586+
* In MySQL non-STRICT mode, when inserting from a SELECT query:
4587+
*
4588+
* When a column is declared as NOT NULL, inserting a NULL value
4589+
* saves an IMPLICIT DEFAULT value instead. This behavior only
4590+
* applies to the INSERT ... SELECT syntax (not VALUES or SET).
4591+
*/
4592+
$is_insert_from_select = 'insertQueryExpression' === $node->rule_name;
4593+
if ( ! $is_strict_mode && $is_insert_from_select && 'NO' === $column['IS_NULLABLE'] ) {
4594+
$implicit_default = self::DATA_TYPE_IMPLICIT_DEFAULT_MAP[ $column['DATA_TYPE'] ] ?? null;
4595+
if ( null !== $implicit_default ) {
4596+
$value = sprintf( 'COALESCE(%s, %s)', $value, $this->connection->quote( $implicit_default ) );
4597+
}
4598+
}
4599+
$fragment .= $value;
45844600
}
45854601
}
45864602

0 commit comments

Comments
 (0)