Skip to content
Open
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
52 changes: 39 additions & 13 deletions packages/mysql-on-sqlite/src/mysql/class-wp-mysql-lexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class WP_MySQL_Lexer {
const SQL_MODE_PIPES_AS_CONCAT = 2;
const SQL_MODE_IGNORE_SPACE = 4;
const SQL_MODE_NO_BACKSLASH_ESCAPES = 8;
const SQL_MODE_ANSI_QUOTES = 16;

/**
* Character masks for frequently used character classes.
Expand Down Expand Up @@ -2209,6 +2210,19 @@ public function __construct(
$this->sql_modes |= self::SQL_MODE_IGNORE_SPACE;
} elseif ( 'NO_BACKSLASH_ESCAPES' === $sql_mode ) {
$this->sql_modes |= self::SQL_MODE_NO_BACKSLASH_ESCAPES;
} elseif ( 'ANSI_QUOTES' === $sql_mode ) {
$this->sql_modes |= self::SQL_MODE_ANSI_QUOTES;
} elseif ( 'ANSI' === $sql_mode ) {
/*
* Expand the composite ANSI mode into its lexer-relevant components.
* The ANSI mode also implies REAL_AS_FLOAT and ONLY_FULL_GROUP_BY,
* which do not affect the lexer.
*
* See: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_ansi
*/
$this->sql_modes |= self::SQL_MODE_PIPES_AS_CONCAT
| self::SQL_MODE_IGNORE_SPACE
| self::SQL_MODE_ANSI_QUOTES;
}
}
}
Expand Down Expand Up @@ -2891,15 +2905,30 @@ private function read_number(): ?int {
*
* Rules:
* 1. Quotes can be escaped by doubling them ('', "", ``).
* 2. Backslashes escape the next character, unless NO_BACKSLASH_ESCAPES is set.
* 2. In string literals, backslashes escape the next character,
* unless the NO_BACKSLASH_ESCAPES SQL mode is set.
* 3. In identifiers, backslashes are always literal and never escape.
*/
private function read_quoted_text(): ?int {
$quote = $this->sql[ $this->bytes_already_read ];
$this->bytes_already_read += 1; // Consume the quote.

$no_backslash_escapes = $this->is_sql_mode_active(
self::SQL_MODE_NO_BACKSLASH_ESCAPES
);
/*
* Determine whether the quote opens an identifier or a string literal.
* An identifier is quoted with a backtick or a double quote when the
* ANSI_QUOTES SQL mode is active. Otherwise, it is a string literal.
*
* See: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html#sqlmode_ansi_quotes
*/
$is_identifier_quote = '`' === $quote
|| ( '"' === $quote && $this->is_sql_mode_active( self::SQL_MODE_ANSI_QUOTES ) );

/*
* Backslash escapes apply only to string literals, and only when the
* NO_BACKSLASH_ESCAPES SQL mode is not set.
*/
$backslash_is_escape = ! $is_identifier_quote
&& ! $this->is_sql_mode_active( self::SQL_MODE_NO_BACKSLASH_ESCAPES );

// We need to look for the closing quote in a loop, as it can be escaped,
// in which case the escape sequence is consumed and the loop continues.
Expand All @@ -2912,9 +2941,9 @@ private function read_quoted_text(): ?int {
$at = $quote_at;

/*
* By default, quotes can be escaped with a "\".
* When NO_BACKSLASH_ESCAPES SQL mode is active, the "\" treated as
* a regular character.
* In string literals, quotes can be escaped with a backslash. When
* NO_BACKSLASH_ESCAPES SQL mode is active, the backslash is treated
* as a regular character. Identifiers never use backslash escaping.
*
* The quote is escaped only when the number of preceding backslashes
* is odd - "\" is an escape sequence, "\\" is an escaped backslash,
Expand All @@ -2925,7 +2954,7 @@ private function read_quoted_text(): ?int {
* sits at the very start of the input. The `?? null` covers
* positive out-of-range indexes belt-and-suspenders.
*/
if ( ! $no_backslash_escapes ) {
if ( $backslash_is_escape ) {
$i = 0;
while ( ( $at - $i - 1 ) >= 0 && '\\' === ( $this->sql[ $at - $i - 1 ] ?? null ) ) {
$i += 1;
Expand All @@ -2948,13 +2977,10 @@ private function read_quoted_text(): ?int {

$this->bytes_already_read = $at;

if ( '`' === $quote ) {
if ( $is_identifier_quote ) {
return self::BACK_TICK_QUOTED_ID;
} elseif ( '"' === $quote ) {
return self::DOUBLE_QUOTED_TEXT;
} else {
return self::SINGLE_QUOTED_TEXT;
}
return '"' === $quote ? self::DOUBLE_QUOTED_TEXT : self::SINGLE_QUOTED_TEXT;
}

private function read_line_comment(): int {
Expand Down
11 changes: 7 additions & 4 deletions packages/mysql-on-sqlite/src/mysql/class-wp-mysql-token.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,14 @@ public function get_value(): string {
$value = substr( $value, 1, -1 );

/*
* When the NO_BACKSLASH_ESCAPES SQL mode is enabled, we only need to
* handle escaped bounding quotes, as the other characters preserve
* their literal values.
* For quoted identifiers and when the NO_BACKSLASH_ESCAPES SQL mode
* is active, we only need to handle escaped bounding quotes, as all
* other characters preserve their literal values.
*/
if ( $this->sql_mode_no_backslash_escapes_enabled ) {
if (
WP_MySQL_Lexer::BACK_TICK_QUOTED_ID === $this->id
|| $this->sql_mode_no_backslash_escapes_enabled
) {
return str_replace( $quote . $quote, $quote, $value );
}

Expand Down
Loading
Loading