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