Passed
Push — master ( 2eb63c...789523 )
by Sergei
02:26
created

AbstractSqlParser::skipQuotedWithEscape()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 10
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Syntax;
6
7
use function strlen;
8
use function substr;
9
10
/**
11
 * SQL parser.
12
 *
13
 * This class provides methods to parse SQL statements and extract placeholders from them.
14
 */
15
abstract class AbstractSqlParser
16
{
17
    /**
18
     * @var int Length of SQL statement.
19
     */
20
    protected int $length;
21
22
    /**
23
     * @var int Current position in SQL statement.
24
     */
25
    protected int $position = 0;
26
27
    /**
28
     * @param string $sql SQL statement to parse.
29
     */
30
    public function __construct(protected string $sql)
31
    {
32
        $this->length = strlen($sql);
33
    }
34
35
    /**
36
     * Returns the next placeholder from the current position in SQL statement.
37
     *
38
     * @param int|null $position Position of the placeholder in SQL statement.
39
     *
40
     * @return string|null The next placeholder or null if it is not found.
41
     */
42
    abstract public function getNextPlaceholder(int|null &$position = null): string|null;
43
44
    /**
45
     * Parses and returns word symbols. Equals to `\w+` in regular expressions.
46
     *
47
     * @return string Parsed word symbols.
48
     */
49
    final protected function parseWord(): string
50
    {
51
        $word = '';
52
        $continue = true;
53
54
        while ($continue && $this->position < $this->length) {
55
            match ($this->sql[$this->position]) {
56
                '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
57
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
58
                'v', 'w', 'x', 'y', 'z',
59
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
60
                'V', 'W', 'X', 'Y', 'Z' => $word .= $this->sql[$this->position++],
61
                default => $continue = false,
62
            };
63
        }
64
65
        return $word;
66
    }
67
68
    /**
69
     * Parses and returns identifier. Equals to `[_a-zA-Z]\w+` in regular expressions.
70
     *
71
     * @return string Parsed identifier.
72
     */
73
    protected function parseIdentifier(): string
74
    {
75
        return match ($this->sql[$this->position]) {
76
            '_',
77
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
78
            'v', 'w', 'x', 'y', 'z',
79
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
80
            'V', 'W', 'X', 'Y', 'Z' => $this->sql[$this->position++] . $this->parseWord(),
81
            default => '',
82
        };
83
    }
84
85
    /**
86
     * Skips quoted string without escape characters.
87
     */
88
    final protected function skipQuotedWithoutEscape(string $endChar): void
89
    {
90
        do {
91
            $this->skipToAfterChar($endChar);
92
        } while (($this->sql[$this->position] ?? null) === $endChar && ++$this->position);
93
    }
94
95
    /**
96
     * Skips quoted string with escape characters.
97
     */
98
    final protected function skipQuotedWithEscape(string $endChar): void
99
    {
100
        for (; $this->position < $this->length; ++$this->position) {
101
            if ($this->sql[$this->position] === $endChar) {
102
                ++$this->position;
103
                return;
104
            }
105
106
            if ($this->sql[$this->position] === '\\') {
107
                ++$this->position;
108
            }
109
        }
110
    }
111
112
    /**
113
     * Skips all specified characters.
114
     */
115
    final protected function skipChars(string $char): void
116
    {
117
        while ($this->position < $this->length && $this->sql[$this->position] === $char) {
118
            ++$this->position;
119
        }
120
    }
121
122
    /**
123
     * Skips to the character after the specified character.
124
     */
125
    final protected function skipToAfterChar(string $char): void
126
    {
127
        for (; $this->position < $this->length; ++$this->position) {
128
            if ($this->sql[$this->position] === $char) {
129
                ++$this->position;
130
                return;
131
            }
132
        }
133
    }
134
135
    /**
136
     * Skips to the character after the specified string.
137
     */
138
    final protected function skipToAfterString(string $string): void
139
    {
140
        $firstChar = $string[0];
141
        $subString = substr($string, 1);
142
        $length = strlen($subString);
143
144
        do {
145
            $this->skipToAfterChar($firstChar);
146
147
            if (substr($this->sql, $this->position, $length) === $subString) {
148
                $this->position += $length;
149
                return;
150
            }
151
        } while ($this->position + $length < $this->length);
152
    }
153
}
154