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