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\Condition; |
||||
9 | use PhpMyAdmin\SqlParser\Components\Expression; |
||||
10 | use PhpMyAdmin\SqlParser\Components\JoinKeyword; |
||||
11 | use PhpMyAdmin\SqlParser\Components\Limit; |
||||
12 | use PhpMyAdmin\SqlParser\Components\OrderKeyword; |
||||
13 | use PhpMyAdmin\SqlParser\Parser; |
||||
14 | use PhpMyAdmin\SqlParser\Parsers\Conditions; |
||||
15 | use PhpMyAdmin\SqlParser\Parsers\ExpressionArray; |
||||
16 | use PhpMyAdmin\SqlParser\Parsers\Expressions; |
||||
17 | use PhpMyAdmin\SqlParser\Parsers\JoinKeywords; |
||||
18 | use PhpMyAdmin\SqlParser\Parsers\Limits; |
||||
19 | use PhpMyAdmin\SqlParser\Parsers\OptionsArrays; |
||||
20 | use PhpMyAdmin\SqlParser\Parsers\OrderKeywords; |
||||
21 | use PhpMyAdmin\SqlParser\Statement; |
||||
22 | use PhpMyAdmin\SqlParser\TokensList; |
||||
23 | use PhpMyAdmin\SqlParser\TokenType; |
||||
24 | |||||
25 | use function stripos; |
||||
26 | use function strlen; |
||||
27 | |||||
28 | /** |
||||
29 | * `DELETE` statement. |
||||
30 | * |
||||
31 | * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name |
||||
32 | * [PARTITION (partition_name,...)] |
||||
33 | * [WHERE where_condition] |
||||
34 | * [ORDER BY ...] |
||||
35 | * [LIMIT row_count] |
||||
36 | * |
||||
37 | * Multi-table syntax |
||||
38 | * |
||||
39 | * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] |
||||
40 | * tbl_name[.*] [, tbl_name[.*]] ... |
||||
41 | * FROM table_references |
||||
42 | * [WHERE where_condition] |
||||
43 | * |
||||
44 | * OR |
||||
45 | * |
||||
46 | * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] |
||||
47 | * FROM tbl_name[.*] [, tbl_name[.*]] ... |
||||
48 | * USING table_references |
||||
49 | * [WHERE where_condition] |
||||
50 | */ |
||||
51 | class DeleteStatement extends Statement |
||||
52 | { |
||||
53 | /** |
||||
54 | * Options for `DELETE` statements. |
||||
55 | * |
||||
56 | * @var array<string, int|array<int, int|string>> |
||||
57 | * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})> |
||||
58 | */ |
||||
59 | public static array $statementOptions = [ |
||||
60 | 'LOW_PRIORITY' => 1, |
||||
61 | 'QUICK' => 2, |
||||
62 | 'IGNORE' => 3, |
||||
63 | ]; |
||||
64 | |||||
65 | /** |
||||
66 | * The clauses of this statement, in order. |
||||
67 | * |
||||
68 | * @see Statement::$clauses |
||||
69 | * |
||||
70 | * @var array<string, array{non-empty-string, int-mask-of<self::ADD_*>}> |
||||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||||
71 | */ |
||||
72 | public static array $clauses = [ |
||||
73 | 'DELETE' => [ |
||||
74 | 'DELETE', |
||||
75 | Statement::ADD_KEYWORD, |
||||
76 | ], |
||||
77 | // Used for options. |
||||
78 | '_OPTIONS' => [ |
||||
79 | '_OPTIONS', |
||||
80 | Statement::ADD_CLAUSE, |
||||
81 | ], |
||||
82 | 'FROM' => [ |
||||
83 | 'FROM', |
||||
84 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
85 | ], |
||||
86 | 'PARTITION' => [ |
||||
87 | 'PARTITION', |
||||
88 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
89 | ], |
||||
90 | 'USING' => [ |
||||
91 | 'USING', |
||||
92 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
93 | ], |
||||
94 | 'WHERE' => [ |
||||
95 | 'WHERE', |
||||
96 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
97 | ], |
||||
98 | 'ORDER BY' => [ |
||||
99 | 'ORDER BY', |
||||
100 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
101 | ], |
||||
102 | 'LIMIT' => [ |
||||
103 | 'LIMIT', |
||||
104 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
105 | ], |
||||
106 | ]; |
||||
107 | |||||
108 | /** |
||||
109 | * Table(s) used as sources for this statement. |
||||
110 | * |
||||
111 | * @var Expression[]|null |
||||
112 | */ |
||||
113 | public array|null $from = null; |
||||
114 | |||||
115 | /** |
||||
116 | * Joins. |
||||
117 | * |
||||
118 | * @var JoinKeyword[]|null |
||||
119 | */ |
||||
120 | public array|null $join = null; |
||||
121 | |||||
122 | /** |
||||
123 | * Tables used as sources for this statement. |
||||
124 | * |
||||
125 | * @var Expression[]|null |
||||
126 | */ |
||||
127 | public array|null $using = null; |
||||
128 | |||||
129 | /** |
||||
130 | * Columns used in this statement. |
||||
131 | * |
||||
132 | * @var Expression[]|null |
||||
133 | */ |
||||
134 | public array|null $columns = null; |
||||
135 | |||||
136 | /** |
||||
137 | * Partitions used as source for this statement. |
||||
138 | */ |
||||
139 | public ArrayObj|null $partition = null; |
||||
140 | |||||
141 | /** |
||||
142 | * Conditions used for filtering each row of the result set. |
||||
143 | * |
||||
144 | * @var Condition[]|null |
||||
145 | */ |
||||
146 | public array|null $where = null; |
||||
147 | |||||
148 | /** |
||||
149 | * Specifies the order of the rows in the result set. |
||||
150 | * |
||||
151 | * @var OrderKeyword[]|null |
||||
152 | */ |
||||
153 | public array|null $order = null; |
||||
154 | |||||
155 | /** |
||||
156 | * Conditions used for limiting the size of the result set. |
||||
157 | */ |
||||
158 | public Limit|null $limit = null; |
||||
159 | |||||
160 | 4 | public function build(): string |
|||
161 | { |
||||
162 | 4 | $ret = 'DELETE ' . $this->options->build(); |
|||
0 ignored issues
–
show
The method
build() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
163 | |||||
164 | 4 | if ($this->columns !== null && $this->columns !== []) { |
|||
165 | 2 | $ret .= ' ' . Expressions::buildAll($this->columns); |
|||
166 | } |
||||
167 | |||||
168 | 4 | if ($this->from !== null && $this->from !== []) { |
|||
169 | 4 | $ret .= ' FROM ' . Expressions::buildAll($this->from); |
|||
170 | } |
||||
171 | |||||
172 | 4 | if ($this->join !== null && $this->join !== []) { |
|||
173 | 2 | $ret .= ' ' . JoinKeywords::buildAll($this->join); |
|||
174 | } |
||||
175 | |||||
176 | 4 | if ($this->using !== null && $this->using !== []) { |
|||
177 | 2 | $ret .= ' USING ' . Expressions::buildAll($this->using); |
|||
178 | } |
||||
179 | |||||
180 | 4 | if ($this->where !== null && $this->where !== []) { |
|||
181 | 4 | $ret .= ' WHERE ' . Conditions::buildAll($this->where); |
|||
182 | } |
||||
183 | |||||
184 | 4 | if ($this->order !== null && $this->order !== []) { |
|||
185 | 2 | $ret .= ' ORDER BY ' . OrderKeywords::buildAll($this->order); |
|||
186 | } |
||||
187 | |||||
188 | 4 | if ($this->limit !== null && strlen((string) $this->limit) > 0) { |
|||
189 | 2 | $ret .= ' LIMIT ' . $this->limit->build(); |
|||
190 | } |
||||
191 | |||||
192 | 4 | return $ret; |
|||
193 | } |
||||
194 | |||||
195 | /** |
||||
196 | * @param Parser $parser the instance that requests parsing |
||||
197 | * @param TokensList $list the list of tokens to be parsed |
||||
198 | */ |
||||
199 | 62 | public function parse(Parser $parser, TokensList $list): void |
|||
200 | { |
||||
201 | 62 | ++$list->idx; // Skipping `DELETE`. |
|||
202 | |||||
203 | // parse any options if provided |
||||
204 | 62 | $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions); |
|||
205 | 62 | ++$list->idx; |
|||
206 | |||||
207 | /** |
||||
208 | * The state of the parser. |
||||
209 | * |
||||
210 | * Below are the states of the parser. |
||||
211 | * |
||||
212 | * 0 ---------------------------------[ FROM ]----------------------------------> 2 |
||||
213 | * 0 ------------------------------[ table[.*] ]--------------------------------> 1 |
||||
214 | * 1 ---------------------------------[ FROM ]----------------------------------> 2 |
||||
215 | * 2 --------------------------------[ USING ]----------------------------------> 3 |
||||
216 | * 2 --------------------------------[ WHERE ]----------------------------------> 4 |
||||
217 | * 2 --------------------------------[ ORDER ]----------------------------------> 5 |
||||
218 | * 2 --------------------------------[ LIMIT ]----------------------------------> 6 |
||||
219 | */ |
||||
220 | 62 | $state = 0; |
|||
221 | |||||
222 | /** |
||||
223 | * If the query is multi-table or not. |
||||
224 | */ |
||||
225 | 62 | $multiTable = false; |
|||
226 | |||||
227 | 62 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
228 | /** |
||||
229 | * Token parsed at this moment. |
||||
230 | */ |
||||
231 | 62 | $token = $list->tokens[$list->idx]; |
|||
232 | |||||
233 | // End of statement. |
||||
234 | 62 | if ($token->type === TokenType::Delimiter) { |
|||
235 | 38 | break; |
|||
236 | } |
||||
237 | |||||
238 | 62 | if ($state === 0) { |
|||
239 | 62 | if ($token->type === TokenType::Keyword) { |
|||
240 | 52 | if ($token->keyword !== 'FROM') { |
|||
241 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
242 | 2 | break; |
|||
243 | } |
||||
244 | |||||
245 | 50 | ++$list->idx; // Skip 'FROM' |
|||
246 | 50 | $this->from = ExpressionArray::parse($parser, $list); |
|||
247 | |||||
248 | 50 | $state = 2; |
|||
249 | } else { |
||||
250 | 12 | $this->columns = ExpressionArray::parse($parser, $list); |
|||
251 | 12 | $state = 1; |
|||
252 | } |
||||
253 | 56 | } elseif ($state === 1) { |
|||
254 | 12 | if ($token->type !== TokenType::Keyword) { |
|||
255 | 2 | $parser->error('Unexpected token.', $token); |
|||
256 | 2 | break; |
|||
257 | } |
||||
258 | |||||
259 | 10 | if ($token->keyword !== 'FROM') { |
|||
260 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
261 | 2 | break; |
|||
262 | } |
||||
263 | |||||
264 | 8 | ++$list->idx; // Skip 'FROM' |
|||
265 | 8 | $this->from = ExpressionArray::parse($parser, $list); |
|||
266 | |||||
267 | 8 | $state = 2; |
|||
268 | 50 | } elseif ($state === 2) { |
|||
269 | 50 | if ($token->type === TokenType::Keyword) { |
|||
270 | 50 | if (stripos($token->keyword, 'JOIN') !== false) { |
|||
271 | 4 | ++$list->idx; |
|||
272 | 4 | $this->join = JoinKeywords::parse($parser, $list); |
|||
273 | |||||
274 | // remain in state = 2 |
||||
275 | } else { |
||||
276 | 50 | switch ($token->keyword) { |
|||
277 | 50 | case 'USING': |
|||
278 | 14 | ++$list->idx; // Skip 'USING' |
|||
279 | 14 | $this->using = ExpressionArray::parse($parser, $list); |
|||
280 | 14 | $state = 3; |
|||
281 | |||||
282 | 14 | $multiTable = true; |
|||
283 | 14 | break; |
|||
284 | 38 | case 'WHERE': |
|||
285 | 30 | ++$list->idx; // Skip 'WHERE' |
|||
286 | 30 | $this->where = Conditions::parse($parser, $list); |
|||
287 | 30 | $state = 4; |
|||
288 | 30 | break; |
|||
289 | 8 | case 'ORDER BY': |
|||
290 | 4 | ++$list->idx; // Skip 'ORDER BY' |
|||
291 | 4 | $this->order = OrderKeywords::parse($parser, $list); |
|||
292 | 4 | $state = 5; |
|||
293 | 4 | break; |
|||
294 | 4 | case 'LIMIT': |
|||
295 | 2 | ++$list->idx; // Skip 'LIMIT' |
|||
296 | 2 | $this->limit = Limits::parse($parser, $list); |
|||
297 | 2 | $state = 6; |
|||
298 | 2 | break; |
|||
299 | default: |
||||
300 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
301 | 2 | break 2; |
|||
302 | } |
||||
303 | } |
||||
304 | } |
||||
305 | 34 | } elseif ($state === 3) { |
|||
306 | 14 | if ($token->type !== TokenType::Keyword) { |
|||
307 | 4 | $parser->error('Unexpected token.', $token); |
|||
308 | 4 | break; |
|||
309 | } |
||||
310 | |||||
311 | 10 | if ($token->keyword !== 'WHERE') { |
|||
312 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
313 | 2 | break; |
|||
314 | } |
||||
315 | |||||
316 | 8 | ++$list->idx; // Skip 'WHERE' |
|||
317 | 8 | $this->where = Conditions::parse($parser, $list); |
|||
318 | 8 | $state = 4; |
|||
319 | 24 | } elseif ($state === 4) { |
|||
320 | 22 | if ($multiTable === true && $token->type === TokenType::Keyword) { |
|||
321 | 4 | $parser->error('This type of clause is not valid in Multi-table queries.', $token); |
|||
322 | 4 | break; |
|||
323 | } |
||||
324 | |||||
325 | 18 | if ($token->type === TokenType::Keyword) { |
|||
326 | 18 | switch ($token->keyword) { |
|||
327 | 18 | case 'ORDER BY': |
|||
328 | 14 | ++$list->idx; // Skip 'ORDER BY' |
|||
329 | 14 | $this->order = OrderKeywords::parse($parser, $list); |
|||
330 | 14 | $state = 5; |
|||
331 | 14 | break; |
|||
332 | 4 | case 'LIMIT': |
|||
333 | 2 | ++$list->idx; // Skip 'LIMIT' |
|||
334 | 2 | $this->limit = Limits::parse($parser, $list); |
|||
335 | 2 | $state = 6; |
|||
336 | 2 | break; |
|||
337 | default: |
||||
338 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
339 | 2 | break 2; |
|||
340 | } |
||||
341 | } |
||||
342 | 12 | } elseif ($state === 5) { |
|||
343 | 12 | if ($token->type === TokenType::Keyword) { |
|||
344 | 12 | if ($token->keyword !== 'LIMIT') { |
|||
345 | 4 | $parser->error('Unexpected keyword.', $token); |
|||
346 | 4 | break; |
|||
347 | } |
||||
348 | |||||
349 | 8 | ++$list->idx; // Skip 'LIMIT' |
|||
350 | 8 | $this->limit = Limits::parse($parser, $list); |
|||
351 | 8 | $state = 6; |
|||
352 | } |
||||
353 | } |
||||
354 | } |
||||
355 | |||||
356 | 62 | if ($state >= 2) { |
|||
357 | 56 | foreach ($this->from as $fromExpr) { |
|||
358 | 56 | $fromExpr->database = $fromExpr->table; |
|||
359 | 56 | $fromExpr->table = $fromExpr->column; |
|||
360 | 56 | $fromExpr->column = null; |
|||
361 | } |
||||
362 | } |
||||
363 | |||||
364 | 62 | --$list->idx; |
|||
365 | } |
||||
366 | } |
||||
367 |