packages/ztd-query-postgres/tests/Unit/Rewrite/Transformer/PgSqlTransformerTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Transformer;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Exception\UnsupportedSqlException;
11use ZtdQuery\Platform\Postgres\Rewrite\Transformer\DeleteTransformer;
12use ZtdQuery\Platform\Postgres\Rewrite\Transformer\InsertTransformer;
13use ZtdQuery\Platform\Postgres\Rewrite\Transformer\MergeTransformer;
14use ZtdQuery\Platform\Postgres\Rewrite\Transformer\PgSqlTransformer;
15use ZtdQuery\Platform\Postgres\Rewrite\Transformer\SelectTransformer;
16use ZtdQuery\Platform\Postgres\Rewrite\Transformer\UpdateTransformer;
17use ZtdQuery\Platform\Postgres\Sql\PgSqlIdentifierQuoter;
18use ZtdQuery\Platform\Postgres\Sql\PgSqlParser;
19use ZtdQuery\Platform\Postgres\Sql\Value\PgSqlCastRenderer;
20use ZtdQuery\Schema\ColumnDeclaration;
21use ZtdQuery\Schema\ColumnTypeFamily;
22use ZtdQuery\Schema\Key\IdentityGenerationStrategy;
23
24#[CoversClass(PgSqlTransformer::class)]
25#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Relation\PgSqlSelectRelationParser::class)]
26#[UsesClass(PgSqlParser::class)]
27#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\PostgreSqlLexicalMasker::class)]
28#[UsesClass(SelectTransformer::class)]
29#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Sampling\PgSqlTableSampleParser::class)]
30#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Sampling\PgSqlTableSampleRewriter::class)]
31#[UsesClass(InsertTransformer::class)]
32#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\InsertRowRenderer::class)]
33#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\InsertSelectRenderer::class)]
34#[UsesClass(MergeTransformer::class)]
35#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\PgSqlMergeParser::class)]
36#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\PgSqlMergeStatement::class)]
37#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\PgSqlMergeClause::class)]
38#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\PgSqlMergeMatchKind::class)]
39#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\PgSqlMergeActionKind::class)]
40#[UsesClass(UpdateTransformer::class)]
41#[UsesClass(DeleteTransformer::class)]
42#[UsesClass(PgSqlCastRenderer::class)]
43#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Value\PgSqlValueRenderer::class)]
44#[UsesClass(PgSqlIdentifierQuoter::class)]
45#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Cte\PgSqlCteShadowComposer::class)]
46#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Upsert\PgSqlNativeUpsertProjector::class)]
47#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\GeneratedColumn\PgSqlGeneratedColumnProjector::class)]
48#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\PgSqlLexerProfile::class)]
49#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Conflict\ColumnSet::class)]
50#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Cte\HeaderParser::class)]
51#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Cte\IdentifierReferences::class)]
52#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Cte\PrefixMerge::class)]
53#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Cte\ShadowDependencies::class)]
54#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\ActionClause::class)]
55#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\BranchTokens::class)]
56#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\RelationTarget::class)]
57#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Merge\StatementParts::class)]
58#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Relation\FromClause::class)]
59#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Relation\RelationReference::class)]
60#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Sampling\SampleClause::class)]
61#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Sampling\SampleTokens::class)]
62#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Statement\StatementClassifier::class)]
63#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Statement\ConflictClause::class)]
64#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Lexing\IdentifierDecoder::class)]
65#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Statement\TargetTableParser::class)]
66#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Dml\Update\UpdateClauseParser::class)]
67#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Dml\Delete\DeleteClauseParser::class)]
68#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Dml\Insert\InsertClauseParser::class)]
69#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Dml\Insert\InsertClauseParser::class)]
70#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Statement\TableDefinitionClauses::class)]
71#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Conflict\PgSqlConflictTarget::class)]
72#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Sampling\PgSqlTableSample::class)]
73#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Sampling\PgSqlTableSampleMethod::class)]
74#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Sampling\SampleProjection::class)]
75#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Sampling\TableColumns::class)]
76#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Upsert\ConflictPredicate::class)]
77#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Upsert\ExpressionBinder::class)]
78#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Value\CastTypeMapper::class)]
79#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Lexing\CommentSpan::class)]
80#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Lexing\QuotedSpan::class)]
81#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Value\NativeCastTarget::class)]
82#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Value\BinaryStream::class)]
83#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\Value\LiteralText::class)]
84#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Cte\RowSourceRenderer::class)]
85#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Insert\ExpressionCast::class)]
86#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Insert\OrderedExpressions::class)]
87#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Insert\SelectProjection::class)]
88#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Insert\UpsertProjection::class)]
89#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Insert\ValueProjection::class)]
90#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Merge\MatchConditions::class)]
91#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Merge\RowActions::class)]
92#[UsesClass(\ZtdQuery\Platform\Postgres\Rewrite\Transformer\Update\ColumnProjection::class)]
93final class PgSqlTransformerTest extends TestCase
94{
95    public function testTransformSelectDelegatesToSelectTransformer(): void
96    {
97        $parser = new PgSqlParser();
98        $selectTransformer = new SelectTransformer();
99        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
100        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
101        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
102        $transformer = new PgSqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
103        $result = $transformer->transform('SELECT * FROM users', ['users' => ['alias' => '"users"', 'rows' => [['id' => 1, 'name' => 'Alice']], 'columns' => ['id', 'name'], 'columnTypes' => ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER'), 'name' => new ColumnDeclaration(ColumnTypeFamily::STRING, 'TEXT')]]]);
104        self::assertStringContainsString('WITH', $result);
105    }
106
107    public function testTransformInsertDelegatesToInsertTransformer(): void
108    {
109        $parser = new PgSqlParser();
110        $selectTransformer = new SelectTransformer();
111        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
112        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
113        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
114        $transformer = new PgSqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
115        $result = $transformer->transform("INSERT INTO users (id, name) VALUES (1, 'Alice')", ['users' => ['alias' => '"users"', 'rows' => [], 'columns' => ['id', 'name'], 'columnTypes' => ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER'), 'name' => new ColumnDeclaration(ColumnTypeFamily::STRING, 'TEXT')]]]);
116        self::assertNotEmpty($result);
117    }
118
119    public function testTransformUpdateDelegatesToUpdateTransformer(): void
120    {
121        $parser = new PgSqlParser();
122        $selectTransformer = new SelectTransformer();
123        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
124        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
125        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
126        $transformer = new PgSqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
127        $result = $transformer->transform("UPDATE users SET name = 'Bob' WHERE id = 1", ['users' => ['alias' => '"users"', 'rows' => [['id' => 1, 'name' => 'Alice']], 'columns' => ['id', 'name'], 'columnTypes' => ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER'), 'name' => new ColumnDeclaration(ColumnTypeFamily::STRING, 'TEXT')]]]);
128        self::assertNotEmpty($result);
129    }
130
131    public function testTransformDeleteDelegatesToDeleteTransformer(): void
132    {
133        $parser = new PgSqlParser();
134        $selectTransformer = new SelectTransformer();
135        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
136        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
137        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
138        $transformer = new PgSqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
139        $result = $transformer->transform('DELETE FROM users WHERE id = 1', ['users' => ['alias' => '"users"', 'rows' => [['id' => 1, 'name' => 'Alice']], 'columns' => ['id', 'name'], 'columnTypes' => ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER'), 'name' => new ColumnDeclaration(ColumnTypeFamily::STRING, 'TEXT')]]]);
140        self::assertNotEmpty($result);
141    }
142
143    public function testTransformMergeDelegatesToMergeTransformer(): void
144    {
145        $parser = new PgSqlParser();
146        $selectTransformer = new SelectTransformer();
147        $transformer = new PgSqlTransformer(
148            $parser,
149            $selectTransformer,
150            new InsertTransformer($parser, $selectTransformer),
151            new UpdateTransformer($parser, $selectTransformer),
152            new DeleteTransformer($parser, $selectTransformer),
153        );
154        $result = $transformer->transform(
155            'MERGE INTO users u USING source s ON u.id = s.id WHEN MATCHED THEN DELETE',
156            [
157                'users' => [
158                    'rows' => [['id' => 1]],
159                    'columns' => ['id'],
160                    'columnTypes' => ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER')],
161                ],
162                'source' => [
163                    'rows' => [['id' => 1]],
164                    'columns' => ['id'],
165                    'columnTypes' => ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER')],
166                ],
167            ],
168        );
169
170        self::assertStringContainsString('WHERE NOT (EXISTS', $result);
171        $transformer->commitRewriteState();
172    }
173
174    public function testCommitRewriteStateCommitsGeneratedIdentityValues(): void
175    {
176        $parser = new PgSqlParser();
177        $selectTransformer = new SelectTransformer();
178        $transformer = new PgSqlTransformer(
179            $parser,
180            $selectTransformer,
181            new InsertTransformer($parser, $selectTransformer),
182            new UpdateTransformer($parser, $selectTransformer),
183            new DeleteTransformer($parser, $selectTransformer),
184        );
185        $tables = ['users' => [
186            'rows' => [],
187            'columns' => ['id', 'name'],
188            'columnTypes' => [],
189            'identityStrategies' => ['id' => IdentityGenerationStrategy::Sequence],
190        ]];
191
192        $first = $transformer->transform("INSERT INTO users (name) VALUES ('first')", $tables);
193        $transformer->commitRewriteState();
194        $second = $transformer->transform("INSERT INTO users (name) VALUES ('second')", $tables);
195
196        self::assertStringContainsString('1 AS "id"', $first);
197        self::assertStringContainsString('2 AS "id"', $second);
198    }
199
200    public function testTransformUnsupportedStatementThrows(): void
201    {
202        $parser = new PgSqlParser();
203        $selectTransformer = new SelectTransformer();
204        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
205        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
206        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
207        $transformer = new PgSqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
208        $this->expectException(UnsupportedSqlException::class);
209        $transformer->transform('CREATE TABLE test (id INTEGER)', []);
210    }
211}
212