Skip to content

Commit 63460dd

Browse files
committed
Add basic support for SELECT statements
1 parent 3449b0b commit 63460dd

File tree

1 file changed

+127
-11
lines changed

1 file changed

+127
-11
lines changed

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

Lines changed: 127 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use SQLite3;
1111
use WP_MySQL_Lexer;
1212
use WP_MySQL_Parser;
13+
use WP_MySQL_Token;
1314
use WP_Parser_Grammar;
15+
use WP_Parser_Node;
1416
use WP_SQLite_PDO_User_Defined_Functions;
1517

1618
class WP_SQLite_Driver {
@@ -502,17 +504,6 @@ public function get_return_value() {
502504
return $this->return_value;
503505
}
504506

505-
/**
506-
* Executes a MySQL query in SQLite.
507-
*
508-
* @param string $query The query.
509-
*
510-
* @throws Exception If the query is not supported.
511-
*/
512-
private function execute_mysql_query( $query ) {
513-
//@TODO: Implement the query translation.
514-
}
515-
516507
/**
517508
* Executes a query in SQLite.
518509
*
@@ -681,6 +672,93 @@ public function rollback() {
681672
return $this->last_exec_returned;
682673
}
683674

675+
/**
676+
* Executes a MySQL query in SQLite.
677+
*
678+
* @param string $query The query.
679+
*
680+
* @throws Exception If the query is not supported.
681+
*/
682+
private function execute_mysql_query( WP_Parser_Node $ast ) {
683+
if ( 'query' !== $ast->rule_name ) {
684+
throw new Exception( sprintf( 'Expected "query" node, got: "%s"', $ast->rule_name ) );
685+
}
686+
687+
$children = $ast->get_child_nodes();
688+
if ( count( $children ) !== 1 ) {
689+
throw new Exception( sprintf( 'Expected 1 child, got: %d', count( $children ) ) );
690+
}
691+
692+
$ast = $children[0]->get_child_node();
693+
switch ( $ast->rule_name ) {
694+
case 'selectStatement':
695+
$this->query_type = 'SELECT';
696+
$query = $this->translate( $ast->get_child() );
697+
$stmt = $this->execute_sqlite_query( $query );
698+
$this->set_results_from_fetched_data(
699+
$stmt->fetchAll( $this->pdo_fetch_mode )
700+
);
701+
break;
702+
default:
703+
throw new Exception( sprintf( 'Unsupported statement type: "%s"', $ast->rule_name ) );
704+
}
705+
}
706+
707+
private function translate( $ast ) {
708+
if ( null === $ast ) {
709+
return null;
710+
}
711+
712+
if ( $ast instanceof WP_MySQL_Token ) {
713+
return $this->translate_token( $ast );
714+
}
715+
716+
if ( ! $ast instanceof WP_Parser_Node ) {
717+
throw new Exception( 'translate_query only accepts WP_MySQL_Token and WP_Parser_Node instances' );
718+
}
719+
720+
$rule_name = $ast->rule_name;
721+
switch ( $rule_name ) {
722+
case 'qualifiedIdentifier':
723+
case 'dotIdentifier':
724+
return $this->translate_sequence( $ast->get_children(), '' );
725+
case 'textStringLiteral':
726+
if ( $ast->has_child_token( WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT ) ) {
727+
return WP_SQLite_Token_Factory::double_quoted_value(
728+
$ast->get_child_token( WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT )->value
729+
)->value;
730+
}
731+
if ( $ast->has_child_token( WP_MySQL_Lexer::SINGLE_QUOTED_TEXT ) ) {
732+
return WP_SQLite_Token_Factory::raw(
733+
$ast->get_child_token( WP_MySQL_Lexer::SINGLE_QUOTED_TEXT )->value
734+
)->value;
735+
}
736+
// Fall through to the default case.
737+
738+
default:
739+
return $this->translate_sequence( $ast->get_children() );
740+
}
741+
}
742+
743+
private function translate_token( WP_MySQL_Token $token ) {
744+
switch ( $token->id ) {
745+
case WP_MySQL_Lexer::EOF:
746+
return null;
747+
case WP_MySQL_Lexer::IDENTIFIER:
748+
return '"' . trim( $token->value, '`"' ) . '"';
749+
default:
750+
return $token->value;
751+
}
752+
}
753+
754+
private function translate_sequence( array $nodes, string $separator = ' ' ): string {
755+
$parts = array();
756+
foreach ( $nodes as $node ) {
757+
$parts[] = $this->translate( $node );
758+
}
759+
return implode( $separator, $parts );
760+
}
761+
684762
/**
685763
* This method makes database directory and .htaccess file.
686764
*
@@ -745,6 +823,44 @@ private function flush() {
745823
$this->executed_sqlite_queries = array();
746824
}
747825

826+
/**
827+
* Method to set the results from the fetched data.
828+
*
829+
* @param array $data The data to set.
830+
*/
831+
private function set_results_from_fetched_data( $data ) {
832+
if ( null === $this->results ) {
833+
$this->results = $data;
834+
}
835+
if ( is_array( $this->results ) ) {
836+
$this->num_rows = count( $this->results );
837+
$this->last_select_found_rows = count( $this->results );
838+
}
839+
$this->return_value = $this->results;
840+
}
841+
842+
/**
843+
* Method to set the results from the affected rows.
844+
*
845+
* @param int|null $override Override the affected rows.
846+
*/
847+
private function set_result_from_affected_rows( $override = null ) {
848+
/*
849+
* SELECT CHANGES() is a workaround for the fact that
850+
* $stmt->rowCount() returns "0" (zero) with the
851+
* SQLite driver at all times.
852+
* Source: https://www.php.net/manual/en/pdostatement.rowcount.php
853+
*/
854+
if ( null === $override ) {
855+
$this->affected_rows = (int) $this->execute_sqlite_query( 'select changes()' )->fetch()[0];
856+
} else {
857+
$this->affected_rows = $override;
858+
}
859+
$this->return_value = $this->affected_rows;
860+
$this->num_rows = $this->affected_rows;
861+
$this->results = $this->affected_rows;
862+
}
863+
748864
/**
749865
* Error handler.
750866
*

0 commit comments

Comments
 (0)