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