packages/sql-faker/src/PostgreSql/Generation/Lexeme/HashBoundLexemeGenerator.php
1<?php
2
3declare(strict_types=1);
4
5namespace SqlFaker\PostgreSql\Generation\Lexeme;
6
7use Override;
8use SqlFaker\Generation\Lexeme\Lexeme;
9use SqlFaker\Generation\Lexeme\LexemeCandidates;
10use SqlFaker\Generation\Lexeme\LexemeGenerator;
11use SqlFaker\Generation\Lexeme\LexemeInput;
12use SqlFaker\Generation\Lexeme\LexemeSequence;
13
14/**
15 * Implements the modulus/remainder name checks in PostgreSQL 17.2 gram.y/PartitionBoundSpec.
16 * @see https://github.com/postgres/postgres/blob/REL_17_2/src/backend/parser/gram.y
17 */
18final class HashBoundLexemeGenerator implements LexemeGenerator
19{
20 /**
21 * Both orders remain reachable; a previously selected name in the same bound is not repeated.
22 */
23 #[Override]
24 public function generate(LexemeInput $input): ?LexemeCandidates
25 {
26 if ($input->terminal()->name !== 'HASH_BOUND_NAME') {
27 return null;
28 }
29 $scope = $input->terminal()->ancestor('PartitionBoundSpec');
30 $taken = [];
31 foreach ($input->right->parts as $part) {
32 if ($part->lexeme->origin->name === 'HASH_BOUND_NAME'
33 && $part->lexeme->origin->ancestor('PartitionBoundSpec') === $scope) {
34 $taken[] = strtolower($part->lexeme->text);
35 }
36 }
37 $candidates = [];
38 foreach ($input->requested === null ? ['modulus', 'remainder'] : [$input->requested] as $word) {
39 $name = strtolower($word);
40 if (in_array($name, ['modulus', 'remainder'], true) && !in_array($name, $taken, true)) {
41 $candidates[] = new LexemeSequence([
42 new Lexeme($word, 'identifier', $input->terminal(), 'gram.y:PartitionBoundSpec'),
43 ], 'postgresql.hash-bound:' . $word);
44 }
45 }
46 return LexemeCandidates::of(...$candidates);
47 }
48}
49