Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
91 / 91 |
|
100.00% |
18 / 18 |
CRAP | |
100.00% |
1 / 1 |
LoadData | |
100.00% |
91 / 91 |
|
100.00% |
18 / 18 |
40 | |
100.00% |
1 / 1 |
renderOptions | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
5 | |||
infile | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
renderInfile | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
intoTable | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
renderIntoTable | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
charset | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
renderCharset | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
columnsTerminatedBy | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
columnsEnclosedBy | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
columnsEscapedBy | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
renderColumns | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
8 | |||
linesStartingBy | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
linesTerminatedBy | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
renderLines | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
ignoreLines | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
renderIgnoreLines | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
sql | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
run | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php declare(strict_types=1); |
2 | /* |
3 | * This file is part of Aplus Framework Database Library. |
4 | * |
5 | * (c) Natan Felles <natanfelles@gmail.com> |
6 | * |
7 | * For the full copyright and license information, please view the LICENSE |
8 | * file that was distributed with this source code. |
9 | */ |
10 | namespace Framework\Database\Manipulation; |
11 | |
12 | use InvalidArgumentException; |
13 | use LogicException; |
14 | |
15 | /** |
16 | * Class LoadData. |
17 | * |
18 | * @see https://mariadb.com/kb/en/load-data-infile/ |
19 | * |
20 | * @package database |
21 | */ |
22 | class LoadData extends Statement |
23 | { |
24 | use Traits\Set; |
25 | |
26 | /** |
27 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/ |
28 | * |
29 | * @var string |
30 | */ |
31 | public const OPT_LOW_PRIORITY = 'LOW_PRIORITY'; |
32 | /** |
33 | * @see https://mariadb.com/kb/en/load-data-infile/#priority-and-concurrency |
34 | * |
35 | * @var string |
36 | */ |
37 | public const OPT_CONCURRENT = 'CONCURRENT'; |
38 | /** |
39 | * @see https://mariadb.com/kb/en/load-data-infile/#load-data-local-infile |
40 | * |
41 | * @var string |
42 | */ |
43 | public const OPT_LOCAL = 'LOCAL'; |
44 | |
45 | protected function renderOptions() : ?string |
46 | { |
47 | if ( ! $this->hasOptions()) { |
48 | return null; |
49 | } |
50 | $options = $this->sql['options']; |
51 | foreach ($options as &$option) { |
52 | $input = $option; |
53 | $option = \strtoupper($option); |
54 | if ( ! \in_array($option, [ |
55 | static::OPT_LOW_PRIORITY, |
56 | static::OPT_CONCURRENT, |
57 | static::OPT_LOCAL, |
58 | ], true)) { |
59 | throw new InvalidArgumentException("Invalid option: {$input}"); |
60 | } |
61 | } |
62 | unset($option); |
63 | $intersection = \array_intersect( |
64 | $options, |
65 | [static::OPT_LOW_PRIORITY, static::OPT_CONCURRENT] |
66 | ); |
67 | if (\count($intersection) > 1) { |
68 | throw new LogicException('Options LOW_PRIORITY and CONCURRENT can not be used together'); |
69 | } |
70 | return \implode(' ', $options); |
71 | } |
72 | |
73 | /** |
74 | * @param string $filename |
75 | * |
76 | * @return static |
77 | */ |
78 | public function infile(string $filename) : static |
79 | { |
80 | $this->sql['infile'] = $filename; |
81 | return $this; |
82 | } |
83 | |
84 | protected function renderInfile() : string |
85 | { |
86 | if (empty($this->sql['infile'])) { |
87 | throw new LogicException('INFILE statement is required'); |
88 | } |
89 | $filename = $this->database->quote($this->sql['infile']); |
90 | return " INFILE {$filename}"; |
91 | } |
92 | |
93 | /** |
94 | * @param string $table |
95 | * |
96 | * @return static |
97 | */ |
98 | public function intoTable(string $table) : static |
99 | { |
100 | $this->sql['table'] = $table; |
101 | return $this; |
102 | } |
103 | |
104 | protected function renderIntoTable() : string |
105 | { |
106 | if (empty($this->sql['table'])) { |
107 | throw new LogicException('Table is required'); |
108 | } |
109 | return ' INTO TABLE ' . $this->database->protectIdentifier($this->sql['table']); |
110 | } |
111 | |
112 | /** |
113 | * @param string $charset |
114 | * |
115 | * @see https://mariadb.com/kb/en/supported-character-sets-and-collations/ |
116 | * |
117 | * @return static |
118 | */ |
119 | public function charset(string $charset) : static |
120 | { |
121 | $this->sql['charset'] = $charset; |
122 | return $this; |
123 | } |
124 | |
125 | protected function renderCharset() : ?string |
126 | { |
127 | if ( ! isset($this->sql['charset'])) { |
128 | return null; |
129 | } |
130 | return " CHARACTER SET {$this->sql['charset']}"; |
131 | } |
132 | |
133 | /** |
134 | * @param string $str |
135 | * |
136 | * @return static |
137 | */ |
138 | public function columnsTerminatedBy(string $str) : static |
139 | { |
140 | $this->sql['columns_terminated_by'] = $this->database->quote($str); |
141 | return $this; |
142 | } |
143 | |
144 | /** |
145 | * @param string $char |
146 | * @param bool $optionally |
147 | * |
148 | * @return static |
149 | */ |
150 | public function columnsEnclosedBy(string $char, bool $optionally = false) : static |
151 | { |
152 | $this->sql['columns_enclosed_by'] = $this->database->quote($char); |
153 | $this->sql['columns_enclosed_by_opt'] = $optionally; |
154 | return $this; |
155 | } |
156 | |
157 | /** |
158 | * @param string $char |
159 | * |
160 | * @return static |
161 | */ |
162 | public function columnsEscapedBy(string $char) : static |
163 | { |
164 | $this->sql['columns_escaped_by'] = $this->database->quote($char); |
165 | return $this; |
166 | } |
167 | |
168 | protected function renderColumns() : ?string |
169 | { |
170 | if ( ! isset($this->sql['columns_terminated_by']) |
171 | && ! isset($this->sql['columns_enclosed_by']) |
172 | && ! isset($this->sql['columns_escaped_by'])) { |
173 | return null; |
174 | } |
175 | $part = ' COLUMNS' . \PHP_EOL; |
176 | if (isset($this->sql['columns_terminated_by'])) { |
177 | $part .= ' TERMINATED BY ' . $this->sql['columns_terminated_by'] . \PHP_EOL; |
178 | } |
179 | if (isset($this->sql['columns_enclosed_by'])) { |
180 | if (isset($this->sql['columns_enclosed_by_opt'])) { |
181 | $part .= ' OPTIONALLY'; |
182 | } |
183 | $part .= ' ENCLOSED BY ' . $this->sql['columns_enclosed_by'] . \PHP_EOL; |
184 | } |
185 | if (isset($this->sql['columns_escaped_by'])) { |
186 | $part .= ' ESCAPED BY ' . $this->sql['columns_escaped_by'] . \PHP_EOL; |
187 | } |
188 | return $part; |
189 | } |
190 | |
191 | /** |
192 | * @param string $str |
193 | * |
194 | * @return static |
195 | */ |
196 | public function linesStartingBy(string $str) : static |
197 | { |
198 | $this->sql['lines_starting_by'] = $this->database->quote($str); |
199 | return $this; |
200 | } |
201 | |
202 | /** |
203 | * @param string $str |
204 | * |
205 | * @return static |
206 | */ |
207 | public function linesTerminatedBy(string $str) : static |
208 | { |
209 | $this->sql['lines_terminated_by'] = $this->database->quote($str); |
210 | return $this; |
211 | } |
212 | |
213 | protected function renderLines() : ?string |
214 | { |
215 | if ( ! isset($this->sql['lines_starting_by']) |
216 | && ! isset($this->sql['lines_terminated_by'])) { |
217 | return null; |
218 | } |
219 | $part = ' LINES' . \PHP_EOL; |
220 | if (isset($this->sql['lines_starting_by'])) { |
221 | $part .= ' STARTING BY ' . $this->sql['lines_starting_by'] . \PHP_EOL; |
222 | } |
223 | if (isset($this->sql['lines_terminated_by'])) { |
224 | $part .= ' TERMINATED BY ' . $this->sql['lines_terminated_by'] . \PHP_EOL; |
225 | } |
226 | return $part; |
227 | } |
228 | |
229 | /** |
230 | * @param int $number |
231 | * |
232 | * @return static |
233 | */ |
234 | public function ignoreLines(int $number) : static |
235 | { |
236 | $this->sql['ignore_lines'] = $number; |
237 | return $this; |
238 | } |
239 | |
240 | protected function renderIgnoreLines() : ?string |
241 | { |
242 | if ( ! isset($this->sql['ignore_lines'])) { |
243 | return null; |
244 | } |
245 | return " IGNORE {$this->sql['ignore_lines']} LINES"; |
246 | } |
247 | |
248 | /** |
249 | * @return string |
250 | */ |
251 | public function sql() : string |
252 | { |
253 | $sql = 'LOAD DATA' . \PHP_EOL; |
254 | $part = $this->renderOptions(); |
255 | if ($part) { |
256 | $sql .= $part . \PHP_EOL; |
257 | } |
258 | $sql .= $this->renderInfile() . \PHP_EOL; |
259 | $sql .= $this->renderIntoTable() . \PHP_EOL; |
260 | $part = $this->renderCharset(); |
261 | if ($part) { |
262 | $sql .= $part . \PHP_EOL; |
263 | } |
264 | $sql .= $this->renderColumns(); |
265 | $sql .= $this->renderLines(); |
266 | $part = $this->renderIgnoreLines(); |
267 | if ($part) { |
268 | $sql .= $part . \PHP_EOL; |
269 | } |
270 | return $sql; |
271 | } |
272 | |
273 | public function run() : int|string |
274 | { |
275 | return $this->database->exec($this->sql()); |
276 | } |
277 | } |