1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace PhpMyAdmin\SqlParser\Components; |
||
6 | |||
7 | use PhpMyAdmin\SqlParser\Component; |
||
8 | use PhpMyAdmin\SqlParser\Parser; |
||
9 | use PhpMyAdmin\SqlParser\Parsers\Expressions; |
||
10 | use PhpMyAdmin\SqlParser\Parsers\OptionsArrays; |
||
11 | use PhpMyAdmin\SqlParser\TokensList; |
||
12 | |||
13 | use function implode; |
||
14 | use function trim; |
||
15 | |||
16 | /** |
||
17 | * `INTO` keyword parser. |
||
18 | */ |
||
19 | final class IntoKeyword implements Component |
||
20 | { |
||
21 | /** |
||
22 | * FIELDS/COLUMNS Options for `SELECT...INTO` statements. |
||
23 | */ |
||
24 | private const STATEMENT_FIELDS_OPTIONS = [ |
||
25 | 'TERMINATED BY' => [ |
||
26 | 1, |
||
27 | 'expr', |
||
28 | ], |
||
29 | 'OPTIONALLY' => 2, |
||
30 | 'ENCLOSED BY' => [ |
||
31 | 3, |
||
32 | 'expr', |
||
33 | ], |
||
34 | 'ESCAPED BY' => [ |
||
35 | 4, |
||
36 | 'expr', |
||
37 | ], |
||
38 | ]; |
||
39 | |||
40 | /** |
||
41 | * LINES Options for `SELECT...INTO` statements. |
||
42 | */ |
||
43 | private const STATEMENT_LINES_OPTIONS = [ |
||
44 | 'STARTING BY' => [ |
||
45 | 1, |
||
46 | 'expr', |
||
47 | ], |
||
48 | 'TERMINATED BY' => [ |
||
49 | 2, |
||
50 | 'expr', |
||
51 | ], |
||
52 | ]; |
||
53 | |||
54 | /** |
||
55 | * Type of target (OUTFILE or SYMBOL). |
||
56 | */ |
||
57 | public string|null $type = null; |
||
58 | |||
59 | /** |
||
60 | * The destination, which can be a table or a file. |
||
61 | */ |
||
62 | public string|Expression|null $dest = null; |
||
63 | |||
64 | /** |
||
65 | * The name of the columns. |
||
66 | * |
||
67 | * @var string[]|null |
||
68 | */ |
||
69 | public array|null $columns = null; |
||
70 | |||
71 | /** |
||
72 | * The values to be selected into (SELECT .. INTO @var1). |
||
73 | * |
||
74 | * @var Expression[]|null |
||
75 | */ |
||
76 | public array|null $values = null; |
||
77 | |||
78 | /** |
||
79 | * Options for FIELDS/COLUMNS keyword. |
||
80 | * |
||
81 | * @see IntoKeyword::STATEMENT_FIELDS_OPTIONS |
||
82 | */ |
||
83 | public OptionsArray|null $fieldsOptions = null; |
||
84 | |||
85 | /** |
||
86 | * Whether to use `FIELDS` or `COLUMNS` while building. |
||
87 | */ |
||
88 | public bool|null $fieldsKeyword = null; |
||
89 | |||
90 | /** |
||
91 | * Options for OPTIONS keyword. |
||
92 | * |
||
93 | * @see IntoKeyword::STATEMENT_LINES_OPTIONS |
||
94 | */ |
||
95 | public OptionsArray|null $linesOptions = null; |
||
96 | |||
97 | /** |
||
98 | * @param string|null $type type of destination (may be OUTFILE) |
||
99 | * @param string|Expression|null $dest actual destination |
||
100 | * @param string[]|null $columns column list of destination |
||
101 | * @param Expression[]|null $values selected fields |
||
102 | * @param OptionsArray|null $fieldsOptions options for FIELDS/COLUMNS keyword |
||
103 | * @param bool|null $fieldsKeyword options for OPTIONS keyword |
||
104 | */ |
||
105 | 106 | public function __construct( |
|
106 | string|null $type = null, |
||
107 | string|Expression|null $dest = null, |
||
108 | array|null $columns = null, |
||
109 | array|null $values = null, |
||
110 | OptionsArray|null $fieldsOptions = null, |
||
111 | bool|null $fieldsKeyword = null, |
||
112 | ) { |
||
113 | 106 | $this->type = $type; |
|
114 | 106 | $this->dest = $dest; |
|
115 | 106 | $this->columns = $columns; |
|
116 | 106 | $this->values = $values; |
|
117 | 106 | $this->fieldsOptions = $fieldsOptions; |
|
118 | 106 | $this->fieldsKeyword = $fieldsKeyword; |
|
119 | } |
||
120 | |||
121 | /** |
||
122 | * @param Parser $parser The parser |
||
123 | * @param TokensList $list A token list |
||
124 | * @param string $keyword The keyword |
||
125 | */ |
||
126 | 10 | public function parseFileOptions(Parser $parser, TokensList $list, string $keyword = 'FIELDS'): void |
|
127 | { |
||
128 | 10 | ++$list->idx; |
|
129 | |||
130 | 10 | if ($keyword === 'FIELDS' || $keyword === 'COLUMNS') { |
|
131 | // parse field options |
||
132 | 10 | $this->fieldsOptions = OptionsArrays::parse($parser, $list, self::STATEMENT_FIELDS_OPTIONS); |
|
133 | |||
134 | 10 | $this->fieldsKeyword = ($keyword === 'FIELDS'); |
|
135 | } else { |
||
136 | // parse line options |
||
137 | 6 | $this->linesOptions = OptionsArrays::parse($parser, $list, self::STATEMENT_LINES_OPTIONS); |
|
138 | } |
||
139 | } |
||
140 | |||
141 | 18 | public function build(): string |
|
142 | { |
||
143 | 18 | if ($this->dest instanceof Expression) { |
|
144 | 10 | $columns = ! empty($this->columns) ? '(`' . implode('`, `', $this->columns) . '`)' : ''; |
|
145 | |||
146 | 10 | return $this->dest . $columns; |
|
147 | } |
||
148 | |||
149 | 8 | if (isset($this->values)) { |
|
150 | 4 | return Expressions::buildAll($this->values); |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
151 | } |
||
152 | |||
153 | 4 | $ret = 'OUTFILE "' . $this->dest . '"'; |
|
154 | |||
155 | 4 | $fieldsOptionsString = $this->fieldsOptions?->build() ?? ''; |
|
156 | 4 | if (trim($fieldsOptionsString) !== '') { |
|
157 | 2 | $ret .= $this->fieldsKeyword ? ' FIELDS' : ' COLUMNS'; |
|
158 | 2 | $ret .= ' ' . $fieldsOptionsString; |
|
159 | } |
||
160 | |||
161 | 4 | $linesOptionsString = $this->linesOptions?->build() ?? ''; |
|
162 | 4 | if (trim($linesOptionsString) !== '') { |
|
163 | 2 | $ret .= ' LINES ' . $linesOptionsString; |
|
164 | } |
||
165 | |||
166 | 4 | return $ret; |
|
167 | } |
||
168 | |||
169 | 10 | public function __toString(): string |
|
170 | { |
||
171 | 10 | return $this->build(); |
|
172 | } |
||
173 | } |
||
174 |