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