diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fcfc802d..1e0fa42a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2472,7 +2472,7 @@ parameters: - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\)\\.$#" - count: 30 + count: 34 path: tests/Lexer/IsMethodsTest.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 10ebc58e..85d6aed5 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -811,7 +811,7 @@ $token $token - + $this->delimiter $this->str[$this->last++] $this->str[$this->last] @@ -836,6 +836,7 @@ $this->str[++$this->last] $this->str[++$this->last] $this->str[++$this->last] + $this->str[++$this->last] null diff --git a/src/Context.php b/src/Context.php index d332450c..d82808af 100644 --- a/src/Context.php +++ b/src/Context.php @@ -443,10 +443,19 @@ public static function isComment($str, $end = false) return Token::FLAG_COMMENT_BASH; } - // If comment is opening C style (/*), warning, it could be a MySQL command (/*!) + // If comment is opening C style (/*), warning, it could be + // - a MySQL command (/*!) + // - a MariaDB command (/*M!) if (($len > 1) && ($str[0] === '/') && ($str[1] === '*')) { - return ($len > 2) && ($str[2] === '!') ? - Token::FLAG_COMMENT_MYSQL_CMD : Token::FLAG_COMMENT_C; + if ($len > 2 && $str[2] === '!') { + return Token::FLAG_COMMENT_MYSQL_CMD; + } + + if ($len > 3 && $str[2] === 'M' && $str[3] === '!') { + return Token::FLAG_COMMENT_MARIADB_CMD; + } + + return Token::FLAG_COMMENT_C; } // If comment is closing C style (*/), warning, it could conflicts with wildcard and a real opening C style. diff --git a/src/Lexer.php b/src/Lexer.php index 0e34bc38..2160b935 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -693,10 +693,21 @@ public function parseComment() return new Token($token, Token::TYPE_COMMENT, $flags); } - // Checking if this is a MySQL-specific command. - if ($this->last + 1 < $this->len && $this->str[$this->last + 1] === '!') { - $flags |= Token::FLAG_COMMENT_MYSQL_CMD; + // Checking if this is a MySQL (/*!) or MariaDB (/*M!) specific command. + if ( + $this->last + 1 < $this->len && + ($this->str[$this->last + 1] === '!' || + ($this->str[$this->last + 1] === 'M' && + $this->last + 2 < $this->len && + $this->str[$this->last + 2] === '!')) + ) { $token .= $this->str[++$this->last]; + if ($this->str[$this->last] === '!') { + $flags |= Token::FLAG_COMMENT_MYSQL_CMD; + } else { + $flags |= Token::FLAG_COMMENT_MARIADB_CMD; + $token .= $this->str[++$this->last]; + } while ( ++$this->last < $this->len diff --git a/src/Token.php b/src/Token.php index e4a2f45d..81e5a2e0 100644 --- a/src/Token.php +++ b/src/Token.php @@ -152,6 +152,7 @@ class Token public const FLAG_COMMENT_C = 2; public const FLAG_COMMENT_SQL = 4; public const FLAG_COMMENT_MYSQL_CMD = 8; + public const FLAG_COMMENT_MARIADB_CMD = 16; // Operators related flags. public const FLAG_OPERATOR_ARITHMETIC = 1; diff --git a/tests/Lexer/IsMethodsTest.php b/tests/Lexer/IsMethodsTest.php index afc8948a..226f39b8 100644 --- a/tests/Lexer/IsMethodsTest.php +++ b/tests/Lexer/IsMethodsTest.php @@ -61,6 +61,10 @@ public function testIsComment(): void { $this->assertEquals(Token::FLAG_COMMENT_BASH, Context::isComment('#')); $this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('/*')); + $this->assertEquals(Token::FLAG_COMMENT_MYSQL_CMD, Context::isComment('/*!')); + $this->assertEquals(Token::FLAG_COMMENT_MYSQL_CMD, Context::isComment('/*!50000')); + $this->assertEquals(Token::FLAG_COMMENT_MARIADB_CMD, Context::isComment('/*M!')); + $this->assertEquals(Token::FLAG_COMMENT_MARIADB_CMD, Context::isComment('/*M!100300')); $this->assertEquals(Token::FLAG_COMMENT_C, Context::isComment('*/')); $this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment('-- ')); $this->assertEquals(Token::FLAG_COMMENT_SQL, Context::isComment("--\t")); diff --git a/tests/Parser/CreateStatementTest.php b/tests/Parser/CreateStatementTest.php index c6f1e5a3..d9d28c7f 100644 --- a/tests/Parser/CreateStatementTest.php +++ b/tests/Parser/CreateStatementTest.php @@ -60,6 +60,7 @@ public static function createProvider(): array ['parser/parseCreateTableErr5'], ['parser/parseCreateTableSelect'], ['parser/parseCreateTableAsSelect'], + ['parser/parseCreateTableColumnCompressed'], ['parser/parseCreateTableLike'], ['parser/parseCreateTableSpatial'], ['parser/parseCreateTableSRID'], diff --git a/tests/data/lexer/lexComment.in b/tests/data/lexer/lexComment.in index 86ed94f9..7f4d8d61 100644 --- a/tests/data/lexer/lexComment.in +++ b/tests/data/lexer/lexComment.in @@ -1,4 +1,5 @@ # comment -SELECT /*!50000 STRAIGHT_JOIN */ col1 FROM table1, table2 /* select query */ +SELECT /*!50000 STRAIGHT_JOIN */ col1 FROM table1, table2; /* select query */ -- comment --- comment 2 \ No newline at end of file +-- comment 2 +SELECT /*M!100300 1, */ 2; \ No newline at end of file diff --git a/tests/data/lexer/lexComment.out b/tests/data/lexer/lexComment.out index 8af1028b..d4a2dcf5 100644 --- a/tests/data/lexer/lexComment.out +++ b/tests/data/lexer/lexComment.out @@ -1,10 +1,10 @@ { - "query": "# comment\nSELECT /*!50000 STRAIGHT_JOIN */ col1 FROM table1, table2 /* select query */\n-- comment\n-- comment 2", + "query": "# comment\nSELECT /*!50000 STRAIGHT_JOIN */ col1 FROM table1, table2; /* select query */\n-- comment\n-- comment 2\nSELECT /*M!100300 1, */ 2;", "lexer": { "@type": "PhpMyAdmin\\SqlParser\\Lexer", - "str": "# comment\nSELECT /*!50000 STRAIGHT_JOIN */ col1 FROM table1, table2 /* select query */\n-- comment\n-- comment 2", - "len": 110, - "last": 111, + "str": "# comment\nSELECT /*!50000 STRAIGHT_JOIN */ col1 FROM table1, table2; /* select query */\n-- comment\n-- comment 2\nSELECT /*M!100300 1, */ 2;", + "len": 138, + "last": 138, "list": { "@type": "PhpMyAdmin\\SqlParser\\TokensList", "tokens": [ @@ -170,6 +170,15 @@ "flags": 0, "position": 61 }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": ";", + "value": ";", + "keyword": null, + "type": 9, + "flags": 0, + "position": 67 + }, { "@type": "PhpMyAdmin\\SqlParser\\Token", "token": " ", @@ -177,7 +186,7 @@ "keyword": null, "type": 3, "flags": 0, - "position": 67 + "position": 68 }, { "@type": "PhpMyAdmin\\SqlParser\\Token", @@ -186,7 +195,7 @@ "keyword": null, "type": 4, "flags": 2, - "position": 68 + "position": 69 }, { "@type": "PhpMyAdmin\\SqlParser\\Token", @@ -195,7 +204,7 @@ "keyword": null, "type": 3, "flags": 0, - "position": 86 + "position": 87 }, { "@type": "PhpMyAdmin\\SqlParser\\Token", @@ -204,7 +213,7 @@ "keyword": null, "type": 4, "flags": 4, - "position": 87 + "position": 88 }, { "@type": "PhpMyAdmin\\SqlParser\\Token", @@ -213,7 +222,7 @@ "keyword": null, "type": 3, "flags": 0, - "position": 97 + "position": 98 }, { "@type": "PhpMyAdmin\\SqlParser\\Token", @@ -222,7 +231,115 @@ "keyword": null, "type": 4, "flags": 4, - "position": 98 + "position": 99 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "\n", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 111 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "SELECT", + "value": "SELECT", + "keyword": "SELECT", + "type": 1, + "flags": 3, + "position": 112 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 118 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "/*M!100300", + "value": "/*M!100300", + "keyword": null, + "type": 4, + "flags": 18, + "position": 119 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 129 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "1", + "value": 1, + "keyword": null, + "type": 6, + "flags": 0, + "position": 130 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": ",", + "value": ",", + "keyword": null, + "type": 2, + "flags": 16, + "position": 131 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 132 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "*/", + "value": "*/", + "keyword": null, + "type": 4, + "flags": 2, + "position": 133 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 135 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "2", + "value": 2, + "keyword": null, + "type": 6, + "flags": 0, + "position": 136 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": ";", + "value": ";", + "keyword": null, + "type": 9, + "flags": 0, + "position": 137 }, { "@type": "PhpMyAdmin\\SqlParser\\Token", @@ -234,7 +351,7 @@ "position": null } ], - "count": 25, + "count": 38, "idx": 0 }, "delimiter": ";", diff --git a/tests/data/parser/parseCreateTableColumnCompressed.in b/tests/data/parser/parseCreateTableColumnCompressed.in new file mode 100644 index 00000000..f6fb1ab5 --- /dev/null +++ b/tests/data/parser/parseCreateTableColumnCompressed.in @@ -0,0 +1 @@ +CREATE TABLE `t` (`c1` longtext /*M!100301 COMPRESSED*/); \ No newline at end of file diff --git a/tests/data/parser/parseCreateTableColumnCompressed.out b/tests/data/parser/parseCreateTableColumnCompressed.out new file mode 100644 index 00000000..9dc7d1dd --- /dev/null +++ b/tests/data/parser/parseCreateTableColumnCompressed.out @@ -0,0 +1,258 @@ +{ + "query": "CREATE TABLE `t` (`c1` longtext /*M!100301 COMPRESSED*/);", + "lexer": { + "@type": "PhpMyAdmin\\SqlParser\\Lexer", + "str": "CREATE TABLE `t` (`c1` longtext /*M!100301 COMPRESSED*/);", + "len": 57, + "last": 57, + "list": { + "@type": "PhpMyAdmin\\SqlParser\\TokensList", + "tokens": [ + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "CREATE", + "value": "CREATE", + "keyword": "CREATE", + "type": 1, + "flags": 3, + "position": 0 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 6 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "TABLE", + "value": "TABLE", + "keyword": "TABLE", + "type": 1, + "flags": 3, + "position": 7 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 12 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "`t`", + "value": "t", + "keyword": null, + "type": 8, + "flags": 2, + "position": 13 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 16 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "(", + "value": "(", + "keyword": null, + "type": 2, + "flags": 16, + "position": 17 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "`c1`", + "value": "c1", + "keyword": null, + "type": 8, + "flags": 2, + "position": 18 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 22 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "longtext", + "value": "LONGTEXT", + "keyword": "LONGTEXT", + "type": 1, + "flags": 11, + "position": 23 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 31 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "/*M!100301", + "value": "/*M!100301", + "keyword": null, + "type": 4, + "flags": 18, + "position": 32 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": " ", + "value": " ", + "keyword": null, + "type": 3, + "flags": 0, + "position": 42 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "COMPRESSED", + "value": "COMPRESSED", + "keyword": "COMPRESSED", + "type": 1, + "flags": 1, + "position": 43 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": "*/", + "value": "*/", + "keyword": null, + "type": 4, + "flags": 2, + "position": 53 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": ")", + "value": ")", + "keyword": null, + "type": 2, + "flags": 16, + "position": 55 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": ";", + "value": ";", + "keyword": null, + "type": 9, + "flags": 0, + "position": 56 + }, + { + "@type": "PhpMyAdmin\\SqlParser\\Token", + "token": null, + "value": null, + "keyword": null, + "type": 9, + "flags": 0, + "position": null + } + ], + "count": 18, + "idx": 18 + }, + "delimiter": ";", + "delimiterLen": 1, + "strict": false, + "errors": [] + }, + "parser": { + "@type": "PhpMyAdmin\\SqlParser\\Parser", + "list": { + "@type": "@1" + }, + "statements": [ + { + "@type": "PhpMyAdmin\\SqlParser\\Statements\\CreateStatement", + "name": { + "@type": "PhpMyAdmin\\SqlParser\\Components\\Expression", + "database": null, + "table": "t", + "column": null, + "expr": "`t`", + "alias": null, + "function": null, + "subquery": null + }, + "entityOptions": { + "@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray", + "options": [] + }, + "fields": [ + { + "@type": "PhpMyAdmin\\SqlParser\\Components\\CreateDefinition", + "name": "c1", + "isConstraint": null, + "type": { + "@type": "PhpMyAdmin\\SqlParser\\Components\\DataType", + "name": "LONGTEXT", + "parameters": [], + "options": { + "@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray", + "options": [] + } + }, + "key": null, + "references": null, + "options": { + "@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray", + "options": { + "16": "COMPRESSED" + } + } + } + ], + "with": null, + "select": null, + "like": null, + "partitionBy": null, + "partitionsNum": null, + "subpartitionBy": null, + "subpartitionsNum": null, + "partitions": null, + "table": null, + "return": null, + "parameters": null, + "body": [], + "options": { + "@type": "PhpMyAdmin\\SqlParser\\Components\\OptionsArray", + "options": { + "6": "TABLE" + } + }, + "first": 0, + "last": 16 + } + ], + "brackets": 0, + "strict": false, + "errors": [] + }, + "errors": { + "lexer": [], + "parser": [] + } +} \ No newline at end of file