Skip to content

Commit 80160cf

Browse files
committed
Use PDO SQLite statement as a source for MySQL proxy statement
1 parent 4aac70a commit 80160cf

3 files changed

Lines changed: 118 additions & 169 deletions

File tree

wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,13 @@ class WP_PDO_MySQL_On_SQLite extends PDO {
469469
*/
470470
private $last_sqlite_queries = array();
471471

472+
/**
473+
* A PDO SQLite statement that represents the result of the last emulated query.
474+
*
475+
* @var PDOStatement
476+
*/
477+
private $last_result_statement;
478+
472479
/**
473480
* Results of the last emulated query.
474481
*
@@ -772,7 +779,8 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
772779
if ( null === $fetch_mode ) {
773780
// When the default FETCH_BOTH is not set explicitly, additional
774781
// arguments are ignored, and the argument count is not validated.
775-
$fetch_mode = PDO::FETCH_BOTH;
782+
$fetch_mode = PDO::FETCH_BOTH;
783+
$fetch_mode_args = array();
776784
} elseif ( PDO::FETCH_COLUMN === $fetch_mode ) {
777785
if ( 3 !== $arg_count ) {
778786
throw new ArgumentCountError(
@@ -883,11 +891,20 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
883891
$this->commit_wrapper_transaction();
884892
}
885893

894+
/*
895+
* For now, create all statements from data loaded in memory. This is
896+
* a temporary solution until all queries set their result statement.
897+
*
898+
* TODO: Use "$this->last_result_statement" with an actual PDO SQLite
899+
* statement whenever possible rather than loading all data.
900+
*/
886901
$columns = is_array( $this->last_column_meta ) ? $this->last_column_meta : array();
887902
$rows = is_array( $this->last_result ) ? $this->last_result : array();
888903
$affected_rows = is_int( $this->last_return_value ) ? $this->last_return_value : 0;
889904

890-
$stmt = new WP_PDO_Synthetic_Statement( $this, $columns, $rows, $affected_rows );
905+
$this->last_result_statement = $this->create_result_statement_from_data( $columns, $rows );
906+
907+
$stmt = new WP_PDO_Proxy_Statement( $this->last_result_statement, $affected_rows );
891908
$stmt->setFetchMode( $fetch_mode, ...$fetch_mode_args );
892909
return $stmt;
893910
} catch ( Throwable $e ) {
@@ -6348,6 +6365,80 @@ private function flush(): void {
63486365
$this->wrapper_transaction_type = null;
63496366
}
63506367

6368+
/**
6369+
* Create a PDO SQLite statement from the specified columns and rows.
6370+
*
6371+
* Some emulated MySQL queries don't have an SQLite counterpart and their
6372+
* result data may be generated without a corresponding SQLite statement.
6373+
* In such cases, we can generate a simple SQLite SELECT query that will
6374+
* provide us with the PDOStatement API for the given column and row data.
6375+
*
6376+
* @param array $columns The columns of the result set.
6377+
* @param array $rows The rows of the result set.
6378+
* @return PDOStatement The corresponding PDO SQLite statement.
6379+
*/
6380+
private function create_result_statement_from_data( array $columns, array $rows ): PDOStatement {
6381+
$pdo = $this->connection->get_pdo();
6382+
6383+
/*
6384+
* With 0 columns, we need to create a PDO statement that has no columns.
6385+
* We can use an unknown PRAGMA command, as these are ignored by SQLite.
6386+
*/
6387+
if ( 0 === count( $columns ) ) {
6388+
return $pdo->query( 'PRAGMA unknown_pragma_no_columns;' );
6389+
}
6390+
6391+
/*
6392+
* Create an SQLite statement that returns the specified columns and rows.
6393+
* This can be done using a SELECT statement in the following form:
6394+
*
6395+
* -- A dummy header row to assign correct column names.
6396+
* SELECT NULL AS `col1`, NULL AS `col2`, ... WHERE FALSE
6397+
*
6398+
* UNION ALL
6399+
*
6400+
* -- The actual data rows.
6401+
* VALUES
6402+
* (val11, val12, ...),
6403+
* (val21, val22, ...),
6404+
* ...
6405+
*/
6406+
6407+
// Construct column header row ("SELECT <column-list> WHERE FALSE").
6408+
$query = 'SELECT ';
6409+
foreach ( $columns as $i => $column ) {
6410+
$query .= $i > 0 ? ', ' : '';
6411+
$query .= 'NULL AS ' . $pdo->quote( $column['name'] );
6412+
}
6413+
$query .= ' WHERE FALSE';
6414+
6415+
// UNION ALL
6416+
if ( count( $rows ) > 0 ) {
6417+
$query .= ' UNION ALL VALUES ';
6418+
}
6419+
6420+
// Construct data rows ("VALUES <row-list>").
6421+
foreach ( $rows as $i => $row ) {
6422+
$query .= $i > 0 ? ', ' : '';
6423+
$query .= '(';
6424+
foreach ( array_values( $row ) as $j => $value ) {
6425+
$query .= $j > 0 ? ', ' : '';
6426+
if ( null === $value ) {
6427+
$query .= 'NULL';
6428+
} elseif ( is_string( $value ) && strpos( $value, "\0" ) !== false ) {
6429+
// Handle null characters; see self::translate_string_literal().
6430+
$query .= sprintf( "CAST(x'%s' AS TEXT)", bin2hex( $value ) );
6431+
} elseif ( is_string( $value ) ) {
6432+
$query .= $pdo->quote( $value );
6433+
} else {
6434+
$query .= $value;
6435+
}
6436+
}
6437+
$query .= ')';
6438+
}
6439+
return $pdo->query( $query );
6440+
}
6441+
63516442
/**
63526443
* Set results of a query() call using fetched data.
63536444
*

0 commit comments

Comments
 (0)