Skip to content

Commit 9bc0ad0

Browse files
committed
Consolidate SQLite identifier escaping in WP_SQLite_Connection, improve docs
1 parent 809d2ec commit 9bc0ad0

File tree

3 files changed

+51
-26
lines changed

3 files changed

+51
-26
lines changed

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

+31
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,37 @@ public function quote( $value, int $type = PDO::PARAM_STR ): string {
129129
return $this->pdo->quote( $value, $type );
130130
}
131131

132+
/**
133+
* Quote an SQLite identifier.
134+
*
135+
* Wraps the identifier in backticks and escapes backtick characters within.
136+
* Quoted identifiers in SQLite are represented by string constants:
137+
*
138+
* A string constant is formed by enclosing the string in single quotes (').
139+
* A single quote within the string can be encoded by putting two single
140+
* quotes in a row - as in Pascal. C-style escapes using the backslash
141+
* character are not supported because they are not standard SQL.
142+
*
143+
* See: https://www.sqlite.org/lang_expr.html#literal_values_constants_
144+
*
145+
* Although sparsely documented, this applies to backtick and double quoted
146+
* string constants as well, so only the quote character needs to be escaped.
147+
*
148+
* We use backtick quotes instead of the SQL standard double quotes, due to
149+
* an SQLite quirk causing double-quoted strings to be accepted as literals:
150+
*
151+
* This misfeature means that a misspelled double-quoted identifier will
152+
* be interpreted as a string literal, rather than generating an error.
153+
*
154+
* See: https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted
155+
*
156+
* @param string $unquoted_identifier The unquoted identifier value.
157+
* @return string The quoted identifier value.
158+
*/
159+
public function quote_identifier( string $unquoted_identifier ): string {
160+
return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`';
161+
}
162+
132163
/**
133164
* Get the PDO object.
134165
*

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -3525,13 +3525,11 @@ private function unquote_sqlite_identifier( string $quoted_identifier ): string
35253525
/**
35263526
* Quote an SQLite identifier.
35273527
*
3528-
* Wrap the identifier in backticks and escape backtick values within.
3529-
*
35303528
* @param string $unquoted_identifier The unquoted identifier value.
35313529
* @return string The quoted identifier value.
35323530
*/
35333531
private function quote_sqlite_identifier( string $unquoted_identifier ): string {
3534-
return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`';
3532+
return $this->connection->quote_identifier( $unquoted_identifier );
35353533
}
35363534

35373535

wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php

+19-23
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ class WP_SQLite_Information_Schema_Reconstructor {
2323
*/
2424
private $driver;
2525

26+
/**
27+
* An instance of the SQLite connection.
28+
*
29+
* @var WP_SQLite_Connection
30+
*/
31+
private $connection;
32+
2633
/**
2734
* A service for managing MySQL INFORMATION_SCHEMA tables in SQLite.
2835
*
@@ -41,6 +48,7 @@ public function __construct(
4148
WP_SQLite_Information_Schema_Builder $schema_builder
4249
) {
4350
$this->driver = $driver;
51+
$this->connection = $driver->get_connection();
4452
$this->schema_builder = $schema_builder;
4553
}
4654

@@ -78,7 +86,7 @@ public function ensure_correct_information_schema(): void {
7886
// Remove information schema records for tables that don't exist.
7987
foreach ( $information_schema_tables as $table ) {
8088
if ( ! in_array( $table, $sqlite_tables, true ) ) {
81-
$sql = sprintf( 'DROP TABLE %s', $this->quote_sqlite_identifier( $table ) );
89+
$sql = sprintf( 'DROP TABLE %s', $this->connection->quote_identifier( $table ) ); // TODO: mysql quote
8290
$ast = $this->driver->create_parser( $sql )->parse();
8391
if ( null === $ast ) {
8492
throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
@@ -124,7 +132,7 @@ private function get_information_schema_table_names(): array {
124132
return $this->driver->execute_sqlite_query(
125133
sprintf(
126134
'SELECT table_name FROM %s ORDER BY table_name',
127-
$this->quote_sqlite_identifier( $tables_table )
135+
$this->connection->quote_identifier( $tables_table )
128136
)
129137
)->fetchAll( PDO::FETCH_COLUMN );
130138
}
@@ -212,7 +220,7 @@ private function generate_create_table_statement( string $table_name ): string {
212220
$columns = $this->driver->execute_sqlite_query(
213221
sprintf(
214222
'PRAGMA table_xinfo(%s)',
215-
$this->quote_sqlite_identifier( $table_name )
223+
$this->connection->quote_identifier( $table_name )
216224
)
217225
)->fetchAll( PDO::FETCH_ASSOC );
218226

@@ -244,7 +252,7 @@ private function generate_create_table_statement( string $table_name ): string {
244252
if ( count( $pk_columns ) > 0 ) {
245253
$quoted_pk_columns = array();
246254
foreach ( $pk_columns as $pk_column ) {
247-
$quoted_pk_columns[] = $this->quote_sqlite_identifier( $pk_column );
255+
$quoted_pk_columns[] = $this->connection->quote_identifier( $pk_column );
248256
}
249257
$definitions[] = sprintf( 'PRIMARY KEY (%s)', implode( ', ', $quoted_pk_columns ) );
250258
}
@@ -253,7 +261,7 @@ private function generate_create_table_statement( string $table_name ): string {
253261
$keys = $this->driver->execute_sqlite_query(
254262
sprintf(
255263
'PRAGMA index_list(%s)',
256-
$this->quote_sqlite_identifier( $table_name )
264+
$this->connection->quote_identifier( $table_name )
257265
)
258266
)->fetchAll( PDO::FETCH_ASSOC );
259267

@@ -268,7 +276,7 @@ private function generate_create_table_statement( string $table_name ): string {
268276

269277
return sprintf(
270278
"CREATE TABLE %s (\n %s\n)",
271-
$this->quote_sqlite_identifier( $table_name ),
279+
$this->connection->quote_identifier( $table_name ),
272280
implode( ",\n ", $definitions )
273281
);
274282
}
@@ -284,7 +292,7 @@ private function generate_create_table_statement( string $table_name ): string {
284292
*/
285293
private function generate_column_definition( string $table_name, array $column_info ): string {
286294
$definition = array();
287-
$definition[] = $this->quote_sqlite_identifier( $column_info['name'] );
295+
$definition[] = $this->connection->quote_identifier( $column_info['name'] );
288296

289297
// Data type.
290298
$mysql_type = $this->get_cached_mysql_data_type( $table_name, $column_info['name'] );
@@ -379,14 +387,14 @@ private function generate_key_definition( string $table_name, array $key_info, a
379387
* the generated MySQL definition, they follow implicit MySQL naming.
380388
*/
381389
if ( ! str_starts_with( $name, 'sqlite_autoindex_' ) ) {
382-
$definition[] = $this->quote_sqlite_identifier( $name );
390+
$definition[] = $this->connection->quote_identifier( $name );
383391
}
384392

385393
// Key columns.
386394
$key_columns = $this->driver->execute_sqlite_query(
387395
sprintf(
388396
'PRAGMA index_info(%s)',
389-
$this->quote_sqlite_identifier( $key_info['name'] )
397+
$this->connection->quote_identifier( $key_info['name'] )
390398
)
391399
)->fetchAll( PDO::FETCH_ASSOC );
392400
$cols = array();
@@ -423,11 +431,11 @@ private function generate_key_definition( string $table_name, array $key_info, a
423431
) {
424432
$cols[] = sprintf(
425433
'%s(%d)',
426-
$this->quote_sqlite_identifier( $column['name'] ),
434+
$this->connection->quote_identifier( $column['name'] ),
427435
min( $column_length ?? $max_prefix_length, $max_prefix_length )
428436
);
429437
} else {
430-
$cols[] = $this->quote_sqlite_identifier( $column['name'] );
438+
$cols[] = $this->connection->quote_identifier( $column['name'] );
431439
}
432440
}
433441

@@ -640,18 +648,6 @@ private function escape_mysql_string_literal( string $literal ): string {
640648
return "'" . addcslashes( $literal, "\0\n\r'\"\Z" ) . "'";
641649
}
642650

643-
/**
644-
* Quote an SQLite identifier.
645-
*
646-
* Wrap the identifier in backticks and escape backtick values within.
647-
*
648-
* @param string $unquoted_identifier The unquoted identifier value.
649-
* @return string The quoted identifier value.
650-
*/
651-
private function quote_sqlite_identifier( string $unquoted_identifier ): string {
652-
return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`';
653-
}
654-
655651
/**
656652
* Unquote a quoted MySQL identifier.
657653
*

0 commit comments

Comments
 (0)