packages/ztd-query-mysql/tests/Unit/Sql/Value/MySqlValueRendererTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql\Value;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use RuntimeException;
11use Stringable;
12use ZtdQuery\Platform\MySql\Sql\Value\MySqlValueRenderer;
13use ZtdQuery\Schema\ColumnDeclaration;
14use ZtdQuery\Schema\ColumnTypeFamily;
15
16#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\CastTypeResolver::class)]
17
18
19#[CoversClass(MySqlValueRenderer::class)]
20#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\MySqlCastRenderer::class)]
21#[CoversClass(\ZtdQuery\Platform\MySql\Sql\Value\ScalarExpression::class)]
22#[CoversClass(\ZtdQuery\Platform\MySql\Sql\Value\StringCoercion::class)]
23final class MySqlValueRendererTest extends TestCase
24{
25    public function testRenderValueTextUsesHexEncodingInsteadOfSqlEscapeMode(): void
26    {
27        $renderer = new MySqlValueRenderer();
28
29        self::assertSame(
30            "CAST(CONVERT(X'706174685c746f5c66696c65' USING utf8mb4) AS CHAR)",
31            $renderer->renderValue('path\\to\\file', new ColumnDeclaration(ColumnTypeFamily::STRING, 'VARCHAR(255)')),
32        );
33    }
34
35    public function testBinaryUsesLosslessHexLiteral(): void
36    {
37        $renderer = new MySqlValueRenderer();
38
39        self::assertSame(
40            "CAST(X'0001ff' AS BINARY)",
41            $renderer->renderValue("\x00\x01\xFF", new ColumnDeclaration(ColumnTypeFamily::BINARY, 'BLOB')),
42        );
43    }
44
45    public function testNullRetainsDeclaredType(): void
46    {
47        $renderer = new MySqlValueRenderer();
48
49        self::assertSame('NULL', $renderer->renderValue(null, new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INT')));
50    }
51
52    public function testInferredFloatUsesRoundTripRepresentation(): void
53    {
54        $renderer = new MySqlValueRenderer();
55
56        self::assertSame('2.718281828459045', $renderer->renderValue(2.718281828459045));
57    }
58
59    public function testInferredAndDeclaredBooleansRemainDistinct(): void
60    {
61        $renderer = new MySqlValueRenderer();
62
63        self::assertSame('TRUE', $renderer->renderValue(true));
64        self::assertSame(
65            "CAST('1' AS UNSIGNED)",
66            $renderer->renderValue(true, new ColumnDeclaration(ColumnTypeFamily::BOOLEAN, 'BOOLEAN')),
67        );
68    }
69
70    public function testInferredAndDeclaredIntegersRemainDistinct(): void
71    {
72        $renderer = new MySqlValueRenderer();
73
74        self::assertSame('CAST(42 AS SIGNED)', $renderer->renderValue(42));
75        self::assertSame(
76            "CAST('42' AS SIGNED)",
77            $renderer->renderValue(42, new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INT')),
78        );
79    }
80
81    public function testInferredStringableUsesItsSqlRepresentation(): void
82    {
83        $value = new class () implements Stringable {
84            public function __toString(): string
85            {
86                return 'CURRENT_TIMESTAMP';
87            }
88        };
89
90        self::assertSame('CURRENT_TIMESTAMP', (new MySqlValueRenderer())->renderValue($value));
91    }
92
93    public function testTextEscapesSingleQuotes(): void
94    {
95        self::assertSame(
96            "CAST('O''Reilly' AS CHAR)",
97            (new MySqlValueRenderer())->renderValue("O'Reilly"),
98        );
99    }
100
101    public function testStreamValueIsReadWithoutChangingItsPosition(): void
102    {
103        $stream = fopen('php://memory', 'r+');
104        self::assertIsResource($stream);
105        fwrite($stream, 'stream value');
106        fseek($stream, 3);
107
108        self::assertSame(
109            "CAST('stream value' AS CHAR)",
110            (new MySqlValueRenderer())->renderValue($stream),
111        );
112        self::assertSame(3, ftell($stream));
113        fclose($stream);
114    }
115
116    public function testRejectsNonScalarValues(): void
117    {
118        $renderer = new MySqlValueRenderer();
119
120        $this->expectException(RuntimeException::class);
121        $renderer->renderValue([]);
122    }
123    public function testStoredYearZeroIsNotReinterpretedAsAStringYear(): void
124    {
125        $renderer = new MySqlValueRenderer();
126        $year = new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'year(4)');
127        self::assertSame("CAST('0' AS SIGNED)", $renderer->renderValue(0, $year));
128        self::assertSame("CAST('0' AS SIGNED)", $renderer->renderValue('0', $year));
129        self::assertSame("CAST('1978' AS SIGNED)", $renderer->renderValue(1978, $year));
130    }
131
132    public function testStringFamilyYearRemainsTextual(): void
133    {
134        self::assertSame("CAST('0' AS CHAR)", (new MySqlValueRenderer())->renderValue('0', new ColumnDeclaration(ColumnTypeFamily::STRING, 'YEAR')));
135    }
136
137}
138