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