Passed
Pull Request — master (#806)
by Sergei
02:26
created

SqlParser::parseIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 7
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 9
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
class SqlParser
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
    public function getNextPlaceholder(int|null &$position = null): string|null
43
    {
44
        $result = null;
45
        $length = $this->length - 1;
46
47
        while ($this->position < $length) {
48
            $pos = $this->position++;
49
50
            match ($this->sql[$pos]) {
51
                ':' => ($word = $this->parseWord()) === ''
52
                    ? $this->skipChars(':')
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->skipChars(':') targeting Yiisoft\Db\Syntax\SqlParser::skipChars() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
53
                    : $result = ':' . $word,
54
                '"', "'" => $this->skipQuotedWithoutEscape($this->sql[$pos]),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->skipQuotedWithoutEscape($this->sql[$pos]) targeting Yiisoft\Db\Syntax\SqlPar...ipQuotedWithoutEscape() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
55
                '-' => $this->sql[$this->position] === '-'
56
                    ? ++$this->position && $this->skipToAfterChar("\n")
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->skipToAfterChar(' ') targeting Yiisoft\Db\Syntax\SqlParser::skipToAfterChar() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
57
                    : null,
58
                '/' => $this->sql[$this->position] === '*'
59
                    ? ++$this->position && $this->skipToAfterString('*/')
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->skipToAfterString('*/') targeting Yiisoft\Db\Syntax\SqlParser::skipToAfterString() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
60
                    : null,
61
                default => null,
62
            };
63
64
            if ($result !== null) {
65
                $position = $pos;
66
67
                return $result;
68
            }
69
        }
70
71
        return null;
72
    }
73
74
    /**
75
     * Parses and returns word symbols. Equals to `\w+` in regular expressions.
76
     *
77
     * @return string Parsed word symbols.
78
     */
79
    final protected function parseWord(): string
80
    {
81
        $word = '';
82
        $continue = true;
83
84
        while ($continue && $this->position < $this->length) {
85
            match ($this->sql[$this->position]) {
86
                '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
87
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
88
                'v', 'w', 'x', 'y', 'z',
89
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
90
                'V', 'W', 'X', 'Y', 'Z' => $word .= $this->sql[$this->position++],
91
                default => $continue = false,
92
            };
93
        }
94
95
        return $word;
96
    }
97
98
    /**
99
     * Parses and returns identifier. Equals to `[_a-zA-Z]\w+` in regular expressions.
100
     *
101
     * @return string Parsed identifier.
102
     */
103
    protected function parseIdentifier(): string
104
    {
105
        return match ($this->sql[$this->position]) {
106
            '_',
107
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
108
            'v', 'w', 'x', 'y', 'z',
109
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
110
            'V', 'W', 'X', 'Y', 'Z' => $this->sql[$this->position++] . $this->parseWord(),
111
            default => '',
112
        };
113
    }
114
115
    /**
116
     * Skips quoted string without escape characters.
117
     */
118
    final protected function skipQuotedWithoutEscape(string $endChar): void
119
    {
120
        do {
121
            $this->skipToAfterChar($endChar);
122
        } while (($this->sql[$this->position] ?? null) === $endChar && ++$this->position);
123
    }
124
125
    /**
126
     * Skips quoted string with escape characters.
127
     */
128
    final protected function skipQuotedWithEscape(string $endChar): void
129
    {
130
        for (; $this->position < $this->length; ++$this->position) {
131
            if ($this->sql[$this->position] === $endChar) {
132
                ++$this->position;
133
                return;
134
            }
135
136
            if ($this->sql[$this->position] === '\\') {
137
                ++$this->position;
138
            }
139
        }
140
    }
141
142
    /**
143
     * Skips all specified characters.
144
     */
145
    final protected function skipChars(string $char): void
146
    {
147
        while ($this->position < $this->length && $this->sql[$this->position] === $char) {
148
            ++$this->position;
149
        }
150
    }
151
152
    /**
153
     * Skips to the character after the specified character.
154
     */
155
    final protected function skipToAfterChar(string $char): void
156
    {
157
        for (; $this->position < $this->length; ++$this->position) {
158
            if ($this->sql[$this->position] === $char) {
159
                ++$this->position;
160
                return;
161
            }
162
        }
163
    }
164
165
    /**
166
     * Skips to the character after the specified string.
167
     */
168
    final protected function skipToAfterString(string $string): void
169
    {
170
        $firstChar = $string[0];
171
        $subString = substr($string, 1);
172
        $length = strlen($subString);
173
174
        do {
175
            $this->skipToAfterChar($firstChar);
176
177
            if (substr($this->sql, $this->position, $length) === $subString) {
178
                $this->position += $length;
179
                return;
180
            }
181
        } while ($this->position + $length < $this->length);
182
    }
183
}
184