Skip to content
This repository was archived by the owner on Jun 2, 2025. It is now read-only.

Information schema cleanup and improvements #49

Merged
merged 9 commits into from
May 26, 2025
354 changes: 354 additions & 0 deletions tests/WP_SQLite_Driver_Metadata_Tests.php
Original file line number Diff line number Diff line change
@@ -458,4 +458,358 @@ public function testBogusQuery() {
'SELECT 1, BOGUS(1) FROM bogus;'
);
}

public function testInformationSchemaTableConstraintsCreateTable(): void {
$this->assertQuery(
'CREATE TABLE t (
a INT PRIMARY KEY,
b INT UNIQUE,
c INT,
d INT,
CONSTRAINT unique_b_c UNIQUE (b, c),
INDEX inex_c_d (c, d)
)'
);

$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'b',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'unique_b_c',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);
}

public function testInformationSchemaTableConstraintsDropTable(): void {
$this->assertQuery( 'CREATE TABLE t (a INT PRIMARY KEY, b INT UNIQUE)' );
$this->assertQuery( 'DROP TABLE t' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals( array(), $result );
}

public function testInformationSchemaTableConstraintsAddColumn(): void {
$this->assertQuery(
'CREATE TABLE t ( a INT )'
);

// Add a column with a primary key constraint.
$this->assertQuery( 'ALTER TABLE t ADD COLUMN b INT PRIMARY KEY' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
),
$result
);

$this->assertQuery( 'ALTER TABLE t ADD COLUMN c INT UNIQUE' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'c',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);
}

public function testInformationSchemaTableConstraintsChangeColumn(): void {
$this->assertQuery( 'CREATE TABLE t (a INT, b INT)' );

// Add a primary key constraint.
$this->assertQuery( 'ALTER TABLE t CHANGE COLUMN a a INT PRIMARY KEY' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
),
$result
);

// Add a unique constraint.
$this->assertQuery( 'ALTER TABLE t MODIFY COLUMN b INT UNIQUE' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'b',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);
}

public function testInformationSchemaTableConstraintsDropColumn(): void {
$this->assertQuery(
'CREATE TABLE t (
id INT,
a INT,
b INT,
c INT,
CONSTRAINT c_primary PRIMARY KEY (a, b),
CONSTRAINT c_unique UNIQUE (b, c),
INDEX id (a, b, c)
)'
);

$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'c_unique',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);

// Drop column "b" - all constraints will remain.
$this->assertQuery( 'ALTER TABLE t DROP COLUMN b' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'c_unique',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);

// Drop column "c" - the unique constraint will be removed.
$this->assertQuery( 'ALTER TABLE t DROP COLUMN c' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
),
$result
);

// Drop column "a" - the primary key will be removed.
$this->assertQuery( 'ALTER TABLE t DROP COLUMN a' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals( array(), $result );
}

public function testInformationSchemaTableConstraintsAddConstraint(): void {
$this->assertQuery( 'CREATE TABLE t (a INT, b INT)' );

// Add a primary key constraint.
$this->assertQuery( 'ALTER TABLE t ADD CONSTRAINT primary_key_a PRIMARY KEY (a)' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
),
$result
);

// Add a unique constraint.
$this->assertQuery( 'ALTER TABLE t ADD CONSTRAINT unique_b UNIQUE (b)' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'unique_b',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);

// Add a unique constraint with a composite key.
$this->assertQuery( 'ALTER TABLE t ADD CONSTRAINT unique_a_b UNIQUE (a, b)' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'PRIMARY',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'PRIMARY KEY',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'unique_b',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'unique_a_b',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);
}

public function testInformationSchemaTableConstraintsDropIndex(): void {
$this->assertQuery( 'CREATE TABLE t (a INT PRIMARY KEY, b INT UNIQUE)' );

// Drop the primary key index.
$this->assertQuery( 'ALTER TABLE t DROP INDEX `PRIMARY`' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals(
array(
(object) array(
'CONSTRAINT_CATALOG' => 'def',
'CONSTRAINT_SCHEMA' => 'wp',
'CONSTRAINT_NAME' => 'b',
'TABLE_SCHEMA' => 'wp',
'TABLE_NAME' => 't',
'CONSTRAINT_TYPE' => 'UNIQUE',
'ENFORCED' => 'YES',
),
),
$result
);

// Drop the unique index.
$this->assertQuery( 'ALTER TABLE t DROP INDEX b' );
$result = $this->assertQuery( "SELECT * FROM information_schema.table_constraints WHERE table_name = 't'" );
$this->assertEquals( array(), $result );
}
}
183 changes: 183 additions & 0 deletions tests/WP_SQLite_Driver_Tests.php
Original file line number Diff line number Diff line change
@@ -4704,6 +4704,107 @@ public function testAlterTableDuplicateKeyNameWithUnique(): void {
$this->assertSame( '42S21', $exception->getCode() );
}

public function testConstraintName(): void {
$this->assertQuery(
'CREATE TABLE t ( id INT, CONSTRAINT cst_id UNIQUE (id) )'
);

$result = $this->assertQuery( 'SHOW INDEX FROM t' );
$this->assertCount( 1, $result );
$this->assertSame( 'cst_id', $result[0]->Key_name );
}

public function testIndexNamePrecedesConstraintName(): void {
$this->assertQuery(
'CREATE TABLE t ( id INT, CONSTRAINT cst_id UNIQUE idx_id (id) )'
);

$result = $this->assertQuery( 'SHOW INDEX FROM t' );
$this->assertCount( 1, $result );
$this->assertSame( 'idx_id', $result[0]->Key_name );

$result = $this->assertQuery( 'SHOW CREATE TABLE t' );
$this->assertCount( 1, $result );
$this->assertSame(
implode(
"\n",
array(
'CREATE TABLE `t` (',
' `id` int DEFAULT NULL,',
' UNIQUE KEY `idx_id` (`id`)',
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci',
)
),
$result[0]->{'Create Table'}
);
}

public function testImplicitIndexNames(): void {
$this->assertQuery(
'CREATE TABLE t (
id INT UNIQUE,
id_2 INT UNIQUE,
value INT,
UNIQUE (id),
UNIQUE (id, value)
)'
);

$result = $this->assertQuery( 'SHOW INDEX FROM t' );
$this->assertCount( 5, $result );

$this->assertSame( 'id', $result[0]->Key_name );
$this->assertSame( 'id', $result[0]->Column_name );

$this->assertSame( 'id_2', $result[1]->Key_name );
$this->assertSame( 'id_2', $result[1]->Column_name );

$this->assertSame( 'id_3', $result[2]->Key_name );
$this->assertSame( 'id', $result[2]->Column_name );

$this->assertSame( 'id_4', $result[3]->Key_name );
$this->assertSame( 'id', $result[3]->Column_name );

$this->assertSame( 'id_4', $result[4]->Key_name );
$this->assertSame( 'value', $result[4]->Column_name );
}

public function testValidDuplicateConstraintNames(): void {
$this->assertQuery(
'CREATE TABLE t (
id INT,
CONSTRAINT cid PRIMARY KEY (id),
CONSTRAINT cid UNIQUE (id)
-- Not yet supported: CONSTRAINT cid CHECK (id > 0),
-- Not yet supported: CONSTRAINT cid FOREIGN KEY (id) REFERENCES t (id)
)'
);

// No exception. This table definition is valid in MySQL.
// Constraint names must be unique per constraint type, not per table.
}

public function testMultipleTablesWithSameConstraintNames(): void {
$this->assertQuery(
'CREATE TABLE t1 (
id INT,
CONSTRAINT c_primary PRIMARY KEY (id),
CONSTRAINT c_unique UNIQUE (id)
)'
);

$this->assertQuery(
'CREATE TABLE t2 (
id INT,
CONSTRAINT c_primary PRIMARY KEY (id),
CONSTRAINT c_unique UNIQUE (id)
)'
);

// No exception. This is valid in MySQL.
// Primary and unique key names must be unique per table, not per schema.
}

public function testNoBackslashEscapesSqlMode(): void {
$backslash = chr( 92 );

@@ -5004,4 +5105,86 @@ function ( string $utf8_literal ) {
$quote( chr( 0xC0 ) . chr( 39 ) )
);
}

public function testColumnNamesAreNotCaseSensitive(): void {
$this->assertQuery( 'CREATE TABLE t (value TEXT)' );

// INSERT.
$this->assertQuery( "INSERT INTO t (value) VALUES ('one')" );
$this->assertQuery( "INSERT INTO t (VaLuE) VALUES ('two')" );
$result = $this->assertQuery( 'SELECT * FROM t' );
$this->assertCount( 2, $result );

// SELECT.
$result = $this->assertQuery( "SELECT * FROM t WHERE value = 'one'" );
$this->assertCount( 1, $result );
$this->assertSame( 'one', $result[0]->value );

$result = $this->assertQuery( "SELECT * FROM t WHERE VaLuE = 'two'" );
$this->assertCount( 1, $result );
$this->assertSame( 'two', $result[0]->value );

// UPDATE.
$this->assertQuery( "UPDATE t SET value = 'one-updated' WHERE value = 'one'" );
$result = $this->assertQuery( "SELECT * FROM t WHERE value = 'one-updated'" );
$this->assertCount( 1, $result );
$this->assertSame( 'one-updated', $result[0]->value );

$this->assertQuery( "UPDATE t SET VALUE = 'two-updated' WHERE VaLuE = 'two'" );
$result = $this->assertQuery( "SELECT * FROM t WHERE value = 'two-updated'" );
$this->assertCount( 1, $result );
$this->assertSame( 'two-updated', $result[0]->value );

// DELETE.
$this->assertQuery( "DELETE FROM t WHERE value = 'one-updated'" );
$result = $this->assertQuery( 'SELECT * FROM t' );
$this->assertCount( 1, $result );
$this->assertSame( 'two-updated', $result[0]->value );

$this->assertQuery( "DELETE FROM t WHERE VaLuE = 'two-updated'" );
$result = $this->assertQuery( 'SELECT * FROM t' );
$this->assertCount( 0, $result );

// ALTER TABLE.
$this->assertQuery( 'ALTER TABLE t CHANGE COLUMN VaLuE value_changed TEXT' );
$this->assertQuery( 'ALTER TABLE t CHANGE COLUMN value_changed value TEXT' );

// ADD COLUMN.
$this->assertQuery( 'ALTER TABLE t ADD COLUMN added TEXT' );
$exception = null;
try {
$this->assertQuery( 'ALTER TABLE t ADD COLUMN AdDeD TEXT' );
} catch ( Throwable $e ) {
$exception = $e;
}
$this->assertNotNull( $exception );
$this->assertStringContainsString(
"Column already exists: 1060 Duplicate column name 'AdDeD'",
$exception->getMessage()
);

// DROP COLUMN.
$this->assertQuery( 'ALTER TABLE t DROP COLUMN added' );
$result = $this->assertQuery( 'SHOW COLUMNS FROM t' );
$this->assertCount( 1, $result );
$this->assertSame( 'value', $result[0]->Field );
}

public function testAliasesMustBeAscii(): void {
$this->expectException( WP_SQLite_Driver_Exception::class );
$this->expectExceptionMessage( 'The SQLite driver only supports ASCII characters in identifiers.' );
$this->assertQuery( 'SELECT 123 AS `ńôñ-ášçíì`' );
}

public function testTableNamesMustBeAscii(): void {
$this->expectException( WP_SQLite_Driver_Exception::class );
$this->expectExceptionMessage( 'The SQLite driver only supports ASCII characters in identifiers.' );
$this->assertQuery( 'CREATE TABLE `ńôñ-ášçíì` (id INT)' );
}

public function testColumnNamesMustBeAscii(): void {
$this->expectException( WP_SQLite_Driver_Exception::class );
$this->expectExceptionMessage( 'The SQLite driver only supports ASCII characters in identifiers.' );
$this->assertQuery( 'CREATE TABLE t (`ńôñ-ášçíì` INT)' );
}
}
64 changes: 47 additions & 17 deletions tests/WP_SQLite_Driver_Translation_Tests.php

Large diffs are not rendered by default.

38 changes: 29 additions & 9 deletions wp-includes/sqlite-ast/class-wp-sqlite-driver.php
Original file line number Diff line number Diff line change
@@ -1286,14 +1286,20 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
$columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' );
$column_names = $this->execute_sqlite_query(
sprintf(
'SELECT COLUMN_NAME FROM %s WHERE table_schema = ? AND table_name = ?',
'SELECT
COLUMN_NAME,
LOWER(COLUMN_NAME) AS COLUMN_NAME_LOWERCASE
FROM %s WHERE table_schema = ? AND table_name = ?',
$this->quote_sqlite_identifier( $columns_table )
),
array( $this->db_name, $table_name )
)->fetchAll( PDO::FETCH_COLUMN );
)->fetchAll( PDO::FETCH_ASSOC );

// Track column renames and removals.
$column_map = array_combine( $column_names, $column_names );
$column_map = array_combine(
array_column( $column_names, 'COLUMN_NAME_LOWERCASE' ),
array_column( $column_names, 'COLUMN_NAME' )
);
foreach ( $node->get_descendant_nodes( 'alterListItem' ) as $action ) {
$first_token = $action->get_first_child_token();

@@ -1302,7 +1308,7 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
$name = $this->translate( $action->get_first_child_node( 'fieldIdentifier' ) );
if ( null !== $name ) {
$name = $this->unquote_sqlite_identifier( $name );
unset( $column_map[ $name ] );
unset( $column_map[ strtolower( $name ) ] );
}
break;
case WP_MySQL_Lexer::CHANGE_SYMBOL:
@@ -1313,7 +1319,7 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
$this->translate( $action->get_first_child_node( 'identifier' ) )
);

$column_map[ $old_name ] = $new_name;
$column_map[ strtolower( $old_name ) ] = $new_name;
break;
case WP_MySQL_Lexer::RENAME_SYMBOL:
$column_ref = $action->get_first_child_node( 'fieldIdentifier' );
@@ -1325,7 +1331,7 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void {
$this->translate( $action->get_first_child_node( 'identifier' ) )
);

$column_map[ $old_name ] = $new_name;
$column_map[ strtolower( $old_name ) ] = $new_name;
}
break;
}
@@ -2115,7 +2121,21 @@ private function translate( $node ): ?string {
case 'identifierKeyword':
return '`' . $this->translate( $node->get_first_child() ) . '`';
case 'pureIdentifier':
return $this->translate_pure_identifier( $node );
$value = $this->translate_pure_identifier( $node );

/*
* At the moment, we only support ASCII bytes in all identifiers.
* This is because SQLite doesn't support case-insensitive Unicode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great to surface this error in an explicit way, thank you!

Thinking about the future, if identifiers are case-insensitive in PHP, could we lowercase them effectively in PHP and pass the transformed names to SQLite? Although that opens more questions – are column names raw bytes, or are they normalized? Yeah, perhaps not going there is for the best.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamziel It's definitely something we could solve on the PHP side (the simplest way probably requires mbstring), but yeah, we'd need to be careful exactly about what you mention — the column name will be stored as raw bytes in MySQL, but comparisons would need to be done normalized.

* character matching: https://sqlite.org/faq.html#q18
*/
for ( $i = 0; $i < strlen( $value ); $i++ ) {
if ( ord( $value[ $i ] ) > 127 ) {
throw $this->new_driver_exception(
'The SQLite driver only supports ASCII characters in identifiers.'
);
}
}
return $value;
case 'textStringLiteral':
return $this->translate_string_literal( $node );
case 'dataType':
@@ -3045,7 +3065,7 @@ private function translate_update_list_in_non_strict_mode( string $table_name, W
$columns_table = $this->information_schema_builder->get_table_name( $is_temporary, 'columns' );
$columns = $this->execute_sqlite_query(
'
SELECT column_name, is_nullable, data_type, column_default
SELECT LOWER(column_name) AS COLUMN_NAME, is_nullable, data_type, column_default
FROM ' . $this->quote_sqlite_identifier( $columns_table ) . '
WHERE table_schema = ?
AND table_name = ?
@@ -3062,7 +3082,7 @@ private function translate_update_list_in_non_strict_mode( string $table_name, W

// Get column info.
$column_name = $this->unquote_sqlite_identifier( $this->translate( $column_ref ) );
$column_info = $column_map[ $column_name ];
$column_info = $column_map[ strtolower( $column_name ) ];
$data_type = $column_info['DATA_TYPE'];
$is_nullable = 'YES' === $column_info['IS_NULLABLE'];
$default = $column_info['COLUMN_DEFAULT'];
668 changes: 444 additions & 224 deletions wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php

Large diffs are not rendered by default.