Skip to content
Draft
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
49 changes: 36 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,18 @@ 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 ) {
/*
* The composite 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 +2904,26 @@ 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. In quoted identifiers, backslashes
* are always literal; only doubling the quote escapes it.
*/
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
);
/*
* A backtick always quotes an identifier, and a double quote quotes an
* identifier under ANSI_QUOTES; otherwise it opens a string literal.
* Backslash escapes apply only to string literals, and only when the
* NO_BACKSLASH_ESCAPES SQL mode is not set.
*
* 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_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 +2936,10 @@ 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, a quote can be escaped with a "\", unless the
* NO_BACKSLASH_ESCAPES SQL mode is active, in which case the "\" is
* treated as a regular character. Quoted 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 +2950,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 +2973,11 @@ private function read_quoted_text(): ?int {

$this->bytes_already_read = $at;

if ( '`' === $quote ) {
// Identifiers (backtick, or double quote under ANSI_QUOTES) share a token type.
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
12 changes: 8 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,15 @@ 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.
* Quoted identifiers never use backslash escaping, and when the
* NO_BACKSLASH_ESCAPES SQL mode is enabled string literals don't
* either. In both cases, we only need to handle escaped bounding
* quotes, as the 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