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