Skip to content

Commit 174916a

Browse files
committed
Add support for LIKE BINARY
1 parent f5a52ca commit 174916a

File tree

3 files changed

+250
-78
lines changed

3 files changed

+250
-78
lines changed

tests/WP_SQLite_Driver_Tests.php

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,13 +3033,13 @@ public function testTranslatesUtf8SELECT() {
30333033
$this->assertQuery( 'DELETE FROM _options' );
30343034
}
30353035

3036-
public function testTranslateLikeBinaryAndGlob() {
3036+
public function testTranslateLikeBinary() {
30373037
// Create a temporary table for testing
30383038
$this->assertQuery(
3039-
"CREATE TABLE _tmp_table (
3040-
ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
3041-
name varchar(20) NOT NULL default ''
3042-
);"
3039+
'CREATE TABLE _tmp_table (
3040+
ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL,
3041+
name varchar(20)
3042+
)'
30433043
);
30443044

30453045
// Insert data into the table
@@ -3052,70 +3052,111 @@ public function testTranslateLikeBinaryAndGlob() {
30523052
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special%chars');" );
30533053
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special_chars');" );
30543054
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special\\chars');" );
3055+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('aste*risk');" );
3056+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('question?mark');" );
30553057

3056-
// Test case-sensitive LIKE BINARY
3058+
// Test exact string
30573059
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'first'" );
30583060
$this->assertCount( 1, $result );
30593061
$this->assertEquals( 'first', $result[0]->name );
30603062

3061-
// Test case-sensitive LIKE BINARY with wildcard %
3063+
// Test exact string with no matches
3064+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'third'" );
3065+
$this->assertCount( 0, $result );
3066+
3067+
// Test mixed case
3068+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'First'" );
3069+
$this->assertCount( 0, $result );
3070+
3071+
// Test % wildcard
30623072
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f%'" );
30633073
$this->assertCount( 1, $result );
30643074
$this->assertEquals( 'first', $result[0]->name );
30653075

3066-
// Test case-sensitive LIKE BINARY with wildcard _
3076+
// Test % wildcard with no matches
3077+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'x%'" );
3078+
$this->assertCount( 0, $result );
3079+
3080+
// Test "%" character (not a wildcard)
3081+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'special\\%chars'" );
3082+
$this->assertCount( 1, $result );
3083+
$this->assertEquals( 'special%chars', $result[0]->name );
3084+
3085+
// Test _ wildcard
30673086
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f_rst'" );
30683087
$this->assertCount( 1, $result );
30693088
$this->assertEquals( 'first', $result[0]->name );
30703089

3071-
// Test case-insensitive LIKE
3072-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE 'FIRST'" );
3073-
$this->assertCount( 2, $result ); // Should match both 'first' and 'FIRST'
3074-
3075-
// Test mixed case with LIKE BINARY
3076-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'First'" );
3077-
$this->assertCount( 0, $result );
3078-
3079-
// Test no matches with LIKE BINARY
3080-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'third'" );
3090+
// Test _ wildcard with no matches
3091+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'x_yz'" );
30813092
$this->assertCount( 0, $result );
30823093

3083-
// Test GLOB equivalent for case-sensitive matching with wildcard
3084-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'f*'" );
3094+
// Test "_" character (not a wildcard)
3095+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'special\\_chars'" );
30853096
$this->assertCount( 1, $result );
3086-
$this->assertEquals( 'first', $result[0]->name );
3097+
$this->assertEquals( 'special_chars', $result[0]->name );
30873098

3088-
// Test GLOB with single character wildcard
3089-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'f?rst'" );
3099+
// Test escaping of "*"
3100+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'aste*risk'" );
30903101
$this->assertCount( 1, $result );
3091-
$this->assertEquals( 'first', $result[0]->name );
3102+
$this->assertEquals( 'aste*risk', $result[0]->name );
30923103

3093-
// Test GLOB with no matches
3094-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'S*'" );
3104+
// Test escaping of "*" with no matches
3105+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f*'" );
30953106
$this->assertCount( 0, $result );
30963107

3097-
// Test GLOB case sensitivity with LIKE and GLOB
3098-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'first';" );
3099-
$this->assertCount( 1, $result ); // Should only match 'first'
3108+
// Test escaping of "?"
3109+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'question?mark'" );
3110+
$this->assertCount( 1, $result );
3111+
$this->assertEquals( 'question?mark', $result[0]->name );
31003112

3101-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'FIRST';" );
3102-
$this->assertCount( 1, $result ); // Should only match 'FIRST'
3113+
// Test escaping of "?" with no matches
3114+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f?rst'" );
3115+
$this->assertCount( 0, $result );
31033116

3104-
// Test NULL comparison with LIKE BINARY
3105-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'first';" );
3106-
$this->assertCount( 1, $result );
3107-
$this->assertEquals( 'first', $result[0]->name );
3117+
// Test escaping of character class
3118+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY '[f]irst'" );
3119+
$this->assertCount( 0, $result );
31083120

3109-
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE name LIKE BINARY NULL;' );
3110-
$this->assertCount( 0, $result ); // NULL comparison should return no results
3121+
// Test NULL
3122+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE name LIKE BINARY NULL' );
3123+
$this->assertCount( 0, $result );
31113124

31123125
// Test pattern with special characters using LIKE BINARY
3113-
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY '%special%';" );
3126+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY '%special%'" );
31143127
$this->assertCount( 4, $result );
31153128
$this->assertEquals( '%special%', $result[0]->name );
31163129
$this->assertEquals( 'special%chars', $result[1]->name );
31173130
$this->assertEquals( 'special_chars', $result[2]->name );
3118-
$this->assertEquals( 'specialchars', $result[3]->name );
3131+
$this->assertEquals( 'special\chars', $result[3]->name );
3132+
3133+
// Test escaping - "\t" is a tab character
3134+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'firs\\t'" );
3135+
$this->assertCount( 0, $result );
3136+
3137+
// Test escaping - "\\t" is "t" (input resolves to "\t", which LIKE resolves to "t")
3138+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'firs\\\\t'" );
3139+
$this->assertCount( 1, $result );
3140+
$this->assertEquals( 'first', $result[0]->name );
3141+
3142+
// Test escaping - "\%" is a "%" literal
3143+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'special\\%chars'" );
3144+
$this->assertCount( 1, $result );
3145+
$this->assertEquals( 'special%chars', $result[0]->name );
3146+
3147+
// Test escaping - "\\%" is also a "%" literal
3148+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'special\\\\%chars'" );
3149+
$this->assertCount( 1, $result );
3150+
$this->assertEquals( 'special%chars', $result[0]->name );
3151+
3152+
// Test escaping - "\\\%" is "\" and a wildcard
3153+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'special\\\\\\%chars'" );
3154+
$this->assertCount( 1, $result );
3155+
$this->assertEquals( 'special\\chars', $result[0]->name );
3156+
3157+
// Test LIKE without BINARY
3158+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE 'FIRST'" );
3159+
$this->assertCount( 2, $result ); // Should match both 'first' and 'FIRST'
31193160
}
31203161

31213162
public function testOnConflictReplace() {

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,12 @@ private function translate( $ast ) {
13131313
throw $this->not_supported_exception(
13141314
sprintf( 'data type: %s', $child->value )
13151315
);
1316+
case 'predicateOperations':
1317+
$token = $ast->get_child_token();
1318+
if ( WP_MySQL_Lexer::LIKE_SYMBOL === $token->id ) {
1319+
return $this->translate_like( $ast );
1320+
}
1321+
return $this->translate_sequence( $ast->get_children() );
13161322
case 'systemVariable':
13171323
// @TODO: Emulate some system variables, or use reasonable defaults.
13181324
// See: https://dev.mysql.com/doc/refman/8.4/en/server-system-variable-reference.html
@@ -1346,6 +1352,13 @@ private function translate_token( WP_MySQL_Token $token ) {
13461352
return '"' . trim( $token->value, '`"' ) . '"';
13471353
case WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL:
13481354
return 'AUTOINCREMENT';
1355+
case WP_MySQL_Lexer::BINARY_SYMBOL:
1356+
/*
1357+
* There is no "BINARY expr" equivalent in SQLite. We can look for
1358+
* the BINARY keyword in particular cases (with REGEXP, LIKE, etc.)
1359+
* and then remove it from the translated output here.
1360+
*/
1361+
return null;
13491362
default:
13501363
return $token->value;
13511364
}
@@ -1370,6 +1383,39 @@ private function translate_sequence( array $nodes, string $separator = ' ' ): ?s
13701383
return implode( $separator, $parts );
13711384
}
13721385

1386+
private function translate_like( WP_Parser_Node $node ): string {
1387+
$tokens = $node->get_descendant_tokens();
1388+
$is_binary = isset( $tokens[1] ) && WP_MySQL_Lexer::BINARY_SYMBOL === $tokens[1]->id;
1389+
1390+
if ( true === $is_binary ) {
1391+
$children = $node->get_children();
1392+
return sprintf(
1393+
'GLOB _helper_like_to_glob_pattern(%s)',
1394+
$this->translate( $children[1] )
1395+
);
1396+
}
1397+
1398+
/*
1399+
* @TODO: Implement the ESCAPE '...' clause.
1400+
*/
1401+
1402+
/*
1403+
* @TODO: Implement more correct LIKE behavior.
1404+
*
1405+
* While SQLite supports the LIKE operator, it seems to differ from the
1406+
* MySQL behavior in some ways:
1407+
*
1408+
* 1. In SQLite, LIKE is case-insensitive only for ASCII characters
1409+
* ('a' LIKE 'A' is TRUE but 'æ' LIKE 'Æ' is FALSE)
1410+
* 2. In MySQL, LIKE interprets some escape sequences. See the contents
1411+
* of the "_helper_like_to_glob_pattern" function.
1412+
*
1413+
* We'll probably need to overload the like() function:
1414+
* https://www.sqlite.org/lang_corefunc.html#like
1415+
*/
1416+
return $this->translate_sequence( $node->get_children() );
1417+
}
1418+
13731419
private function get_sqlite_create_table_statement( string $table_name, ?string $new_table_name = null ): array {
13741420
// 1. Get table info.
13751421
$table_info = $this->execute_sqlite_query(

0 commit comments

Comments
 (0)