summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/SQL/Parser.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/SQL/Parser.php')
-rw-r--r--vendor/doctrine/dbal/src/SQL/Parser.php129
1 files changed, 129 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/SQL/Parser.php b/vendor/doctrine/dbal/src/SQL/Parser.php
new file mode 100644
index 0000000..ad30f09
--- /dev/null
+++ b/vendor/doctrine/dbal/src/SQL/Parser.php
@@ -0,0 +1,129 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\SQL;
6
7use Doctrine\DBAL\SQL\Parser\Exception;
8use Doctrine\DBAL\SQL\Parser\Exception\RegularExpressionError;
9use Doctrine\DBAL\SQL\Parser\Visitor;
10
11use function array_merge;
12use function assert;
13use function current;
14use function implode;
15use function key;
16use function next;
17use function preg_last_error;
18use function preg_match;
19use function reset;
20use function sprintf;
21use function strlen;
22
23use const PREG_NO_ERROR;
24
25/**
26 * The SQL parser that focuses on identifying prepared statement parameters. It implements parsing other tokens like
27 * string literals and comments only as a way to not confuse their contents with the the parameter placeholders.
28 *
29 * The parsing logic and the implementation is inspired by the PHP PDO parser.
30 *
31 * @internal
32 *
33 * @see https://github.com/php/php-src/blob/php-7.4.12/ext/pdo/pdo_sql_parser.re#L49-L69
34 */
35final class Parser
36{
37 private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/';
38
39 private const BACKTICK_IDENTIFIER = '`[^`]*`';
40 private const BRACKET_IDENTIFIER = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]';
41 private const MULTICHAR = ':{2,}';
42 private const NAMED_PARAMETER = ':[a-zA-Z0-9_]+';
43 private const POSITIONAL_PARAMETER = '(?<!\\?)\\?(?!\\?)';
44 private const ONE_LINE_COMMENT = '--[^\r\n]*';
45 private const MULTI_LINE_COMMENT = '/\*([^*]+|\*+[^/*])*\**\*/';
46 private const SPECIAL = '[' . self::SPECIAL_CHARS . ']';
47 private const OTHER = '[^' . self::SPECIAL_CHARS . ']+';
48
49 private readonly string $sqlPattern;
50
51 public function __construct(bool $mySQLStringEscaping)
52 {
53 if ($mySQLStringEscaping) {
54 $patterns = [
55 $this->getMySQLStringLiteralPattern("'"),
56 $this->getMySQLStringLiteralPattern('"'),
57 ];
58 } else {
59 $patterns = [
60 $this->getAnsiSQLStringLiteralPattern("'"),
61 $this->getAnsiSQLStringLiteralPattern('"'),
62 ];
63 }
64
65 $patterns = array_merge($patterns, [
66 self::BACKTICK_IDENTIFIER,
67 self::BRACKET_IDENTIFIER,
68 self::MULTICHAR,
69 self::ONE_LINE_COMMENT,
70 self::MULTI_LINE_COMMENT,
71 self::OTHER,
72 ]);
73
74 $this->sqlPattern = sprintf('(%s)', implode('|', $patterns));
75 }
76
77 /**
78 * Parses the given SQL statement
79 *
80 * @throws Exception
81 */
82 public function parse(string $sql, Visitor $visitor): void
83 {
84 /** @var array<string,callable> $patterns */
85 $patterns = [
86 self::NAMED_PARAMETER => static function (string $sql) use ($visitor): void {
87 $visitor->acceptNamedParameter($sql);
88 },
89 self::POSITIONAL_PARAMETER => static function (string $sql) use ($visitor): void {
90 $visitor->acceptPositionalParameter($sql);
91 },
92 $this->sqlPattern => static function (string $sql) use ($visitor): void {
93 $visitor->acceptOther($sql);
94 },
95 self::SPECIAL => static function (string $sql) use ($visitor): void {
96 $visitor->acceptOther($sql);
97 },
98 ];
99
100 $offset = 0;
101
102 while (($handler = current($patterns)) !== false) {
103 if (preg_match('~\G' . key($patterns) . '~s', $sql, $matches, 0, $offset) === 1) {
104 $handler($matches[0]);
105 reset($patterns);
106
107 $offset += strlen($matches[0]);
108 } elseif (preg_last_error() !== PREG_NO_ERROR) {
109 // @codeCoverageIgnoreStart
110 throw RegularExpressionError::new();
111 // @codeCoverageIgnoreEnd
112 } else {
113 next($patterns);
114 }
115 }
116
117 assert($offset === strlen($sql));
118 }
119
120 private function getMySQLStringLiteralPattern(string $delimiter): string
121 {
122 return $delimiter . '((\\\\.)|(?![' . $delimiter . '\\\\]).)*' . $delimiter;
123 }
124
125 private function getAnsiSQLStringLiteralPattern(string $delimiter): string
126 {
127 return $delimiter . '[^' . $delimiter . ']*' . $delimiter;
128 }
129}