Skip to content

Commit 46528e8

Browse files
kozeraristathjeroenpfcostasovo
authored
Sqlite: Fix LIKE BINARY queries (#149)
This PR modifies the SQLite database integration plugin ensuring LIKE BINARY queries are supported. The translator has been updated to utilize the `GLOB` SQLite function to support the `LIKE BINARY` clause in Sqlite, ensuring it respects case sensitivity. ### Example query **MySQL** ```sql SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f%' ``` **SQLite** ```sql SELECT * FROM _tmp_table WHERE name GLOB 'f*' ``` --------- Co-authored-by: Ari Stathopoulos <[email protected]> Co-authored-by: Jeroen P <[email protected]> Co-authored-by: Rostislav Wolný <[email protected]>
1 parent dd6bd6b commit 46528e8

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

tests/WP_SQLite_Translator_Tests.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,6 +2895,91 @@ public function testTranslatesUtf8SELECT() {
28952895
$this->assertQuery( 'DELETE FROM _options' );
28962896
}
28972897

2898+
public function testTranslateLikeBinaryAndGlob() {
2899+
// Create a temporary table for testing
2900+
$this->assertQuery(
2901+
"CREATE TABLE _tmp_table (
2902+
ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
2903+
name varchar(20) NOT NULL default ''
2904+
);"
2905+
);
2906+
2907+
// Insert data into the table
2908+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('first');" );
2909+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('FIRST');" );
2910+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('second');" );
2911+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('');" );
2912+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('%special%');" );
2913+
$this->assertQuery( 'INSERT INTO _tmp_table (name) VALUES (NULL);' );
2914+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special%chars');" );
2915+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special_chars');" );
2916+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('special\\chars');" );
2917+
2918+
// Test case-sensitive LIKE BINARY
2919+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'first'" );
2920+
$this->assertCount( 1, $result );
2921+
$this->assertEquals( 'first', $result[0]->name );
2922+
2923+
// Test case-sensitive LIKE BINARY with wildcard %
2924+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f%'" );
2925+
$this->assertCount( 1, $result );
2926+
$this->assertEquals( 'first', $result[0]->name );
2927+
2928+
// Test case-sensitive LIKE BINARY with wildcard _
2929+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'f_rst'" );
2930+
$this->assertCount( 1, $result );
2931+
$this->assertEquals( 'first', $result[0]->name );
2932+
2933+
// Test case-insensitive LIKE
2934+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE 'FIRST'" );
2935+
$this->assertCount( 2, $result ); // Should match both 'first' and 'FIRST'
2936+
2937+
// Test mixed case with LIKE BINARY
2938+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'First'" );
2939+
$this->assertCount( 0, $result );
2940+
2941+
// Test no matches with LIKE BINARY
2942+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'third'" );
2943+
$this->assertCount( 0, $result );
2944+
2945+
// Test GLOB equivalent for case-sensitive matching with wildcard
2946+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'f*'" );
2947+
$this->assertCount( 1, $result );
2948+
$this->assertEquals( 'first', $result[0]->name );
2949+
2950+
// Test GLOB with single character wildcard
2951+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'f?rst'" );
2952+
$this->assertCount( 1, $result );
2953+
$this->assertEquals( 'first', $result[0]->name );
2954+
2955+
// Test GLOB with no matches
2956+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'S*'" );
2957+
$this->assertCount( 0, $result );
2958+
2959+
// Test GLOB case sensitivity with LIKE and GLOB
2960+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'first';" );
2961+
$this->assertCount( 1, $result ); // Should only match 'first'
2962+
2963+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name GLOB 'FIRST';" );
2964+
$this->assertCount( 1, $result ); // Should only match 'FIRST'
2965+
2966+
// Test NULL comparison with LIKE BINARY
2967+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY 'first';" );
2968+
$this->assertCount( 1, $result );
2969+
$this->assertEquals( 'first', $result[0]->name );
2970+
2971+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE name LIKE BINARY NULL;' );
2972+
$this->assertCount( 0, $result ); // NULL comparison should return no results
2973+
2974+
// Test pattern with special characters using LIKE BINARY
2975+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name LIKE BINARY '%special%';" );
2976+
$this->assertCount( 4, $result );
2977+
$this->assertEquals( '%special%', $result[0]->name );
2978+
$this->assertEquals( 'special%chars', $result[1]->name );
2979+
$this->assertEquals( 'special_chars', $result[2]->name );
2980+
$this->assertEquals( 'specialchars', $result[3]->name );
2981+
}
2982+
28982983
public function testOnConflictReplace() {
28992984
$this->assertQuery(
29002985
"CREATE TABLE _tmp_table (

wp-includes/sqlite/class-wp-sqlite-translator.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,7 @@ private function translate_expression( $token ) {
20682068
|| $this->translate_regexp_functions( $token )
20692069
|| $this->capture_group_by( $token )
20702070
|| $this->translate_ungrouped_having( $token )
2071+
|| $this->translate_like_binary( $token )
20712072
|| $this->translate_like_escape( $token )
20722073
|| $this->translate_left_function( $token )
20732074
);
@@ -2592,6 +2593,57 @@ private function translate_regexp_functions( $token ) {
25922593
}
25932594
return true;
25942595
}
2596+
/**
2597+
* Translate LIKE BINARY to SQLite equivalent using GLOB.
2598+
*
2599+
* @param WP_SQLite_Token $token The token to translate.
2600+
*
2601+
* @return bool
2602+
*/
2603+
private function translate_like_binary( $token ): bool {
2604+
if ( ! $token->matches( WP_SQLite_Token::TYPE_KEYWORD, null, array( 'LIKE' ) ) ) {
2605+
return false;
2606+
}
2607+
2608+
$next = $this->rewriter->peek_nth( 2 );
2609+
if ( ! $next || ! $next->matches( WP_SQLite_Token::TYPE_KEYWORD, null, array( 'BINARY' ) ) ) {
2610+
return false;
2611+
}
2612+
2613+
$this->rewriter->skip(); // Skip 'LIKE'
2614+
$this->rewriter->skip(); // Skip 'BINARY'
2615+
2616+
$pattern_token = $this->rewriter->peek();
2617+
$this->rewriter->skip(); // Skip the pattern token
2618+
2619+
$this->rewriter->add( new WP_SQLite_Token( 'GLOB', WP_SQLite_Token::TYPE_KEYWORD ) );
2620+
$this->rewriter->add( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ) );
2621+
2622+
$escaped_pattern = $this->escape_like_to_glob( $pattern_token->value );
2623+
$this->rewriter->add( new WP_SQLite_Token( $escaped_pattern, WP_SQLite_Token::TYPE_STRING ) );
2624+
$this->rewriter->add( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_WHITESPACE ) );
2625+
2626+
return true;
2627+
}
2628+
2629+
/**
2630+
* Escape LIKE pattern to GLOB pattern.
2631+
*
2632+
* @param string $pattern The LIKE pattern.
2633+
* @return string The escaped GLOB pattern.
2634+
*/
2635+
private function escape_like_to_glob( $pattern ) {
2636+
// Remove surrounding quotes
2637+
$pattern = trim( $pattern, "'\"" );
2638+
2639+
$pattern = str_replace( '%', '*', $pattern );
2640+
$pattern = str_replace( '_', '?', $pattern );
2641+
2642+
// No need to escape special characters in this case
2643+
// because GLOB doesn't require escaping in the same way LIKE does
2644+
// Return the pattern wrapped in single quotes
2645+
return "'" . $pattern . "'";
2646+
}
25952647

25962648
/**
25972649
* Detect GROUP BY.

0 commit comments

Comments
 (0)