packages/ztd-query-sqlite/tests/Unit/Sql/Attach/SqliteInMemoryAttachStatementTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql\Attach;
6
7use Generator;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\DataProvider;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use ZtdQuery\Platform\Sqlite\Sql\Attach\SqliteInMemoryAttachStatement;
13
14#[CoversClass(SqliteInMemoryAttachStatement::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Attach\AttachTokens::class)]
16#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
17final class SqliteInMemoryAttachStatementTest extends TestCase
18{
19    public function testIsSafeAcceptsOnlyLiteralInMemoryAttachments(): void
20    {
21        self::assertTrue(SqliteInMemoryAttachStatement::isSafe("ATTACH ':memory:' AS db2"));
22        self::assertTrue(SqliteInMemoryAttachStatement::isSafe("ATTACH DATABASE ':memory:' AS \"db2\";"));
23        self::assertTrue(SqliteInMemoryAttachStatement::isSafe("ATTACH /* safe */ ':memory:' AS [db2]"));
24        self::assertTrue(SqliteInMemoryAttachStatement::isSafe("attach ':memory:' as `db2`"));
25        self::assertTrue(SqliteInMemoryAttachStatement::isSafe("ATTACH ':memory:' AS \"db alias\""));
26    }
27
28    public function testRejectsPersistentOrDynamicAttachments(): void
29    {
30        self::assertFalse(SqliteInMemoryAttachStatement::isSafe("ATTACH 'test.sqlite' AS db2"));
31        self::assertFalse(SqliteInMemoryAttachStatement::isSafe("ATTACH ('file:' || :name) AS db2"));
32        self::assertFalse(SqliteInMemoryAttachStatement::isSafe("ATTACH ':memory:' AS db2; DELETE FROM users"));
33        self::assertFalse(SqliteInMemoryAttachStatement::isSafe('DETACH DATABASE db2'));
34    }
35
36    #[DataProvider('providerIncompleteOrExtendedAttachmentShapes')]
37    public function testRejectsIncompleteOrExtendedAttachmentShapes(string $sql): void
38    {
39        self::assertFalse(SqliteInMemoryAttachStatement::isSafe($sql));
40    }
41
42    /**
43     * @return Generator<string, array{string}>
44     */
45    public static function providerIncompleteOrExtendedAttachmentShapes(): Generator
46    {
47        yield 'empty' => [''];
48        yield 'keyword only' => ['ATTACH'];
49        yield 'path without alias' => ["ATTACH ':memory:'"];
50        yield 'missing alias' => ["ATTACH ':memory:' AS"];
51        yield 'database missing alias' => ["ATTACH DATABASE ':memory:' AS"];
52        yield 'missing as' => ["ATTACH ':memory:' db2"];
53        yield 'extra token' => ["ATTACH ':memory:' AS db2 extra"];
54        yield 'trailing closing parenthesis' => ["ATTACH ':memory:' AS db2 )"];
55        yield 'string alias' => ["ATTACH ':memory:' AS 'db2'"];
56        yield 'empty bracket alias' => ["ATTACH ':memory:' AS []"];
57        yield 'unterminated bracket alias' => ["ATTACH ':memory:' AS [db2"];
58        yield 'numeric bracket alias' => ["ATTACH ':memory:' AS [42]"];
59        yield 'second statement' => ["ATTACH ':memory:' AS db2; ATTACH ':memory:' AS db3"];
60    }
61}
62