packages/ztd-query-sqlite/tests/Unit/Sql/Statement/StatementStructureTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql\Statement;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure;
11
12#[CoversClass(StatementStructure::class)]
13#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
14#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
16#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
19final class StatementStructureTest extends TestCase
20{
21    public function testSplitStatementsSplitSingleStatement(): void
22    {
23        $parser = new StatementStructure();
24        $result = $parser->splitStatements('SELECT * FROM users');
25        self::assertCount(1, $result);
26        self::assertSame('SELECT * FROM users', $result[0]);
27    }
28
29    public function testSplitStatementsSplitMultipleStatements(): void
30    {
31        $parser = new StatementStructure();
32        $result = $parser->splitStatements('SELECT 1; SELECT 2');
33        self::assertCount(2, $result);
34        self::assertSame('SELECT 1', $result[0]);
35        self::assertSame('SELECT 2', $result[1]);
36    }
37
38    public function testSplitStatementsIgnoresSemicolonInString(): void
39    {
40        $parser = new StatementStructure();
41        $result = $parser->splitStatements("SELECT 'a;b' FROM t");
42        self::assertCount(1, $result);
43    }
44
45    public function testSplitStatementsIgnoresSemicolonInParentheses(): void
46    {
47        $parser = new StatementStructure();
48        $result = $parser->splitStatements('SELECT (1;2) FROM t');
49        self::assertCount(1, $result);
50    }
51
52    public function testExtractSelectTables(): void
53    {
54        $parser = new StatementStructure();
55        $tables = $parser->extractSelectTables('SELECT * FROM users');
56        self::assertSame(['users'], $tables);
57    }
58
59    public function testExtractSelectTablesWithJoin(): void
60    {
61        $parser = new StatementStructure();
62        $tables = $parser->extractSelectTables('SELECT * FROM users JOIN orders ON users.id = orders.user_id');
63        self::assertContains('users', $tables);
64        self::assertContains('orders', $tables);
65    }
66
67    public function testExtractSelectTablesDeduplicatesAndReindexesInFirstSeenOrder(): void
68    {
69        $parser = new StatementStructure();
70
71        self::assertSame(
72            ['users', 'orders'],
73            $parser->extractSelectTables(
74                'SELECT * FROM users UNION SELECT * FROM users UNION SELECT * FROM orders'
75            ),
76        );
77    }
78
79    public function testExtractSelectTablesExtractSelectTableAfterBlockComment(): void
80    {
81        $parser = new StatementStructure();
82        self::assertSame(['my_table'], $parser->extractSelectTables('SELECT * FROM /* table */ my_table'));
83    }
84
85    public function testExtractSelectTablesExtractSelectTableIgnoresKeywordsInsideLineComment(): void
86    {
87        $parser = new StatementStructure();
88        $sql = "-- SELECT * FROM other_table WHERE DELETE UPDATE INSERT\nSELECT * FROM items ORDER BY id";
89        self::assertSame(['items'], $parser->extractSelectTables($sql));
90    }
91
92    public function testExtractSelectTablesExtractSelectTableIgnoresFromInsideStringLiteral(): void
93    {
94        $parser = new StatementStructure();
95        $sql = "SELECT id, 'from other_table' AS label FROM items LIMIT 1";
96        self::assertSame(['items'], $parser->extractSelectTables($sql));
97    }
98
99    public function testExtractSelectTablesExtractSelectTableIgnoresJoinInsideStringLiteral(): void
100    {
101        $parser = new StatementStructure();
102        $sql = "SELECT id, 'JOIN other_table' AS label FROM items LIMIT 1";
103        self::assertSame(['items'], $parser->extractSelectTables($sql));
104    }
105
106    public function testSplitStatementsIgnoresSemicolonInDoubleQuote(): void
107    {
108        $parser = new StatementStructure();
109        $result = $parser->splitStatements('SELECT "a;b" FROM t');
110        self::assertCount(1, $result);
111    }
112
113    public function testSplitStatementsHandlesLineComment(): void
114    {
115        $parser = new StatementStructure();
116        $result = $parser->splitStatements("SELECT 1; -- comment\nSELECT 2");
117        self::assertCount(2, $result);
118    }
119
120    public function testSplitStatementsHandlesBlockComment(): void
121    {
122        $parser = new StatementStructure();
123        $result = $parser->splitStatements('SELECT 1; /* comment */ SELECT 2');
124        self::assertCount(2, $result);
125    }
126
127    public function testSplitStatementsHandlesUnclosedBlockComment(): void
128    {
129        $parser = new StatementStructure();
130        $result = $parser->splitStatements('SELECT 1 /* unclosed comment');
131        self::assertCount(1, $result);
132    }
133
134    public function testSplitStatementsHandlesLineCommentAtEnd(): void
135    {
136        $parser = new StatementStructure();
137        $result = $parser->splitStatements('SELECT 1 -- trailing comment');
138        self::assertCount(1, $result);
139    }
140
141    public function testSplitStatementsHandlesEscapedSingleQuote(): void
142    {
143        $parser = new StatementStructure();
144        $result = $parser->splitStatements("SELECT 'it''s'; SELECT 2");
145        self::assertCount(2, $result);
146    }
147
148    public function testSplitStatementsHandlesEscapedDoubleQuote(): void
149    {
150        $parser = new StatementStructure();
151        $result = $parser->splitStatements('SELECT "col""name"; SELECT 2');
152        self::assertCount(2, $result);
153    }
154
155    public function testSplitStatementsHandlesNestedParens(): void
156    {
157        $parser = new StatementStructure();
158        $result = $parser->splitStatements('SELECT (1 + (2 + 3)); SELECT 2');
159        self::assertCount(2, $result);
160    }
161
162    public function testSplitStatementsEmptyInput(): void
163    {
164        $parser = new StatementStructure();
165        $result = $parser->splitStatements('');
166        self::assertSame([], $result);
167    }
168
169    public function testSplitStatementsTrailingSemicolon(): void
170    {
171        $parser = new StatementStructure();
172        $result = $parser->splitStatements('SELECT 1;');
173        self::assertCount(1, $result);
174    }
175
176    public function testExtractSelectTablesWithAlias(): void
177    {
178        $parser = new StatementStructure();
179        $tables = $parser->extractSelectTables('SELECT * FROM users u');
180        self::assertContains('users', $tables);
181    }
182
183    public function testExtractSelectTablesWithAsAlias(): void
184    {
185        $parser = new StatementStructure();
186        $tables = $parser->extractSelectTables('SELECT * FROM users AS u');
187        self::assertContains('users', $tables);
188    }
189
190    public function testExtractSelectTablesMultiple(): void
191    {
192        $parser = new StatementStructure();
193        $tables = $parser->extractSelectTables('SELECT * FROM users, orders');
194        self::assertContains('users', $tables);
195        self::assertContains('orders', $tables);
196    }
197
198    public function testExtractSelectTablesWithWhere(): void
199    {
200        $parser = new StatementStructure();
201        $tables = $parser->extractSelectTables('SELECT * FROM users WHERE id = 1');
202        self::assertSame(['users'], $tables);
203    }
204
205    public function testExtractSelectTablesWithGroupBy(): void
206    {
207        $parser = new StatementStructure();
208        $tables = $parser->extractSelectTables('SELECT * FROM users GROUP BY name');
209        self::assertSame(['users'], $tables);
210    }
211
212    public function testExtractSelectTablesWithOrderBy(): void
213    {
214        $parser = new StatementStructure();
215        $tables = $parser->extractSelectTables('SELECT * FROM users ORDER BY id');
216        self::assertSame(['users'], $tables);
217    }
218
219    public function testExtractSelectTablesWithLimit(): void
220    {
221        $parser = new StatementStructure();
222        $tables = $parser->extractSelectTables('SELECT * FROM users LIMIT 10');
223        self::assertSame(['users'], $tables);
224    }
225
226    public function testExtractSelectTablesQuoted(): void
227    {
228        $parser = new StatementStructure();
229        $tables = $parser->extractSelectTables('SELECT * FROM "users"');
230        self::assertContains('users', $tables);
231    }
232
233    public function testExtractSelectTablesNoFrom(): void
234    {
235        $parser = new StatementStructure();
236        $tables = $parser->extractSelectTables('SELECT 1');
237        self::assertSame([], $tables);
238    }
239
240    public function testExtractSelectTablesWithLeftJoin(): void
241    {
242        $parser = new StatementStructure();
243        $tables = $parser->extractSelectTables('SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id');
244        self::assertContains('users', $tables);
245        self::assertContains('orders', $tables);
246    }
247
248    public function testExtractSelectTablesWithInnerJoin(): void
249    {
250        $parser = new StatementStructure();
251        $tables = $parser->extractSelectTables('SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id');
252        self::assertContains('users', $tables);
253        self::assertContains('orders', $tables);
254    }
255
256    public function testExtractSelectTablesWithHaving(): void
257    {
258        $parser = new StatementStructure();
259        $tables = $parser->extractSelectTables('SELECT * FROM users HAVING count(*) > 1');
260        self::assertSame(['users'], $tables);
261    }
262
263    public function testExtractSelectTablesWithUnion(): void
264    {
265        $parser = new StatementStructure();
266        $tables = $parser->extractSelectTables('SELECT * FROM users UNION SELECT * FROM admins');
267        self::assertContains('users', $tables);
268    }
269
270    public function testSplitStatementsEmpty(): void
271    {
272        $parser = new StatementStructure();
273        $result = $parser->splitStatements('  ;  ;  ');
274        self::assertSame([], $result);
275    }
276
277    public function testExtractSelectTablesEmptyExpr(): void
278    {
279        $parser = new StatementStructure();
280        $tables = $parser->extractSelectTables('SELECT * FROM , users');
281        self::assertContains('users', $tables);
282    }
283
284    public function testExtractSelectTablesLowercase(): void
285    {
286        $parser = new StatementStructure();
287        $tables = $parser->extractSelectTables('select * from users where id = 1');
288        self::assertSame(['users'], $tables);
289    }
290
291    public function testExtractSelectTablesWithJoinLowercase(): void
292    {
293        $parser = new StatementStructure();
294        $tables = $parser->extractSelectTables('select * from users join orders on users.id = orders.user_id');
295        self::assertContains('users', $tables);
296        self::assertContains('orders', $tables);
297    }
298
299    public function testExtractSelectTablesWithGroupByLowercase(): void
300    {
301        $parser = new StatementStructure();
302        $tables = $parser->extractSelectTables('select * from users group by name');
303        self::assertSame(['users'], $tables);
304    }
305
306    public function testExtractSelectTablesWithOrderByLowercase(): void
307    {
308        $parser = new StatementStructure();
309        $tables = $parser->extractSelectTables('select * from users order by id');
310        self::assertSame(['users'], $tables);
311    }
312
313    public function testExtractSelectTablesWithLimitLowercase(): void
314    {
315        $parser = new StatementStructure();
316        $tables = $parser->extractSelectTables('select * from users limit 10');
317        self::assertSame(['users'], $tables);
318    }
319
320    public function testExtractSelectTablesLeftJoinLowercase(): void
321    {
322        $parser = new StatementStructure();
323        $tables = $parser->extractSelectTables('select * from users left join orders on users.id = orders.user_id');
324        self::assertContains('users', $tables);
325        self::assertContains('orders', $tables);
326    }
327
328    public function testExtractSelectTablesCrossJoin(): void
329    {
330        $parser = new StatementStructure();
331        $tables = $parser->extractSelectTables('SELECT * FROM users CROSS JOIN orders');
332        self::assertContains('users', $tables);
333    }
334
335    public function testExtractSelectTablesNaturalJoin(): void
336    {
337        $parser = new StatementStructure();
338        $tables = $parser->extractSelectTables('SELECT * FROM users NATURAL JOIN orders');
339        self::assertContains('users', $tables);
340    }
341
342    public function testExtractSelectTablesRightJoin(): void
343    {
344        $parser = new StatementStructure();
345        $tables = $parser->extractSelectTables('SELECT * FROM users RIGHT JOIN orders ON 1=1');
346        self::assertContains('users', $tables);
347        self::assertContains('orders', $tables);
348    }
349
350    public function testSplitStatementsWithEscapedSingleQuotes(): void
351    {
352        $parser = new StatementStructure();
353        $result = $parser->splitStatements("SELECT 'it''s'; SELECT 2");
354        self::assertCount(2, $result);
355        self::assertStringContainsString("it''s", $result[0]);
356        self::assertSame('SELECT 2', $result[1]);
357    }
358
359    public function testSplitStatementsWithEscapedDoubleQuotes(): void
360    {
361        $parser = new StatementStructure();
362        $result = $parser->splitStatements('SELECT "a""b"; SELECT 2');
363        self::assertCount(2, $result);
364        self::assertStringContainsString('a""b', $result[0]);
365        self::assertSame('SELECT 2', $result[1]);
366    }
367
368    public function testSplitStatementsWithLineComment(): void
369    {
370        $parser = new StatementStructure();
371        $result = $parser->splitStatements("SELECT 1 -- comment\n; SELECT 2");
372        self::assertCount(2, $result);
373    }
374
375    public function testSplitStatementsWithLineCommentNoNewline(): void
376    {
377        $parser = new StatementStructure();
378        $result = $parser->splitStatements('SELECT 1 -- comment at end');
379        self::assertCount(1, $result);
380        self::assertStringContainsString('SELECT 1', $result[0]);
381        self::assertStringContainsString('-- comment at end', $result[0]);
382    }
383
384    public function testSplitStatementsWithBlockComment(): void
385    {
386        $parser = new StatementStructure();
387        $result = $parser->splitStatements('SELECT /* block */ 1; SELECT 2');
388        self::assertCount(2, $result);
389        self::assertStringContainsString('/* block */', $result[0]);
390    }
391
392    public function testSplitStatementsWithUnclosedBlockComment(): void
393    {
394        $parser = new StatementStructure();
395        $result = $parser->splitStatements('SELECT 1 /* unclosed');
396        self::assertCount(1, $result);
397        self::assertStringContainsString('/* unclosed', $result[0]);
398    }
399
400    public function testSplitStatementsParenthesesNestedDepth(): void
401    {
402        $parser = new StatementStructure();
403        $result = $parser->splitStatements('SELECT (1, (2, 3)); SELECT 4');
404        self::assertCount(2, $result);
405        self::assertStringContainsString('(1, (2, 3))', $result[0]);
406    }
407
408    public function testSplitStatementsClosingParenAtZeroDepth(): void
409    {
410        $parser = new StatementStructure();
411        $result = $parser->splitStatements('SELECT 1) ; SELECT 2');
412        self::assertCount(2, $result);
413    }
414
415    public function testSplitStatementsEmptyStatements(): void
416    {
417        $parser = new StatementStructure();
418        $result = $parser->splitStatements(';;; ');
419        self::assertSame([], $result);
420    }
421
422    public function testSplitStatementsSemicolonInsideParens(): void
423    {
424        $parser = new StatementStructure();
425        $result = $parser->splitStatements("SELECT (';')");
426        self::assertCount(1, $result);
427    }
428
429    public function testExtractSelectTablesWithJoinV2(): void
430    {
431        $parser = new StatementStructure();
432        $tables = $parser->extractSelectTables('SELECT * FROM users JOIN orders ON users.id = orders.user_id');
433        self::assertContains('users', $tables);
434        self::assertContains('orders', $tables);
435    }
436
437    public function testExtractSelectTablesWithAliasV2(): void
438    {
439        $parser = new StatementStructure();
440        $tables = $parser->extractSelectTables('SELECT * FROM users AS u');
441        self::assertContains('users', $tables);
442    }
443
444    public function testExtractSelectTablesWithImplicitAlias(): void
445    {
446        $parser = new StatementStructure();
447        $tables = $parser->extractSelectTables('SELECT * FROM users u');
448        self::assertContains('users', $tables);
449    }
450
451    public function testExtractSelectTablesMultipleV2(): void
452    {
453        $parser = new StatementStructure();
454        $tables = $parser->extractSelectTables('SELECT * FROM users, orders WHERE users.id = orders.user_id');
455        self::assertContains('users', $tables);
456        self::assertContains('orders', $tables);
457    }
458
459    public function testExtractSelectTablesEmptyFrom(): void
460    {
461        $parser = new StatementStructure();
462        $tables = $parser->extractSelectTables('SELECT 1');
463        self::assertSame([], $tables);
464    }
465
466    public function testExtractSelectTablesQuotedTable(): void
467    {
468        $parser = new StatementStructure();
469        $tables = $parser->extractSelectTables('SELECT * FROM "my table"');
470        self::assertNotEmpty($tables);
471    }
472
473    public function testExtractSelectTablesWithGroupByV2(): void
474    {
475        $parser = new StatementStructure();
476        $tables = $parser->extractSelectTables('SELECT name, COUNT(*) FROM users GROUP BY name');
477        self::assertContains('users', $tables);
478    }
479
480    public function testExtractSelectTablesWithHavingV2(): void
481    {
482        $parser = new StatementStructure();
483        $tables = $parser->extractSelectTables('SELECT name, COUNT(*) FROM users HAVING COUNT(*) > 1');
484        self::assertContains('users', $tables);
485    }
486
487    public function testExtractSelectTablesWithOrderByV2(): void
488    {
489        $parser = new StatementStructure();
490        $tables = $parser->extractSelectTables('SELECT * FROM users ORDER BY name');
491        self::assertContains('users', $tables);
492    }
493
494    public function testExtractSelectTablesWithLimitV2(): void
495    {
496        $parser = new StatementStructure();
497        $tables = $parser->extractSelectTables('SELECT * FROM users LIMIT 10');
498        self::assertContains('users', $tables);
499    }
500
501    public function testExtractSelectTablesWithUnionV2(): void
502    {
503        $parser = new StatementStructure();
504        $tables = $parser->extractSelectTables('SELECT * FROM users UNION SELECT * FROM admins');
505        self::assertContains('users', $tables);
506    }
507
508    public function testExtractSelectTablesLeftJoin(): void
509    {
510        $parser = new StatementStructure();
511        $tables = $parser->extractSelectTables('SELECT * FROM users LEFT JOIN orders ON users.id = orders.uid');
512        self::assertContains('orders', $tables);
513    }
514
515    public function testExtractSelectTablesInnerJoin(): void
516    {
517        $parser = new StatementStructure();
518        $tables = $parser->extractSelectTables('SELECT * FROM users INNER JOIN orders ON 1=1');
519        self::assertContains('orders', $tables);
520    }
521
522    public function testExtractSelectTablesCrossJoinV2(): void
523    {
524        $parser = new StatementStructure();
525        $tables = $parser->extractSelectTables('SELECT * FROM users CROSS JOIN orders');
526        self::assertContains('orders', $tables);
527    }
528
529    public function testExtractSelectTablesNaturalJoinV2(): void
530    {
531        $parser = new StatementStructure();
532        $tables = $parser->extractSelectTables('SELECT * FROM users NATURAL JOIN orders');
533        self::assertContains('orders', $tables);
534    }
535
536    public function testSplitStatementsOnlySemicolons(): void
537    {
538        $parser = new StatementStructure();
539        self::assertSame([], $parser->splitStatements(';'));
540    }
541
542    public function testSplitStatementsSingleNoSemicolon(): void
543    {
544        $parser = new StatementStructure();
545        $result = $parser->splitStatements('SELECT 1');
546        self::assertCount(1, $result);
547        self::assertSame('SELECT 1', $result[0]);
548    }
549
550    public function testExtractSelectTablesExtractTableFromExprEmpty(): void
551    {
552        $parser = new StatementStructure();
553        $tables = $parser->extractSelectTables('SELECT * FROM ');
554        self::assertSame([], $tables);
555    }
556
557    public function testExtractSelectTablesWithQuotedJoinTable(): void
558    {
559        $parser = new StatementStructure();
560        $tables = $parser->extractSelectTables('SELECT * FROM t JOIN "other table" ON t.id = "other table".tid');
561        self::assertContains('other table', $tables);
562    }
563
564    public function testExtractSelectTablesJoinEmptyTable(): void
565    {
566        $parser = new StatementStructure();
567        $tables = $parser->extractSelectTables('SELECT * FROM t');
568        self::assertContains('t', $tables);
569    }
570
571    public function testSplitStatementsPreservesDoubleQuoteContent(): void
572    {
573        $parser = new StatementStructure();
574        $result = $parser->splitStatements('SELECT "col;name" FROM t');
575        self::assertCount(1, $result);
576        self::assertStringContainsString('"col;name"', $result[0]);
577    }
578
579    public function testSplitStatementsPreservesSingleQuoteContent(): void
580    {
581        $parser = new StatementStructure();
582        $result = $parser->splitStatements("SELECT 'val;ue' FROM t");
583        self::assertCount(1, $result);
584        self::assertStringContainsString("'val;ue'", $result[0]);
585    }
586
587    public function testExtractSelectTablesCaseInsensitive(): void
588    {
589        $parser = new StatementStructure();
590        $tables = $parser->extractSelectTables('select * from users where id = 1');
591        self::assertContains('users', $tables);
592    }
593
594    public function testExtractSelectTablesExtractTableFromExprAlias(): void
595    {
596        $parser = new StatementStructure();
597        $tables = $parser->extractSelectTables('SELECT * FROM users AS u');
598        self::assertContains('users', $tables);
599    }
600
601    public function testExtractSelectTablesExtractTableFromExprSpaceAlias(): void
602    {
603        $parser = new StatementStructure();
604        $tables = $parser->extractSelectTables('SELECT * FROM users u');
605        self::assertContains('users', $tables);
606    }
607
608    public function testExtractSelectTablesExtractTableFromExprEmptyEntry(): void
609    {
610        $parser = new StatementStructure();
611        $tables = $parser->extractSelectTables('SELECT 1');
612        self::assertSame([], $tables);
613    }
614
615    public function testSplitStatementsDoubleQuoteEscape(): void
616    {
617        $parser = new StatementStructure();
618        $result = $parser->splitStatements('SELECT "col""name"; SELECT 1');
619        self::assertCount(2, $result);
620    }
621
622    public function testSplitStatementsSingleQuoteEscape(): void
623    {
624        $parser = new StatementStructure();
625        $result = $parser->splitStatements("SELECT 'it''s'; SELECT 1");
626        self::assertCount(2, $result);
627    }
628
629    public function testSplitStatementsLineComment(): void
630    {
631        $parser = new StatementStructure();
632        $result = $parser->splitStatements("SELECT 1; -- comment\nSELECT 2");
633        self::assertCount(2, $result);
634    }
635
636    public function testSplitStatementsBlockComment(): void
637    {
638        $parser = new StatementStructure();
639        $result = $parser->splitStatements('SELECT 1; /* block */ SELECT 2');
640        self::assertCount(2, $result);
641    }
642
643    public function testSplitStatementsUnclosedBlockComment(): void
644    {
645        $parser = new StatementStructure();
646        $result = $parser->splitStatements('SELECT 1 /* unclosed block');
647        self::assertCount(1, $result);
648    }
649
650    public function testSplitStatementsUnclosedLineComment(): void
651    {
652        $parser = new StatementStructure();
653        $result = $parser->splitStatements('SELECT 1 -- end');
654        self::assertCount(1, $result);
655    }
656
657    public function testSplitStatementsParenthesizedSubquery(): void
658    {
659        $parser = new StatementStructure();
660        $result = $parser->splitStatements('SELECT (SELECT 1; SELECT 2)');
661        self::assertCount(1, $result);
662    }
663
664    public function testExtractSelectTablesFromMultipleTables(): void
665    {
666        $parser = new StatementStructure();
667        $tables = $parser->extractSelectTables('SELECT * FROM users, orders WHERE users.id = orders.user_id');
668        self::assertContains('users', $tables);
669        self::assertContains('orders', $tables);
670    }
671
672    public function testExtractSelectTablesWithJoinSkipsJoined(): void
673    {
674        $parser = new StatementStructure();
675        $tables = $parser->extractSelectTables('SELECT * FROM users JOIN orders ON users.id = orders.user_id');
676        self::assertContains('users', $tables);
677    }
678
679    public function testExtractSelectTablesReturnsEmptyForSubqueryOnly(): void
680    {
681        $parser = new StatementStructure();
682        $tables = $parser->extractSelectTables('SELECT * FROM (SELECT 1)');
683        self::assertSame([], $tables);
684    }
685
686    public function testExtractSelectTablesMultiline(): void
687    {
688        $parser = new StatementStructure();
689        $tables = $parser->extractSelectTables("SELECT * FROM users,\norders WHERE id = 1");
690        self::assertContains('users', $tables);
691    }
692
693    public function testExtractSelectTablesSelectTablesComeFromSelectScopesOnly(): void
694    {
695        $parser = new StatementStructure();
696        $sql = 'SELECT printf(\'from %s\', name) FROM events WHERE id IN (SELECT event_id FROM archived_events)';
697
698        self::assertSame(['events', 'archived_events'], $parser->extractSelectTables($sql));
699    }
700
701    public function testStatementTailFindsMainStatementAfterWith(): void
702    {
703        $parser = new StatementStructure();
704        self::assertSame('UPDATE users SET id = 2', $parser->statementTail('WITH t AS (SELECT 1) UPDATE users SET id = 2', ['UPDATE']));
705        self::assertSame('SELECT 1', $parser->statementTail('/* hint */ SELECT 1', ['UPDATE']));
706    }
707
708}
709