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\FunctionCall; |
||
11 | use PhpMyAdmin\SqlParser\Components\GroupKeyword; |
||
12 | use PhpMyAdmin\SqlParser\Components\IndexHint; |
||
13 | use PhpMyAdmin\SqlParser\Components\IntoKeyword; |
||
14 | use PhpMyAdmin\SqlParser\Components\JoinKeyword; |
||
15 | use PhpMyAdmin\SqlParser\Components\Limit; |
||
16 | use PhpMyAdmin\SqlParser\Components\OptionsArray; |
||
17 | use PhpMyAdmin\SqlParser\Components\OrderKeyword; |
||
18 | use PhpMyAdmin\SqlParser\Exceptions\ParserException; |
||
19 | use PhpMyAdmin\SqlParser\Parser; |
||
20 | use PhpMyAdmin\SqlParser\Parsers\OptionsArrays; |
||
21 | use PhpMyAdmin\SqlParser\Statement; |
||
22 | use PhpMyAdmin\SqlParser\TokensList; |
||
23 | use PhpMyAdmin\SqlParser\TokenType; |
||
24 | |||
25 | use function array_key_exists; |
||
26 | use function is_string; |
||
27 | |||
28 | /** |
||
29 | * `SELECT` statement. |
||
30 | * |
||
31 | * SELECT |
||
32 | * [ALL | DISTINCT | DISTINCTROW ] |
||
33 | * [HIGH_PRIORITY] |
||
34 | * [MAX_STATEMENT_TIME = N] |
||
35 | * [STRAIGHT_JOIN] |
||
36 | * [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] |
||
37 | * [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] |
||
38 | * select_expr [, select_expr ...] |
||
39 | * [FROM table_references |
||
40 | * [PARTITION partition_list] |
||
41 | * [WHERE where_condition] |
||
42 | * [GROUP BY {col_name | expr | position} |
||
43 | * [ASC | DESC], ... [WITH ROLLUP]] |
||
44 | * [HAVING where_condition] |
||
45 | * [ORDER BY {col_name | expr | position} |
||
46 | * [ASC | DESC], ...] |
||
47 | * [LIMIT {[offset,] row_count | row_count OFFSET offset}] |
||
48 | * [PROCEDURE procedure_name(argument_list)] |
||
49 | * [INTO OUTFILE 'file_name' |
||
50 | * [CHARACTER SET charset_name] |
||
51 | * export_options |
||
52 | * | INTO DUMPFILE 'file_name' |
||
53 | * | INTO var_name [, var_name]] |
||
54 | * [FOR UPDATE | LOCK IN SHARE MODE]] |
||
55 | */ |
||
56 | class SelectStatement extends Statement |
||
57 | { |
||
58 | /** |
||
59 | * Options for `SELECT` statements and their slot ID. |
||
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 | 'ALL' => 1, |
||
66 | 'DISTINCT' => 1, |
||
67 | 'DISTINCTROW' => 1, |
||
68 | 'HIGH_PRIORITY' => 2, |
||
69 | 'MAX_STATEMENT_TIME' => [ |
||
70 | 3, |
||
71 | 'var=', |
||
72 | ], |
||
73 | 'STRAIGHT_JOIN' => 4, |
||
74 | 'SQL_SMALL_RESULT' => 5, |
||
75 | 'SQL_BIG_RESULT' => 6, |
||
76 | 'SQL_BUFFER_RESULT' => 7, |
||
77 | 'SQL_CACHE' => 8, |
||
78 | 'SQL_NO_CACHE' => 8, |
||
79 | 'SQL_CALC_FOUND_ROWS' => 9, |
||
80 | ]; |
||
81 | |||
82 | protected const STATEMENT_GROUP_OPTIONS = ['WITH ROLLUP' => 1]; |
||
83 | |||
84 | protected const STATEMENT_END_OPTIONS = [ |
||
85 | 'FOR UPDATE' => 1, |
||
86 | 'LOCK IN SHARE MODE' => 1, |
||
87 | ]; |
||
88 | |||
89 | /** |
||
90 | * The clauses of this statement, in order. |
||
91 | * |
||
92 | * @see Statement::$clauses |
||
93 | * |
||
94 | * @var array<string, array{non-empty-string, int-mask-of<self::ADD_*>}> |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
95 | */ |
||
96 | public static array $clauses = [ |
||
97 | 'SELECT' => [ |
||
98 | 'SELECT', |
||
99 | Statement::ADD_KEYWORD, |
||
100 | ], |
||
101 | // Used for options. |
||
102 | '_OPTIONS' => [ |
||
103 | '_OPTIONS', |
||
104 | Statement::ADD_CLAUSE, |
||
105 | ], |
||
106 | // Used for selected expressions. |
||
107 | '_SELECT' => [ |
||
108 | 'SELECT', |
||
109 | Statement::ADD_CLAUSE, |
||
110 | ], |
||
111 | 'INTO' => [ |
||
112 | 'INTO', |
||
113 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
114 | ], |
||
115 | 'FROM' => [ |
||
116 | 'FROM', |
||
117 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
118 | ], |
||
119 | 'FORCE' => [ |
||
120 | 'FORCE', |
||
121 | Statement::ADD_CLAUSE, |
||
122 | ], |
||
123 | 'USE' => [ |
||
124 | 'USE', |
||
125 | Statement::ADD_CLAUSE, |
||
126 | ], |
||
127 | 'IGNORE' => [ |
||
128 | 'IGNORE', |
||
129 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
130 | ], |
||
131 | 'PARTITION' => [ |
||
132 | 'PARTITION', |
||
133 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
134 | ], |
||
135 | |||
136 | 'JOIN' => [ |
||
137 | 'JOIN', |
||
138 | Statement::ADD_CLAUSE, |
||
139 | ], |
||
140 | 'FULL JOIN' => [ |
||
141 | 'FULL JOIN', |
||
142 | Statement::ADD_CLAUSE, |
||
143 | ], |
||
144 | 'INNER JOIN' => [ |
||
145 | 'INNER JOIN', |
||
146 | Statement::ADD_CLAUSE, |
||
147 | ], |
||
148 | 'LEFT JOIN' => [ |
||
149 | 'LEFT JOIN', |
||
150 | Statement::ADD_CLAUSE, |
||
151 | ], |
||
152 | 'LEFT OUTER JOIN' => [ |
||
153 | 'LEFT OUTER JOIN', |
||
154 | Statement::ADD_CLAUSE, |
||
155 | ], |
||
156 | 'RIGHT JOIN' => [ |
||
157 | 'RIGHT JOIN', |
||
158 | Statement::ADD_CLAUSE, |
||
159 | ], |
||
160 | 'RIGHT OUTER JOIN' => [ |
||
161 | 'RIGHT OUTER JOIN', |
||
162 | Statement::ADD_CLAUSE, |
||
163 | ], |
||
164 | 'NATURAL JOIN' => [ |
||
165 | 'NATURAL JOIN', |
||
166 | Statement::ADD_CLAUSE, |
||
167 | ], |
||
168 | 'NATURAL LEFT JOIN' => [ |
||
169 | 'NATURAL LEFT JOIN', |
||
170 | Statement::ADD_CLAUSE, |
||
171 | ], |
||
172 | 'NATURAL RIGHT JOIN' => [ |
||
173 | 'NATURAL RIGHT JOIN', |
||
174 | Statement::ADD_CLAUSE, |
||
175 | ], |
||
176 | 'NATURAL LEFT OUTER JOIN' => [ |
||
177 | 'NATURAL LEFT OUTER JOIN', |
||
178 | Statement::ADD_CLAUSE, |
||
179 | ], |
||
180 | 'NATURAL RIGHT OUTER JOIN' => [ |
||
181 | 'NATURAL RIGHT JOIN', |
||
182 | Statement::ADD_CLAUSE, |
||
183 | ], |
||
184 | 'WHERE' => [ |
||
185 | 'WHERE', |
||
186 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
187 | ], |
||
188 | 'GROUP BY' => [ |
||
189 | 'GROUP BY', |
||
190 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
191 | ], |
||
192 | '_GROUP_OPTIONS' => [ |
||
193 | '_GROUP_OPTIONS', |
||
194 | Statement::ADD_CLAUSE, |
||
195 | ], |
||
196 | 'HAVING' => [ |
||
197 | 'HAVING', |
||
198 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
199 | ], |
||
200 | 'ORDER BY' => [ |
||
201 | 'ORDER BY', |
||
202 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
203 | ], |
||
204 | 'LIMIT' => [ |
||
205 | 'LIMIT', |
||
206 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
207 | ], |
||
208 | 'PROCEDURE' => [ |
||
209 | 'PROCEDURE', |
||
210 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||
211 | ], |
||
212 | 'UNION' => [ |
||
213 | 'UNION', |
||
214 | Statement::ADD_CLAUSE, |
||
215 | ], |
||
216 | 'EXCEPT' => [ |
||
217 | 'EXCEPT', |
||
218 | Statement::ADD_CLAUSE, |
||
219 | ], |
||
220 | 'INTERSECT' => [ |
||
221 | 'INTERSECT', |
||
222 | Statement::ADD_CLAUSE, |
||
223 | ], |
||
224 | '_END_OPTIONS' => [ |
||
225 | '_END_OPTIONS', |
||
226 | Statement::ADD_CLAUSE, |
||
227 | ], |
||
228 | // These are available only when `UNION` is present. |
||
229 | // 'ORDER BY' => ['ORDER BY', Statement::ADD_CLAUSE|Statement::ADD_KEYWORD], |
||
230 | // 'LIMIT' => ['LIMIT', Statement::ADD_CLAUSE|Statement::ADD_KEYWORD], |
||
231 | ]; |
||
232 | |||
233 | /** |
||
234 | * Expressions that are being selected by this statement. |
||
235 | * |
||
236 | * @var Expression[] |
||
237 | */ |
||
238 | public array $expr = []; |
||
239 | |||
240 | /** |
||
241 | * Tables used as sources for this statement. |
||
242 | * |
||
243 | * @var Expression[] |
||
244 | */ |
||
245 | public array $from = []; |
||
246 | |||
247 | /** |
||
248 | * Index hints |
||
249 | * |
||
250 | * @var IndexHint[]|null |
||
251 | */ |
||
252 | public array|null $indexHints = null; |
||
253 | |||
254 | /** |
||
255 | * Partitions used as source for this statement. |
||
256 | */ |
||
257 | public ArrayObj|null $partition = null; |
||
258 | |||
259 | /** |
||
260 | * Conditions used for filtering each row of the result set. |
||
261 | * |
||
262 | * @var Condition[]|null |
||
263 | */ |
||
264 | public array|null $where = null; |
||
265 | |||
266 | /** |
||
267 | * Conditions used for grouping the result set. |
||
268 | * |
||
269 | * @var GroupKeyword[]|null |
||
270 | */ |
||
271 | public array|null $group = null; |
||
272 | |||
273 | /** |
||
274 | * List of options available for the GROUP BY component. |
||
275 | */ |
||
276 | public OptionsArray|null $groupOptions = null; |
||
277 | |||
278 | /** |
||
279 | * Conditions used for filtering the result set. |
||
280 | * |
||
281 | * @var Condition[]|null |
||
282 | */ |
||
283 | public array|null $having = null; |
||
284 | |||
285 | /** |
||
286 | * Specifies the order of the rows in the result set. |
||
287 | * |
||
288 | * @var OrderKeyword[]|null |
||
289 | */ |
||
290 | public array|null $order = null; |
||
291 | |||
292 | /** |
||
293 | * Conditions used for limiting the size of the result set. |
||
294 | */ |
||
295 | public Limit|null $limit = null; |
||
296 | |||
297 | /** |
||
298 | * Procedure that should process the data in the result set. |
||
299 | */ |
||
300 | public FunctionCall|null $procedure = null; |
||
301 | |||
302 | /** |
||
303 | * Destination of this result set. |
||
304 | */ |
||
305 | public IntoKeyword|null $into = null; |
||
306 | |||
307 | /** |
||
308 | * Joins. |
||
309 | * |
||
310 | * @var JoinKeyword[]|null |
||
311 | */ |
||
312 | public array|null $join = null; |
||
313 | |||
314 | /** |
||
315 | * Unions. |
||
316 | * |
||
317 | * @var SelectStatement[] |
||
318 | */ |
||
319 | public array $union = []; |
||
320 | |||
321 | /** |
||
322 | * The end options of this query. |
||
323 | * |
||
324 | * @see SelectStatement::STATEMENT_END_OPTIONS |
||
325 | */ |
||
326 | public OptionsArray|null $endOptions = null; |
||
327 | |||
328 | /** |
||
329 | * Parses the statements defined by the tokens list. |
||
330 | * |
||
331 | * @param Parser $parser the instance that requests parsing |
||
332 | * @param TokensList $list the list of tokens to be parsed |
||
333 | * |
||
334 | * @throws ParserException |
||
335 | */ |
||
336 | 384 | public function parse(Parser $parser, TokensList $list): void |
|
337 | { |
||
338 | /** |
||
339 | * Array containing all list of clauses parsed. |
||
340 | * This is used to check for duplicates. |
||
341 | */ |
||
342 | 384 | $parsedClauses = []; |
|
343 | |||
344 | // This may be corrected by the parser. |
||
345 | 384 | $this->first = $list->idx; |
|
346 | |||
347 | /** |
||
348 | * Whether options were parsed or not. |
||
349 | * For statements that do not have any options this is set to `true` by |
||
350 | * default. |
||
351 | */ |
||
352 | 384 | $parsedOptions = static::$statementOptions === []; |
|
353 | |||
354 | 384 | for (; $list->idx < $list->count; ++$list->idx) { |
|
355 | /** |
||
356 | * Token parsed at this moment. |
||
357 | */ |
||
358 | 384 | $token = $list->tokens[$list->idx]; |
|
359 | |||
360 | // End of statement. |
||
361 | 384 | if ($token->type === TokenType::Delimiter) { |
|
362 | 364 | break; |
|
363 | } |
||
364 | |||
365 | // Checking if this closing bracket is the pair for a bracket |
||
366 | // outside the statement. |
||
367 | 384 | if (($token->value === ')') && ($parser->brackets > 0)) { |
|
368 | 14 | --$parser->brackets; |
|
369 | 14 | continue; |
|
370 | } |
||
371 | |||
372 | // Only keywords are relevant here. Other parts of the query are |
||
373 | // processed in the functions below. |
||
374 | 384 | if ($token->type !== TokenType::Keyword) { |
|
375 | 36 | if (($token->type !== TokenType::Comment) && ($token->type !== TokenType::Whitespace)) { |
|
376 | 20 | $parser->error('Unexpected token.', $token); |
|
377 | } |
||
378 | |||
379 | 36 | continue; |
|
380 | } |
||
381 | |||
382 | // Unions are parsed by the parser because they represent more than |
||
383 | // one statement. |
||
384 | if ( |
||
385 | 384 | ($token->keyword === 'UNION') || |
|
386 | 384 | ($token->keyword === 'UNION ALL') || |
|
387 | 384 | ($token->keyword === 'UNION DISTINCT') || |
|
388 | 384 | ($token->keyword === 'EXCEPT') || |
|
389 | 384 | ($token->keyword === 'INTERSECT') |
|
390 | ) { |
||
391 | 40 | break; |
|
392 | } |
||
393 | |||
394 | 384 | $lastIdx = $list->idx; |
|
395 | |||
396 | // ON DUPLICATE KEY UPDATE ... |
||
397 | // has to be parsed in parent statement (INSERT or REPLACE) |
||
398 | // so look for it and break |
||
399 | 384 | if ($token->value === 'ON') { |
|
400 | 6 | ++$list->idx; // Skip ON |
|
401 | |||
402 | // look for ON DUPLICATE KEY UPDATE |
||
403 | 6 | $first = $list->getNextOfType(TokenType::Keyword); |
|
404 | 6 | $second = $list->getNextOfType(TokenType::Keyword); |
|
405 | 6 | $third = $list->getNextOfType(TokenType::Keyword); |
|
406 | |||
407 | if ( |
||
408 | 6 | $first && $second && $third |
|
409 | 6 | && $first->value === 'DUPLICATE' |
|
410 | 6 | && $second->value === 'KEY' |
|
411 | 6 | && $third->value === 'UPDATE' |
|
412 | ) { |
||
413 | 6 | $list->idx = $lastIdx; |
|
414 | 6 | break; |
|
415 | } |
||
416 | } |
||
417 | |||
418 | 384 | $list->idx = $lastIdx; |
|
419 | |||
420 | /** |
||
421 | * The name of the class that is used for parsing. |
||
422 | */ |
||
423 | 384 | $class = null; |
|
424 | |||
425 | /** |
||
426 | * The name of the field where the result of the parsing is stored. |
||
427 | */ |
||
428 | 384 | $field = null; |
|
429 | |||
430 | /** |
||
431 | * Parser's options. |
||
432 | */ |
||
433 | 384 | $options = []; |
|
434 | |||
435 | // Looking for duplicated clauses. |
||
436 | if ( |
||
437 | 384 | is_string($token->value) |
|
438 | && ( |
||
439 | 384 | isset(Parser::KEYWORD_PARSERS[$token->value]) |
|
440 | 384 | || ( |
|
441 | 384 | isset(Parser::STATEMENT_PARSERS[$token->value]) |
|
442 | 384 | && Parser::STATEMENT_PARSERS[$token->value] !== '' |
|
443 | 384 | ) |
|
444 | ) |
||
445 | ) { |
||
446 | 384 | if (array_key_exists($token->value, $parsedClauses)) { |
|
447 | 14 | $parser->error('This type of clause was previously parsed.', $token); |
|
448 | 14 | break; |
|
449 | } |
||
450 | |||
451 | 384 | $parsedClauses[$token->value] = true; |
|
452 | } |
||
453 | |||
454 | // Checking if this is the beginning of a clause. |
||
455 | // Fix Issue #221: As `truncate` is not a keyword, |
||
456 | // but it might be the beginning of a statement of truncate, |
||
457 | // so let the value use the keyword field for truncate type. |
||
458 | 384 | $tokenValue = $token->keyword === 'TRUNCATE' ? $token->keyword : $token->value; |
|
459 | 384 | if (is_string($tokenValue) && isset(Parser::KEYWORD_PARSERS[$tokenValue]) && $list->idx < $list->count) { |
|
460 | 384 | $class = Parser::KEYWORD_PARSERS[$tokenValue]['class']; |
|
461 | 384 | $field = Parser::KEYWORD_PARSERS[$tokenValue]['field']; |
|
462 | 384 | if (isset(Parser::KEYWORD_PARSERS[$tokenValue]['options'])) { |
|
463 | 326 | $options = Parser::KEYWORD_PARSERS[$tokenValue]['options']; |
|
464 | } |
||
465 | } |
||
466 | |||
467 | // Checking if this is the beginning of the statement. |
||
468 | if ( |
||
469 | 384 | isset(Parser::STATEMENT_PARSERS[$token->keyword]) |
|
470 | 384 | && Parser::STATEMENT_PARSERS[$token->keyword] !== '' |
|
471 | ) { |
||
472 | 384 | if (static::$clauses !== [] && is_string($token->value) && ! isset(static::$clauses[$token->value])) { |
|
473 | // Some keywords (e.g. `SET`) may be the beginning of a |
||
474 | // statement and a clause. |
||
475 | // If such keyword was found, and it cannot be a clause of |
||
476 | // this statement it means it is a new statement, but no |
||
477 | // delimiter was found between them. |
||
478 | 4 | $parser->error( |
|
479 | 4 | 'A new statement was found, but no delimiter between it and the previous one.', |
|
480 | 4 | $token, |
|
481 | 4 | ); |
|
482 | 4 | break; |
|
483 | } |
||
484 | |||
485 | 384 | if (! $parsedOptions) { |
|
486 | 384 | if (! array_key_exists((string) $token->value, static::$statementOptions)) { |
|
487 | // Skipping keyword because if it is not a option. |
||
488 | 384 | ++$list->idx; |
|
489 | } |
||
490 | |||
491 | 384 | $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions); |
|
492 | 384 | $parsedOptions = true; |
|
493 | } |
||
494 | 326 | } elseif ($class === null) { |
|
495 | 34 | if ($token->value === 'WITH ROLLUP') { |
|
496 | // Handle group options in Select statement |
||
497 | 10 | $this->groupOptions = OptionsArrays::parse($parser, $list, self::STATEMENT_GROUP_OPTIONS); |
|
498 | 26 | } elseif ($token->value === 'FOR UPDATE' || $token->value === 'LOCK IN SHARE MODE') { |
|
499 | // Handle special end options in Select statement |
||
500 | 8 | $this->endOptions = OptionsArrays::parse($parser, $list, self::STATEMENT_END_OPTIONS); |
|
501 | } else { |
||
502 | // There is no parser for this keyword and isn't the beginning |
||
503 | // of a statement (so no options) either. |
||
504 | 18 | $parser->error('Unrecognized keyword.', $token); |
|
505 | 18 | continue; |
|
506 | } |
||
507 | } |
||
508 | |||
509 | 384 | if ($class === null) { |
|
510 | 18 | continue; |
|
511 | } |
||
512 | |||
513 | // Parsing this keyword. |
||
514 | // We can't parse keyword at the end of statement |
||
515 | 384 | if ($list->idx >= $list->count) { |
|
516 | $parser->error('Keyword at end of statement.', $token); |
||
517 | continue; |
||
518 | } |
||
519 | |||
520 | 384 | ++$list->idx; // Skipping keyword or last option. |
|
521 | 384 | $this->$field = $class::parse($parser, $list, $options); |
|
522 | } |
||
523 | |||
524 | // This may be corrected by the parser. |
||
525 | 384 | $this->last = --$list->idx; // Go back to last used token. |
|
526 | } |
||
527 | |||
528 | /** |
||
529 | * Gets the clauses of this statement. |
||
530 | * |
||
531 | * @return array<string, array{non-empty-string, int-mask-of<Statement::ADD_*>}> |
||
0 ignored issues
–
show
|
|||
532 | */ |
||
533 | 360 | public function getClauses(): array |
|
534 | { |
||
535 | // This is a cheap fix for `SELECT` statements that contain `UNION`. |
||
536 | // The `ORDER BY` and `LIMIT` clauses should be at the end of the |
||
537 | // statement. |
||
538 | 360 | if ($this->union !== []) { |
|
539 | 10 | $clauses = static::$clauses; |
|
540 | 10 | unset($clauses['ORDER BY'], $clauses['LIMIT']); |
|
541 | 10 | $clauses['ORDER BY'] = [ |
|
542 | 10 | 'ORDER BY', |
|
543 | 10 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
|
544 | 10 | ]; |
|
545 | 10 | $clauses['LIMIT'] = [ |
|
546 | 10 | 'LIMIT', |
|
547 | 10 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
|
548 | 10 | ]; |
|
549 | |||
550 | 10 | return $clauses; |
|
551 | } |
||
552 | |||
553 | 360 | return static::$clauses; |
|
554 | } |
||
555 | |||
556 | /** |
||
557 | * Gets a list of all aliases and their original names. |
||
558 | * |
||
559 | * @param string $database the name of the database |
||
560 | * |
||
561 | * @return array<string, array<string, array<string, array<string, array<string, string>|string|null>>|null>> |
||
562 | */ |
||
563 | 14 | public function getAliases(string $database): array |
|
564 | { |
||
565 | 14 | if ($this->expr === [] || $this->from === []) { |
|
566 | 2 | return []; |
|
567 | } |
||
568 | |||
569 | 12 | $retval = []; |
|
570 | |||
571 | 12 | $tables = []; |
|
572 | |||
573 | /** |
||
574 | * Expressions that may contain aliases. |
||
575 | * These are extracted from `FROM` and `JOIN` keywords. |
||
576 | */ |
||
577 | 12 | $expressions = $this->from; |
|
578 | |||
579 | // Adding expressions from JOIN. |
||
580 | 12 | if (! empty($this->join)) { |
|
581 | 2 | foreach ($this->join as $join) { |
|
582 | 2 | $expressions[] = $join->expr; |
|
583 | } |
||
584 | } |
||
585 | |||
586 | 12 | foreach ($expressions as $expr) { |
|
587 | 12 | if (! isset($expr->table) || ($expr->table === '')) { |
|
588 | 4 | continue; |
|
589 | } |
||
590 | |||
591 | 8 | $thisDb = isset($expr->database) && ($expr->database !== '') ? |
|
592 | 8 | $expr->database : $database; |
|
593 | |||
594 | 8 | if (! isset($retval[$thisDb])) { |
|
595 | 8 | $retval[$thisDb] = [ |
|
596 | 8 | 'alias' => null, |
|
597 | 8 | 'tables' => [], |
|
598 | 8 | ]; |
|
599 | } |
||
600 | |||
601 | 8 | if (! isset($retval[$thisDb]['tables'][$expr->table])) { |
|
602 | 8 | $retval[$thisDb]['tables'][$expr->table] = [ |
|
603 | 8 | 'alias' => isset($expr->alias) && ($expr->alias !== '') ? |
|
604 | 6 | $expr->alias : null, |
|
605 | 8 | 'columns' => [], |
|
606 | 8 | ]; |
|
607 | } |
||
608 | |||
609 | 8 | if (! isset($tables[$thisDb])) { |
|
610 | 8 | $tables[$thisDb] = []; |
|
611 | } |
||
612 | |||
613 | 8 | $tables[$thisDb][$expr->alias] = $expr->table; |
|
614 | } |
||
615 | |||
616 | 12 | foreach ($this->expr as $expr) { |
|
617 | 12 | if (! isset($expr->column, $expr->alias) || ($expr->column === '') || ($expr->alias === '')) { |
|
618 | 8 | continue; |
|
619 | } |
||
620 | |||
621 | 6 | $thisDb = isset($expr->database) && ($expr->database !== '') ? |
|
622 | 6 | $expr->database : $database; |
|
623 | |||
624 | 6 | if (isset($expr->table) && ($expr->table !== '')) { |
|
625 | 4 | $thisTable = $tables[$thisDb][$expr->table] ?? $expr->table; |
|
626 | 4 | $retval[$thisDb]['tables'][$thisTable]['columns'][$expr->column] = $expr->alias; |
|
627 | } else { |
||
628 | 6 | foreach ($retval[$thisDb]['tables'] as &$table) { |
|
629 | 6 | $table['columns'][$expr->column] = $expr->alias; |
|
630 | } |
||
631 | } |
||
632 | } |
||
633 | |||
634 | 12 | return $retval; |
|
635 | } |
||
636 | } |
||
637 |