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
-