InsertStatement::parse()   D
last analyzed

Complexity

Conditions 26
Paths 10

Size

Total Lines 109
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 59
CRAP Score 26

Importance

Changes 0
Metric Value
cc 26
eloc 58
c 0
b 0
f 0
nc 10
nop 2
dl 0
loc 109
ccs 59
cts 59
cp 1
crap 26
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Statements;
6
7
use PhpMyAdmin\SqlParser\Components\ArrayObj;
8
use PhpMyAdmin\SqlParser\Components\IntoKeyword;
9
use PhpMyAdmin\SqlParser\Components\SetOperation;
10
use PhpMyAdmin\SqlParser\Parser;
11
use PhpMyAdmin\SqlParser\Parsers\Array2d;
12
use PhpMyAdmin\SqlParser\Parsers\ArrayObjs;
13
use PhpMyAdmin\SqlParser\Parsers\IntoKeywords;
14
use PhpMyAdmin\SqlParser\Parsers\OptionsArrays;
15
use PhpMyAdmin\SqlParser\Parsers\SetOperations;
16
use PhpMyAdmin\SqlParser\Statement;
17
use PhpMyAdmin\SqlParser\TokensList;
18
use PhpMyAdmin\SqlParser\TokenType;
19
20
use function strlen;
21
use function trim;
22
23
/**
24
 * `INSERT` statement.
25
 *
26
 * INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
27
 *     [INTO] tbl_name
28
 *     [PARTITION (partition_name,...)]
29
 *     [(col_name,...)]
30
 *     {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
31
 *     [ ON DUPLICATE KEY UPDATE
32
 *       col_name=expr
33
 *         [, col_name=expr] ... ]
34
 *
35
 * or
36
 *
37
 * INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
38
 *     [INTO] tbl_name
39
 *     [PARTITION (partition_name,...)]
40
 *     SET col_name={expr | DEFAULT}, ...
41
 *     [ ON DUPLICATE KEY UPDATE
42
 *       col_name=expr
43
 *         [, col_name=expr] ... ]
44
 *
45
 * or
46
 *
47
 * INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
48
 *     [INTO] tbl_name
49
 *     [PARTITION (partition_name,...)]
50
 *     [(col_name,...)]
51
 *     SELECT ...
52
 *     [ ON DUPLICATE KEY UPDATE
53
 *       col_name=expr
54
 *         [, col_name=expr] ... ]
55
 */
56
class InsertStatement extends Statement
57
{
58
    /**
59
     * Options for `INSERT` statements.
60
     *
61
     * @var array<string, int|array<int, int|string>>
62
     * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
63
     */
64
    public static array $statementOptions = [
65
        'LOW_PRIORITY' => 1,
66
        'DELAYED' => 2,
67
        'HIGH_PRIORITY' => 3,
68
        'IGNORE' => 4,
69
    ];
70
71
    /**
72
     * Tables used as target for this statement.
73
     */
74
    public IntoKeyword|null $into = null;
75
76
    /**
77
     * Values to be inserted.
78
     *
79
     * @var ArrayObj[]|null
80
     */
81
    public array|null $values = null;
82
83
    /**
84
     * If SET clause is present
85
     * holds the SetOperation.
86
     *
87
     * @var SetOperation[]|null
88
     */
89
    public array|null $set = null;
90
91
    /**
92
     * If SELECT clause is present
93
     * holds the SelectStatement.
94
     */
95
    public SelectStatement|null $select = null;
96
97
    /**
98
     * If WITH CTE is present
99
     * holds the WithStatement.
100
     */
101
    public WithStatement|null $with = null;
102
103
    /**
104
     * If ON DUPLICATE KEY UPDATE clause is present
105
     * holds the SetOperation.
106
     *
107
     * @var SetOperation[]|null
108
     */
109
    public array|null $onDuplicateSet = null;
110
111 2
    public function build(): string
112
    {
113 2
        $ret = 'INSERT ' . $this->options;
114 2
        $ret = trim($ret) . ' INTO ' . $this->into;
115
116 2
        if ($this->values !== null && $this->values !== []) {
117 2
            $ret .= ' VALUES ' . ArrayObjs::buildAll($this->values);
118 2
        } elseif ($this->set !== null && $this->set !== []) {
119 2
            $ret .= ' SET ' . SetOperations::buildAll($this->set);
120 2
        } elseif ($this->select !== null && strlen((string) $this->select) > 0) {
121 2
            $ret .= ' ' . $this->select->build();
122
        }
123
124 2
        if ($this->onDuplicateSet !== null && $this->onDuplicateSet !== []) {
125 2
            $ret .= ' ON DUPLICATE KEY UPDATE ' . SetOperations::buildAll($this->onDuplicateSet);
126
        }
127
128 2
        return $ret;
129
    }
130
131
    /**
132
     * @param Parser     $parser the instance that requests parsing
133
     * @param TokensList $list   the list of tokens to be parsed
134
     */
135 52
    public function parse(Parser $parser, TokensList $list): void
136
    {
137 52
        ++$list->idx; // Skipping `INSERT`.
138
139
        // parse any options if provided
140 52
        $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions);
141 52
        ++$list->idx;
142
143
        /**
144
         * The state of the parser.
145
         *
146
         * Below are the states of the parser.
147
         *
148
         *      0 ---------------------------------[ INTO ]----------------------------------> 1
149
         *
150
         *      1 -------------------------[ VALUES/VALUE/SET/SELECT ]-----------------------> 2
151
         *
152
         *      2 -------------------------[ ON DUPLICATE KEY UPDATE ]-----------------------> 3
153
         */
154 52
        $state = 0;
155
156
        /**
157
         * For keeping track of semi-states on encountering
158
         * ON DUPLICATE KEY UPDATE ...
159
         */
160 52
        $miniState = 0;
161
162 52
        for (; $list->idx < $list->count; ++$list->idx) {
163
            /**
164
             * Token parsed at this moment.
165
             */
166 52
            $token = $list->tokens[$list->idx];
167
168
            // End of statement.
169 52
            if ($token->type === TokenType::Delimiter) {
170 42
                break;
171
            }
172
173
            // Skipping whitespaces and comments.
174 50
            if (($token->type === TokenType::Whitespace) || ($token->type === TokenType::Comment)) {
175 30
                continue;
176
            }
177
178 50
            if ($state === 0) {
179 50
                if ($token->type === TokenType::Keyword && $token->keyword !== 'INTO') {
180 2
                    $parser->error('Unexpected keyword.', $token);
181 2
                    break;
182
                }
183
184 48
                ++$list->idx;
185 48
                $this->into = IntoKeywords::parse(
186 48
                    $parser,
187 48
                    $list,
188 48
                    ['fromInsert' => true],
189 48
                );
190
191 48
                $state = 1;
192 48
            } elseif ($state === 1) {
193 48
                if ($token->type !== TokenType::Keyword) {
194 2
                    $parser->error('Unexpected token.', $token);
195 2
                    break;
196
                }
197
198 46
                if ($token->keyword === 'VALUE' || $token->keyword === 'VALUES') {
199 30
                    ++$list->idx; // skip VALUES
200
201 30
                    $this->values = Array2d::parse($parser, $list);
202 20
                } elseif ($token->keyword === 'SET') {
203 6
                    ++$list->idx; // skip SET
204
205 6
                    $this->set = SetOperations::parse($parser, $list);
206 16
                } elseif ($token->keyword === 'SELECT') {
207 6
                    $this->select = new SelectStatement($parser, $list);
208 10
                } elseif ($token->keyword === 'WITH') {
209 8
                    $this->with = new WithStatement($parser, $list);
210
                } else {
211 2
                    $parser->error('Unexpected keyword.', $token);
212 2
                    break;
213
                }
214
215 44
                $state = 2;
216 44
                $miniState = 1;
217 16
            } elseif ($state === 2) {
218 16
                $lastCount = $miniState;
219
220 16
                if ($miniState === 1 && $token->keyword === 'ON') {
221 12
                    ++$miniState;
222 16
                } elseif ($miniState === 2 && $token->keyword === 'DUPLICATE') {
223 12
                    ++$miniState;
224 16
                } elseif ($miniState === 3 && $token->keyword === 'KEY') {
225 12
                    ++$miniState;
226 16
                } elseif ($miniState === 4 && $token->keyword === 'UPDATE') {
227 10
                    ++$miniState;
228
                }
229
230 16
                if ($lastCount === $miniState) {
231 6
                    $parser->error('Unexpected token.', $token);
232 6
                    break;
233
                }
234
235 12
                if ($miniState === 5) {
236 10
                    ++$list->idx;
237 10
                    $this->onDuplicateSet = SetOperations::parse($parser, $list);
238 10
                    $state = 3;
239
                }
240
            }
241
        }
242
243 52
        --$list->idx;
244
    }
245
}
246