Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public static function register_for( $pdo ): self {
'localtimestamp' => 'now',
'isnull' => 'isnull',
'regexp' => 'regexp',
'regexp_like' => 'regexp_like',
'field' => 'field',
'log' => 'log',
'least' => 'least',
Expand Down Expand Up @@ -565,12 +566,15 @@ public function isnull( $field ) {
}

/**
* Method to emulate MySQL REGEXP() function.
* Method to emulate MySQL REGEXP operator.
*
* @param string $pattern Regular expression to match.
* @param string $field Haystack.
* SQLite runs "X REGEXP Y" as "regexp(Y, X)". MySQL runs the operator
* as REGEXP_LIKE(), so this method uses regexp_like().
*
* @return integer 1 if matched, 0 if not matched.
* @param string|null $pattern Regular expression to match.
* @param string|null $field Haystack.
*
* @return int|null 1 if matched, 0 if not matched, NULL if an argument is NULL.
*/
public function regexp( $pattern, $field ) {
/*
Expand All @@ -587,17 +591,62 @@ public function regexp( $pattern, $field ) {
* be reasonably safe since PHP does not allow null bytes in
* regular expressions anyway.
*/
if ( "\x00" === $pattern[0] ) {
$pattern = substr( $pattern, 1 );
$flags = '';
} else {
// Otherwise, the search is case-insensitive.
$flags = 'i';
if ( is_string( $pattern ) && 0 === strpos( $pattern, "\x00" ) ) {
return $this->regexp_like( $field, substr( $pattern, 1 ), 'c' );
}
return $this->regexp_like( $field, $pattern );
}

/**
* Method to emulate MySQL REGEXP_LIKE() function.
*
* @param string|null $subject Haystack.
* @param string|null $pattern Regular expression to match.
* @param string|null $match_type Optional. Letters that change how the pattern matches.
* Without "c" or "i", the match is case-insensitive.
*
* @throws Exception When the match type has an unknown letter.
* @return int|null 1 if matched, 0 if not matched, NULL if an argument is NULL.
*/
public function regexp_like( $subject, $pattern, $match_type = '' ) {
if ( null === $match_type ) {
return null;
}

/*
* Apply the letters in order, so that the last of "c" and "i" wins, like in MySQL.
* The "u" letter needs no flag, as PCRE treats only "\n" as a line break by default.
*/
$case_insensitive = true;
$flags = '';
$length = strlen( $match_type );
for ( $i = 0; $i < $length; $i++ ) {
$letter = $match_type[ $i ];
if ( 'c' === $letter ) {
$case_insensitive = false;
} elseif ( 'i' === $letter ) {
$case_insensitive = true;
} elseif ( 'm' === $letter ) {
$flags .= 'm';
} elseif ( 'n' === $letter ) {
$flags .= 's';
} elseif ( 'u' !== $letter ) {
throw new Exception( 'Incorrect arguments to regexp_like' );
}
}
if ( $case_insensitive ) {
$flags .= 'i';
}

// Like MySQL, reject an invalid match type even for a NULL subject or pattern.
if ( null === $subject || null === $pattern ) {
return null;
}

$pattern = str_replace( '/', '\/', $pattern );
$pattern = '/' . $pattern . '/' . $flags;

return preg_match( $pattern, $field );
return preg_match( $pattern, $subject );
Comment thread
JanJakes marked this conversation as resolved.
Resolved
}

/**
Expand Down
46 changes: 46 additions & 0 deletions packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,52 @@ public static function regexpOperators() {
);
}

/**
* @dataProvider regexpLikeQueries
*/
public function testRegexpLike( $query, $expected_result ) {
$this->assertQuery( $query );
$this->assertSame( $expected_result, $this->last_result[0]->result );
}

public static function regexpLikeQueries() {
return array(
'no match type' => array( "SELECT REGEXP_LIKE('ABC', 'b') AS result", '1' ),
'empty match type' => array( "SELECT REGEXP_LIKE('ABC', 'b', '') AS result", '1' ),
'case-sensitive' => array( "SELECT REGEXP_LIKE('ABC', 'b', 'c') AS result", '0' ),
'case-insensitive' => array( "SELECT REGEXP_LIKE('ABC', 'b', 'i') AS result", '1' ),
'last "i" wins' => array( "SELECT REGEXP_LIKE('ABC', 'b', 'ci') AS result", '1' ),
'last "c" wins' => array( "SELECT REGEXP_LIKE('ABC', 'b', 'ic') AS result", '0' ),
'single line' => array( "SELECT REGEXP_LIKE('a\\nb', '^b') AS result", '0' ),
'multiline' => array( "SELECT REGEXP_LIKE('a\\nb', '^b', 'm') AS result", '1' ),
'dot without line break' => array( "SELECT REGEXP_LIKE('a\\nb', 'a.b') AS result", '0' ),
'dot with line break' => array( "SELECT REGEXP_LIKE('a\\nb', 'a.b', 'n') AS result", '1' ),
'Unix line breaks' => array( "SELECT REGEXP_LIKE('a\\nb', '^b', 'mu') AS result", '1' ),
'repeated letters' => array( "SELECT REGEXP_LIKE('a\\nb', '^b', 'mm') AS result", '1' ),
'NULL subject' => array( "SELECT REGEXP_LIKE(NULL, 'b') AS result", null ),
'NULL pattern' => array( "SELECT REGEXP_LIKE('ABC', NULL) AS result", null ),
'NULL match type' => array( "SELECT REGEXP_LIKE('ABC', 'b', NULL) AS result", null ),
'NULL REGEXP subject' => array( "SELECT NULL REGEXP 'b' AS result", null ),
'NULL REGEXP pattern' => array( "SELECT 'ABC' REGEXP NULL AS result", null ),
'NULL NOT REGEXP' => array( "SELECT NULL NOT REGEXP 'b' AS result", null ),
);
}

/**
* @dataProvider regexpLikeInvalidMatchTypeQueries
*/
public function testRegexpLikeWithInvalidMatchType( $query ) {
$this->assertQueryError( $query, 'Incorrect arguments to regexp_like' );
}

public static function regexpLikeInvalidMatchTypeQueries() {
return array(
'subject and pattern' => array( "SELECT REGEXP_LIKE('ABC', 'b', 'x')" ),
'NULL subject' => array( "SELECT REGEXP_LIKE(NULL, 'b', 'x')" ),
'NULL pattern' => array( "SELECT REGEXP_LIKE('ABC', NULL, 'x')" ),
);
}

public function testInsertDateNow() {
$this->assertQuery(
"INSERT INTO _dates (option_name, option_value) VALUES ('first', now());"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

require_once __DIR__ . '/WP_MySQL_On_SQLite_Regexp_Test_Case.php';

/**
* Check REGEXP_INSTR() against MySQL's results for the REGEXP test cases.
*/
class WP_MySQL_On_SQLite_Regexp_Instr_Tests extends WP_MySQL_On_SQLite_Regexp_Test_Case {
const CHECKS = array( 'regexp_instr', 'regexp_instr_end' );

protected function setUp(): void {
$this->markTestSkipped( 'REGEXP_INSTR() is not supported yet.' );
}
}
Loading
Loading