packages/ztd-query-sqlite/tests/Unit/SqliteSessionFactoryTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Config\ZtdConfig;
11use ZtdQuery\Connection\ConnectionInterface;
12use ZtdQuery\Connection\StatementInterface;
13use ZtdQuery\Platform\Sqlite\Connection\Parameter\SqlitePdoParameterBindingCompiler;
14use ZtdQuery\Platform\Sqlite\Connection\Result\SqlitePdoResultColumnTypeResolver;
15use ZtdQuery\Platform\Sqlite\Rewrite\SqliteQueryGuard;
16use ZtdQuery\Platform\Sqlite\Rewrite\SqliteRewriter;
17use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\DeleteTransformer;
18use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertTransformer;
19use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\SelectTransformer;
20use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\SqliteTransformer;
21use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\UpdateTransformer;
22use ZtdQuery\Platform\Sqlite\Schema\SqliteSchemaParser;
23use ZtdQuery\Platform\Sqlite\Schema\SqliteSchemaReflector;
24use ZtdQuery\Platform\Sqlite\Shadow\SqliteMutationResolver;
25use ZtdQuery\Platform\Sqlite\Sql\Attach\SqliteInMemoryAttachStatement;
26use ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter;
27use ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker;
28use ZtdQuery\Platform\Sqlite\Sql\SqliteParser;
29use ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer;
30use ZtdQuery\Platform\Sqlite\SqliteSessionFactory;
31
32#[CoversClass(SqliteSessionFactory::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Connection\Parameter\ParameterReplacements::class)]
34#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\AlterTableMutation::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\AddColumnResolver::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\AlteredTableProjection::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\ColumnDefinitionEditor::class)]
38#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\DropColumnResolver::class)]
39#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\RenameResolver::class)]
40#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Resolution\InsertMutationResolver::class)]
41#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Resolution\MutationTableLookup::class)]
42#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Resolution\RowMutationResolver::class)]
43#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Resolution\TableMutationResolver::class)]
44#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Alter\AlterOperationParser::class)]
45#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Attach\AttachTokens::class)]
46#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
47#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
48#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
49#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
50#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
51#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
52#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
53#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
54#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
55#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
56#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
57#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Returning\ReturningItemParser::class)]
58#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
59#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
60#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
61#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Transaction\TransactionTokens::class)]
62#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
63#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ArithmeticExpressionParser::class)]
64#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ComparisonExpressionParser::class)]
65#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ExpressionCursor::class)]
66#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ExpressionTokenDecoder::class)]
67#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\LogicalExpressionParser::class)]
68#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\PrimaryExpressionParser::class)]
69#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Context\ReferencedTableLookup::class)]
70#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Context\TableContextBuilder::class)]
71#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteDependencies::class)]
72#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteHeaderParser::class)]
73#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CtePrefixMerger::class)]
74#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteReferences::class)]
75#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\FullTextColumns::class)]
76#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\MatchExpressionRewriter::class)]
77#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Index\IndexHintTokens::class)]
78#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\CastTypeMapper::class)]
79#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueExpressionRenderer::class)]
80#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueLiteralRenderer::class)]
81#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\SqlEdits::class)]
82#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\StatementRewriter::class)]
83#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Upsert\UpsertConflictPredicate::class)]
84#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Upsert\UpsertExpressionBinder::class)]
85#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\ColumnDefinitionParser::class)]
86#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableBodyParser::class)]
87#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableDefinitionBuilder::class)]
88#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\VirtualTableParser::class)]
89#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyEntryParser::class)]
90#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyTokens::class)]
91#[UsesClass(SqliteCastRenderer::class)]
92#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\SqliteColumnTypeMapper::class)]
93#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\SqliteCteShadowComposer::class)]
94#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\SqliteForeignKeyDefinitionParser::class)]
95#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\SqliteFullTextSearchRewriter::class)]
96#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector::class)]
97#[UsesClass(SqliteIdentifierQuoter::class)]
98#[UsesClass(SqliteInMemoryAttachStatement::class)]
99#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Index\SqliteIndexHintStripper::class)]
100#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
101#[UsesClass(SqliteLexicalMasker::class)]
102#[UsesClass(SqliteMutationResolver::class)]
103#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Upsert\SqliteNativeUpsertProjector::class)]
104#[UsesClass(SqliteParser::class)]
105#[UsesClass(SqlitePdoParameterBindingCompiler::class)]
106#[UsesClass(SqlitePdoResultColumnTypeResolver::class)]
107#[UsesClass(SqliteQueryGuard::class)]
108#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Diagnostic\SqliteReadOnlyDiagnosticStatement::class)]
109#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Returning\SqliteReturningProjectionParser::class)]
110#[UsesClass(SqliteRewriter::class)]
111#[UsesClass(SqliteSchemaParser::class)]
112#[UsesClass(SqliteSchemaReflector::class)]
113#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
114#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Transaction\SqliteTransactionStatementParser::class)]
115#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\SqliteUpsertExpressionParser::class)]
116#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer::class)]
117#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\View\SqliteViewDefinitionParser::class)]
118#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\View\SqliteViewShadowRenderer::class)]
119#[UsesClass(DeleteTransformer::class)]
120#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer::class)]
121#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer::class)]
122#[UsesClass(InsertTransformer::class)]
123#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\Insert\InsertProjectionBuilder::class)]
124#[UsesClass(SelectTransformer::class)]
125#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\Select\ShadowCteRenderer::class)]
126#[UsesClass(SqliteTransformer::class)]
127#[UsesClass(UpdateTransformer::class)]
128final class SqliteSessionFactoryTest extends TestCase
129{
130 public function testCreateRegistersReflectedViews(): void
131 {
132 $empty = self::createStub(StatementInterface::class);
133 $empty->method('fetchAll')->willReturn([]);
134 $views = self::createStub(StatementInterface::class);
135 $views->method('fetchAll')->willReturn([
136 ['name' => 'active_users', 'sql' => 'CREATE VIEW active_users AS SELECT 1 AS id'],
137 ]);
138 $connection = self::createStub(ConnectionInterface::class);
139 $connection->method('query')->willReturnCallback(
140 static fn (string $sql): StatementInterface => str_contains($sql, "type='view'") ? $views : $empty,
141 );
142
143 $session = (new SqliteSessionFactory())->create($connection, ZtdConfig::default());
144
145 self::assertSame(
146 "WITH \"active_users\" AS (SELECT 1 AS id)\nSELECT * FROM active_users",
147 $session->rewrite('SELECT * FROM active_users')->sql(),
148 );
149 }
150
151 public function testCreateReturnsSession(): void
152 {
153 $statement = static::createStub(StatementInterface::class);
154 $statement->method('fetchAll')->willReturn([]);
155
156 $connection = static::createStub(ConnectionInterface::class);
157 $connection->method('query')->willReturn($statement);
158
159 $config = new ZtdConfig();
160 $factory = new SqliteSessionFactory();
161 $session = $factory->create($connection, $config);
162
163 self::assertTrue($session->isEnabled());
164 self::assertInstanceOf(SqlitePdoParameterBindingCompiler::class, $session->parameterBindingCompiler());
165 self::assertInstanceOf(SqlitePdoResultColumnTypeResolver::class, $session->resultColumnTypeResolver());
166 }
167
168 public function testCreateWithExistingTablesRegistersDefinitions(): void
169 {
170 $statement = static::createStub(StatementInterface::class);
171 $statement->method('fetchAll')->willReturn([
172 ['name' => 'users', 'sql' => 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)'],
173 ]);
174
175 $connection = static::createStub(ConnectionInterface::class);
176 $connection->method('query')->willReturn($statement);
177
178 $config = new ZtdConfig();
179 $factory = new SqliteSessionFactory();
180 $session = $factory->create($connection, $config);
181
182 self::assertTrue($session->isEnabled());
183
184 $plan = $session->rewrite('SELECT * FROM users');
185 self::assertStringContainsString('WITH', $plan->sql());
186 }
187
188 public function testCreateWithUnparseableSchemaSkipsTable(): void
189 {
190 $statement = static::createStub(StatementInterface::class);
191 $statement->method('fetchAll')->willReturn([
192 ['name' => 'bad', 'sql' => 'not valid sql'],
193 ['name' => 'good', 'sql' => 'CREATE TABLE good (id INTEGER PRIMARY KEY)'],
194 ]);
195
196 $connection = static::createStub(ConnectionInterface::class);
197 $connection->method('query')->willReturn($statement);
198
199 $config = new ZtdConfig();
200 $factory = new SqliteSessionFactory();
201 $session = $factory->create($connection, $config);
202
203 self::assertTrue($session->isEnabled());
204 }
205}
206