@@ -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