packages/ztd-query-core/tests/Unit/Sql/Profile/SqlQuoteProfileTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql\Profile;
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\SqlQuoteProfile;
14use ZtdQuery\Sql\Profile\SqlSymbolProfile;
15
16#[CoversClass(SqlQuoteProfile::class)]
17#[UsesClass(SqlSymbolProfile::class)]
18#[UsesClass(LexicalDelimiters::class)]
19#[UsesClass(LexicalPattern::class)]
20final class SqlQuoteProfileTest extends TestCase
21{
22 public function testStringQuoteClosingAnswersWhatClosesAStringThatQuoteOpened(): void
23 {
24 $profile = FakeSqlLexerProfiles::quotes(stringQuotePairs: ['<<' => '>>']);
25
26 self::assertSame(['>>', null], [$profile->stringQuoteClosing('<<'), $profile->stringQuoteClosing('"')]);
27 }
28
29 public function testIdentifierQuoteClosingAnswersWhatClosesANameThatQuoteOpened(): void
30 {
31 $profile = FakeSqlLexerProfiles::quotes(identifierQuotePairs: ['[' => ']']);
32
33 self::assertSame([']', null], [$profile->identifierQuoteClosing('['), $profile->identifierQuoteClosing('`')]);
34 }
35
36 public function testUnquoteIdentifierReadsADoubledQuoteAsTheQuoteItself(): void
37 {
38 self::assertSame('a"b', FakeSqlLexerProfiles::quotes()->unquoteIdentifier('"a""b"'));
39 }
40
41 public function testUnquoteIdentifierLeavesANameThatWasNeverQuotedAsItStands(): void
42 {
43 self::assertSame('a', FakeSqlLexerProfiles::quotes()->unquoteIdentifier('a'));
44 }
45
46 public function testQuotedIdentifierValueAnswersNothingForANameThatWasNeverQuoted(): void
47 {
48 self::assertNull(FakeSqlLexerProfiles::quotes()->quotedIdentifierValue('a'));
49 }
50
51 public function testQuotedIdentifierValueAnswersNothingForANameThatWasNeverClosed(): void
52 {
53 self::assertNull(FakeSqlLexerProfiles::quotes()->quotedIdentifierValue('"a'));
54 }
55
56 public function testQuotedIdentifierValueReadsADoubledQuoteAsTheQuoteItself(): void
57 {
58 self::assertSame('a"b', FakeSqlLexerProfiles::quotes()->quotedIdentifierValue('"a""b"'));
59 }
60
61 public function testDollarQuoteDelimiterAtAnswersTheTagThatOpensARun(): void
62 {
63 $profile = FakeSqlLexerProfiles::quotes(dollarQuoteDelimiterPattern: '/^\$[a-z]*\$/');
64
65 self::assertSame(['$t$', null], [$profile->dollarQuoteDelimiterAt('$t$a', 0), $profile->dollarQuoteDelimiterAt('a', 0)]);
66 }
67
68 public function testStringUsesBackslashEscapesWhereTheDialectSaysSoOfEveryString(): void
69 {
70 $profile = FakeSqlLexerProfiles::quotes(backslashEscapedStrings: true);
71
72 self::assertTrue($profile->stringUsesBackslashEscapes("'a'", 0, FakeSqlLexerProfiles::symbols()));
73 }
74
75 public function testStringUsesBackslashEscapesOnlyWhereThePrefixIsNotTheTailOfAName(): void
76 {
77 $profile = FakeSqlLexerProfiles::quotes(backslashEscapedStringPrefixes: ['E']);
78 $symbols = FakeSqlLexerProfiles::symbols();
79
80 self::assertSame(
81 [true, false, false],
82 [
83 $profile->stringUsesBackslashEscapes("E'a'", 1, $symbols),
84 $profile->stringUsesBackslashEscapes("xE'a'", 2, $symbols),
85 $profile->stringUsesBackslashEscapes("'a'", 0, $symbols),
86 ],
87 );
88 }
89}
90