diff --git a/src/MySqlMultiQueryParser.php b/src/MySqlMultiQueryParser.php index 5156747..5fc66a9 100644 --- a/src/MySqlMultiQueryParser.php +++ b/src/MySqlMultiQueryParser.php @@ -52,6 +52,7 @@ private function getQueryPattern(string $delimiter): string | \" (*PRUNE) (?: \\\\. | [^\"] )*+ \" | \` (*PRUNE) (?: [^\`]++ | \`\` )*+ \` | /\\* (*PRUNE) (?: [^*]++ | \\*(?!/) )*+ \\*/ + | (?!$delimiterPattern) (\\$(?:[a-zA-Z_\\x80-\\xFF][\\w\\x80-\\xFF]*+)?\\$) (*PRUNE) (?: [^$]++ | (?!\\g{-1})\\$ )*+ \\g{-1} | --[^\\n]*+(?:\\n|\\z) | \\#[^\\n]*+(?:\\n|\\z) | (?!$delimiterPattern) . diff --git a/tests/cases/MySqlMultiQueryParserTest.phpt b/tests/cases/MySqlMultiQueryParserTest.phpt index dc6de41..3ffd3c7 100644 --- a/tests/cases/MySqlMultiQueryParserTest.phpt +++ b/tests/cases/MySqlMultiQueryParserTest.phpt @@ -29,7 +29,7 @@ class MySqlMultiQueryParserTest extends MultiQueryParserTestCase protected function getExpectedFileQueryCount(): int { - return 60; + return 61; } @@ -148,6 +148,21 @@ class MySqlMultiQueryParserTest extends MultiQueryParserTestCase "SELECT 1", ], ], + // DELIMITER $$ with $mle$ dollar-quoted body + [ + implode("\n", [ + 'SELECT 1;', + 'DELIMITER $$', + 'CREATE FUNCTION gcd(a INT, b INT) RETURNS INT NO SQL LANGUAGE JAVASCRIPT AS $mle$ let x = a; let y = b; $mle$$$', + 'DELIMITER ;', + 'SELECT 2;', + ]), + [ + 'SELECT 1', + 'CREATE FUNCTION gcd(a INT, b INT) RETURNS INT NO SQL LANGUAGE JAVASCRIPT AS $mle$ let x = a; let y = b; $mle$', + 'SELECT 2', + ], + ], ]; } @@ -242,6 +257,21 @@ class MySqlMultiQueryParserTest extends MultiQueryParserTestCase // Escaped backticks (doubled) inside backtick identifiers ["SELECT `col``name` FROM t;", ["SELECT `col``name` FROM t"]], + + // Dollar-quoted strings in JavaScript stored programs + [ + 'CREATE FUNCTION gcd(a INT, b INT) RETURNS INT NO SQL LANGUAGE JAVASCRIPT AS $mle$ let x = a; let y = b; $mle$;', + ['CREATE FUNCTION gcd(a INT, b INT) RETURNS INT NO SQL LANGUAGE JAVASCRIPT AS $mle$ let x = a; let y = b; $mle$'], + ], + [ + 'CREATE FUNCTION js_add(a INT, b INT) RETURNS INT LANGUAGE JAVASCRIPT AS $$ return a + b; $$;', + ['CREATE FUNCTION js_add(a INT, b INT) RETURNS INT LANGUAGE JAVASCRIPT AS $$ return a + b; $$'], + ], + // Nested dollar-quoted strings with different tags + [ + 'CREATE FUNCTION nested() RETURNS INT LANGUAGE JAVASCRIPT AS $mle$ let s = $inner$;$inner$; return 1; $mle$;', + ['CREATE FUNCTION nested() RETURNS INT LANGUAGE JAVASCRIPT AS $mle$ let s = $inner$;$inner$; return 1; $mle$'], + ], ]; } @@ -297,6 +327,11 @@ class MySqlMultiQueryParserTest extends MultiQueryParserTestCase ["SELECT `col;na", "me` FROM t;"], ["SELECT `col;name` FROM t"], ], + // Dollar-quoted string spanning chunks + [ + ['CREATE FUNCTION f() RETURNS INT LANGUAGE JAVASCRIPT AS $mle$ let x = 1;', ' return x; $mle$;'], + ['CREATE FUNCTION f() RETURNS INT LANGUAGE JAVASCRIPT AS $mle$ let x = 1; return x; $mle$'], + ], ]; } } diff --git a/tests/data/mysql.sql b/tests/data/mysql.sql index 520af17..4cb5937 100644 --- a/tests/data/mysql.sql +++ b/tests/data/mysql.sql @@ -225,6 +225,21 @@ INSERT INTO contents (id, type, thread_id, replied_at) VALUES (1, 'thread', NULL INSERT INTO contents (id, type, thread_id, replied_at) VALUES (2, 'comment', 1, '2020-01-01 12:00:00'); INSERT INTO contents (id, type, thread_id, replied_at) VALUES (3, 'comment', 1, '2020-01-02 12:00:00'); +CREATE FUNCTION gcd(a INT, b INT) + RETURNS INT + NO SQL + LANGUAGE JAVASCRIPT AS +$mle$ + let x = Math.abs(a); + let y = Math.abs(b); + while (y) { + var t = y; + y = x % y; + x = t; + } + return x; +$mle$; + # Hash comment with semicolons; should be ignored; entirely SELECT `backtick;identifier` FROM authors WHERE name = 'test'; # Another hash comment