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