@@ -425,6 +425,20 @@ class WP_SQLite_Driver {
425425 'STRICT_TRANS_TABLES ' ,
426426 );
427427
428+ /**
429+ * System session variables that are explicitly set by the SET statement.
430+ *
431+ * @var array
432+ */
433+ private $ system_session_variables = array ();
434+
435+ /**
436+ * User session variables.
437+ *
438+ * @var array
439+ */
440+ private $ user_session_variables = array ();
441+
428442 /**
429443 * Constructor.
430444 *
@@ -2089,17 +2103,31 @@ private function execute_set_statement( WP_Parser_Node $node ): void {
20892103 }
20902104
20912105 if (
2106+ $ part instanceof WP_MySQL_Token
2107+ && WP_MySQL_Lexer::NAMES_SYMBOL === $ part ->id
2108+ ) {
2109+ // "SET NAMES ..." is a no-op for now.
2110+ // TODO: Validate charset compatibility with UTF-8.
2111+ } elseif (
20922112 $ part instanceof WP_Parser_Node
20932113 && (
20942114 'internalVariableName ' === $ part ->rule_name
20952115 || 'setSystemVariable ' === $ part ->rule_name
20962116 )
20972117 ) {
2118+ // Set a system variable.
20982119 array_shift ( $ definition ); // Remove the '='.
20992120 $ value = array_shift ( $ definition );
21002121 $ this ->execute_set_system_variable_statement ( $ part , $ value , $ default_type );
2122+ } elseif (
2123+ $ part instanceof WP_Parser_Node
2124+ && 'userVariable ' === $ part ->rule_name
2125+ ) {
2126+ // Set a user variable.
2127+ array_shift ( $ definition ); // Remove the '='.
2128+ $ value = array_shift ( $ definition );
2129+ $ this ->execute_set_user_variable_statement ( $ part , $ value );
21012130 } else {
2102- // TODO: Support user variables (in-memory or a temporary table).
21032131 throw $ this ->new_not_supported_exception (
21042132 sprintf ( 'SET statement: %s ' , $ node ->rule_name )
21052133 );
@@ -2141,12 +2169,14 @@ private function execute_set_system_variable_statement(
21412169 $ type = $ var_ident_type ->get_first_child_token ()->id ;
21422170 }
21432171
2144- // Get the variable value.
2145- $ value = $ this ->translate ( $ value_node );
2146- $ value = str_replace ( " '' " , " ' " , $ value );
2147- $ value = substr ( $ value , 1 , - 1 );
2172+ // Get the variable value. To support expressions, we'll use a SQLite query.
2173+ $ value = $ this ->execute_sqlite_query (
2174+ sprintf ( ' SELECT %s ' , $ this -> translate ( $ value_node ) )
2175+ )-> fetchColumn ( );
21482176
21492177 if ( WP_MySQL_Lexer::SESSION_SYMBOL === $ type ) {
2178+ $ this ->system_session_variables [ $ name ] = $ value ;
2179+
21502180 if ( 'sql_mode ' === $ name ) {
21512181 $ modes = explode ( ', ' , strtoupper ( $ value ) );
21522182 $ this ->active_sql_modes = $ modes ;
@@ -2162,6 +2192,30 @@ private function execute_set_system_variable_statement(
21622192 // TODO: Handle GLOBAL, PERSIST, and PERSIST_ONLY types.
21632193 }
21642194
2195+ /**
2196+ * Translate and execute a MySQL SET statement for user variables.
2197+ *
2198+ * @param WP_Parser_Node $set_var_node The "internalVariableName" or "setSystemVariable" AST node.
2199+ * @param WP_Parser_Node $value_node The "setExprOrDefault" AST node.
2200+ * @throws WP_SQLite_Driver_Exception When the query execution fails.
2201+ */
2202+ private function execute_set_user_variable_statement (
2203+ WP_Parser_Node $ set_var_node ,
2204+ WP_Parser_Node $ value_node
2205+ ): void {
2206+ $ name = $ this ->unquote_sqlite_identifier (
2207+ $ this ->translate ( $ set_var_node ->get_first_child () )
2208+ );
2209+ $ name = strtolower ( substr ( $ name , 1 ) ); // Remove '@', normalize case.
2210+
2211+ // Get the variable value. To support expressions, we'll use a SQLite query.
2212+ $ value = $ this ->execute_sqlite_query (
2213+ sprintf ( 'SELECT %s ' , $ this ->translate ( $ value_node ) )
2214+ )->fetchColumn ();
2215+
2216+ $ this ->user_session_variables [ $ name ] = $ value ;
2217+ }
2218+
21652219 /**
21662220 * Translate and execute a MySQL administration statement in SQLite.
21672221 *
@@ -2365,6 +2419,18 @@ private function translate( $node ): ?string {
23652419 throw $ this ->new_not_supported_exception (
23662420 sprintf ( 'data type: %s ' , $ child ->get_value () )
23672421 );
2422+ case 'selectItem ' :
2423+ $ alias = $ node ->get_first_child_node ( 'selectAlias ' );
2424+ if ( null === $ alias ) {
2425+ return sprintf (
2426+ '%s AS %s ' ,
2427+ $ this ->translate ( $ node ->get_first_child_node ( 'expr ' ) ),
2428+ $ this ->quote_sqlite_identifier (
2429+ substr ( $ this ->last_mysql_query , $ node ->get_start (), $ node ->get_length () )
2430+ )
2431+ );
2432+ }
2433+ return $ this ->translate_sequence ( $ node ->get_children () );
23682434 case 'fromClause ' :
23692435 // FROM DUAL is MySQL-specific syntax that means "FROM no tables"
23702436 // and it is equivalent to omitting the FROM clause entirely.
@@ -2417,13 +2483,18 @@ private function translate( $node ): ?string {
24172483
24182484 // TODO: The '% AS %' syntax is compatible with SELECT lists only.
24192485 // We need to translate it differently when used as a value.
2486+ return $ value ;
24202487 return sprintf (
24212488 '%s AS %s ' ,
24222489 $ value ,
24232490 $ this ->quote_sqlite_identifier (
24242491 '@@ ' . ( $ type_token ? "{$ type_token ->get_value ()}. " : '' ) . $ original_name
24252492 )
24262493 );
2494+ case 'userVariable ' :
2495+ $ name = $ this ->unquote_sqlite_identifier ( $ this ->translate ( $ node ->get_first_child () ) );
2496+ $ name = strtolower ( substr ( $ name , 1 ) ); // Remove '@', normalize case.
2497+ return $ this ->user_session_variables [ $ name ] ?? 'NULL ' ;
24272498 case 'castType ' :
24282499 // Translate "CAST(... AS BINARY)" to "CAST(... AS BLOB)".
24292500 if ( $ node ->has_child_token ( WP_MySQL_Lexer::BINARY_SYMBOL ) ) {
@@ -2880,15 +2951,11 @@ private function translate_function_call( WP_Parser_Node $node ): string {
28802951 case 'CONCAT ' :
28812952 return '( ' . implode ( ' || ' , $ args ) . ') ' ;
28822953 case 'FOUND_ROWS ' :
2883- // @TODO: The following implementation with an alias assumes
2884- // that the function is used in the SELECT field list.
2885- // For compatibility with more complex use cases, it may
2886- // be better to register it as a custom SQLite function.
28872954 $ found_rows = $ this ->last_sql_calc_found_rows ;
28882955 if ( null === $ found_rows && is_array ( $ this ->last_result ) ) {
28892956 $ found_rows = count ( $ this ->last_result );
28902957 }
2891- return sprintf ( " (SELECT %d) AS 'FOUND_ROWS()' " , $ found_rows ) ;
2958+ return $ found_rows ;
28922959 default :
28932960 return $ this ->translate_sequence ( $ node ->get_children () );
28942961 }
0 commit comments