packages/ztd-query-core/tests/Unit/Sql/Reader/SqlDelimitedReaderTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql\Reader;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use Tests\Fake\FakeSqlLexerProfiles;
11use ZtdQuery\Sql\LexicalDelimiters;
12use ZtdQuery\Sql\LexicalPattern;
13use ZtdQuery\Sql\Profile\SqlCommentProfile;
14use ZtdQuery\Sql\Profile\SqlParameterProfile;
15use ZtdQuery\Sql\Profile\SqlQuoteProfile;
16use ZtdQuery\Sql\Profile\SqlSymbolProfile;
17use ZtdQuery\Sql\Reader\SqlDelimitedReader;
18use ZtdQuery\Sql\Reader\SqlLexeme;
19use ZtdQuery\Sql\SqlLexerProfile;
20use ZtdQuery\Sql\SqlTokenKind;
21
22#[CoversClass(SqlDelimitedReader::class)]
23#[UsesClass(SqlLexeme::class)]
24#[UsesClass(SqlLexerProfile::class)]
25#[UsesClass(LexicalDelimiters::class)]
26#[UsesClass(LexicalPattern::class)]
27#[UsesClass(SqlCommentProfile::class)]
28#[UsesClass(SqlParameterProfile::class)]
29#[UsesClass(SqlQuoteProfile::class)]
30#[UsesClass(SqlSymbolProfile::class)]
31final class SqlDelimitedReaderTest extends TestCase
32{
33 public function testReadAtAnswersNothingWhereNoDelimiterOpens(): void
34 {
35 self::assertNull((new SqlDelimitedReader())->readAt('SELECT', 0, FakeSqlLexerProfiles::standard()));
36 }
37
38 public function testReadAtReadsAQuotedRunOfTextAsAString(): void
39 {
40 $lexeme = (new SqlDelimitedReader())->readAt("'a b' x", 0, FakeSqlLexerProfiles::standard());
41
42 self::assertSame([SqlTokenKind::String, 5], [$lexeme?->kind, $lexeme?->end]);
43 }
44
45 public function testReadAtReadsAQuotedNameAsAnIdentifierRatherThanAString(): void
46 {
47 $lexeme = (new SqlDelimitedReader())->readAt('"a b" x', 0, FakeSqlLexerProfiles::standard());
48
49 self::assertSame([SqlTokenKind::QuotedIdentifier, 5], [$lexeme?->kind, $lexeme?->end]);
50 }
51
52 public function testReadAtReadsADollarQuotedRunUpToTheTagThatOpenedIt(): void
53 {
54 $lexeme = (new SqlDelimitedReader())->readAt('$t$a b$t$x', 0, FakeSqlLexerProfiles::allCapabilities());
55
56 self::assertSame([SqlTokenKind::String, 9], [$lexeme?->kind, $lexeme?->end]);
57 }
58
59 public function testReadAtReadsADollarQuotedRunToTheEndOfAStatementThatNeverCloses(): void
60 {
61 $lexeme = (new SqlDelimitedReader())->readAt('$t$a b', 0, FakeSqlLexerProfiles::allCapabilities());
62
63 self::assertSame([SqlTokenKind::String, 6], [$lexeme?->kind, $lexeme?->end]);
64 }
65
66 public function testEndOfDelimitedAnswersWhereTheClosingDelimiterLeftOff(): void
67 {
68 self::assertSame(3, (new SqlDelimitedReader())->endOfDelimited("'a'x", 0, "'", "'", false));
69 }
70
71 public function testEndOfDelimitedReadsPastADoubledDelimiterBecauseItWritesTheDelimiter(): void
72 {
73 self::assertSame(5, (new SqlDelimitedReader())->endOfDelimited("'a'''", 0, "'", "'", false));
74 }
75
76 public function testEndOfDelimitedReadsPastAnEscapedDelimiterWhereBackslashesEscape(): void
77 {
78 self::assertSame(5, (new SqlDelimitedReader())->endOfDelimited("'a\\''", 0, "'", "'", true));
79 }
80
81 public function testEndOfDelimitedStopsAtTheEndOfAStatementThatNeverClosedTheRun(): void
82 {
83 self::assertSame(2, (new SqlDelimitedReader())->endOfDelimited("'a", 0, "'", "'", false));
84 }
85}
86