packages/sql-faker/tests/Unit/PostgreSql/Generation/Rewrite/Name/FunctionNameRuleTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\PostgreSql\Generation\Rewrite\Name;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Generation\Token\ProductionOccurrence;
12use SqlFaker\Generation\Token\TerminalOccurrence;
13use SqlFaker\Generation\Token\TerminalSequence;
14use SqlFaker\PostgreSql\Generation\Rewrite\Name\FunctionNameRule;
15
16#[CoversClass(FunctionNameRule::class)]
17#[UsesClass(ProductionOccurrence::class)]
18#[UsesClass(TerminalOccurrence::class)]
19#[UsesClass(TerminalSequence::class)]
20final class FunctionNameRuleTest extends TestCase
21{
22    public function testRewriteKeepsFunctionQualificationAndColumnIndirection(): void
23    {
24        $function = new TerminalOccurrence('[', 4, [0, 1, 2, 3], ['root', 'func_name', 'indirection', 'indirection_el']);
25        $column = new TerminalOccurrence('[', 9, [0, 6, 7, 8], ['root', 'columnref', 'indirection', 'indirection_el']);
26        $input = new TerminalSequence([$function, $column], [$function, $column], [], [
27            new ProductionOccurrence(3, 2, 'indirection_el', 2), new ProductionOccurrence(8, 7, 'indirection_el', 2),
28        ]);
29        $result = (new FunctionNameRule())->rewrite($input);
30        self::assertSame(['.', 'IDENT', '['], $result->names());
31        self::assertSame($column, $result->terminals[2]);
32        self::assertSame($function->ancestors, $result->terminals[0]->ancestors);
33        self::assertSame($result, (new FunctionNameRule())->rewrite($result));
34        self::assertSame($input->original, $result->original);
35        self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
36    }
37
38    public function testIsFunctionNameRejectsArgumentExpressionsAndRecognizesRecursiveIndirection(): void
39    {
40        $rule = new FunctionNameRule();
41        self::assertTrue($rule->isFunctionName(['func_name', 'indirection', 'indirection', 'indirection_el']));
42        self::assertTrue($rule->isFunctionName(['function_with_argtypes', 'indirection', 'indirection_el']));
43        self::assertFalse($rule->isFunctionName(['function_with_argtypes', 'func_args', 'columnref', 'indirection', 'indirection_el']));
44        self::assertFalse($rule->isFunctionName([]));
45        self::assertTrue($rule->isFunctionName(['func_name', 'indirection_el']));
46        self::assertFalse($rule->isFunctionName(['func_name', 'columnref', 'indirection_el']));
47    }
48
49    public function testRewriteReplacesAStarBetweenUnaffectedExpressionsAndKeepsTrailingOutput(): void
50    {
51        $prefix = new TerminalOccurrence('SELECT', 10);
52        $expression = new TerminalOccurrence('.', 11, [0, 1], ['columnref', 'indirection_el']);
53        $star = new TerminalOccurrence('.', 12, [2, 3], ['func_name', 'indirection_el']);
54        $suffix = new TerminalOccurrence('*', 13, [2, 3], ['func_name', 'indirection_el']);
55        $tail = new TerminalOccurrence('TAIL', 14);
56        $input = new TerminalSequence([$prefix, $expression, $star, $suffix, $tail], [], [], [
57            new ProductionOccurrence(0, null, 'columnref', 0), new ProductionOccurrence(1, 0, 'indirection_el', 0),
58            new ProductionOccurrence(2, null, 'func_name', 0), new ProductionOccurrence(3, 2, 'indirection_el', 0),
59            new ProductionOccurrence(4, 2, 'indirection_el', 0),
60        ]);
61        $result = (new FunctionNameRule())->rewrite($input);
62        self::assertSame(['SELECT', '.', '.', 'IDENT', 'TAIL'], $result->names());
63        self::assertSame($expression, $result->terminals[1]);
64        self::assertSame($tail, $result->terminals[4]);
65        self::assertSame($star->ancestors, $result->terminals[3]->ancestors);
66    }
67
68    #[DataProvider('providerFunctionScopes')]
69    public function testRewriteLimitsFunctionNamesToCatalogSchemaAndFunction(string $scope): void
70    {
71        $base = new TerminalOccurrence('IDENT', 10, [0], [$scope]);
72        $first = new TerminalOccurrence('.', 11, [0, 1, 2], [$scope, 'indirection', 'indirection_el']);
73        $firstName = new TerminalOccurrence('IDENT', 12, [0, 1, 2], [$scope, 'indirection', 'indirection_el']);
74        $second = new TerminalOccurrence('.', 13, [0, 1, 3], [$scope, 'indirection', 'indirection_el']);
75        $secondName = new TerminalOccurrence('IDENT', 14, [0, 1, 3], [$scope, 'indirection', 'indirection_el']);
76        $third = new TerminalOccurrence('.', 15, [0, 1, 4], [$scope, 'indirection', 'indirection_el']);
77        $thirdName = new TerminalOccurrence('IDENT', 16, [0, 1, 4], [$scope, 'indirection', 'indirection_el']);
78        $terminals = [$base, $first, $firstName, $second, $secondName, $third, $thirdName];
79        $input = new TerminalSequence($terminals, $terminals, [], [
80            new ProductionOccurrence(0, null, $scope, 0), new ProductionOccurrence(1, 0, 'indirection', 0),
81            new ProductionOccurrence(2, 1, 'indirection_el', 0), new ProductionOccurrence(3, 1, 'indirection_el', 0),
82            new ProductionOccurrence(4, 1, 'indirection_el', 0),
83        ]);
84        $rule = new FunctionNameRule();
85        $result = $rule->rewrite($input);
86        self::assertSame([$base, $first, $firstName, $second, $secondName], $result->terminals);
87        self::assertSame($input->original, $result->original);
88        self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
89        self::assertSame($input->productions, $result->productions);
90        self::assertSame($result, $rule->rewrite($result));
91    }
92
93    /**
94     * @return iterable<string, array{string}>
95     */
96    public static function providerFunctionScopes(): iterable
97    {
98        yield 'call' => ['func_name'];
99        yield 'signature' => ['function_with_argtypes'];
100    }
101}
102