Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.87% |
56 / 71 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
GrammarAbstract | |
78.87% |
56 / 71 |
|
37.50% |
3 / 8 |
46.55 | |
0.00% |
0 / 1 |
setDateTimeFormat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
compileQuery | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
compilePostQuerys | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
compileComponents | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getDateFormat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expressionizeTableColumn | |
54.55% |
6 / 11 |
|
0.00% |
0 / 1 |
14.01 | |||
compileSystem | |
89.47% |
17 / 19 |
|
0.00% |
0 / 1 |
5.03 | |||
compileValue | |
80.65% |
25 / 31 |
|
0.00% |
0 / 1 |
16.63 | |||
compileColumnQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\DataStorage\Database |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\DataStorage\Database; |
16 | |
17 | use phpOMS\Contract\SerializableInterface; |
18 | use phpOMS\DataStorage\Database\Query\Column; |
19 | use phpOMS\DataStorage\Database\Query\Parameter; |
20 | |
21 | /** |
22 | * Grammar. |
23 | * |
24 | * @package phpOMS\DataStorage\Database |
25 | * @license OMS License 2.0 |
26 | * @link https://jingga.app |
27 | * @since 1.0.0 |
28 | */ |
29 | abstract class GrammarAbstract |
30 | { |
31 | /** |
32 | * Comment style. |
33 | * |
34 | * @var string |
35 | * @since 1.0.0 |
36 | */ |
37 | public string $comment = '--'; |
38 | |
39 | /** |
40 | * String quotes style. |
41 | * |
42 | * @var string |
43 | * @since 1.0.0 |
44 | */ |
45 | public string $valueQuotes = '\''; |
46 | |
47 | /** |
48 | * System identifier. |
49 | * |
50 | * @var string |
51 | * @since 1.0.0 |
52 | */ |
53 | public string $systemIdentifierStart = '"'; |
54 | |
55 | /** |
56 | * System identifier. |
57 | * |
58 | * @var string |
59 | * @since 1.0.0 |
60 | */ |
61 | public string $systemIdentifierEnd = '"'; |
62 | |
63 | /** |
64 | * And operator. |
65 | * |
66 | * @var string |
67 | * @since 1.0.0 |
68 | */ |
69 | public string $and = 'AND'; |
70 | |
71 | /** |
72 | * Or operator. |
73 | * |
74 | * @var string |
75 | * @since 1.0.0 |
76 | */ |
77 | public string $or = 'OR'; |
78 | |
79 | /** |
80 | * Special keywords. |
81 | * |
82 | * @var string[] |
83 | * @since 1.0.0 |
84 | */ |
85 | public array $specialKeywords = [ |
86 | 'COUNT(', |
87 | 'MAX(', |
88 | 'MIN(', |
89 | 'SUM(', |
90 | 'DATE(', |
91 | 'YEAR(', |
92 | 'MONTH(', |
93 | ]; |
94 | |
95 | /** |
96 | * Datetime format. |
97 | * |
98 | * @var string |
99 | * @since 1.0.0 |
100 | */ |
101 | public string $datetimeFormat = 'Y-m-d H:i:s'; |
102 | |
103 | /** |
104 | * Set the datetime format |
105 | * |
106 | * @param string $format Datetime format |
107 | * |
108 | * @return void |
109 | * |
110 | * @since 1.0.0 |
111 | */ |
112 | public function setDateTimeFormat(string $format) : void |
113 | { |
114 | $this->datetimeFormat = $format; |
115 | } |
116 | |
117 | /** |
118 | * Compile to query. |
119 | * |
120 | * @param BuilderAbstract $query Builder |
121 | * |
122 | * @return string |
123 | * |
124 | * @since 1.0.0 |
125 | */ |
126 | public function compileQuery(BuilderAbstract $query) : string |
127 | { |
128 | $components = $this->compileComponents($query); |
129 | $queryString = ''; |
130 | |
131 | foreach ($components as $component) { |
132 | if ($component !== '') { |
133 | $queryString .= $component . ' '; |
134 | } |
135 | } |
136 | |
137 | return \substr($queryString, 0, -1) . ';'; |
138 | } |
139 | |
140 | /** |
141 | * Compile post querys. |
142 | * |
143 | * These are queries, which should be run after the main query (e.g. table alters, trigger definitions etc.) |
144 | * |
145 | * @param BuilderAbstract $query Builder |
146 | * |
147 | * @return string[] |
148 | * |
149 | * @since 1.0.0 |
150 | */ |
151 | public function compilePostQuerys(BuilderAbstract $query) : array |
152 | { |
153 | return []; |
154 | } |
155 | |
156 | /** |
157 | * Compile components. |
158 | * |
159 | * @param BuilderAbstract $query Builder |
160 | * |
161 | * @return string[] |
162 | * |
163 | * @throws \InvalidArgumentException |
164 | * |
165 | * @since 1.0.0 |
166 | */ |
167 | abstract protected function compileComponents(BuilderAbstract $query) : array; |
168 | |
169 | /** |
170 | * Get date format. |
171 | * |
172 | * @return string |
173 | * |
174 | * @since 1.0.0 |
175 | */ |
176 | public function getDateFormat() : string |
177 | { |
178 | return 'Y-m-d H:i:s'; |
179 | } |
180 | |
181 | /** |
182 | * Expressionize elements. |
183 | * |
184 | * @param array $elements Elements |
185 | * @param bool $column Is column? |
186 | * |
187 | * @return string |
188 | * |
189 | * @throws \InvalidArgumentException |
190 | * |
191 | * @since 1.0.0 |
192 | */ |
193 | protected function expressionizeTableColumn(array $elements, bool $column = true) : string |
194 | { |
195 | $expression = ''; |
196 | |
197 | foreach ($elements as $key => $element) { |
198 | if (\is_string($element)) { |
199 | $expression .= $this->compileSystem($element) |
200 | . (\is_string($key) ? ' as ' . $key : '') . ', '; |
201 | } elseif ($element instanceof \Closure) { |
202 | $expression .= $element() . (\is_string($key) ? ' as ' . $key : '') . ', '; |
203 | } elseif ($element instanceof BuilderAbstract) { |
204 | $expression .= $element->toSql() . (\is_string($key) ? ' as ' . $key : '') . ', '; |
205 | } else { |
206 | throw new \InvalidArgumentException(); |
207 | } |
208 | } |
209 | |
210 | return \rtrim($expression, ', '); |
211 | } |
212 | |
213 | /** |
214 | * Compile system. |
215 | * |
216 | * A system is a table, a sub query or special keyword. |
217 | * |
218 | * @param string $system System |
219 | * |
220 | * @return string |
221 | * |
222 | * @since 1.0.0 |
223 | */ |
224 | protected function compileSystem(string $system) : string |
225 | { |
226 | $identifierStart = $this->systemIdentifierStart; |
227 | $identifierEnd = $this->systemIdentifierEnd; |
228 | |
229 | if (\stripos($system, '(') !== false) { |
230 | $identifierStart = ''; |
231 | $identifierEnd = ''; |
232 | } |
233 | |
234 | // The following code could have been handled with \explode more elegantly but \explode needs more memory and more time |
235 | // Normally this wouldn't be a problem but in this case there are so many function calls to this routine, |
236 | // that it makes sense to make this "minor" improvement. |
237 | if (($pos = \stripos($system, '.')) !== false) { |
238 | $split = [\substr($system, 0, $pos), \substr($system, $pos + 1)]; |
239 | |
240 | $identifierTwoStart = $identifierStart; |
241 | $identifierTwoEnd = $identifierEnd; |
242 | |
243 | if ($split[1] === '*') { |
244 | $identifierTwoStart = ''; |
245 | $identifierTwoEnd = ''; |
246 | } |
247 | |
248 | return $identifierStart . $split[0] . $identifierEnd |
249 | . '.' |
250 | . $identifierTwoStart . $split[1] . $identifierTwoEnd; |
251 | } |
252 | |
253 | if ($system === '*') { |
254 | $identifierStart = ''; |
255 | $identifierEnd = ''; |
256 | } |
257 | |
258 | return $identifierStart . $system . $identifierEnd; |
259 | } |
260 | |
261 | /** |
262 | * Compile value. |
263 | * |
264 | * @param BuilderAbstract $query Query builder |
265 | * @param mixed $value Value |
266 | * |
267 | * @return string returns a string representation of the value |
268 | * |
269 | * @throws \InvalidArgumentException throws this exception if the value to compile is not supported by this function |
270 | * |
271 | * @since 1.0.0 |
272 | */ |
273 | protected function compileValue(BuilderAbstract $query, mixed $value) : string |
274 | { |
275 | if (\is_string($value)) { |
276 | return $query->quote($value); |
277 | } elseif (\is_int($value)) { |
278 | return (string) $value; |
279 | } elseif (\is_array($value)) { |
280 | $value = \array_values($value); |
281 | $count = \count($value) - 1; |
282 | $values = '('; |
283 | |
284 | for ($i = 0; $i < $count; ++$i) { |
285 | $values .= $this->compileValue($query, $value[$i]) . ', '; |
286 | } |
287 | |
288 | return $values . $this->compileValue($query, $value[$count]) . ')'; |
289 | } elseif ($value instanceof \DateTime) { |
290 | return $query->quote($value->format($this->datetimeFormat)); |
291 | } elseif ($value === null) { |
292 | return 'NULL'; |
293 | } elseif (\is_bool($value)) { |
294 | return (string) ((int) $value); |
295 | } elseif (\is_float($value)) { |
296 | return \rtrim(\rtrim(\number_format($value, 5, '.', ''), '0'), '.'); |
297 | } elseif ($value instanceof Column) { |
298 | return '(' . \rtrim($this->compileColumnQuery($value), ';') . ')'; |
299 | } elseif ($value instanceof BuilderAbstract) { |
300 | return '(' . \rtrim($value->toSql(), ';') . ')'; |
301 | } elseif ($value instanceof \JsonSerializable) { |
302 | $encoded = \json_encode($value); |
303 | |
304 | return $encoded ? $encoded : 'NULL'; |
305 | } elseif ($value instanceof SerializableInterface) { |
306 | return $value->serialize(); |
307 | } elseif ($value instanceof Parameter) { |
308 | return $value->__toString(); |
309 | } else { |
310 | throw new \InvalidArgumentException(\gettype($value)); |
311 | } |
312 | } |
313 | |
314 | /** |
315 | * Compile column query. |
316 | * |
317 | * @param Column $column Where query |
318 | * |
319 | * @return string |
320 | * |
321 | * @since 1.0.0 |
322 | */ |
323 | protected function compileColumnQuery(Column $column) : string |
324 | { |
325 | return $column->toSql(); |
326 | } |
327 | } |