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\CreateDefinition; |
||||
9 | use PhpMyAdmin\SqlParser\Components\DataType; |
||||
10 | use PhpMyAdmin\SqlParser\Components\Expression; |
||||
11 | use PhpMyAdmin\SqlParser\Components\OptionsArray; |
||||
12 | use PhpMyAdmin\SqlParser\Components\ParameterDefinition; |
||||
13 | use PhpMyAdmin\SqlParser\Components\PartitionDefinition; |
||||
14 | use PhpMyAdmin\SqlParser\Parser; |
||||
15 | use PhpMyAdmin\SqlParser\Parsers\ArrayObjs; |
||||
16 | use PhpMyAdmin\SqlParser\Parsers\CreateDefinitions; |
||||
17 | use PhpMyAdmin\SqlParser\Parsers\DataTypes; |
||||
18 | use PhpMyAdmin\SqlParser\Parsers\Expressions; |
||||
19 | use PhpMyAdmin\SqlParser\Parsers\OptionsArrays; |
||||
20 | use PhpMyAdmin\SqlParser\Parsers\ParameterDefinitions; |
||||
21 | use PhpMyAdmin\SqlParser\Parsers\PartitionDefinitions; |
||||
22 | use PhpMyAdmin\SqlParser\Statement; |
||||
23 | use PhpMyAdmin\SqlParser\Token; |
||||
24 | use PhpMyAdmin\SqlParser\TokensList; |
||||
25 | use PhpMyAdmin\SqlParser\TokenType; |
||||
26 | |||||
27 | use function is_array; |
||||
28 | use function trim; |
||||
29 | |||||
30 | /** |
||||
31 | * `CREATE` statement. |
||||
32 | */ |
||||
33 | class CreateStatement extends Statement |
||||
34 | { |
||||
35 | /** |
||||
36 | * Options for `CREATE` statements. |
||||
37 | * |
||||
38 | * @var array<string, int|array<int, int|string>> |
||||
39 | * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})> |
||||
40 | */ |
||||
41 | public static array $statementOptions = [ |
||||
42 | // CREATE TABLE |
||||
43 | 'TEMPORARY' => 1, |
||||
44 | |||||
45 | // CREATE VIEW |
||||
46 | 'OR REPLACE' => 2, |
||||
47 | 'ALGORITHM' => [ |
||||
48 | 3, |
||||
49 | 'var=', |
||||
50 | ], |
||||
51 | // `DEFINER` is also used for `CREATE FUNCTION / PROCEDURE` |
||||
52 | 'DEFINER' => [ |
||||
53 | 4, |
||||
54 | 'expr=', |
||||
55 | ], |
||||
56 | // Used in `CREATE VIEW` |
||||
57 | 'SQL SECURITY' => [ |
||||
58 | 5, |
||||
59 | 'var', |
||||
60 | ], |
||||
61 | |||||
62 | 'DATABASE' => 6, |
||||
63 | 'EVENT' => 6, |
||||
64 | 'FUNCTION' => 6, |
||||
65 | 'INDEX' => 6, |
||||
66 | 'UNIQUE INDEX' => 6, |
||||
67 | 'FULLTEXT INDEX' => 6, |
||||
68 | 'SPATIAL INDEX' => 6, |
||||
69 | 'PROCEDURE' => 6, |
||||
70 | 'SERVER' => 6, |
||||
71 | 'TABLE' => 6, |
||||
72 | 'TABLESPACE' => 6, |
||||
73 | 'TRIGGER' => 6, |
||||
74 | 'USER' => 6, |
||||
75 | 'VIEW' => 6, |
||||
76 | 'SCHEMA' => 6, |
||||
77 | |||||
78 | // CREATE TABLE |
||||
79 | 'IF NOT EXISTS' => 7, |
||||
80 | ]; |
||||
81 | |||||
82 | /** |
||||
83 | * All database options. |
||||
84 | */ |
||||
85 | private const DATABASE_OPTIONS = [ |
||||
86 | 'CHARACTER SET' => [ |
||||
87 | 1, |
||||
88 | 'var=', |
||||
89 | ], |
||||
90 | 'CHARSET' => [ |
||||
91 | 1, |
||||
92 | 'var=', |
||||
93 | ], |
||||
94 | 'DEFAULT CHARACTER SET' => [ |
||||
95 | 1, |
||||
96 | 'var=', |
||||
97 | ], |
||||
98 | 'DEFAULT CHARSET' => [ |
||||
99 | 1, |
||||
100 | 'var=', |
||||
101 | ], |
||||
102 | 'DEFAULT COLLATE' => [ |
||||
103 | 2, |
||||
104 | 'var=', |
||||
105 | ], |
||||
106 | 'COLLATE' => [ |
||||
107 | 2, |
||||
108 | 'var=', |
||||
109 | ], |
||||
110 | ]; |
||||
111 | |||||
112 | /** |
||||
113 | * All table options. |
||||
114 | */ |
||||
115 | private const TABLE_OPTIONS = [ |
||||
116 | 'ENGINE' => [ |
||||
117 | 1, |
||||
118 | 'var=', |
||||
119 | ], |
||||
120 | 'AUTO_INCREMENT' => [ |
||||
121 | 2, |
||||
122 | 'var=', |
||||
123 | ], |
||||
124 | 'AVG_ROW_LENGTH' => [ |
||||
125 | 3, |
||||
126 | 'var', |
||||
127 | ], |
||||
128 | 'CHARACTER SET' => [ |
||||
129 | 4, |
||||
130 | 'var=', |
||||
131 | ], |
||||
132 | 'CHARSET' => [ |
||||
133 | 4, |
||||
134 | 'var=', |
||||
135 | ], |
||||
136 | 'DEFAULT CHARACTER SET' => [ |
||||
137 | 4, |
||||
138 | 'var=', |
||||
139 | ], |
||||
140 | 'DEFAULT CHARSET' => [ |
||||
141 | 4, |
||||
142 | 'var=', |
||||
143 | ], |
||||
144 | 'CHECKSUM' => [ |
||||
145 | 5, |
||||
146 | 'var', |
||||
147 | ], |
||||
148 | 'DEFAULT COLLATE' => [ |
||||
149 | 6, |
||||
150 | 'var=', |
||||
151 | ], |
||||
152 | 'COLLATE' => [ |
||||
153 | 6, |
||||
154 | 'var=', |
||||
155 | ], |
||||
156 | 'COMMENT' => [ |
||||
157 | 7, |
||||
158 | 'var=', |
||||
159 | ], |
||||
160 | 'CONNECTION' => [ |
||||
161 | 8, |
||||
162 | 'var', |
||||
163 | ], |
||||
164 | 'DATA DIRECTORY' => [ |
||||
165 | 9, |
||||
166 | 'var', |
||||
167 | ], |
||||
168 | 'DELAY_KEY_WRITE' => [ |
||||
169 | 10, |
||||
170 | 'var', |
||||
171 | ], |
||||
172 | 'INDEX DIRECTORY' => [ |
||||
173 | 11, |
||||
174 | 'var', |
||||
175 | ], |
||||
176 | 'INSERT_METHOD' => [ |
||||
177 | 12, |
||||
178 | 'var', |
||||
179 | ], |
||||
180 | 'KEY_BLOCK_SIZE' => [ |
||||
181 | 13, |
||||
182 | 'var', |
||||
183 | ], |
||||
184 | 'MAX_ROWS' => [ |
||||
185 | 14, |
||||
186 | 'var', |
||||
187 | ], |
||||
188 | 'MIN_ROWS' => [ |
||||
189 | 15, |
||||
190 | 'var', |
||||
191 | ], |
||||
192 | 'PACK_KEYS' => [ |
||||
193 | 16, |
||||
194 | 'var', |
||||
195 | ], |
||||
196 | 'PASSWORD' => [ |
||||
197 | 17, |
||||
198 | 'var', |
||||
199 | ], |
||||
200 | 'ROW_FORMAT' => [ |
||||
201 | 18, |
||||
202 | 'var', |
||||
203 | ], |
||||
204 | 'TABLESPACE' => [ |
||||
205 | 19, |
||||
206 | 'var', |
||||
207 | ], |
||||
208 | 'STORAGE' => [ |
||||
209 | 20, |
||||
210 | 'var', |
||||
211 | ], |
||||
212 | 'UNION' => [ |
||||
213 | 21, |
||||
214 | 'var', |
||||
215 | ], |
||||
216 | 'PAGE_COMPRESSED' => [ |
||||
217 | 22, |
||||
218 | 'var', |
||||
219 | ], |
||||
220 | 'PAGE_COMPRESSION_LEVEL' => [ |
||||
221 | 23, |
||||
222 | 'var', |
||||
223 | ], |
||||
224 | ]; |
||||
225 | |||||
226 | /** |
||||
227 | * All function options. |
||||
228 | */ |
||||
229 | private const FUNCTION_OPTIONS = [ |
||||
230 | 'NOT' => [ |
||||
231 | 2, |
||||
232 | 'var', |
||||
233 | ], |
||||
234 | 'FUNCTION' => [ |
||||
235 | 3, |
||||
236 | 'var=', |
||||
237 | ], |
||||
238 | 'PROCEDURE' => [ |
||||
239 | 3, |
||||
240 | 'var=', |
||||
241 | ], |
||||
242 | 'CONTAINS SQL' => 4, |
||||
243 | 'NO SQL' => 4, |
||||
244 | 'READS SQL DATA' => 4, |
||||
245 | 'MODIFIES SQL DATA' => 4, |
||||
246 | 'SQL SECURITY' => [ |
||||
247 | 6, |
||||
248 | 'var', |
||||
249 | ], |
||||
250 | 'LANGUAGE' => [ |
||||
251 | 7, |
||||
252 | 'var', |
||||
253 | ], |
||||
254 | 'COMMENT' => [ |
||||
255 | 8, |
||||
256 | 'var', |
||||
257 | ], |
||||
258 | |||||
259 | 'CREATE' => 1, |
||||
260 | 'DETERMINISTIC' => 2, |
||||
261 | ]; |
||||
262 | |||||
263 | /** |
||||
264 | * All trigger options. |
||||
265 | */ |
||||
266 | private const TRIGGER_OPTIONS = [ |
||||
267 | 'BEFORE' => 1, |
||||
268 | 'AFTER' => 1, |
||||
269 | 'INSERT' => 2, |
||||
270 | 'UPDATE' => 2, |
||||
271 | 'DELETE' => 2, |
||||
272 | ]; |
||||
273 | |||||
274 | /** |
||||
275 | * The name of the entity that is created. |
||||
276 | * |
||||
277 | * Used by all `CREATE` statements. |
||||
278 | */ |
||||
279 | public Expression|null $name = null; |
||||
280 | |||||
281 | /** |
||||
282 | * The options of the entity (table, procedure, function, etc.). |
||||
283 | * |
||||
284 | * Used by `CREATE TABLE`, `CREATE FUNCTION` and `CREATE PROCEDURE`. |
||||
285 | * |
||||
286 | * @see CreateStatement::TABLE_OPTIONS |
||||
287 | * @see CreateStatement::FUNCTION_OPTIONS |
||||
288 | * @see CreateStatement::TRIGGER_OPTIONS |
||||
289 | */ |
||||
290 | public OptionsArray|null $entityOptions = null; |
||||
291 | |||||
292 | /** |
||||
293 | * If `CREATE TABLE`, a list of columns and keys. |
||||
294 | * If `CREATE VIEW`, a list of columns. |
||||
295 | * |
||||
296 | * Used by `CREATE TABLE` and `CREATE VIEW`. |
||||
297 | * |
||||
298 | * @var CreateDefinition[]|ArrayObj|null |
||||
299 | */ |
||||
300 | public array|ArrayObj|null $fields = null; |
||||
301 | |||||
302 | /** |
||||
303 | * If `CREATE TABLE WITH`. |
||||
304 | * If `CREATE TABLE AS WITH`. |
||||
305 | * If `CREATE VIEW AS WITH`. |
||||
306 | * |
||||
307 | * Used by `CREATE TABLE`, `CREATE VIEW` |
||||
308 | */ |
||||
309 | public WithStatement|null $with = null; |
||||
310 | |||||
311 | /** |
||||
312 | * If `CREATE TABLE ... SELECT`. |
||||
313 | * If `CREATE VIEW AS ` ... SELECT`. |
||||
314 | * |
||||
315 | * Used by `CREATE TABLE`, `CREATE VIEW` |
||||
316 | */ |
||||
317 | public SelectStatement|null $select = null; |
||||
318 | |||||
319 | /** |
||||
320 | * If `CREATE TABLE ... LIKE`. |
||||
321 | * |
||||
322 | * Used by `CREATE TABLE` |
||||
323 | */ |
||||
324 | public Expression|null $like = null; |
||||
325 | |||||
326 | /** |
||||
327 | * Expression used for partitioning. |
||||
328 | */ |
||||
329 | public string|null $partitionBy = null; |
||||
330 | |||||
331 | /** |
||||
332 | * The number of partitions. |
||||
333 | */ |
||||
334 | public int|null $partitionsNum = null; |
||||
335 | |||||
336 | /** |
||||
337 | * Expression used for subpartitioning. |
||||
338 | */ |
||||
339 | public string|null $subpartitionBy = null; |
||||
340 | |||||
341 | /** |
||||
342 | * The number of subpartitions. |
||||
343 | */ |
||||
344 | public int|null $subpartitionsNum = null; |
||||
345 | |||||
346 | /** |
||||
347 | * The partition of the new table. |
||||
348 | * |
||||
349 | * @var PartitionDefinition[]|null |
||||
350 | */ |
||||
351 | public array|null $partitions = null; |
||||
352 | |||||
353 | /** |
||||
354 | * If `CREATE TRIGGER` the name of the table. |
||||
355 | * |
||||
356 | * Used by `CREATE TRIGGER`. |
||||
357 | */ |
||||
358 | public Expression|null $table = null; |
||||
359 | |||||
360 | /** |
||||
361 | * The return data type of this routine. |
||||
362 | * |
||||
363 | * Used by `CREATE FUNCTION`. |
||||
364 | */ |
||||
365 | public DataType|null $return = null; |
||||
366 | |||||
367 | /** |
||||
368 | * The parameters of this routine. |
||||
369 | * |
||||
370 | * Used by `CREATE FUNCTION` and `CREATE PROCEDURE`. |
||||
371 | * |
||||
372 | * @var ParameterDefinition[]|null |
||||
373 | */ |
||||
374 | public array|null $parameters = null; |
||||
375 | |||||
376 | /** |
||||
377 | * The body of this function or procedure. |
||||
378 | * For views, it is the select statement that creates the view. |
||||
379 | * Used by `CREATE FUNCTION`, `CREATE PROCEDURE` and `CREATE VIEW`. |
||||
380 | * |
||||
381 | * @var Token[] |
||||
382 | */ |
||||
383 | public array $body = []; |
||||
384 | |||||
385 | 44 | public function build(): string |
|||
386 | { |
||||
387 | 44 | $fields = ''; |
|||
388 | 44 | if ($this->fields !== null && $this->fields !== []) { |
|||
389 | 28 | if (is_array($this->fields)) { |
|||
390 | 26 | $fields = CreateDefinitions::buildAll($this->fields) . ' '; |
|||
391 | } else { |
||||
392 | 2 | $fields = $this->fields->build(); |
|||
393 | } |
||||
394 | } |
||||
395 | |||||
396 | 44 | if ($this->options->has('DATABASE') || $this->options->has('SCHEMA')) { |
|||
0 ignored issues
–
show
|
|||||
397 | 2 | return 'CREATE ' |
|||
398 | 2 | . $this->options->build() . ' ' |
|||
399 | 2 | . $this->name->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. ![]() |
|||||
400 | 2 | . $this->entityOptions->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. ![]() |
|||||
401 | } |
||||
402 | |||||
403 | 42 | if ($this->options->has('TABLE')) { |
|||
404 | 28 | if ($this->select !== null) { |
|||
405 | 2 | return 'CREATE ' |
|||
406 | 2 | . $this->options->build() . ' ' |
|||
407 | 2 | . $this->name->build() . ' ' |
|||
408 | 2 | . $this->select->build(); |
|||
409 | } |
||||
410 | |||||
411 | 26 | if ($this->like !== null) { |
|||
412 | 2 | return 'CREATE ' |
|||
413 | 2 | . $this->options->build() . ' ' |
|||
414 | 2 | . $this->name->build() . ' LIKE ' |
|||
415 | 2 | . $this->like->build(); |
|||
416 | } |
||||
417 | |||||
418 | 26 | if ($this->with !== null) { |
|||
419 | 2 | return 'CREATE ' |
|||
420 | 2 | . $this->options->build() . ' ' |
|||
421 | 2 | . $this->name->build() . ' ' |
|||
422 | 2 | . $this->with->build(); |
|||
423 | } |
||||
424 | |||||
425 | 26 | $partition = ''; |
|||
426 | |||||
427 | 26 | if (! empty($this->partitionBy)) { |
|||
428 | 6 | $partition .= "\nPARTITION BY " . $this->partitionBy; |
|||
429 | } |
||||
430 | |||||
431 | 26 | if (! empty($this->partitionsNum)) { |
|||
432 | 2 | $partition .= "\nPARTITIONS " . $this->partitionsNum; |
|||
433 | } |
||||
434 | |||||
435 | 26 | if (! empty($this->subpartitionBy)) { |
|||
436 | 4 | $partition .= "\nSUBPARTITION BY " . $this->subpartitionBy; |
|||
437 | } |
||||
438 | |||||
439 | 26 | if (! empty($this->subpartitionsNum)) { |
|||
440 | 2 | $partition .= "\nSUBPARTITIONS " . $this->subpartitionsNum; |
|||
441 | } |
||||
442 | |||||
443 | 26 | if (! empty($this->partitions)) { |
|||
444 | 6 | $partition .= "\n" . PartitionDefinitions::buildAll($this->partitions); |
|||
445 | } |
||||
446 | |||||
447 | 26 | return 'CREATE ' |
|||
448 | 26 | . $this->options->build() . ' ' |
|||
449 | 26 | . $this->name->build() . ' ' |
|||
450 | 26 | . $fields |
|||
451 | 26 | . ($this->entityOptions?->build() ?? '') |
|||
452 | 26 | . $partition; |
|||
453 | } |
||||
454 | |||||
455 | 16 | if ($this->options->has('VIEW')) { |
|||
456 | 4 | $builtStatement = ''; |
|||
457 | 4 | if ($this->select !== null) { |
|||
458 | 2 | $builtStatement = $this->select->build(); |
|||
459 | 4 | } elseif ($this->with !== null) { |
|||
460 | 4 | $builtStatement = $this->with->build(); |
|||
461 | } |
||||
462 | |||||
463 | 4 | return 'CREATE ' |
|||
464 | 4 | . $this->options->build() . ' ' |
|||
465 | 4 | . $this->name->build() . ' ' |
|||
466 | 4 | . $fields . ' AS ' . $builtStatement |
|||
467 | 4 | . TokensList::buildFromArray($this->body) . ' ' |
|||
468 | 4 | . ($this->entityOptions?->build() ?? ''); |
|||
469 | } |
||||
470 | |||||
471 | 12 | if ($this->options->has('TRIGGER')) { |
|||
472 | 4 | return 'CREATE ' |
|||
473 | 4 | . $this->options->build() . ' ' |
|||
474 | 4 | . $this->name->build() . ' ' |
|||
475 | 4 | . $this->entityOptions->build() . ' ' |
|||
476 | 4 | . 'ON ' . $this->table->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. ![]() |
|||||
477 | 4 | . 'FOR EACH ROW ' . TokensList::buildFromArray($this->body); |
|||
478 | } |
||||
479 | |||||
480 | 8 | if ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) { |
|||
481 | 6 | $tmp = ''; |
|||
482 | 6 | if ($this->options->has('FUNCTION')) { |
|||
483 | 4 | $tmp = 'RETURNS ' . $this->return->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. ![]() |
|||||
484 | } |
||||
485 | |||||
486 | 6 | return 'CREATE ' |
|||
487 | 6 | . $this->options->build() . ' ' |
|||
488 | 6 | . $this->name->build() . ' ' |
|||
489 | 6 | . ParameterDefinitions::buildAll($this->parameters) . ' ' |
|||
0 ignored issues
–
show
It seems like
$this->parameters can also be of type null ; however, parameter $component of PhpMyAdmin\SqlParser\Par...Definitions::buildAll() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
490 | 6 | . $tmp . ' ' . $this->entityOptions->build() . ' ' |
|||
491 | 6 | . TokensList::buildFromArray($this->body); |
|||
492 | } |
||||
493 | |||||
494 | 2 | return 'CREATE ' |
|||
495 | 2 | . $this->options->build() . ' ' |
|||
496 | 2 | . $this->name->build() . ' ' |
|||
497 | 2 | . TokensList::buildFromArray($this->body); |
|||
498 | } |
||||
499 | |||||
500 | /** |
||||
501 | * @param Parser $parser the instance that requests parsing |
||||
502 | * @param TokensList $list the list of tokens to be parsed |
||||
503 | */ |
||||
504 | 216 | public function parse(Parser $parser, TokensList $list): void |
|||
505 | { |
||||
506 | 216 | ++$list->idx; // Skipping `CREATE`. |
|||
507 | |||||
508 | // Parsing options. |
||||
509 | 216 | $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions); |
|||
510 | 216 | ++$list->idx; // Skipping last option. |
|||
511 | |||||
512 | 216 | $isDatabase = $this->options->has('DATABASE') || $this->options->has('SCHEMA'); |
|||
513 | 216 | $fieldName = $isDatabase ? 'database' : 'table'; |
|||
514 | |||||
515 | // Parsing the field name. |
||||
516 | 216 | $this->name = Expressions::parse( |
|||
517 | 216 | $parser, |
|||
518 | 216 | $list, |
|||
519 | 216 | [ |
|||
520 | 216 | 'parseField' => $fieldName, |
|||
521 | 216 | 'breakOnAlias' => true, |
|||
522 | 216 | ], |
|||
523 | 216 | ); |
|||
524 | |||||
525 | 216 | if ($this->name === null) { |
|||
526 | 2 | $parser->error('The name of the entity was expected.', $list->tokens[$list->idx]); |
|||
527 | } else { |
||||
528 | 214 | ++$list->idx; // Skipping field. |
|||
529 | } |
||||
530 | |||||
531 | /** |
||||
532 | * Token parsed at this moment. |
||||
533 | */ |
||||
534 | 216 | $token = $list->tokens[$list->idx]; |
|||
535 | 216 | $nextidx = $list->idx + 1; |
|||
536 | 216 | while ($nextidx < $list->count && $list->tokens[$nextidx]->type === TokenType::Whitespace) { |
|||
537 | 138 | ++$nextidx; |
|||
538 | } |
||||
539 | |||||
540 | 216 | if ($isDatabase) { |
|||
541 | 10 | $this->entityOptions = OptionsArrays::parse($parser, $list, self::DATABASE_OPTIONS); |
|||
542 | 206 | } elseif ($this->options->has('TABLE')) { |
|||
543 | 132 | if (($token->type === TokenType::Keyword) && ($token->keyword === 'SELECT')) { |
|||
544 | /* CREATE TABLE ... SELECT */ |
||||
545 | 4 | $this->select = new SelectStatement($parser, $list); |
|||
546 | 128 | } elseif ($token->type === TokenType::Keyword && ($token->keyword === 'WITH')) { |
|||
547 | /* CREATE TABLE WITH */ |
||||
548 | 12 | $this->with = new WithStatement($parser, $list); |
|||
549 | } elseif ( |
||||
550 | 118 | ($token->type === TokenType::Keyword) && ($token->keyword === 'AS') |
|||
551 | 118 | && ($list->tokens[$nextidx]->type === TokenType::Keyword) |
|||
552 | ) { |
||||
553 | 4 | if ($list->tokens[$nextidx]->value === 'SELECT') { |
|||
554 | /* CREATE TABLE ... AS SELECT */ |
||||
555 | 2 | $list->idx = $nextidx; |
|||
556 | 2 | $this->select = new SelectStatement($parser, $list); |
|||
557 | 2 | } elseif ($list->tokens[$nextidx]->value === 'WITH') { |
|||
558 | /* CREATE TABLE WITH */ |
||||
559 | 2 | $list->idx = $nextidx; |
|||
560 | 2 | $this->with = new WithStatement($parser, $list); |
|||
561 | } |
||||
562 | 114 | } elseif ($token->type === TokenType::Keyword && $token->keyword === 'LIKE') { |
|||
563 | /* CREATE TABLE `new_tbl` LIKE 'orig_tbl' */ |
||||
564 | 6 | $list->idx = $nextidx; |
|||
565 | 6 | $this->like = Expressions::parse( |
|||
566 | 6 | $parser, |
|||
567 | 6 | $list, |
|||
568 | 6 | [ |
|||
569 | 6 | 'parseField' => 'table', |
|||
570 | 6 | 'breakOnAlias' => true, |
|||
571 | 6 | ], |
|||
572 | 6 | ); |
|||
573 | // The 'LIKE' keyword was found, but no table_name was found next to it |
||||
574 | 6 | if ($this->like === null) { |
|||
575 | 2 | $parser->error('A table name was expected.', $list->tokens[$list->idx]); |
|||
576 | } |
||||
577 | } else { |
||||
578 | 110 | $this->fields = CreateDefinitions::parse($parser, $list); |
|||
579 | 110 | if ($this->fields === []) { |
|||
580 | 6 | $parser->error('At least one column definition was expected.', $list->tokens[$list->idx]); |
|||
581 | } |
||||
582 | |||||
583 | 110 | ++$list->idx; |
|||
584 | |||||
585 | 110 | $this->entityOptions = OptionsArrays::parse($parser, $list, self::TABLE_OPTIONS); |
|||
586 | |||||
587 | /** |
||||
588 | * The field that is being filled (`partitionBy` or |
||||
589 | * `subpartitionBy`). |
||||
590 | */ |
||||
591 | 110 | $field = null; |
|||
592 | |||||
593 | /** |
||||
594 | * The number of brackets. `false` means no bracket was found |
||||
595 | * previously. At least one bracket is required to validate the |
||||
596 | * expression. |
||||
597 | */ |
||||
598 | 110 | $brackets = false; |
|||
599 | |||||
600 | /* |
||||
601 | * Handles partitions. |
||||
602 | */ |
||||
603 | 110 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
604 | /** |
||||
605 | * Token parsed at this moment. |
||||
606 | */ |
||||
607 | 110 | $token = $list->tokens[$list->idx]; |
|||
608 | |||||
609 | // End of statement. |
||||
610 | 110 | if ($token->type === TokenType::Delimiter) { |
|||
611 | 90 | break; |
|||
612 | } |
||||
613 | |||||
614 | // Skipping comments. |
||||
615 | 110 | if ($token->type === TokenType::Comment) { |
|||
616 | 2 | continue; |
|||
617 | } |
||||
618 | |||||
619 | 110 | if (($token->type === TokenType::Keyword) && ($token->keyword === 'PARTITION BY')) { |
|||
620 | 16 | $field = 'partitionBy'; |
|||
621 | 16 | $brackets = false; |
|||
622 | 110 | } elseif (($token->type === TokenType::Keyword) && ($token->keyword === 'SUBPARTITION BY')) { |
|||
623 | 12 | $field = 'subpartitionBy'; |
|||
624 | 12 | $brackets = false; |
|||
625 | 110 | } elseif (($token->type === TokenType::Keyword) && ($token->keyword === 'PARTITIONS')) { |
|||
626 | 6 | $token = $list->getNextOfType(TokenType::Number); |
|||
627 | 6 | --$list->idx; // `getNextOfType` also advances one position. |
|||
628 | 6 | $this->partitionsNum = $token->value; |
|||
629 | 110 | } elseif (($token->type === TokenType::Keyword) && ($token->keyword === 'SUBPARTITIONS')) { |
|||
630 | 6 | $token = $list->getNextOfType(TokenType::Number); |
|||
631 | 6 | --$list->idx; // `getNextOfType` also advances one position. |
|||
632 | 6 | $this->subpartitionsNum = $token->value; |
|||
633 | 110 | } elseif (! empty($field)) { |
|||
634 | /* |
||||
635 | * Handling the content of `PARTITION BY` and `SUBPARTITION BY`. |
||||
636 | */ |
||||
637 | |||||
638 | // Counting brackets. |
||||
639 | 16 | if ($token->type === TokenType::Operator) { |
|||
640 | 16 | if ($token->value === '(') { |
|||
641 | // This is used instead of `++$brackets` because, |
||||
642 | // initially, `$brackets` is `false` cannot be |
||||
643 | // incremented. |
||||
644 | 16 | $brackets += 1; |
|||
645 | 16 | } elseif ($token->value === ')') { |
|||
646 | 16 | --$brackets; |
|||
647 | } |
||||
648 | } |
||||
649 | |||||
650 | // Building the expression used for partitioning. |
||||
651 | 16 | $this->$field .= $token->type === TokenType::Whitespace ? ' ' : $token->token; |
|||
652 | |||||
653 | // Last bracket was read, the expression ended. |
||||
654 | // Comparing with `0` and not `false`, because `false` means |
||||
655 | // that no bracket was found and at least one must is |
||||
656 | // required. |
||||
657 | 16 | if ($brackets === 0) { |
|||
658 | 16 | $this->$field = trim($this->$field); |
|||
659 | 16 | $field = null; |
|||
660 | } |
||||
661 | 110 | } elseif (($token->type === TokenType::Operator) && ($token->value === '(')) { |
|||
662 | 20 | if (! empty($this->partitionBy)) { |
|||
663 | 14 | $this->partitions = ArrayObjs::parse( |
|||
0 ignored issues
–
show
It seems like
PhpMyAdmin\SqlParser\Par...ionDefinitions::class)) of type PhpMyAdmin\SqlParser\Components\ArrayObj is incompatible with the declared type PhpMyAdmin\SqlParser\Com...titionDefinition[]|null of property $partitions .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||||
664 | 14 | $parser, |
|||
665 | 14 | $list, |
|||
666 | 14 | ['type' => PartitionDefinitions::class], |
|||
667 | 14 | ); |
|||
668 | } |
||||
669 | |||||
670 | 20 | break; |
|||
671 | } |
||||
672 | } |
||||
673 | } |
||||
674 | 78 | } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) { |
|||
675 | 32 | $this->parameters = ParameterDefinitions::parse($parser, $list); |
|||
676 | 32 | if ($this->options->has('FUNCTION')) { |
|||
677 | 16 | $prevToken = $token; |
|||
678 | 16 | $token = $list->getNextOfType(TokenType::Keyword); |
|||
679 | 16 | if ($token === null || $token->keyword !== 'RETURNS') { |
|||
680 | 6 | $parser->error('A "RETURNS" keyword was expected.', $token ?? $prevToken); |
|||
681 | } else { |
||||
682 | 10 | ++$list->idx; |
|||
683 | 10 | $this->return = DataTypes::parse($parser, $list); |
|||
684 | } |
||||
685 | } |
||||
686 | |||||
687 | 32 | ++$list->idx; |
|||
688 | |||||
689 | 32 | $this->entityOptions = OptionsArrays::parse($parser, $list, self::FUNCTION_OPTIONS); |
|||
690 | 32 | ++$list->idx; |
|||
691 | |||||
692 | 32 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
693 | 28 | $token = $list->tokens[$list->idx]; |
|||
694 | 28 | if ($token->type === TokenType::Delimiter) { |
|||
695 | 28 | break; |
|||
696 | } |
||||
697 | |||||
698 | 28 | $this->body[] = $token; |
|||
699 | } |
||||
700 | 46 | } elseif ($this->options->has('VIEW')) { |
|||
701 | /** @var Token $token */ |
||||
702 | 30 | $token = $list->getNext(); // Skipping whitespaces and comments. |
|||
703 | |||||
704 | // Parsing columns list. |
||||
705 | 30 | if (($token->type === TokenType::Operator) && ($token->value === '(')) { |
|||
706 | 6 | --$list->idx; // getNext() also goes forward one field. |
|||
707 | 6 | $this->fields = ArrayObjs::parse($parser, $list); |
|||
708 | 6 | ++$list->idx; // Skipping last token from the array. |
|||
709 | 6 | $list->getNext(); |
|||
710 | } |
||||
711 | |||||
712 | // Parsing the SELECT expression if the view started with it. |
||||
713 | if ( |
||||
714 | 30 | $token->type === TokenType::Keyword |
|||
715 | 30 | && $token->keyword === 'AS' |
|||
716 | 30 | && $list->tokens[$nextidx]->type === TokenType::Keyword |
|||
717 | ) { |
||||
718 | 24 | if ($list->tokens[$nextidx]->value === 'SELECT') { |
|||
719 | 18 | $list->idx = $nextidx; |
|||
720 | 18 | $this->select = new SelectStatement($parser, $list); |
|||
721 | 18 | ++$list->idx; // Skipping last token from the select. |
|||
722 | 8 | } elseif ($list->tokens[$nextidx]->value === 'WITH') { |
|||
723 | 8 | ++$list->idx; |
|||
724 | 8 | $this->with = new WithStatement($parser, $list); |
|||
725 | } |
||||
726 | } |
||||
727 | |||||
728 | // Parsing all other tokens |
||||
729 | 30 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
730 | 30 | $token = $list->tokens[$list->idx]; |
|||
731 | 30 | if ($token->type === TokenType::Delimiter) { |
|||
732 | 30 | break; |
|||
733 | } |
||||
734 | |||||
735 | 14 | $this->body[] = $token; |
|||
736 | } |
||||
737 | 16 | } elseif ($this->options->has('TRIGGER')) { |
|||
738 | // Parsing the time and the event. |
||||
739 | 6 | $this->entityOptions = OptionsArrays::parse($parser, $list, self::TRIGGER_OPTIONS); |
|||
740 | 6 | ++$list->idx; |
|||
741 | |||||
742 | 6 | $list->getNextOfTypeAndValue(TokenType::Keyword, 'ON'); |
|||
743 | 6 | ++$list->idx; // Skipping `ON`. |
|||
744 | |||||
745 | // Parsing the name of the table. |
||||
746 | 6 | $this->table = Expressions::parse( |
|||
747 | 6 | $parser, |
|||
748 | 6 | $list, |
|||
749 | 6 | [ |
|||
750 | 6 | 'parseField' => 'table', |
|||
751 | 6 | 'breakOnAlias' => true, |
|||
752 | 6 | ], |
|||
753 | 6 | ); |
|||
754 | 6 | ++$list->idx; |
|||
755 | |||||
756 | 6 | $list->getNextOfTypeAndValue(TokenType::Keyword, 'FOR EACH ROW'); |
|||
757 | 6 | ++$list->idx; // Skipping `FOR EACH ROW`. |
|||
758 | |||||
759 | 6 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
760 | 6 | $token = $list->tokens[$list->idx]; |
|||
761 | 6 | if ($token->type === TokenType::Delimiter) { |
|||
762 | 6 | break; |
|||
763 | } |
||||
764 | |||||
765 | 6 | $this->body[] = $token; |
|||
766 | } |
||||
767 | } else { |
||||
768 | 10 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
769 | 10 | $token = $list->tokens[$list->idx]; |
|||
770 | 10 | if ($token->type === TokenType::Delimiter) { |
|||
771 | 10 | break; |
|||
772 | } |
||||
773 | |||||
774 | 4 | $this->body[] = $token; |
|||
775 | } |
||||
776 | } |
||||
777 | } |
||||
778 | } |
||||
779 |
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.