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