packages/ztd-query-mysql/tests/Unit/MySqlSessionFactoryTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit;
6
7use PhpMyAdmin\SqlParser\Context;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use ZtdQuery\Config\ZtdConfig;
12use ZtdQuery\Connection\ConnectionInterface;
13use ZtdQuery\Connection\StatementInterface;
14use ZtdQuery\Platform\MySql\Connection\MySqlSessionSqlModeReflector;
15use ZtdQuery\Platform\MySql\Connection\Result\MySqlResultColumnTypeResolver;
16use ZtdQuery\Platform\MySql\MySqlSessionFactory;
17use ZtdQuery\Platform\MySql\Rewrite\MySqlQueryGuard;
18use ZtdQuery\Platform\MySql\Rewrite\MySqlRewriter;
19use ZtdQuery\Platform\MySql\Rewrite\Transformer\DeleteTransformer;
20use ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertTransformer;
21use ZtdQuery\Platform\MySql\Rewrite\Transformer\MySqlTransformer;
22use ZtdQuery\Platform\MySql\Rewrite\Transformer\ReplaceTransformer;
23use ZtdQuery\Platform\MySql\Rewrite\Transformer\SelectTransformer;
24use ZtdQuery\Platform\MySql\Rewrite\Transformer\UpdateTransformer;
25use ZtdQuery\Platform\MySql\Schema\MySqlSchemaParser;
26use ZtdQuery\Platform\MySql\Schema\MySqlSchemaReflector;
27use ZtdQuery\Platform\MySql\Shadow\MySqlMutationResolver;
28use ZtdQuery\Platform\MySql\Sql\MySqlIdentifierQuoter;
29use ZtdQuery\Platform\MySql\Sql\MySqlLexerProfile;
30use ZtdQuery\Platform\MySql\Sql\MySqlParser;
31use ZtdQuery\Platform\MySql\Sql\Value\MySqlCastRenderer;
32use ZtdQuery\Sql\SqlTokenKind;
33use ZtdQuery\Sql\SqlTokenStream;
34
35#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\DmlWhereClauseExtractor::class)]
36#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\InsertSelectSourceExtractor::class)]
37#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\AlterTableMutation::class)]
38#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\ColumnAction::class)]
39#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\ColumnAlteration::class)]
40#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\ColumnDefinitionParser::class)]
41#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\CreateTableRenderer::class)]
42#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\OperationApplier::class)]
43#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\PrimaryKeyAlteration::class)]
44#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\StoredColumns::class)]
45#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\Alter\UnsupportedKeyword::class)]
46#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Row\RowMutationResolver::class)]
47#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Table\TableMutationResolver::class)]
48#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\TargetName::class)]
49#[UsesClass(\ZtdQuery\Platform\MySql\Schema\MySqlColumnTypeMapper::class)]
50#[UsesClass(\ZtdQuery\Platform\MySql\Schema\Key\MySqlForeignKeyDefinitionParser::class)]
51#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\MySqlLoadDataProjector::class)]
52#[UsesClass(\ZtdQuery\Platform\MySql\Connection\Result\MySqlMysqliResultColumnTypeResolver::class)]
53#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\MySqlPartitionSelectionRewriter::class)]
54#[UsesClass(\ZtdQuery\Platform\MySql\Schema\Partition\MySqlPartitioningParser::class)]
55#[UsesClass(\ZtdQuery\Platform\MySql\Connection\Result\MySqlPdoResultColumnTypeResolver::class)]
56#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Transaction\MySqlTransactionStatementParser::class)]
57#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Upsert\MySqlUpsertAssignmentExtractor::class)]
58#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Upsert\MySqlUpsertExpressionParser::class)]
59#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Alter\OptionList::class)]
60#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\AssignmentExpression::class)]
61#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Cte\HeaderParser::class)]
62#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Cte\IdentifierReferences::class)]
63#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Diagnostic\DiagnosticKeywords::class)]
64#[UsesClass(\ZtdQuery\Platform\MySql\Sql\OptionalInsertIntoNormalizer::class)]
65#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\ExpressionNames::class)]
66#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\ReferenceReader::class)]
67#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Transaction\TokenReader::class)]
68#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Upsert\AssignmentReader::class)]
69#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Upsert\ExpressionReader::class)]
70#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Upsert\LiteralReader::class)]
71#[UsesClass(\ZtdQuery\Platform\MySql\Shadow\Mutation\Upsert\StringLiteral::class)]
72#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\FullText\ExpressionEditor::class)]
73#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\ColumnMapping::class)]
74#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\InputFile::class)]
75#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\InputFormat::class)]
76#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\InsertQuery::class)]
77#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\RecordParser::class)]
78#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\LoadData\RowProjector::class)]
79#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\SelectionReader::class)]
80#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\SourceProjection::class)]
81#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\ConflictPredicate::class)]
82#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\ExpressionBinder::class)]
83#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\MetadataColumns::class)]
84#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\QualifiedColumn::class)]
85#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Classification\CteStatementKind::class)]
86#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Context\TableContext::class)]
87#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\StatementRewriter::class)]
88#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Validation\AlterTableGuard::class)]
89#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Validation\ReplaceColumns::class)]
90#[UsesClass(\ZtdQuery\Platform\MySql\Schema\DefinitionBuilder::class)]
91#[UsesClass(\ZtdQuery\Platform\MySql\Schema\Key\DefinitionReader::class)]
92#[UsesClass(\ZtdQuery\Platform\MySql\Schema\Key\TokenReader::class)]
93#[UsesClass(\ZtdQuery\Platform\MySql\Schema\Partition\PredicateCompiler::class)]
94#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Delete\ResultSelect::class)]
95#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Delete\TargetProjection::class)]
96#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert\InsertTarget::class)]
97#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert\ReplaceStatementConverter::class)]
98#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert\ResultProjection::class)]
99#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Select\ExpressionAliaser::class)]
100#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Set\OrderRewriter::class)]
101#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Set\ValueNormalizer::class)]
102#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Shadow\CteRows::class)]
103#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Update\ResultSelect::class)]
104#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Update\TargetProjection::class)]
105#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\CastTypeResolver::class)]
106#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Type\RankEdits::class)]
107#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\ScalarExpression::class)]
108#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\StringCoercion::class)]
109#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\UpdateAssignmentExtractor::class)]
110#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\UpdateSourceExtractor::class)]
111#[CoversClass(MySqlSessionFactory::class)]
112#[UsesClass(MySqlLexerProfile::class)]
113#[UsesClass(MySqlMutationResolver::class)]
114#[UsesClass(MySqlCastRenderer::class)]
115#[UsesClass(MySqlIdentifierQuoter::class)]
116#[UsesClass(MySqlParser::class)]
117#[UsesClass(MySqlResultColumnTypeResolver::class)]
118#[UsesClass(MySqlQueryGuard::class)]
119#[UsesClass(MySqlRewriter::class)]
120#[UsesClass(MySqlSchemaParser::class)]
121#[UsesClass(MySqlSchemaReflector::class)]
122#[UsesClass(MySqlSessionSqlModeReflector::class)]
123#[UsesClass(DeleteTransformer::class)]
124#[UsesClass(InsertTransformer::class)]
125#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertRowRenderer::class)]
126#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertSelectRenderer::class)]
127#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\MySqlSelectListAliaser::class)]
128#[UsesClass(MySqlTransformer::class)]
129#[UsesClass(ReplaceTransformer::class)]
130#[UsesClass(SelectTransformer::class)]
131#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\FullText\MySqlFullTextSearchRewriter::class)]
132#[UsesClass(UpdateTransformer::class)]
133#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\MySqlValueRenderer::class)]
134#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Type\MySqlTypeSemantics::class)]
135#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Cte\MySqlCteShadowComposer::class)]
136#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\MySqlNativeUpsertProjector::class)]
137#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\GeneratedColumn\MySqlGeneratedColumnProjector::class)]
138#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Diagnostic\MySqlReadOnlyDiagnosticStatement::class)]
139#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\MySqlSelectRelationParser::class)]
140#[UsesClass(\ZtdQuery\Platform\MySql\Schema\View\MySqlViewDefinitionParser::class)]
141#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\View\MySqlViewShadowRenderer::class)]
142final class MySqlSessionFactoryTest extends TestCase
143{
144 public function testCreateRegistersReflectedViews(): void
145 {
146 $empty = self::createStub(StatementInterface::class);
147 $empty->method('fetchAll')->willReturn([]);
148 $views = self::createStub(StatementInterface::class);
149 $views->method('fetchAll')->willReturn([['name' => 'active_users']]);
150 $create = self::createStub(StatementInterface::class);
151 $create->method('fetchAll')->willReturn([
152 ['Create View' => 'CREATE VIEW active_users AS SELECT 1 AS id'],
153 ]);
154 $connection = self::createStub(ConnectionInterface::class);
155 $connection->method('query')->willReturnCallback(
156 static fn (string $sql): StatementInterface => match ($sql) {
157 'SHOW TABLES' => $empty,
158 "SHOW FULL TABLES WHERE Table_type = 'VIEW'" => $views,
159 'SHOW CREATE VIEW `active_users`' => $create,
160 default => $empty,
161 },
162 );
163
164 $session = (new MySqlSessionFactory())->create($connection, ZtdConfig::default());
165
166 self::assertSame(
167 "WITH `active_users` AS (SELECT 1 AS id)\nSELECT * FROM active_users",
168 $session->rewrite('SELECT * FROM active_users')->sql(),
169 );
170 }
171
172 public function testCreateReturnsSession(): void
173 {
174 $statement = self::createStub(StatementInterface::class);
175 $statement->method('fetchAll')->willReturn([]);
176
177 $connection = self::createStub(ConnectionInterface::class);
178 $connection->method('query')->willReturn($statement);
179
180 $config = new ZtdConfig();
181 $factory = new MySqlSessionFactory();
182 $session = $factory->create($connection, $config);
183
184 self::assertInstanceOf(MySqlResultColumnTypeResolver::class, $session->resultColumnTypeResolver());
185 }
186
187 public function testCreateAppliesReflectedAnsiQuotesModeToProductionLexing(): void
188 {
189 $empty = self::createStub(StatementInterface::class);
190 $empty->method('fetchAll')->willReturn([]);
191 $sqlMode = self::createStub(StatementInterface::class);
192 $sqlMode->method('fetchAll')->willReturn([['ztd_sql_mode' => 'STRICT_TRANS_TABLES,ANSI_QUOTES']]);
193 $connection = self::createStub(ConnectionInterface::class);
194 $connection->method('query')->willReturnCallback(
195 static fn (string $sql): StatementInterface => $sql === 'SELECT @@SESSION.sql_mode AS ztd_sql_mode'
196 ? $sqlMode
197 : $empty,
198 );
199 $previousMode = Context::getMode();
200
201 try {
202 (new MySqlSessionFactory())->create($connection, ZtdConfig::default());
203 $tokens = SqlTokenStream::tokenize('"column"', MySqlLexerProfile::create())->significantTokens();
204
205 self::assertSame(SqlTokenKind::QuotedIdentifier, $tokens[0]->kind);
206 } finally {
207 Context::setMode($previousMode);
208 }
209 }
210}
211