packages/sql-faker/src/MySql/Generation/Rewrite/Column/FieldLengthRule.php

1<?php
2
3declare(strict_types=1);
4
5namespace SqlFaker\MySql\Generation\Rewrite\Column;
6
7use SqlFaker\Generation\Token\RewriteRule;
8use SqlFaker\Generation\Token\TerminalSequence;
9
10/**
11 * create_field.cc/Create_field::init checks display widths after the scanner accepts field_length.
12 * @see https://github.com/mysql/mysql-server/blob/mysql-8.4.7/sql/create_field.cc
13 * @see https://github.com/mysql/mysql-server/blob/mysql-5.6.51/sql/field.cc
14 * @see https://github.com/mysql/mysql-server/blob/mysql-5.7.44/sql/field.cc
15 * @see https://github.com/mysql/mysql-server/blob/mysql-8.4.7/sql/field.cc
16 */
17final class FieldLengthRule implements RewriteRule
18{
19    /**
20     * Applies each type's width domain without changing ordinary numbers or YEAR's separate rule.
21     */
22    public function rewrite(TerminalSequence $sequence): TerminalSequence
23    {
24        return $this->width($this->precision($sequence));
25    }
26
27    /**
28     * Resolves precision and scale together so the digit count can depend on the selected scale.
29     */
30    public function precision(TerminalSequence $sequence): TerminalSequence
31    {
32        foreach ($sequence->occurrences('precision') as $id) {
33            $range = $sequence->range($id);
34            $type = $range === null ? null : $sequence->terminals[$range[0]]->ancestor('type');
35            $owner = $type === null ? null : $sequence->range($type);
36            if ($range === null || $owner === null || $range[1] - $range[0] !== 5) {
37                continue;
38            }
39            $decimal = in_array($sequence->nameAt($owner[0]), ['DECIMAL_SYM', 'NUMERIC_SYM', 'FIXED_SYM'], true);
40            foreach ([$range[0] + 1 => $decimal ? 'DECIMAL_DIGITS_NUMBER' : 'FLOAT_DIGITS_NUMBER', $range[0] + 3 => 'NUMERIC_SCALE_NUMBER'] as $index => $name) {
41                if ($sequence->nameAt($index) !== $name) {
42                    $sequence = $sequence->replace($index, 1, [
43                        $sequence->terminals[$index]->replaced($name, 'sql/create_field.cc:precision-and-scale'),
44                    ], 'sql/create_field.cc:precision-and-scale');
45                }
46            }
47        }
48        return $sequence;
49    }
50
51    /**
52     * Resolves the single width argument using its owning column type.
53     */
54    public function width(TerminalSequence $sequence): TerminalSequence
55    {
56        foreach ($sequence->occurrences('field_length') as $id) {
57            $range = $sequence->range($id);
58            $type = $range === null ? null : $sequence->terminals[$range[0]]->ancestor('type');
59            $owner = $type === null ? null : $sequence->range($type);
60            if ($range === null || $owner === null || $sequence->nameAt($owner[0]) === 'YEAR_SYM') {
61                continue;
62            }
63            $name = $sequence->nameAt($owner[0]);
64            $domain = match ($name) {
65                'INT_SYM', 'TINYINT_SYM', 'SMALLINT_SYM', 'MEDIUMINT_SYM', 'BIGINT_SYM',
66                'CHAR_SYM', 'NCHAR_SYM', 'NATIONAL_SYM', 'BINARY_SYM' => 'DISPLAY_WIDTH_NUMBER',
67                'BIT_SYM' => 'BIT_WIDTH_NUMBER',
68                'DECIMAL_SYM', 'NUMERIC_SYM', 'FIXED_SYM' => 'DECIMAL_PRECISION_NUMBER',
69                'FLOAT_SYM' => 'FLOAT_PRECISION_NUMBER',
70                'VARCHAR_SYM', 'NVARCHAR_SYM', 'VARBINARY_SYM' => 'VARCHAR_LENGTH_NUMBER',
71                default => 'FIELD_LENGTH_NUMBER',
72            };
73            if ($sequence->child($type, 'varchar') !== null || $sequence->child($type, 'nvarchar') !== null) {
74                $domain = 'VARCHAR_LENGTH_NUMBER';
75            }
76            if ($sequence->nameAt($range[0] + 1) === $domain) {
77                continue;
78            }
79            $sequence = $sequence->replace($range[0] + 1, 1, [
80                $sequence->terminals[$range[0] + 1]->replaced($domain, 'sql/create_field.cc:Create_field::init'),
81            ], 'sql/create_field.cc:Create_field::init');
82        }
83        return $sequence;
84    }
85}
86