Skip to content

Commit 0340e23

Browse files
committed
wip
1 parent e49c578 commit 0340e23

3 files changed

Lines changed: 155 additions & 59 deletions

File tree

tests/WP_SQLite_Driver_Tests.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6020,4 +6020,33 @@ public function testDatabaseNameMismatchWithExistingInformationSchemaTableData()
60206020
$this->expectExceptionMessage( "Incorrect database name. The database was created with name 'db-one', but 'db-two' is used in the current session." );
60216021
new WP_SQLite_Driver( $connection, 'db-two' );
60226022
}
6023+
6024+
public function testUserVariables(): void {
6025+
$this->assertQuery( 'SET @my_var = 1' );
6026+
$result = $this->assertQuery( 'SELECT @my_var' );
6027+
$this->assertEquals( 1, $result[0]->{'@my_var'} );
6028+
6029+
$this->assertQuery( 'SET @my_var = @my_var + 1' );
6030+
$result = $this->assertQuery( 'SELECT @my_var' );
6031+
$this->assertEquals( 2, $result[0]->{'@my_var'} );
6032+
6033+
$this->assertQuery( 'SET @my_var = @my_var + 1' );
6034+
$result = $this->assertQuery( 'SELECT @my_var' );
6035+
$this->assertEquals( 3, $result[0]->{'@my_var'} );
6036+
}
6037+
6038+
public function testSetStatement(): void {
6039+
6040+
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
6041+
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
6042+
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
6043+
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
6044+
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
6045+
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
6046+
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
6047+
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
6048+
$this->assertQuery( 'SET NAMES utf8mb4' );
6049+
$this->assertQuery( "SET TIME_ZONE='+00:00'" );
6050+
$this->assertQuery( 'SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT' );
6051+
}
60236052
}

wp-includes/parser/class-wp-parser-node.php

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,34 @@ public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token
160160
}
161161

162162
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
163-
$nodes = array( $this );
164-
while ( count( $nodes ) ) {
165-
$node = array_shift( $nodes );
166-
$child = $node->get_first_child_node( $rule_name );
167-
if ( $child ) {
163+
for ( $i = 0; $i < count( $this->children ); $i++ ) {
164+
$child = $this->children[$i];
165+
if ( ! $child instanceof WP_Parser_Node ) {
166+
continue;
167+
}
168+
if ( null === $rule_name || $child->rule_name === $rule_name ) {
168169
return $child;
169170
}
170-
$children = $node->get_child_nodes();
171-
if ( count( $children ) > 0 ) {
172-
array_push( $nodes, ...$children );
171+
$node = $child->get_first_descendant_node( $rule_name );
172+
if ( $node ) {
173+
return $node;
173174
}
174175
}
175176
return null;
176177
}
177178

178179
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
179-
$nodes = array( $this );
180-
while ( count( $nodes ) ) {
181-
$node = array_shift( $nodes );
182-
$child = $node->get_first_child_token( $token_id );
183-
if ( $child ) {
184-
return $child;
185-
}
186-
$children = $node->get_child_nodes();
187-
if ( count( $children ) > 0 ) {
188-
array_push( $nodes, ...$children );
180+
for ( $i = 0; $i < count( $this->children ); $i++ ) {
181+
$child = $this->children[$i];
182+
if ( $child instanceof WP_Parser_Token ) {
183+
if ( null === $token_id || $child->id === $token_id ) {
184+
return $child;
185+
}
186+
} else {
187+
$token = $child->get_first_descendant_token( $token_id );
188+
if ( $token ) {
189+
return $token;
190+
}
189191
}
190192
}
191193
return null;
@@ -222,45 +224,43 @@ public function get_child_tokens( ?int $token_id = null ): array {
222224
}
223225

224226
public function get_descendants(): array {
225-
$nodes = array( $this );
226-
$all_descendants = array();
227-
while ( count( $nodes ) ) {
228-
$node = array_shift( $nodes );
229-
$all_descendants = array_merge( $all_descendants, $node->get_children() );
230-
$children = $node->get_child_nodes();
231-
if ( count( $children ) > 0 ) {
232-
array_push( $nodes, ...$children );
227+
$descendants = array();
228+
foreach ( $this->children as $child ) {
229+
if ( $child instanceof WP_Parser_Node ) {
230+
$descendants = array_merge( $descendants, $child->get_descendants() );
231+
} else {
232+
$descendants[] = $child;
233233
}
234234
}
235-
return $all_descendants;
235+
return $descendants;
236236
}
237237

238238
public function get_descendant_nodes( ?string $rule_name = null ): array {
239-
$nodes = array( $this );
240-
$all_descendants = array();
241-
while ( count( $nodes ) ) {
242-
$node = array_shift( $nodes );
243-
$all_descendants = array_merge( $all_descendants, $node->get_child_nodes( $rule_name ) );
244-
$children = $node->get_child_nodes();
245-
if ( count( $children ) > 0 ) {
246-
array_push( $nodes, ...$children );
239+
$nodes = array();
240+
foreach ( $this->children as $child ) {
241+
if ( ! $child instanceof WP_Parser_Node ) {
242+
continue;
247243
}
244+
if ( null === $rule_name || $child->rule_name === $rule_name ) {
245+
$nodes[] = $child;
246+
}
247+
$nodes = array_merge( $nodes, $child->get_descendant_nodes( $rule_name ) );
248248
}
249-
return $all_descendants;
249+
return $nodes;
250250
}
251251

252252
public function get_descendant_tokens( ?int $token_id = null ): array {
253-
$nodes = array( $this );
254-
$all_descendants = array();
255-
while ( count( $nodes ) ) {
256-
$node = array_shift( $nodes );
257-
$all_descendants = array_merge( $all_descendants, $node->get_child_tokens( $token_id ) );
258-
$children = $node->get_child_nodes();
259-
if ( count( $children ) > 0 ) {
260-
array_push( $nodes, ...$children );
253+
$tokens = array();
254+
foreach ( $this->children as $child ) {
255+
if ( $child instanceof WP_Parser_Token ) {
256+
if ( null === $token_id || $child->id === $token_id ) {
257+
$tokens[] = $child;
258+
}
259+
} else {
260+
$tokens = array_merge( $tokens, $child->get_descendant_tokens( $token_id ) );
261261
}
262262
}
263-
return $all_descendants;
263+
return $tokens;
264264
}
265265

266266
/**
@@ -278,10 +278,10 @@ public function get_start(): int {
278278
* @return int
279279
*/
280280
public function get_length(): int {
281-
$tokens = $this->get_descendant_tokens();
282-
$last_token = end( $tokens );
283-
$start = $this->get_start();
284-
return $last_token->start + $last_token->length - $start;
281+
$tokens = $this->get_descendant_tokens();
282+
$first_token = reset( $tokens );
283+
$last_token = end( $tokens );
284+
return $last_token->start + $last_token->length - $first_token->start;
285285
}
286286

287287
/*

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

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)