@@ -666,64 +666,34 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo
666666 throw $ this ->new_driver_exception ( 'Multi-query is not supported. ' );
667667 }
668668
669- // Handle transaction commands.
670-
671669 /*
670+ * Determine if we need to wrap the translated queries in a transaction.
671+ *
672672 * [GRAMMAR]
673- * beginWork: BEGIN_SYMBOL WORK_SYMBOL?
673+ * query:
674+ * EOF
675+ * | (simpleStatement | beginWork) (SEMICOLON_SYMBOL EOF? | EOF)
674676 */
675- $ child = $ ast ->get_first_child ();
676- if ( $ child instanceof WP_Parser_Node && 'beginWork ' === $ child ->rule_name ) {
677- $ this ->begin_transaction ();
678- return true ;
677+ $ child_node = $ ast ->get_first_child_node ();
678+ if (
679+ null === $ child_node
680+ || 'beginWork ' === $ child_node ->rule_name
681+ || $ child_node ->has_child_node ( 'transactionOrLockingStatement ' )
682+ ) {
683+ $ wrap_in_transaction = false ;
684+ } else {
685+ $ wrap_in_transaction = true ;
679686 }
680687
681- if ( $ child instanceof WP_Parser_Node && 'simpleStatement ' === $ child ->rule_name ) {
682- /*
683- * [GRAMMAR]
684- * transactionOrLockingStatement:
685- * transactionStatement | savepointStatement | lockStatement | xaStatement
686- */
687- $ subchild = $ child ->get_first_child_node ( 'transactionOrLockingStatement ' );
688- if ( null !== $ subchild ) {
689- $ tokens = $ subchild ->get_descendant_tokens ();
690- $ token1 = $ tokens [0 ];
691- $ token2 = $ tokens [1 ] ?? null ;
692- if (
693- WP_MySQL_Lexer::START_SYMBOL === $ token1 ->id
694- && WP_MySQL_Lexer::TRANSACTION_SYMBOL === $ token2 ->id
695- ) {
696- $ this ->begin_transaction ();
697- return true ;
698- }
699-
700- if (
701- WP_MySQL_Lexer::BEGIN_SYMBOL === $ token1 ->id
702- ) {
703- $ this ->begin_transaction ();
704- return true ;
705- }
706-
707- if (
708- WP_MySQL_Lexer::COMMIT_SYMBOL === $ token1 ->id
709- ) {
710- $ this ->commit ();
711- return true ;
712- }
713-
714- if (
715- WP_MySQL_Lexer::ROLLBACK_SYMBOL === $ token1 ->id
716- ) {
717- $ this ->rollback ();
718- return true ;
719- }
720- }
688+ if ( $ wrap_in_transaction ) {
689+ $ this ->begin_transaction ();
721690 }
722691
723- // Perform all the queries in a nested transaction.
724- $ this ->begin_transaction ();
725692 $ this ->execute_mysql_query ( $ ast );
726- $ this ->commit ();
693+
694+ if ( $ wrap_in_transaction ) {
695+ $ this ->commit ();
696+ }
727697 return $ this ->last_return_value ;
728698 } catch ( Throwable $ e ) {
729699 try {
@@ -895,6 +865,11 @@ private function execute_mysql_query( WP_Parser_Node $node ): void {
895865 );
896866 }
897867
868+ if ( 'beginWork ' === $ children [0 ]->rule_name ) {
869+ $ this ->begin_transaction ();
870+ return ;
871+ }
872+
898873 if ( 'simpleStatement ' !== $ children [0 ]->rule_name ) {
899874 throw $ this ->new_driver_exception (
900875 sprintf ( 'Expected "simpleStatement" node, got: "%s" ' , $ children [0 ]->rule_name )
@@ -904,6 +879,9 @@ private function execute_mysql_query( WP_Parser_Node $node ): void {
904879 // Process the "simpleStatement" AST node.
905880 $ node = $ children [0 ]->get_first_child_node ();
906881 switch ( $ node ->rule_name ) {
882+ case 'transactionOrLockingStatement ' :
883+ $ this ->execute_transaction_or_locking_statement ( $ node );
884+ break ;
907885 case 'selectStatement ' :
908886 $ this ->is_readonly = true ;
909887 $ this ->execute_select_statement ( $ node );
@@ -1017,6 +995,49 @@ private function execute_mysql_query( WP_Parser_Node $node ): void {
1017995 }
1018996 }
1019997
998+ /**
999+ * Execute a MySQL transaction or locking statement in SQLite.
1000+ *
1001+ * @param WP_Parser_Node $node The "transactionOrLockingStatement" AST node.
1002+ * @throws WP_SQLite_Driver_Exception When the query execution fails.
1003+ */
1004+ private function execute_transaction_or_locking_statement ( WP_Parser_Node $ node ): void {
1005+ $ subnode = $ node ->get_first_child_node ();
1006+ $ token = $ node ->get_first_descendant_token ();
1007+ switch ( $ subnode ->rule_name ) {
1008+ case 'transactionStatement ' :
1009+ // START TRANSACTION.
1010+ if ( WP_MySQL_Lexer::START_SYMBOL === $ token ->id ) {
1011+ $ this ->begin_transaction ();
1012+ break ;
1013+ }
1014+
1015+ // COMMIT.
1016+ if ( WP_MySQL_Lexer::COMMIT_SYMBOL === $ token ->id ) {
1017+ $ this ->commit ();
1018+ break ;
1019+ }
1020+
1021+ // Unknown statement. Fall through to the default case.
1022+ case 'savepointStatement ' :
1023+ // ROLLBACK.
1024+ if ( WP_MySQL_Lexer::ROLLBACK_SYMBOL === $ token ->id ) {
1025+ $ this ->rollback ();
1026+ break ;
1027+ }
1028+
1029+ // Unknown statement. Fall through to the default case.
1030+ default :
1031+ throw $ this ->new_not_supported_exception (
1032+ sprintf (
1033+ 'statement type: "%s" > "%s" ' ,
1034+ $ node ->rule_name ,
1035+ $ subnode ->rule_name
1036+ )
1037+ );
1038+ }
1039+ }
1040+
10201041 /**
10211042 * Translate and execute a MySQL SELECT statement in SQLite.
10221043 *
0 commit comments