packages/sql-catalog/tests/Unit/Core/Sql/StatementKindReaderTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Sql;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlCatalog\Core\Sql\PlaceholderScanner;
12use SqlCatalog\Core\Sql\SqlLexer;
13use SqlCatalog\Core\Sql\SqlToken;
14use SqlCatalog\Core\Sql\StatementKind;
15use SqlCatalog\Core\Sql\StatementKindReader;
16use SqlCatalog\Core\Text\LiteralText;
17use SqlCatalog\Core\Text\Origin;
18use SqlCatalog\Core\Text\TextHole;
19use SqlCatalog\Core\Text\TextPattern;
20use SqlCatalog\Core\Type\TypeShape;
21
22#[CoversClass(StatementKindReader::class)]
23#[UsesClass(PlaceholderScanner::class)]
24#[UsesClass(SqlLexer::class)]
25#[UsesClass(SqlToken::class)]
26#[UsesClass(LiteralText::class)]
27#[UsesClass(TextHole::class)]
28#[UsesClass(TextPattern::class)]
29#[UsesClass(TypeShape::class)]
30final class StatementKindReaderTest extends TestCase
31{
32 #[DataProvider('providerRead')]
33 public function testRead(string $sql, StatementKind $expected): void
34 {
35 self::assertSame($expected, (new StatementKindReader())->read(TextPattern::fromText($sql)));
36 }
37
38 /**
39 * @return list<array{string, StatementKind}>
40 */
41 public static function providerRead(): array
42 {
43 return [
44 ['SELECT 1', StatementKind::Select],
45 [' insert into t values (1)', StatementKind::Insert],
46 ['/* lead */ UPDATE t SET a = 1', StatementKind::Update],
47 ['-- lead' . "\n" . 'DELETE FROM t', StatementKind::Delete],
48 ['REPLACE INTO t VALUES (1)', StatementKind::Replace],
49 ['TRUNCATE TABLE t', StatementKind::Truncate],
50 ['CREATE TABLE t (a INT)', StatementKind::Create],
51 ['EXPLAIN SELECT 1', StatementKind::Explain],
52 ['BEGIN', StatementKind::Transaction],
53 ['SET NAMES utf8', StatementKind::Other],
54 ['(SELECT 1) UNION (SELECT 2)', StatementKind::Select],
55 ['WITH t AS (SELECT 1) SELECT * FROM t', StatementKind::Select],
56 ['WITH t AS (SELECT 1) INSERT INTO u SELECT * FROM t', StatementKind::Insert],
57 ['WITH t AS (SELECT 1)', StatementKind::Unknown],
58 ['', StatementKind::Unknown],
59 ['1 + 1', StatementKind::Unknown],
60 ['FLUSH PRIVILEGES', StatementKind::Unknown],
61 ];
62 }
63
64 public function testReadIsUnknownWhenAGapHidesTheLeadingKeyword(): void
65 {
66 $pattern = TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown()))
67 ->concat(TextPattern::fromText(' FROM users'));
68 self::assertSame(StatementKind::Unknown, (new StatementKindReader())->read($pattern));
69 }
70
71 public function testReadAfterCommonTableIgnoresNestedKeywords(): void
72 {
73 $tokens = (new SqlLexer())->tokenize('a AS (SELECT 1) DELETE FROM t');
74 self::assertSame(StatementKind::Delete, (new StatementKindReader())->readAfterCommonTable($tokens));
75 }
76}
77