packages/ztd-query-sqlite/tests/Unit/Rewrite/Transformer/Select/ShadowCteRendererTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Transformer\Select;
6
7use PDO;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use RuntimeException;
12use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\Select\ShadowCteRenderer;
13
14#[CoversClass(ShadowCteRenderer::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\CastTypeMapper::class)]
16#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueExpressionRenderer::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueLiteralRenderer::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer::class)]
19#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter::class)]
21#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer::class)]
22final class ShadowCteRendererTest extends TestCase
23{
24 public function testGenerateCteRendersTypedFixturesExecutableBySqlite(): void
25 {
26 $renderer = new ShadowCteRenderer(
27 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
28 new \ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector(),
29 new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter(),
30 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer(),
31 );
32 $cte = $renderer->generateCte('users', [['id' => 7, 'name' => "O'Brien"], ['id' => 8, 'name' => null]], ['id', 'name'], [], []);
33 $statement = (new PDO('sqlite::memory:'))->query('WITH ' . $cte . ' SELECT * FROM users ORDER BY id');
34 self::assertNotFalse($statement);
35 self::assertSame([['id' => 7, 'name' => "O'Brien"], ['id' => 8, 'name' => null]], $statement->fetchAll(PDO::FETCH_ASSOC));
36 }
37
38 public function testGenerateCteRejectsEmptyUnknownSchemas(): void
39 {
40 $renderer = new ShadowCteRenderer(
41 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
42 new \ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector(),
43 new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter(),
44 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer(),
45 );
46 $this->expectException(RuntimeException::class);
47 $renderer->generateCte('unknown', [], [], [], []);
48 }
49
50 public function testWrapCteQuotesTheProvidedTableAndProjectsGeneratedColumns(): void
51 {
52 $renderer = new ShadowCteRenderer(
53 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
54 new \ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector(),
55 new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter(),
56 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer(),
57 );
58 $cte = $renderer->wrapCte('"items"', 'SELECT 3 AS amount, NULL AS total', ['amount', 'total'], ['total' => '(amount * 2)']);
59 $statement = (new PDO('sqlite::memory:'))->query('WITH ' . $cte . ' SELECT total FROM items');
60 self::assertNotFalse($statement);
61 self::assertSame(6, $statement->fetchColumn());
62 }
63
64 public function testRenderRowPreservesColumnOrderAndMissingValues(): void
65 {
66 $renderer = new ShadowCteRenderer(
67 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
68 new \ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector(),
69 new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter(),
70 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer(),
71 );
72 $sql = $renderer->renderRow(['id' => 9], ['name', 'id'], []);
73 $statement = (new PDO('sqlite::memory:'))->query($sql);
74 self::assertNotFalse($statement);
75 self::assertSame([['name' => null, 'id' => 9]], $statement->fetchAll(PDO::FETCH_ASSOC));
76 }
77
78 public function testRenderEmptyTableRetainsTypedZeroRowProjection(): void
79 {
80 $renderer = new ShadowCteRenderer(
81 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
82 new \ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector(),
83 new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter(),
84 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer(),
85 );
86 self::assertSame('SELECT CAST(NULL AS INTEGER) AS "id", CAST(NULL AS TEXT) AS "name" WHERE 0', $renderer->renderEmptyTable(['id', 'name'], ['id' => new \ZtdQuery\Schema\ColumnDeclaration(\ZtdQuery\Schema\ColumnTypeFamily::INTEGER, 'INTEGER')]));
87 }
88
89 public function testRenderFallbackNullCastUsesText(): void
90 {
91 $renderer = new ShadowCteRenderer(
92 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer(),
93 new \ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector(),
94 new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter(),
95 new \ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer(),
96 );
97 self::assertSame('CAST(NULL AS TEXT)', $renderer->renderFallbackNullCast());
98 }
99
100}
101