packages/ztd-query-sqlite/tests/Unit/Sql/Value/SqliteValueRendererTest.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\Sqlite\Sql\Value\SqliteValueRenderer;
13use ZtdQuery\Schema\ColumnDeclaration;
14use ZtdQuery\Schema\ColumnTypeFamily;
15
16#[CoversClass(SqliteValueRenderer::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\CastTypeMapper::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueExpressionRenderer::class)]
19#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueLiteralRenderer::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer::class)]
21final class SqliteValueRendererTest extends TestCase
22{
23 public function testRenderValueIntegerStringUsesNumericExpression(): void
24 {
25 $renderer = new SqliteValueRenderer();
26
27 self::assertSame(
28 "CAST('42' AS INTEGER)",
29 $renderer->renderValue('42', new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER')),
30 );
31 }
32
33 public function testFloatUsesRoundTripRepresentation(): void
34 {
35 $renderer = new SqliteValueRenderer();
36
37 self::assertSame(
38 "CAST('2.718281828459045' AS REAL)",
39 $renderer->renderValue(2.718281828459045, new ColumnDeclaration(ColumnTypeFamily::FLOAT, 'REAL')),
40 );
41 }
42
43 public function testBinaryUsesLosslessHexLiteral(): void
44 {
45 $renderer = new SqliteValueRenderer();
46
47 self::assertSame(
48 "CAST(X'0001ff' AS BLOB)",
49 $renderer->renderValue("\x00\x01\xFF", new ColumnDeclaration(ColumnTypeFamily::BINARY, 'BLOB')),
50 );
51 }
52
53 public function testNullRetainsDeclaredType(): void
54 {
55 $renderer = new SqliteValueRenderer();
56
57 self::assertSame('NULL', $renderer->renderValue(null, new ColumnDeclaration(ColumnTypeFamily::TEXT, 'TEXT')));
58 }
59
60 public function testTextRemainsText(): void
61 {
62 $renderer = new SqliteValueRenderer();
63
64 self::assertSame("CAST('O''Reilly' AS TEXT)", $renderer->renderValue("O'Reilly"));
65 }
66
67 public function testInferredScalarTypesUseNativeLiterals(): void
68 {
69 $renderer = new SqliteValueRenderer();
70
71 self::assertSame('1', $renderer->renderValue(true));
72 self::assertSame('2.718281828459045', $renderer->renderValue(2.718281828459045));
73 self::assertSame('CAST(42 AS INTEGER)', $renderer->renderValue(42));
74 }
75
76 public function testDeclaredBooleanAndIntegerUseTypedText(): void
77 {
78 $renderer = new SqliteValueRenderer();
79
80 self::assertSame(
81 "CAST('1' AS INTEGER)",
82 $renderer->renderValue(true, new ColumnDeclaration(ColumnTypeFamily::BOOLEAN, 'BOOLEAN')),
83 );
84 self::assertSame(
85 "CAST('0' AS INTEGER)",
86 $renderer->renderValue(false, new ColumnDeclaration(ColumnTypeFamily::BOOLEAN, 'BOOLEAN')),
87 );
88 self::assertSame(
89 "CAST('42' AS INTEGER)",
90 $renderer->renderValue(42, new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER')),
91 );
92 }
93
94 public function testInferredAndDeclaredStringableRemainDistinct(): void
95 {
96 $value = new class () implements Stringable {
97 public function __toString(): string
98 {
99 return 'CURRENT_TIMESTAMP';
100 }
101 };
102 $renderer = new SqliteValueRenderer();
103
104 self::assertSame('CURRENT_TIMESTAMP', $renderer->renderValue($value));
105 self::assertSame(
106 "CAST('CURRENT_TIMESTAMP' AS TEXT)",
107 $renderer->renderValue($value, new ColumnDeclaration(ColumnTypeFamily::TEXT, 'TEXT')),
108 );
109 }
110
111 public function testUntypedArrayIsRejected(): void
112 {
113 $this->expectException(RuntimeException::class);
114
115 (new SqliteValueRenderer())->renderValue(['value']);
116 }
117
118 public function testDeclaredNonScalarUsesSerializedRepresentation(): void
119 {
120 self::assertSame(
121 "CAST('a:1:{i:0;s:5:\"value\";}' AS TEXT)",
122 (new SqliteValueRenderer())->renderValue(['value'], new ColumnDeclaration(ColumnTypeFamily::JSON, 'JSON')),
123 );
124 }
125
126 public function testBinaryStreamIsReadWithoutChangingItsPosition(): void
127 {
128 $stream = fopen('php://memory', 'r+');
129 self::assertIsResource($stream);
130 fwrite($stream, "\x00\x01\xFF");
131 fseek($stream, 1);
132
133 self::assertSame(
134 "CAST(X'0001ff' AS BLOB)",
135 (new SqliteValueRenderer())->renderValue($stream, new ColumnDeclaration(ColumnTypeFamily::BINARY, 'BLOB')),
136 );
137 self::assertSame(1, ftell($stream));
138 fclose($stream);
139 }
140
141 public function testBinaryRejectsNonStringableNonResource(): void
142 {
143 $this->expectException(RuntimeException::class);
144
145 (new SqliteValueRenderer())->renderValue(
146 ['value'],
147 new ColumnDeclaration(ColumnTypeFamily::BINARY, 'BLOB'),
148 );
149 }
150}
151