From 26b7d61916991215a9b7e009ba840f9891525f2e Mon Sep 17 00:00:00 2001 From: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> Date: Sun, 12 Apr 2026 12:57:28 +0200 Subject: [PATCH 1/5] Fix hex string literal (x'...') parsing in CREATE TABLE DEFAULT values Signed-off-by: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> --- src/Lexer.php | 33 ++++++++++++++++++++++++++- src/Token.php | 3 ++- tests/Builder/CreateStatementTest.php | 22 ++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Lexer.php b/src/Lexer.php index 0e34bc38..b0370c5e 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -833,11 +833,17 @@ public function parseNumber() // // 10 -------------------[ 0 to 9 ]-------------------> 4 // + // 11 ----------------------[ ' ]---------------------> 12 + // + // 12 --------[ 0 to 9, A to F, a to f ]--------------> 12 + // 12 ----------------------[ ' ]---------------------> 13 + // // State 1 may be reached by negative numbers. // State 2 is reached only by hex numbers. // State 4 is reached only by float numbers. // State 5 is reached only by numbers in approximate form. // State 7 is reached only by numbers in bit representation. + // State 11 is reached only by hex string literals (x'...'). // State 10 is a forced proxy to state 4 ensuring a starting dot (= "0.something") precedes a digit, and not "e" // or "E" causing wrongly interpreted scientific notation (".e[0 to 9]" is invalid). Such invalid notation could // break the lexer when table names under a given database context starts with ".e[0-9]". @@ -866,6 +872,8 @@ public function parseNumber() $state = 10; } elseif ($this->str[$this->last] === 'b') { $state = 7; + } elseif ($this->str[$this->last] === 'x' || $this->str[$this->last] === 'X') { + $state = 11; } elseif ($this->str[$this->last] !== '+') { // `+` is a valid character in a number. break; @@ -949,6 +957,29 @@ public function parseNumber() } } elseif ($state === 9) { break; + } elseif ($state === 11) { + // x'...' hex string literal - expect opening quote + $flags |= Token::FLAG_NUMBER_HEX_STRING; + if ($this->str[$this->last] !== '\'') { + break; + } + + $state = 12; + } elseif ($state === 12) { + // x'...' hex string literal - hex digits or closing quote + if ($this->str[$this->last] === '\'') { + $state = 13; + } elseif ( + ! ( + ($this->str[$this->last] >= '0' && $this->str[$this->last] <= '9') + || ($this->str[$this->last] >= 'A' && $this->str[$this->last] <= 'F') + || ($this->str[$this->last] >= 'a' && $this->str[$this->last] <= 'f') + ) + ) { + break; + } + } elseif ($state === 13) { + break; } elseif ($state === 10) { $flags |= Token::FLAG_NUMBER_FLOAT; if ($this->str[$this->last] < '0' || $this->str[$this->last] > '9') { @@ -961,7 +992,7 @@ public function parseNumber() $token .= $this->str[$this->last]; } - if ($state === 2 || $state === 3 || ($token !== '.' && $state === 4) || $state === 6 || $state === 9) { + if ($state === 2 || $state === 3 || ($token !== '.' && $state === 4) || $state === 6 || $state === 9 || $state === 13) { --$this->last; return new Token($token, Token::TYPE_NUMBER, $flags); diff --git a/src/Token.php b/src/Token.php index e4a2f45d..19a11b67 100644 --- a/src/Token.php +++ b/src/Token.php @@ -142,6 +142,7 @@ class Token public const FLAG_NUMBER_APPROXIMATE = 4; public const FLAG_NUMBER_NEGATIVE = 8; public const FLAG_NUMBER_BINARY = 16; + public const FLAG_NUMBER_HEX_STRING = 32; // Strings related flags. public const FLAG_STRING_SINGLE_QUOTES = 1; @@ -263,7 +264,7 @@ public function extract() } } elseif (($this->flags & self::FLAG_NUMBER_APPROXIMATE) || ($this->flags & self::FLAG_NUMBER_FLOAT)) { $ret = (float) $ret; - } elseif (! ($this->flags & self::FLAG_NUMBER_BINARY)) { + } elseif (! ($this->flags & self::FLAG_NUMBER_BINARY) && ! ($this->flags & self::FLAG_NUMBER_HEX_STRING)) { $ret = (int) $ret; } diff --git a/tests/Builder/CreateStatementTest.php b/tests/Builder/CreateStatementTest.php index c76497fd..a013826f 100644 --- a/tests/Builder/CreateStatementTest.php +++ b/tests/Builder/CreateStatementTest.php @@ -871,4 +871,26 @@ public function testBuildCreateTableComplexIndexes(): void $stmt->build() ); } + + public function testBuildCreateTableWithHexDefault(): void + { + $sql = "CREATE TABLE `test` (\n" + . " `IP` binary(16) NOT NULL DEFAULT x'00000000000000000000000000000000'\n" + . ') ENGINE=InnoDB'; + + $parser = new Parser($sql); + $this->assertEmpty($parser->errors); + $this->assertEquals($sql, $parser->statements[0]->build()); + } + + public function testBuildCreateTableWithUppercaseHexDefault(): void + { + $sql = "CREATE TABLE `test` (\n" + . " `IP` binary(16) NOT NULL DEFAULT X'FF'\n" + . ')'; + + $parser = new Parser($sql); + $this->assertEmpty($parser->errors); + $this->assertStringContainsString("DEFAULT X'FF'", $parser->statements[0]->build()); + } } From 8041ea3516abe32cd8b27ac301bf1a58044412c6 Mon Sep 17 00:00:00 2001 From: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:20:34 +0200 Subject: [PATCH 2/5] Fix PHPStan and code style issues Signed-off-by: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> --- phpstan-baseline.neon | 5 ----- src/Token.php | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fcfc802d..8c77a17c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1620,11 +1620,6 @@ parameters: count: 1 path: src/Token.php - - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, 16\\> given\\.$#" - count: 1 - path: src/Token.php - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, 2\\> given\\.$#" count: 1 diff --git a/src/Token.php b/src/Token.php index 19a11b67..7a1d4f79 100644 --- a/src/Token.php +++ b/src/Token.php @@ -264,7 +264,10 @@ public function extract() } } elseif (($this->flags & self::FLAG_NUMBER_APPROXIMATE) || ($this->flags & self::FLAG_NUMBER_FLOAT)) { $ret = (float) $ret; - } elseif (! ($this->flags & self::FLAG_NUMBER_BINARY) && ! ($this->flags & self::FLAG_NUMBER_HEX_STRING)) { + } elseif ( + ($this->flags & self::FLAG_NUMBER_BINARY) === 0 + && ($this->flags & self::FLAG_NUMBER_HEX_STRING) === 0 + ) { $ret = (int) $ret; } From c04977e4787b35bf3dd0c885def1b67778083ca3 Mon Sep 17 00:00:00 2001 From: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:34:55 +0200 Subject: [PATCH 3/5] Fix code style, PHPStan and Psalm baselines Signed-off-by: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> --- psalm-baseline.xml | 10 +++++++++- src/Lexer.php | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 50327a72..e6b0678c 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -698,7 +698,7 @@ $this->last $this->last - + $this->str[$this->last + 1] $this->str[$this->last++] $this->str[$this->last] @@ -742,6 +742,14 @@ $this->str[$this->last] $this->str[$this->last] $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] + $this->str[$this->last] $lastToken diff --git a/src/Lexer.php b/src/Lexer.php index b0370c5e..e786eafe 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -992,7 +992,10 @@ public function parseNumber() $token .= $this->str[$this->last]; } - if ($state === 2 || $state === 3 || ($token !== '.' && $state === 4) || $state === 6 || $state === 9 || $state === 13) { + if ( + $state === 2 || $state === 3 || ($token !== '.' && $state === 4) + || $state === 6 || $state === 9 || $state === 13 + ) { --$this->last; return new Token($token, Token::TYPE_NUMBER, $flags); From c93959f88107ff5b169051108c5d8abfe0e1f541 Mon Sep 17 00:00:00 2001 From: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:38:50 +0200 Subject: [PATCH 4/5] Update Psalm baseline for MixedArrayAccess count Signed-off-by: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> --- psalm-baseline.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index e6b0678c..e0d90b90 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -698,7 +698,7 @@ $this->last $this->last - + $this->str[$this->last + 1] $this->str[$this->last++] $this->str[$this->last] From 5081e1b3258e1c8dc160df871214c29e97618359 Mon Sep 17 00:00:00 2001 From: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:48:51 +0200 Subject: [PATCH 5/5] Update PHPStan baseline for test assertions Signed-off-by: Nicolai Ehrhardt <245527909+predictor2718@users.noreply.github.com> --- phpstan-baseline.neon | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8c77a17c..1dcee446 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2095,9 +2095,19 @@ parameters: count: 4 path: tests/Builder/CreateStatementTest.php + - + message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEmpty\\(\\)\\.$#" + count: 2 + path: tests/Builder/CreateStatementTest.php + - message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\)\\.$#" - count: 34 + count: 35 + path: tests/Builder/CreateStatementTest.php + + - + message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\)\\.$#" + count: 1 path: tests/Builder/CreateStatementTest.php -