packages/sql-semantics/tests/Unit/Statement/WriterTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Statement;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Medium;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11
12#[CoversClass(\SqlSemantics\Statement\Writer::class)]
13#[UsesClass(\SqlSemantics\Core\Analysis\Analyzer::class)]
14#[UsesClass(\SqlSemantics\Core\Analysis\ValueReader::class)]
15#[UsesClass(\SqlSemantics\Core\AnalysisException::class)]
16#[UsesClass(\SqlSemantics\Facade\Semantics::class)]
17#[UsesClass(\SqlSemantics\Statement\Element::class)]
18#[UsesClass(\SqlSemantics\Statement\Statement::class)]
19#[UsesClass(\SqlSemantics\Statement\Comments::class)]
20#[UsesClass(\SqlSemantics\Core\Analysis\TriviaReader::class)]
21#[UsesClass(\SqlSemantics\Core\Analysis\SourceComments::class)]
22#[UsesClass(\SqlSemantics\Core\Ast\DialectParser::class)]
23#[UsesClass(\SqlSemantics\Platform\MySql\Platform::class)]
24#[UsesClass(\SqlSemantics\Platform\PostgreSql\Platform::class)]
25#[UsesClass(\SqlSemantics\Platform\Sqlite\Platform::class)]
26#[Medium]
27final class WriterTest extends TestCase
28{
29 public function testRenderPreservesOperandGroupingAfterAnImmutableUpdate(): void
30 {
31 $one = new \SqlSemantics\Statement\Model\Sqlite\Value\TermWithInteger_298801b2('1');
32 $two = new \SqlSemantics\Statement\Model\Sqlite\Value\TermWithInteger_298801b2('2');
33 $three = new \SqlSemantics\Statement\Model\Sqlite\Value\TermWithInteger_298801b2('3');
34 $sum = new \SqlSemantics\Statement\Model\Sqlite\Value\ExprWithExprPlusMinusExpr_82e360dc($one, '+', $two);
35 $original = new \SqlSemantics\Statement\Model\Sqlite\Value\ExprWithExprStarSlashRemExpr_6ca99fe8($one, '*', $three);
36 $updated = $original->withExpr(new \SqlSemantics\Statement\Model\Sqlite\Value\ExprWithLpExprRp_ad646753($sum));
37 self::assertSame('1 * 3', \SqlSemantics\Statement\Writer::render($original));
38 self::assertSame('( 1 + 2 ) * 3', \SqlSemantics\Statement\Writer::render($updated));
39 }
40
41 public function testRenderWritesAFragmentWithoutTreatingItAsACompleteCommand(): void
42 {
43 $value = new \SqlSemantics\Statement\Model\Sqlite\Value\TermWithInteger_298801b2('42');
44 self::assertSame('42', \SqlSemantics\Statement\Writer::render($value));
45 }
46
47 public function testAppendKeepsIdentifierAndKeywordDotBoundaries(): void
48 {
49 $identifier = new \SqlSemantics\Statement\Writer();
50 $identifier->append('select', true);
51 $identifier->append('.');
52 $identifier->append('id', true);
53 self::assertSame('select.id', $identifier->toString());
54 $keyword = new \SqlSemantics\Statement\Writer();
55 $keyword->append('DEFAULT');
56 $keyword->append('.');
57 $keyword->append('id', true);
58 self::assertSame('DEFAULT .id', $keyword->toString());
59 }
60
61 public function testAppendKeepsFunctionAndIdentifierParenthesisBoundaries(): void
62 {
63 $function = new \SqlSemantics\Statement\Writer();
64 $function->append('COUNT');
65 $function->append('(');
66 self::assertSame('COUNT(', $function->toString());
67 $identifier = new \SqlSemantics\Statement\Writer();
68 $identifier->append('count', true);
69 $identifier->append('(');
70 self::assertSame('count (', $identifier->toString());
71 }
72
73 public function testAppendSeparatesOperatorsWithoutCreatingAComment(): void
74 {
75 $writer = new \SqlSemantics\Statement\Writer();
76 $writer->append('-');
77 $writer->append('-');
78 self::assertSame('- -', $writer->toString());
79 }
80
81 public function testToStringPreservesSeparateQuotedValues(): void
82 {
83 $writer = new \SqlSemantics\Statement\Writer();
84 $writer->append("'one'");
85 $writer->append("'two'");
86 self::assertSame("'one' 'two'", $writer->toString());
87 }
88
89 public function testAppendAttachesTheNextValueToAPrefixButSeparatesOperators(): void
90 {
91 $variable = new \SqlSemantics\Statement\Writer();
92 $variable->append('@', prefix: true);
93 $variable->append('@', prefix: true);
94 $variable->append('name', true);
95 self::assertSame('@@name', $variable->toString());
96 $operator = new \SqlSemantics\Statement\Writer();
97 $operator->append('@');
98 $operator->append('-');
99 $operator->append('1');
100 self::assertSame('@ - 1', $operator->toString());
101 }
102
103 public function testCommentsWritesEachCommentBeforeItsSymbolAndEndsALineComment(): void
104 {
105 $comments = new \SqlSemantics\Statement\Comments([0 => ['/* lead */'], 1 => ['-- one', '#two'], 2 => ['/* c */']]);
106 $writer = new \SqlSemantics\Statement\Writer();
107 $writer->comments($comments, 0);
108 $writer->append('SELECT');
109 $writer->comments($comments, 1);
110 $writer->append('COUNT');
111 $writer->comments($comments, 2);
112 $writer->append('(');
113 $writer->append('*');
114 $writer->append(')');
115 $writer->comments($comments, 3);
116 self::assertSame("/* lead */ SELECT -- one\n#two\nCOUNT /* c */ ( * )", $writer->toString());
117 }
118
119 public function testRenderWritesTheCommentsOfEveryValue(): void
120 {
121 $statement = (new \SqlSemantics\Facade\Semantics(\SqlSemantics\Platform\Sqlite\Dialect::Sqlite))->analyze("SELECT /* a */ foo -- b\n FROM items /* c */ WHERE foo = /* d */ 1");
122 self::assertSame("SELECT /* a */ foo -- b\nFROM items /* c */ WHERE foo = /* d */ 1", \SqlSemantics\Statement\Writer::render($statement->command));
123 }
124}
125