Skip to content

Commit 14b8820

Browse files
committed
Inject database name in queries dynamically
1 parent 3951308 commit 14b8820

2 files changed

Lines changed: 142 additions & 2 deletions

File tree

tests/WP_SQLite_Driver_Tests.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9244,4 +9244,81 @@ public function testCheckConstraintNotEnforced(): void {
92449244
$result[0]->{'Create Table'}
92459245
);
92469246
}
9247+
9248+
public function testDynamicDatabaseName(): void {
9249+
// Create a setter for the private property "$db_name".
9250+
$set_db_name = Closure::bind(
9251+
function ( $name ) {
9252+
$this->main_db_name = $name;
9253+
},
9254+
$this->engine,
9255+
WP_SQLite_Driver::class
9256+
);
9257+
9258+
// Default database name.
9259+
$result = $this->assertQuery( 'SELECT schema_name FROM information_schema.schemata ORDER BY schema_name' );
9260+
$this->assertEquals(
9261+
array(
9262+
(object) array( 'SCHEMA_NAME' => 'information_schema' ),
9263+
(object) array( 'SCHEMA_NAME' => 'wp' ),
9264+
),
9265+
$result
9266+
);
9267+
9268+
// Change the database name.
9269+
$set_db_name( 'wp_test_new' );
9270+
$result = $this->assertQuery( 'SELECT schema_name FROM information_schema.schemata ORDER BY schema_name' );
9271+
$this->assertEquals(
9272+
array(
9273+
(object) array( 'SCHEMA_NAME' => 'information_schema' ),
9274+
(object) array( 'SCHEMA_NAME' => 'wp_test_new' ),
9275+
),
9276+
$result
9277+
);
9278+
}
9279+
9280+
public function testDynamicDatabaseNameComplexScenario(): void {
9281+
// Create a setter for the private property "$db_name".
9282+
$set_db_name = Closure::bind(
9283+
function ( $name ) {
9284+
$this->main_db_name = $name;
9285+
},
9286+
$this->engine,
9287+
WP_SQLite_Driver::class
9288+
);
9289+
9290+
$this->assertQuery( 'CREATE TABLE t (id INT, db_name TEXT)' );
9291+
$this->assertQuery( 'INSERT INTO t (id, db_name) VALUES (1, "wp")' );
9292+
$this->assertQuery( 'INSERT INTO t (id, db_name) VALUES (2, "wp_test_new")' );
9293+
$this->assertQuery( 'INSERT INTO t (id, db_name) VALUES (3, "other")' );
9294+
9295+
$set_db_name( 'wp_test_new' );
9296+
9297+
$result = $this->assertQuery(
9298+
"SELECT sq.id, sq.table_schema, sq.table_name, sq.column_name
9299+
FROM (
9300+
SELECT * FROM information_schema.columns ist
9301+
JOIN t ON t.db_name = CONCAT(COALESCE(ist.table_schema, 'default'), '')
9302+
WHERE ist.table_name = 't'
9303+
) sq
9304+
ORDER BY ordinal_position"
9305+
);
9306+
$this->assertEquals(
9307+
array(
9308+
(object) array(
9309+
'id' => '2',
9310+
'TABLE_SCHEMA' => 'wp_test_new',
9311+
'TABLE_NAME' => 't',
9312+
'COLUMN_NAME' => 'id',
9313+
),
9314+
(object) array(
9315+
'id' => '2',
9316+
'TABLE_SCHEMA' => 'wp_test_new',
9317+
'TABLE_NAME' => 't',
9318+
'COLUMN_NAME' => 'db_name',
9319+
),
9320+
),
9321+
$result
9322+
);
9323+
}
92479324
}

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3416,7 +3416,16 @@ private function translate_qualified_identifier(
34163416

34173417
// Object child name (column, index, etc.).
34183418
if ( null !== $child_node ) {
3419-
$parts[] = $this->translate( $child_node );
3419+
$translated = $this->translate( $child_node );
3420+
$name = $this->unquote_sqlite_identifier( $translated );
3421+
$parts[] = $translated;
3422+
3423+
// When targeting a database name column from the information schema,
3424+
// we need to inject the configured database name.
3425+
if ( $this->is_information_schema_db_column( $name ) ) {
3426+
$fully_qualified_column = implode( '.', $parts );
3427+
return $this->inject_configured_database_name( $fully_qualified_column );
3428+
}
34203429
}
34213430

34223431
return implode( '.', $parts );
@@ -3916,7 +3925,16 @@ public function translate_select_item( WP_Parser_Node $node ): string {
39163925
$column_ref = $node->get_first_descendant_node( 'columnRef' );
39173926
$is_column_ref = $column_ref && $item === $this->translate( $column_ref );
39183927
if ( $is_column_ref ) {
3919-
return $item;
3928+
$translated = $this->translate( $column_ref );
3929+
3930+
// When targeting a database name column from the information schema,
3931+
// we need to inject the configured database name and add an alias.
3932+
$identifiers = $column_ref->get_descendant_nodes( 'identifier' );
3933+
$column_name = $this->unquote_sqlite_identifier( $this->translate( end( $identifiers ) ) );
3934+
if ( $this->is_information_schema_db_column( $column_name ) ) {
3935+
return sprintf( '%s AS %s', $translated, strtoupper( $column_name ) );
3936+
}
3937+
return $translated;
39203938
}
39213939

39223940
/*
@@ -3939,6 +3957,51 @@ public function translate_select_item( WP_Parser_Node $node ): string {
39393957
return sprintf( '%s AS %s', $item, $alias );
39403958
}
39413959

3960+
/**
3961+
* Check if a column name appears to target an information schema column that
3962+
* references the database name ("SCHEMA_NAME", "TABLE_SCHEMA", etc.).
3963+
*
3964+
* TODO: Fully resolve the column references to ensure that they are really
3965+
* referencing the information schema tables.
3966+
*
3967+
* @param string $column_name The name of the column to check.
3968+
* @return bool True if the column is an information schema
3969+
* database name column, false otherwise.
3970+
*/
3971+
private function is_information_schema_db_column( string $column_name ): bool {
3972+
static $information_schema_columns = array(
3973+
'SCHEMA_NAME' => true,
3974+
'TABLE_SCHEMA' => true,
3975+
'VIEW_SCHEMA' => true,
3976+
'INDEX_SCHEMA' => true,
3977+
'CONSTRAINT_SCHEMA' => true,
3978+
'UNIQUE_CONSTRAINT_SCHEMA' => true,
3979+
'REFERENCED_TABLE_SCHEMA' => true,
3980+
'TRIGGER_SCHEMA' => true,
3981+
);
3982+
return isset( $information_schema_columns[ strtoupper( $column_name ) ] );
3983+
}
3984+
3985+
/**
3986+
* Translate a name targeting an information schema database name column
3987+
* to an expression that injects the configured database name value.
3988+
*
3989+
* For example, a reference like "`t`.`table_schema`" will be translated to:
3990+
*
3991+
* IIF(`t`.`table_schema` = 'information_schema', `t`.`table_schema`, 'database_name')
3992+
*
3993+
* @param string $column_name The name of the column to translate.
3994+
* @return string The translated value.
3995+
*/
3996+
private function inject_configured_database_name( string $column_name ): string {
3997+
return sprintf(
3998+
"IIF(%s = 'information_schema', %s, %s)",
3999+
$column_name,
4000+
$column_name,
4001+
$this->connection->quote( $this->main_db_name ),
4002+
);
4003+
}
4004+
39424005
/**
39434006
* Recreate an existing table using data in the information schema.
39444007
*

0 commit comments

Comments
 (0)