Skip to content

Commit 18d63ad

Browse files
committed
Merge #650 - Fix hex string literal (x'...') parsing in CREATE TABLE DEFAULT values
Pull-request: #650 Fixes: #649
2 parents e08e9d2 + 5081e1b commit 18d63ad

5 files changed

Lines changed: 83 additions & 9 deletions

File tree

‎phpstan-baseline.neon‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,11 +1620,6 @@ parameters:
16201620
count: 1
16211621
path: src/Token.php
16221622

1623-
-
1624-
message: "#^Only booleans are allowed in a negated boolean, int\\<0, 16\\> given\\.$#"
1625-
count: 1
1626-
path: src/Token.php
1627-
16281623
-
16291624
message: "#^Only booleans are allowed in a negated boolean, int\\<0, 2\\> given\\.$#"
16301625
count: 1
@@ -2150,9 +2145,14 @@ parameters:
21502145
count: 4
21512146
path: tests/Builder/CreateStatementTest.php
21522147

2148+
-
2149+
message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEmpty\\(\\)\\.$#"
2150+
count: 2
2151+
path: tests/Builder/CreateStatementTest.php
2152+
21532153
-
21542154
message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\)\\.$#"
2155-
count: 34
2155+
count: 35
21562156
path: tests/Builder/CreateStatementTest.php
21572157

21582158
-
@@ -2165,6 +2165,11 @@ parameters:
21652165
count: 13
21662166
path: tests/Builder/CreateStatementTest.php
21672167

2168+
-
2169+
message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\)\\.$#"
2170+
count: 1
2171+
path: tests/Builder/CreateStatementTest.php
2172+
21682173
-
21692174
message: "#^Parameter \\#1 \\$component of static method PhpMyAdmin\\\\SqlParser\\\\Components\\\\CreateDefinition\\:\\:build\\(\\) expects array\\<PhpMyAdmin\\\\SqlParser\\\\Components\\\\CreateDefinition\\>\\|PhpMyAdmin\\\\SqlParser\\\\Components\\\\CreateDefinition, array\\<PhpMyAdmin\\\\SqlParser\\\\Components\\\\CreateDefinition\\>\\|PhpMyAdmin\\\\SqlParser\\\\Components\\\\ArrayObj\\|null given\\.$#"
21702175
count: 2

‎psalm-baseline.xml‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@
736736
<code>$this-&gt;str[$this-&gt;last]</code>
737737
<code>$this-&gt;str[$this-&gt;last]</code>
738738
</MixedArgument>
739-
<MixedArrayAccess occurrences="31">
739+
<MixedArrayAccess occurrences="40">
740740
<code>$this-&gt;str[$this-&gt;last + 1]</code>
741741
<code>$this-&gt;str[$this-&gt;last++]</code>
742742
<code>$this-&gt;str[$this-&gt;last]</code>
@@ -768,6 +768,15 @@
768768
<code>$this-&gt;str[$this-&gt;last]</code>
769769
<code>$this-&gt;str[$this-&gt;last]</code>
770770
<code>$this-&gt;str[$this-&gt;last]</code>
771+
<code>$this-&gt;str[$this-&gt;last]</code>
772+
<code>$this-&gt;str[$this-&gt;last]</code>
773+
<code>$this-&gt;str[$this-&gt;last]</code>
774+
<code>$this-&gt;str[$this-&gt;last]</code>
775+
<code>$this-&gt;str[$this-&gt;last]</code>
776+
<code>$this-&gt;str[$this-&gt;last]</code>
777+
<code>$this-&gt;str[$this-&gt;last]</code>
778+
<code>$this-&gt;str[$this-&gt;last]</code>
779+
<code>$this-&gt;str[$this-&gt;last]</code>
771780
</MixedArrayAccess>
772781
<MixedAssignment occurrences="2">
773782
<code>$lastToken</code>

‎src/Lexer.php‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,11 +844,17 @@ public function parseNumber()
844844
//
845845
// 10 -------------------[ 0 to 9 ]-------------------> 4
846846
//
847+
// 11 ----------------------[ ' ]---------------------> 12
848+
//
849+
// 12 --------[ 0 to 9, A to F, a to f ]--------------> 12
850+
// 12 ----------------------[ ' ]---------------------> 13
851+
//
847852
// State 1 may be reached by negative numbers.
848853
// State 2 is reached only by hex numbers.
849854
// State 4 is reached only by float numbers.
850855
// State 5 is reached only by numbers in approximate form.
851856
// State 7 is reached only by numbers in bit representation.
857+
// State 11 is reached only by hex string literals (x'...').
852858
// State 10 is a forced proxy to state 4 ensuring a starting dot (= "0.something") precedes a digit, and not "e"
853859
// or "E" causing wrongly interpreted scientific notation (".e[0 to 9]" is invalid). Such invalid notation could
854860
// break the lexer when table names under a given database context starts with ".e[0-9]".
@@ -877,6 +883,8 @@ public function parseNumber()
877883
$state = 10;
878884
} elseif ($this->str[$this->last] === 'b') {
879885
$state = 7;
886+
} elseif ($this->str[$this->last] === 'x' || $this->str[$this->last] === 'X') {
887+
$state = 11;
880888
} elseif ($this->str[$this->last] !== '+') {
881889
// `+` is a valid character in a number.
882890
break;
@@ -954,6 +962,29 @@ public function parseNumber()
954962
}
955963
} elseif ($state === 9) {
956964
break;
965+
} elseif ($state === 11) {
966+
// x'...' hex string literal - expect opening quote
967+
$flags |= Token::FLAG_NUMBER_HEX_STRING;
968+
if ($this->str[$this->last] !== '\'') {
969+
break;
970+
}
971+
972+
$state = 12;
973+
} elseif ($state === 12) {
974+
// x'...' hex string literal - hex digits or closing quote
975+
if ($this->str[$this->last] === '\'') {
976+
$state = 13;
977+
} elseif (
978+
! (
979+
($this->str[$this->last] >= '0' && $this->str[$this->last] <= '9')
980+
|| ($this->str[$this->last] >= 'A' && $this->str[$this->last] <= 'F')
981+
|| ($this->str[$this->last] >= 'a' && $this->str[$this->last] <= 'f')
982+
)
983+
) {
984+
break;
985+
}
986+
} elseif ($state === 13) {
987+
break;
957988
} elseif ($state === 10) {
958989
$flags |= Token::FLAG_NUMBER_FLOAT;
959990
if ($this->str[$this->last] < '0' || $this->str[$this->last] > '9') {
@@ -966,7 +997,10 @@ public function parseNumber()
966997
$token .= $this->str[$this->last];
967998
}
968999

969-
if ($state === 2 || $state === 3 || ($token !== '.' && $state === 4) || $state === 6 || $state === 9) {
1000+
if (
1001+
$state === 2 || $state === 3 || ($token !== '.' && $state === 4)
1002+
|| $state === 6 || $state === 9 || $state === 13
1003+
) {
9701004
--$this->last;
9711005

9721006
return new Token($token, Token::TYPE_NUMBER, $flags);

‎src/Token.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class Token
142142
public const FLAG_NUMBER_APPROXIMATE = 4;
143143
public const FLAG_NUMBER_NEGATIVE = 8;
144144
public const FLAG_NUMBER_BINARY = 16;
145+
public const FLAG_NUMBER_HEX_STRING = 32;
145146

146147
// Strings related flags.
147148
public const FLAG_STRING_SINGLE_QUOTES = 1;
@@ -264,7 +265,10 @@ public function extract()
264265
}
265266
} elseif (($this->flags & self::FLAG_NUMBER_APPROXIMATE) || ($this->flags & self::FLAG_NUMBER_FLOAT)) {
266267
$ret = (float) $ret;
267-
} elseif (! ($this->flags & self::FLAG_NUMBER_BINARY)) {
268+
} elseif (
269+
($this->flags & self::FLAG_NUMBER_BINARY) === 0
270+
&& ($this->flags & self::FLAG_NUMBER_HEX_STRING) === 0
271+
) {
268272
$ret = (int) $ret;
269273
}
270274

‎tests/Builder/CreateStatementTest.php‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,4 +873,26 @@ public function testBuildCreateTableComplexIndexes(): void
873873
$stmt->build()
874874
);
875875
}
876+
877+
public function testBuildCreateTableWithHexDefault(): void
878+
{
879+
$sql = "CREATE TABLE `test` (\n"
880+
. " `IP` binary(16) NOT NULL DEFAULT x'00000000000000000000000000000000'\n"
881+
. ') ENGINE=InnoDB';
882+
883+
$parser = new Parser($sql);
884+
$this->assertEmpty($parser->errors);
885+
$this->assertEquals($sql, $parser->statements[0]->build());
886+
}
887+
888+
public function testBuildCreateTableWithUppercaseHexDefault(): void
889+
{
890+
$sql = "CREATE TABLE `test` (\n"
891+
. " `IP` binary(16) NOT NULL DEFAULT X'FF'\n"
892+
. ')';
893+
894+
$parser = new Parser($sql);
895+
$this->assertEmpty($parser->errors);
896+
$this->assertStringContainsString("DEFAULT X'FF'", $parser->statements[0]->build());
897+
}
876898
}

0 commit comments

Comments
 (0)