Skip to content

Commit 4869fd4

Browse files
committed
Add support for MySQL user variables
1 parent 899f50c commit 4869fd4

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

tests/WP_SQLite_Driver_Tests.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6055,4 +6055,18 @@ public function testSetStatement(): void {
60556055
$this->assertQuery( 'SET CHARSET utf8mb4' );
60566056
$this->assertQuery( 'SET CHARACTER SET utf8mb4' );
60576057
}
6058+
6059+
public function testUserVariables(): void {
6060+
$this->assertQuery( 'SET @my_var = 1' );
6061+
$result = $this->assertQuery( 'SELECT @my_var' );
6062+
$this->assertEquals( 1, $result[0]->{'@my_var'} );
6063+
6064+
$this->assertQuery( 'SET @my_var = @my_var + 1' );
6065+
$result = $this->assertQuery( 'SELECT @my_var' );
6066+
$this->assertEquals( 2, $result[0]->{'@my_var'} );
6067+
6068+
$this->assertQuery( 'SET @my_var = @my_var + 1' );
6069+
$result = $this->assertQuery( 'SELECT @my_var' );
6070+
$this->assertEquals( 3, $result[0]->{'@my_var'} );
6071+
}
60586072
}

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

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,18 @@ class WP_SQLite_Driver {
425425
'STRICT_TRANS_TABLES',
426426
);
427427

428+
/**
429+
* A name-to-value map of MySQL user variables.
430+
*
431+
* MySQL user variables are session-specific, so we can store them in-memory.
432+
*
433+
* See:
434+
* https://dev.mysql.com/doc/refman/8.4/en/user-variables.html
435+
*
436+
* @var array<string, string>
437+
*/
438+
private $user_variables = array();
439+
428440
/**
429441
* Constructor.
430442
*
@@ -2109,11 +2121,19 @@ private function execute_set_statement( WP_Parser_Node $node ): void {
21092121
|| 'setSystemVariable' === $part->rule_name
21102122
)
21112123
) {
2124+
// Set a system variable.
21122125
array_shift( $definition ); // Remove the '='.
21132126
$value = array_shift( $definition );
21142127
$this->execute_set_system_variable_statement( $part, $value, $default_type );
2128+
} elseif (
2129+
$part instanceof WP_Parser_Node
2130+
&& 'userVariable' === $part->rule_name
2131+
) {
2132+
// Set a user variable.
2133+
array_shift( $definition ); // Remove the '='.
2134+
$value = array_shift( $definition );
2135+
$this->execute_set_user_variable_statement( $part, $value );
21152136
} else {
2116-
// TODO: Support user variables (in-memory or a temporary table).
21172137
throw $this->new_not_supported_exception(
21182138
sprintf( 'SET statement: %s', $node->rule_name )
21192139
);
@@ -2176,6 +2196,26 @@ private function execute_set_system_variable_statement(
21762196
// TODO: Handle GLOBAL, PERSIST, and PERSIST_ONLY types.
21772197
}
21782198

2199+
/**
2200+
* Translate and execute a MySQL SET statement for user variables.
2201+
*
2202+
* @param WP_Parser_Node $user_variable The "userVariable" AST node.
2203+
* @param WP_Parser_Node $expr The "expr" AST node.
2204+
* @throws WP_SQLite_Driver_Exception When the query execution fails.
2205+
*/
2206+
private function execute_set_user_variable_statement(
2207+
WP_Parser_Node $user_variable,
2208+
WP_Parser_Node $expr
2209+
): void {
2210+
$name = $this->unquote_sqlite_identifier(
2211+
$this->translate( $user_variable->get_first_child() )
2212+
);
2213+
$name = strtolower( substr( $name, 1 ) ); // Remove '@', normalize case.
2214+
$value = $this->evaluate_expression( $expr );
2215+
2216+
$this->user_variables[ $name ] = $value;
2217+
}
2218+
21792219
/**
21802220
* Translate and execute a MySQL administration statement in SQLite.
21812221
*
@@ -2258,6 +2298,33 @@ private function execute_administration_statement( WP_Parser_Node $node ): void
22582298
$this->set_results_from_fetched_data( $results );
22592299
}
22602300

2301+
/**
2302+
* Evaluate an expression and return the value, preserving its type.
2303+
*
2304+
* This is used to support expressions in SET statements for MySQL variables.
2305+
*
2306+
* @param WP_Parser_Node $node The "expr" AST node.
2307+
* @return mixed The value of the expression.
2308+
*/
2309+
public function evaluate_expression( WP_Parser_Node $node ) {
2310+
// To support expressions, we'll use a SQLite query.
2311+
$stmt = $this->execute_sqlite_query(
2312+
sprintf( 'SELECT %s', $this->translate( $node ) )
2313+
);
2314+
2315+
// MySQL variables are typed, so we need to preserve the value type.
2316+
$value = $stmt->fetchColumn();
2317+
$type = $stmt->getColumnMeta( 0 )['native_type'];
2318+
if ( 'null' === $type ) {
2319+
return null;
2320+
} elseif ( 'integer' === $type ) {
2321+
return (int) $value;
2322+
} elseif ( 'double' === $type ) {
2323+
return (float) $value;
2324+
}
2325+
return $value;
2326+
}
2327+
22612328
/**
22622329
* Translate a MySQL AST node or token to an SQLite query fragment.
22632330
*
@@ -2428,6 +2495,17 @@ private function translate( $node ): ?string {
24282495
// See: https://dev.mysql.com/doc/refman/8.4/en/server-system-variable-reference.html
24292496
// See: https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html
24302497
return $value;
2498+
case 'userVariable':
2499+
$name = $this->unquote_sqlite_identifier( $this->translate( $node->get_first_child() ) );
2500+
$name = strtolower( substr( $name, 1 ) ); // Remove '@', normalize case.
2501+
$value = $this->user_variables[ $name ] ?? null;
2502+
if ( null === $value ) {
2503+
return 'NULL';
2504+
}
2505+
if ( is_string( $value ) ) {
2506+
return $this->connection->quote( $value );
2507+
}
2508+
return (string) $value;
24312509
case 'castType':
24322510
// Translate "CAST(... AS BINARY)" to "CAST(... AS BLOB)".
24332511
if ( $node->has_child_token( WP_MySQL_Lexer::BINARY_SYMBOL ) ) {

0 commit comments

Comments
 (0)