packages/ztd-query-sqlite/tests/Unit/Rewrite/Transformer/Insert/InsertProjectionBuilderTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Transformer\Insert;
6
7use PDO;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\Insert\InsertProjectionBuilder;
12
13#[CoversClass(InsertProjectionBuilder::class)]
14#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
16#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
19#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
21#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
22#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
23#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
24#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
25#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
26#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
27#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
28#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
29#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\CastTypeMapper::class)]
30#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer::class)]
31#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter::class)]
32#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker::class)]
34#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteParser::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer::class)]
38final class InsertProjectionBuilderTest extends TestCase
39{
40    public function testBuildInsertSelectProjectsMultipleRowsWithGeneratedIdentity(): void
41    {
42        $identity = new \ZtdQuery\Rewrite\ShadowIdentityAllocator();
43        $identity->beginProjection();
44        $builder = new InsertProjectionBuilder(
45            new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
46            $identity,
47            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer(),
48            new \ZtdQuery\Platform\Sqlite\Sql\SqliteParser(),
49            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer(),
50        );
51        $sql = $builder->buildInsertSelect("INSERT INTO users(name) VALUES ('Alice'), ('Bob')", 'users', ['id', 'name'], ['name'], [], [], ['id' => \ZtdQuery\Schema\Key\IdentityGenerationStrategy::MaxValue], [['id' => 7, 'name' => 'Existing']]);
52        $statement = (new PDO('sqlite::memory:'))->query($sql);
53        self::assertNotFalse($statement);
54        self::assertSame([['id' => 8, 'name' => 'Alice'], ['id' => 9, 'name' => 'Bob']], $statement->fetchAll(PDO::FETCH_ASSOC));
55    }
56
57    public function testBuildInsertSelectRejectsMismatchedRowCardinality(): void
58    {
59        $identity = new \ZtdQuery\Rewrite\ShadowIdentityAllocator();
60        $identity->beginProjection();
61        $builder = new InsertProjectionBuilder(
62            new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
63            $identity,
64            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer(),
65            new \ZtdQuery\Platform\Sqlite\Sql\SqliteParser(),
66            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer(),
67        );
68        $this->expectException(\ZtdQuery\Exception\UnsupportedSqlException::class);
69        $builder->buildInsertSelect('INSERT INTO users(id, name) VALUES (1)', 'users', ['id', 'name'], ['id', 'name'], [], [], [], []);
70    }
71
72    public function testBuildInsertRowSelectFillsDefaultsAndAppliesColumnTypes(): void
73    {
74        $identity = new \ZtdQuery\Rewrite\ShadowIdentityAllocator();
75        $identity->beginProjection();
76        $builder = new InsertProjectionBuilder(
77            new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
78            $identity,
79            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer(),
80            new \ZtdQuery\Platform\Sqlite\Sql\SqliteParser(),
81            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer(),
82        );
83        $sql = $builder->buildInsertRowSelect(['7'], 'users', ['id', 'name'], ['id'], ['id' => new \ZtdQuery\Schema\ColumnDeclaration(\ZtdQuery\Schema\ColumnTypeFamily::INTEGER, 'INTEGER')], ['name' => "'guest'"], [], []);
84        $statement = (new PDO('sqlite::memory:'))->query($sql);
85        self::assertNotFalse($statement);
86        self::assertSame([['id' => 7, 'name' => 'guest']], $statement->fetchAll(PDO::FETCH_ASSOC));
87    }
88
89    public function testBuildSelectSourceProjectsAnInsertSelectSource(): void
90    {
91        $identity = new \ZtdQuery\Rewrite\ShadowIdentityAllocator();
92        $identity->beginProjection();
93        $builder = new InsertProjectionBuilder(
94            new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
95            $identity,
96            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer(),
97            new \ZtdQuery\Platform\Sqlite\Sql\SqliteParser(),
98            new \ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer(),
99        );
100        $sql = $builder->buildSelectSource("INSERT INTO users(name) SELECT 'Alice'", 'users', ['id', 'name'], ['name'], [], ['id' => \ZtdQuery\Schema\Key\IdentityGenerationStrategy::MaxValue], [['id' => 3]]);
101        $statement = (new PDO('sqlite::memory:'))->query($sql);
102        self::assertNotFalse($statement);
103        self::assertSame([['id' => 4, 'name' => 'Alice']], $statement->fetchAll(PDO::FETCH_ASSOC));
104    }
105
106    public function testOrderedValuesDiscardsKeysWithoutReorderingExpressions(): void
107    {
108        self::assertSame(['second', 'first'], InsertProjectionBuilder::orderedValues([2 => 'second', 1 => 'first']));
109    }
110
111}
112