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