1 | <?php |
||||
2 | |||||
3 | namespace Bdf\Prime\Query\Compiler; |
||||
4 | |||||
5 | use Bdf\Prime\Exception\PrimeException; |
||||
6 | use Bdf\Prime\Exception\QueryBuildingException; |
||||
7 | use Bdf\Prime\Query\CommandInterface; |
||||
8 | use Bdf\Prime\Query\CompilableClause; |
||||
9 | use Bdf\Prime\Query\Contract\Compilable; |
||||
10 | use Bdf\Prime\Query\Expression\ExpressionInterface; |
||||
11 | use Bdf\Prime\Query\Expression\ExpressionTransformerInterface; |
||||
12 | use Bdf\Prime\Query\Query; |
||||
13 | use Bdf\Prime\Query\QueryInterface; |
||||
14 | use Bdf\Prime\Query\SqlQueryInterface; |
||||
15 | use Bdf\Prime\Types\TypeInterface; |
||||
16 | use Doctrine\DBAL\LockMode; |
||||
17 | use Doctrine\DBAL\Query\Expression\CompositeExpression; |
||||
18 | use UnexpectedValueException; |
||||
19 | |||||
20 | /** |
||||
21 | * Base compiler for SQL queries |
||||
22 | * |
||||
23 | * @template C as \Doctrine\DBAL\Connection |
||||
24 | * @extends AbstractCompiler<\Bdf\Prime\Query\SqlQueryInterface&CompilableClause, \Doctrine\DBAL\Connection&\Bdf\Prime\Connection\ConnectionInterface> |
||||
25 | * @implements QuoteCompilerInterface<\Bdf\Prime\Query\SqlQueryInterface&CompilableClause> |
||||
26 | */ |
||||
27 | class SqlCompiler extends AbstractCompiler implements QuoteCompilerInterface |
||||
28 | { |
||||
29 | /** |
||||
30 | * {@inheritdoc} |
||||
31 | */ |
||||
32 | 3 | public function quote($value) |
|||
33 | { |
||||
34 | 3 | return $this->connection->quote((string) $this->autoConvertValue($value)); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
35 | } |
||||
36 | |||||
37 | /** |
||||
38 | * {@inheritdoc} |
||||
39 | */ |
||||
40 | 929 | public function quoteIdentifier(CompilableClause $query, string $column): string |
|||
41 | { |
||||
42 | 929 | if (!$query->isQuoteIdentifier()) { |
|||
43 | 922 | return $column; |
|||
44 | } |
||||
45 | |||||
46 | 10 | return $this->platform()->grammar()->quoteIdentifier($column); |
|||
47 | } |
||||
48 | |||||
49 | /** |
||||
50 | * Quote a identifier on multiple columns |
||||
51 | * |
||||
52 | * @param SqlQueryInterface&CompilableClause $query |
||||
53 | * @param array $columns |
||||
54 | * |
||||
55 | * @return array |
||||
56 | * @throws PrimeException |
||||
57 | */ |
||||
58 | 1 | public function quoteIdentifiers(CompilableClause $query, array $columns) |
|||
59 | { |
||||
60 | 1 | if (!$query->isQuoteIdentifier()) { |
|||
61 | 1 | return $columns; |
|||
62 | } |
||||
63 | |||||
64 | 1 | return array_map([$this->platform()->grammar(), 'quoteIdentifier'], $columns); |
|||
65 | } |
||||
66 | |||||
67 | /** |
||||
68 | * {@inheritdoc} |
||||
69 | */ |
||||
70 | 668 | protected function doCompileInsert(CompilableClause $query) |
|||
71 | { |
||||
72 | 668 | $query->state()->currentPart = 0; |
|||
73 | |||||
74 | 668 | if ($query->statements['ignore'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('IGNORE')) { |
|||
75 | 1 | if ($this->platform()->grammar()->getName() === 'sqlite') { |
|||
76 | 1 | $insert = 'INSERT OR IGNORE INTO '; |
|||
77 | } else { |
||||
78 | 1 | $insert = 'INSERT IGNORE INTO '; |
|||
79 | } |
||||
80 | 667 | } elseif ($query->statements['replace'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('REPLACE')) { |
|||
81 | 2 | $insert = 'REPLACE INTO '; |
|||
82 | } else { |
||||
83 | 666 | $insert = 'INSERT INTO '; |
|||
84 | } |
||||
85 | |||||
86 | 668 | foreach ($query->statements['tables'] as $table) { |
|||
87 | 667 | return $query->state()->compiled = $insert.$this->quoteIdentifier($query, $table['table']).$this->compileInsertData($query); |
|||
88 | } |
||||
89 | |||||
90 | 1 | throw new QueryBuildingException('The insert table name is missing'); |
|||
91 | } |
||||
92 | |||||
93 | /** |
||||
94 | * Compile the data part of the insert query |
||||
95 | * |
||||
96 | * @param SqlQueryInterface&CompilableClause $query |
||||
97 | * |
||||
98 | * @return string |
||||
99 | * @throws PrimeException |
||||
100 | */ |
||||
101 | 667 | protected function compileInsertData(CompilableClause $query) |
|||
102 | { |
||||
103 | // @todo Do not use QueryInterface |
||||
104 | 667 | if ($query->statements['values']['data'] instanceof QueryInterface) { |
|||
105 | 7 | return $this->compileInsertSelect($query); |
|||
106 | } |
||||
107 | |||||
108 | 664 | list($columns, $values) = $this->compileInsertValues($query); |
|||
109 | |||||
110 | 664 | return ' ('.implode(', ', $columns).') VALUES('.implode(', ', $values).')'; |
|||
111 | } |
||||
112 | |||||
113 | /** |
||||
114 | * Compile an INSERT INTO ... SELECT ... query |
||||
115 | * |
||||
116 | * @param SqlQueryInterface&CompilableClause $query |
||||
117 | * |
||||
118 | * @return string |
||||
119 | * @throws PrimeException |
||||
120 | */ |
||||
121 | 7 | protected function compileInsertSelect(CompilableClause $query) |
|||
122 | { |
||||
123 | /** @var Query $select */ |
||||
124 | 7 | $select = clone $query->statements['values']['data']; // Clone the query for ensure that it'll not be modified |
|||
125 | 7 | $columns = []; |
|||
126 | |||||
127 | // Columns are defined on the select query |
||||
128 | // Alias of the selected columns will be concidered as the INSERT table columns |
||||
129 | 7 | if ($select->statements['columns'] && $select->statements['columns'][0]['column'] !== '*') { |
|||
130 | 3 | foreach ($select->statements['columns'] as &$column) { |
|||
131 | 3 | $alias = $query->preprocessor()->field($column['alias'] ?? $column['column']); |
|||
132 | |||||
133 | // Modify the column alias to match with the INSERT column |
||||
134 | 3 | $column['alias'] = $alias; |
|||
135 | |||||
136 | 3 | $columns[] = $this->quoteIdentifier($query, $alias); |
|||
137 | } |
||||
138 | } |
||||
139 | |||||
140 | 7 | $sql = ' '.$this->compileSelect($select); // @todo Ensure that the query is sql compilable |
|||
141 | 7 | $this->addQueryBindings($query, $select); |
|||
142 | |||||
143 | 7 | return empty($columns) ? $sql : ' ('.implode(', ', $columns).')'.$sql; |
|||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * Compile columns and values to insert |
||||
148 | * |
||||
149 | * @param SqlQueryInterface&CompilableClause $query |
||||
150 | * |
||||
151 | * @return array |
||||
152 | * @throws PrimeException |
||||
153 | */ |
||||
154 | 664 | protected function compileInsertValues(CompilableClause $query) |
|||
155 | { |
||||
156 | 664 | $data = $query->statements['values']; |
|||
157 | |||||
158 | 664 | $columns = []; |
|||
159 | 664 | $values = []; |
|||
160 | |||||
161 | 664 | foreach ($data['data'] as $column => $value) { |
|||
162 | 664 | $type = $data['types'][$column] ?? true; |
|||
163 | 664 | $column = $query->preprocessor()->field($column, $type); |
|||
164 | |||||
165 | // The type cannot be resolved by preprocessor |
||||
166 | 664 | if ($type === true) { |
|||
167 | 1 | $type = null; |
|||
168 | } |
||||
169 | |||||
170 | 664 | $columns[] = $this->quoteIdentifier($query, $column); |
|||
171 | 664 | $values[] = $this->compileTypedValue($query, $value, $type); |
|||
172 | } |
||||
173 | |||||
174 | 664 | return [$columns, $values]; |
|||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * {@inheritdoc} |
||||
179 | */ |
||||
180 | 32 | protected function doCompileUpdate(CompilableClause $query) |
|||
181 | { |
||||
182 | 32 | $query->state()->currentPart = 0; |
|||
183 | |||||
184 | 32 | $values = $this->compileUpdateValues($query); |
|||
185 | |||||
186 | 32 | foreach ($query->statements['tables'] as $table) { |
|||
187 | 31 | return $query->state()->compiled = 'UPDATE ' |
|||
188 | 31 | . $this->quoteIdentifier($query, $table['table']) |
|||
189 | 31 | . ' SET ' . implode(', ', $values) |
|||
190 | 31 | . $this->compileWhere($query) |
|||
191 | 31 | ; |
|||
192 | } |
||||
193 | |||||
194 | 1 | throw new QueryBuildingException('The update table name is missing'); |
|||
195 | } |
||||
196 | |||||
197 | /** |
||||
198 | * Compile columns and values to update |
||||
199 | * |
||||
200 | * @param SqlQueryInterface&CompilableClause $query |
||||
201 | * |
||||
202 | * @return array |
||||
203 | * @throws PrimeException |
||||
204 | */ |
||||
205 | 32 | protected function compileUpdateValues(CompilableClause $query) |
|||
206 | { |
||||
207 | 32 | $data = $query->statements['values']; |
|||
208 | 32 | $values = []; |
|||
209 | |||||
210 | 32 | foreach ($data['data'] as $column => $value) { |
|||
211 | 32 | $type = $data['types'][$column] ?? true; |
|||
212 | 32 | $column = $query->preprocessor()->field($column, $type); |
|||
213 | |||||
214 | 32 | $values[] = $this->quoteIdentifier($query, $column) |
|||
215 | 32 | . ' = ' |
|||
216 | 32 | . $this->compileTypedValue($query, $value, $type); |
|||
217 | } |
||||
218 | |||||
219 | 32 | return $values; |
|||
220 | } |
||||
221 | |||||
222 | /** |
||||
223 | * {@inheritdoc} |
||||
224 | */ |
||||
225 | 30 | protected function doCompileDelete(CompilableClause $query) |
|||
226 | { |
||||
227 | 30 | $query->state()->currentPart = 0; |
|||
228 | |||||
229 | 30 | foreach ($query->statements['tables'] as $table) { |
|||
230 | 29 | return $query->state()->compiled = 'DELETE FROM ' |
|||
231 | 29 | . $this->quoteIdentifier($query, $table['table']) |
|||
232 | 29 | . $this->compileWhere($query) |
|||
233 | 29 | ; |
|||
234 | } |
||||
235 | |||||
236 | 1 | throw new QueryBuildingException('The delete table name is missing'); |
|||
237 | } |
||||
238 | |||||
239 | /** |
||||
240 | * {@inheritdoc} |
||||
241 | */ |
||||
242 | 864 | protected function doCompileSelect(CompilableClause $query) |
|||
243 | { |
||||
244 | 864 | if ($this->isComplexAggregate($query)) { |
|||
245 | 3 | return $query->state()->compiled = $this->compileComplexAggregate($query); |
|||
246 | } |
||||
247 | |||||
248 | 864 | if (!isset($query->state()->compiledParts['columns'])) { |
|||
249 | 864 | $query->state()->currentPart = 'columns'; |
|||
250 | 864 | $query->state()->compiledParts['columns'] = $this->compileColumns($query); |
|||
251 | } |
||||
252 | |||||
253 | 864 | if (!isset($query->state()->compiledParts['from'])) { |
|||
254 | 864 | $query->state()->compiledParts['from'] = $this->compileFrom($query); |
|||
255 | } |
||||
256 | |||||
257 | 864 | if (!isset($query->state()->compiledParts['groups'])) { |
|||
258 | 864 | $query->state()->currentPart = 'groups'; |
|||
259 | 864 | $query->state()->compiledParts['groups'] = $this->compileGroup($query); |
|||
260 | } |
||||
261 | |||||
262 | 864 | if (!isset($query->state()->compiledParts['having'])) { |
|||
263 | 864 | $query->state()->currentPart = 'having'; |
|||
264 | 864 | $query->state()->compiledParts['having'] = $this->compileHaving($query); |
|||
265 | } |
||||
266 | |||||
267 | 864 | if (!isset($query->state()->compiledParts['orders'])) { |
|||
268 | 864 | $query->state()->currentPart = 'orders'; |
|||
269 | 864 | $query->state()->compiledParts['orders'] = $this->compileOrder($query); |
|||
270 | } |
||||
271 | |||||
272 | 864 | if (!isset($query->state()->compiledParts['where'])) { |
|||
273 | 864 | $query->state()->currentPart = 'where'; |
|||
274 | 864 | $query->state()->compiledParts['where'] = $this->compileWhere($query); |
|||
275 | } |
||||
276 | |||||
277 | 864 | if (!isset($query->state()->compiledParts['joins'])) { |
|||
278 | 864 | $query->state()->currentPart = 'joins'; |
|||
279 | 864 | $query->state()->compiledParts['joins'] = $this->compileJoins($query); |
|||
280 | } |
||||
281 | |||||
282 | 864 | if (!isset($query->state()->compiledParts['lock'])) { |
|||
283 | 864 | $query->state()->currentPart = 'lock'; |
|||
284 | 864 | $query->state()->compiledParts['lock'] = $this->compileLock($query); |
|||
285 | } |
||||
286 | |||||
287 | 864 | $sql = $query->state()->compiledParts['columns'] |
|||
288 | 864 | .$query->state()->compiledParts['from'] |
|||
289 | 864 | .$query->state()->compiledParts['joins'] |
|||
290 | 864 | .$query->state()->compiledParts['where'] |
|||
291 | 864 | .$query->state()->compiledParts['groups'] |
|||
292 | 864 | .$query->state()->compiledParts['having'] |
|||
293 | 864 | .$query->state()->compiledParts['orders']; |
|||
294 | |||||
295 | 864 | if ($query->isLimitQuery()) { |
|||
0 ignored issues
–
show
The method
isLimitQuery() does not exist on Bdf\Prime\Query\CompilableClause . It seems like you code against a sub-type of Bdf\Prime\Query\CompilableClause such as Bdf\Prime\Query\AbstractReadCommand .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
296 | 283 | $sql = $this->platform()->grammar()->modifyLimitQuery($sql, $query->statements['limit'], $query->statements['offset']); |
|||
297 | } |
||||
298 | |||||
299 | 864 | return $query->state()->compiled = $sql.$query->state()->compiledParts['lock']; |
|||
300 | } |
||||
301 | |||||
302 | /** |
||||
303 | * Check if the the query is an aggregate which requires to execute the query as temporary table |
||||
304 | * A temporary table is required for DISTINT aggregate with wildcard "*" column |
||||
305 | * |
||||
306 | * @param SqlQueryInterface&CompilableClause $query |
||||
307 | * |
||||
308 | * @return bool |
||||
309 | */ |
||||
310 | 864 | protected function isComplexAggregate(CompilableClause $query) |
|||
311 | { |
||||
312 | 864 | return isset($query->statements['aggregate']) && $query->statements['aggregate'][1] === '*' && $query->statements['distinct']; |
|||
313 | } |
||||
314 | |||||
315 | /** |
||||
316 | * Compile the complexe aggregate query |
||||
317 | * Will generate a query in form : "SELECT [aggregate](*) FROM ([query])" |
||||
318 | * |
||||
319 | * @param SqlQueryInterface&CompilableClause $query |
||||
320 | * |
||||
321 | * @return string |
||||
322 | * @throws PrimeException |
||||
323 | */ |
||||
324 | 3 | protected function compileComplexAggregate(CompilableClause $query) |
|||
325 | { |
||||
326 | 3 | list($function, $column) = $query->statements['aggregate']; |
|||
327 | |||||
328 | 3 | $query->statements['aggregate'] = null; |
|||
329 | 3 | $query->statements['columns'] = $column === '*' ? [] : [['column' => $column, 'alias' => null]]; |
|||
330 | |||||
331 | 3 | return 'SELECT '.$this->compileAggregate($query, $function, '*', false).' FROM ('.$this->doCompileSelect($query).') as derived_query'; |
|||
332 | } |
||||
333 | |||||
334 | /** |
||||
335 | * @param SqlQueryInterface&CompilableClause $query |
||||
336 | * |
||||
337 | * @return string |
||||
338 | * @throws PrimeException |
||||
339 | */ |
||||
340 | 864 | protected function compileColumns(CompilableClause $query) |
|||
341 | { |
||||
342 | 864 | if (!empty($query->statements['aggregate'])) { |
|||
343 | 588 | return 'SELECT '.$this->compileAggregate($query, $query->statements['aggregate'][0], $query->statements['aggregate'][1], $query->statements['distinct']); |
|||
344 | } |
||||
345 | |||||
346 | 629 | if ($query->statements['distinct'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) { |
|||
347 | 8 | $select = 'SELECT DISTINCT '; |
|||
348 | } else { |
||||
349 | 622 | $select = 'SELECT '; |
|||
350 | } |
||||
351 | |||||
352 | 629 | if (empty($query->statements['columns'])) { |
|||
353 | 564 | $root = $query->preprocessor()->root(); |
|||
354 | |||||
355 | 564 | if ($root) { |
|||
356 | 461 | $select .= $this->quoteIdentifier($query, $root).'.'; |
|||
357 | } |
||||
358 | |||||
359 | 564 | return $select.'*'; |
|||
360 | } |
||||
361 | |||||
362 | 76 | $sql = []; |
|||
363 | |||||
364 | 76 | foreach ($query->statements['columns'] as $column) { |
|||
365 | 76 | $sql[] = $this->compileExpressionColumn($query, $column['column'], $column['alias']); |
|||
366 | } |
||||
367 | |||||
368 | 76 | return $select.implode(', ', $sql); |
|||
369 | } |
||||
370 | |||||
371 | /** |
||||
372 | * Compile a SQL function |
||||
373 | * |
||||
374 | * @param SqlQueryInterface&CompilableClause $query |
||||
375 | * @param string $function The sql function |
||||
376 | * @param string $column The column to aggregate |
||||
377 | * @param bool $distinct The distinct status |
||||
378 | * |
||||
379 | * @return string |
||||
380 | * @throws PrimeException |
||||
381 | */ |
||||
382 | 590 | protected function compileAggregate(CompilableClause $query, $function, $column, $distinct) |
|||
383 | { |
||||
384 | 590 | if ($column !== '*') { |
|||
385 | 14 | $column = $query->preprocessor()->field($column); |
|||
386 | 14 | $column = $this->quoteIdentifier($query, $column); |
|||
387 | |||||
388 | 14 | if ($distinct && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) { |
|||
389 | // Le count ne compte pas les fields qui ont une valeur NULL. |
||||
390 | // Pour une pagination, il est important de compter les valeurs null sachant qu'elles seront sélectionnées. |
||||
391 | // La pagination utilise une column que pour le distinct. |
||||
392 | 12 | if ($function === 'pagination') { |
|||
393 | 4 | $column = 'IFNULL('.$column.',"___null___")'; |
|||
394 | } |
||||
395 | |||||
396 | 12 | $column = 'DISTINCT '.$column; |
|||
397 | } |
||||
398 | } |
||||
399 | |||||
400 | switch ($function) { |
||||
401 | 590 | case 'avg' : return $this->platform()->grammar()->getAvgExpression($column).' AS aggregate'; |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...orm::getAvgExpression() has been deprecated: Use AVG() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
402 | 588 | case 'count': return $this->platform()->grammar()->getCountExpression($column).' AS aggregate'; |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...m::getCountExpression() has been deprecated: Use COUNT() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
403 | 23 | case 'max' : return $this->platform()->grammar()->getMaxExpression($column).' AS aggregate'; |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...orm::getMaxExpression() has been deprecated: Use MAX() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
404 | 20 | case 'min' : return $this->platform()->grammar()->getMinExpression($column).' AS aggregate'; |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...orm::getMinExpression() has been deprecated: Use MIN() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
405 | 18 | case 'pagination': return $this->platform()->grammar()->getCountExpression($column).' AS aggregate'; |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...m::getCountExpression() has been deprecated: Use COUNT() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
406 | 3 | case 'sum' : return $this->platform()->grammar()->getSumExpression($column).' AS aggregate'; |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...orm::getSumExpression() has been deprecated: Use SUM() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
407 | |||||
408 | default: |
||||
409 | 1 | $method = 'get'.ucfirst($function).'Expression'; |
|||
410 | 1 | return $this->platform()->grammar()->{$method}($column).' AS aggregate'; |
|||
411 | } |
||||
412 | } |
||||
413 | |||||
414 | /** |
||||
415 | * Compile expression column |
||||
416 | * |
||||
417 | * @param SqlQueryInterface&CompilableClause $query |
||||
418 | * @param mixed $column |
||||
419 | * @param string $alias |
||||
420 | * |
||||
421 | * @return string |
||||
422 | * @throws PrimeException |
||||
423 | */ |
||||
424 | 76 | protected function compileExpressionColumn(CompilableClause $query, $column, $alias = null) |
|||
425 | { |
||||
426 | 76 | if ($column instanceof QueryInterface) { |
|||
427 | 1 | return $this->compileSubQuery($query, $column, $alias); |
|||
428 | } |
||||
429 | |||||
430 | 76 | if ($column instanceof ExpressionInterface) { |
|||
431 | 9 | return $alias !== null |
|||
432 | 7 | ? $column->build($query, $this).' as '.$this->quoteIdentifier($query, $alias) |
|||
433 | 9 | : $column->build($query, $this) |
|||
434 | 9 | ; |
|||
435 | } |
||||
436 | |||||
437 | 69 | $column = $query->preprocessor()->field($column); |
|||
438 | |||||
439 | 69 | if ($column[-1] === '*') { |
|||
440 | 1 | return $column; |
|||
441 | } |
||||
442 | |||||
443 | 69 | return $alias !== null |
|||
444 | 9 | ? $this->quoteIdentifier($query, $column).' as '.$this->quoteIdentifier($query, $alias) |
|||
445 | 69 | : $this->quoteIdentifier($query, $column) |
|||
446 | 69 | ; |
|||
447 | } |
||||
448 | |||||
449 | /** |
||||
450 | * @param SqlQueryInterface&CompilableClause $query |
||||
451 | * |
||||
452 | * @return string |
||||
453 | * @throws PrimeException |
||||
454 | */ |
||||
455 | 864 | protected function compileFrom(CompilableClause $query) |
|||
456 | { |
||||
457 | 864 | $sql = ' FROM '; |
|||
458 | 864 | $isFirst = true; |
|||
459 | |||||
460 | // Loop through all FROM clauses |
||||
461 | 864 | foreach ($this->compileTableAndAliasClause($query, $query->statements['tables']) as $from) { |
|||
462 | 864 | if (!$isFirst) { |
|||
463 | 8 | $sql .= ', '; |
|||
464 | } else { |
||||
465 | 864 | $isFirst = false; |
|||
466 | } |
||||
467 | |||||
468 | 864 | $sql .= $from['sql']; |
|||
469 | } |
||||
470 | |||||
471 | 864 | return $sql; |
|||
472 | } |
||||
473 | |||||
474 | /** |
||||
475 | * @param SqlQueryInterface&CompilableClause $query |
||||
476 | * |
||||
477 | * @return string |
||||
478 | * @throws PrimeException |
||||
479 | */ |
||||
480 | 864 | protected function compileJoins(CompilableClause $query) |
|||
481 | { |
||||
482 | 864 | if (empty($query->statements['joins'])) { |
|||
483 | 843 | return ''; |
|||
484 | } |
||||
485 | |||||
486 | 99 | $sql = ''; |
|||
487 | |||||
488 | 99 | foreach ($this->compileTableAndAliasClause($query, $query->statements['joins']) as $join) { |
|||
489 | 99 | $sql .= ' '.$join['type'].' JOIN '.$join['sql'].' ON '.$this->compileCompilableClauses($query, $join['on']); |
|||
490 | } |
||||
491 | |||||
492 | 99 | return $sql; |
|||
493 | } |
||||
494 | |||||
495 | /** |
||||
496 | * Compile clauses with 'table' and 'alias' keys |
||||
497 | * |
||||
498 | * The table name will be resolved, quoted, and generate the alias if present |
||||
499 | * Duplicate table name or aliases will also be removed from result |
||||
500 | * |
||||
501 | * The compiled table expression will be returned into the 'sql' key |
||||
502 | * All input parameter will be kept on the return value |
||||
503 | * |
||||
504 | * @param SqlQueryInterface&CompilableClause $query |
||||
505 | * @param array $clauses |
||||
506 | * |
||||
507 | * @return array |
||||
508 | * |
||||
509 | * @throws PrimeException |
||||
510 | */ |
||||
511 | 864 | protected function compileTableAndAliasClause(CompilableClause $query, array $clauses): array |
|||
512 | { |
||||
513 | 864 | $databasePrefix = $this->getDatabaseNamePrefix($query); |
|||
514 | 864 | $compiled = []; |
|||
515 | |||||
516 | 864 | foreach ($clauses as $from) { |
|||
517 | 864 | if ($from['table'] instanceof QueryInterface) { |
|||
518 | 3 | $from['sql'] = $this->compileSubQuery($query, $from['table'], $from['alias']); |
|||
519 | 3 | $compiled[] = $from; |
|||
520 | } else { |
||||
521 | 864 | $from = $query->preprocessor()->table($from); |
|||
522 | |||||
523 | 864 | if ($from['alias'] === null) { |
|||
524 | 689 | $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']); |
|||
525 | 689 | $compiled[$from['table']] = $from; |
|||
526 | } else { |
||||
527 | 560 | $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']) . ' ' . $this->quoteIdentifier($query, $from['alias']); |
|||
528 | 560 | $compiled[$from['alias']] = $from; |
|||
529 | } |
||||
530 | } |
||||
531 | } |
||||
532 | |||||
533 | 864 | return $compiled; |
|||
534 | } |
||||
535 | |||||
536 | /** |
||||
537 | * Adding database prefix for sub query x-db |
||||
538 | * |
||||
539 | * @param SqlQueryInterface&CompilableClause $query |
||||
540 | * |
||||
541 | * @return string |
||||
542 | */ |
||||
543 | 864 | protected function getDatabaseNamePrefix(CompilableClause $query): string |
|||
544 | { |
||||
545 | 864 | if ($query instanceof CommandInterface && $query->compiler() !== $this && $query->connection()->getDatabase() !== $this->connection->getDatabase()) { |
|||
546 | 2 | return $query->connection()->getDatabase().'.'; |
|||
547 | } |
||||
548 | |||||
549 | 863 | return ''; |
|||
550 | } |
||||
551 | |||||
552 | /** |
||||
553 | * Compile Where sql |
||||
554 | * |
||||
555 | * @param SqlQueryInterface&CompilableClause $query |
||||
556 | * |
||||
557 | * @return string |
||||
558 | * @throws PrimeException |
||||
559 | */ |
||||
560 | 875 | protected function compileWhere(CompilableClause $query) |
|||
561 | { |
||||
562 | 875 | if (empty($query->statements['where'])) { |
|||
563 | 735 | return ''; |
|||
564 | } |
||||
565 | |||||
566 | 515 | return ' WHERE '.$this->compileCompilableClauses($query, $query->statements['where']); |
|||
567 | } |
||||
568 | |||||
569 | /** |
||||
570 | * Compile having sql |
||||
571 | * |
||||
572 | * @param SqlQueryInterface&CompilableClause $query |
||||
573 | * |
||||
574 | * @return string |
||||
575 | * @throws PrimeException |
||||
576 | */ |
||||
577 | 864 | protected function compileHaving(CompilableClause $query) |
|||
578 | { |
||||
579 | 864 | if (empty($query->statements['having'])) { |
|||
580 | 855 | return ''; |
|||
581 | } |
||||
582 | |||||
583 | 9 | return ' HAVING '.$this->compileCompilableClauses($query, $query->statements['having']); |
|||
584 | } |
||||
585 | |||||
586 | /** |
||||
587 | * @param SqlQueryInterface&CompilableClause $query |
||||
588 | * @param array $clauses |
||||
589 | * |
||||
590 | * @return string |
||||
591 | * @throws PrimeException |
||||
592 | */ |
||||
593 | 529 | protected function compileCompilableClauses(CompilableClause $query, array &$clauses) |
|||
594 | { |
||||
595 | 529 | $sql = []; |
|||
596 | 529 | $i = 0; |
|||
597 | |||||
598 | // Permet de retirer le niveau du nested |
||||
599 | 529 | if (count($clauses) === 1 && isset($clauses[0]['nested'])) { |
|||
600 | 209 | $result = $this->compileCompilableClauses($query, $clauses[0]['nested']); |
|||
601 | /* |
||||
602 | * We check he if where expression has added constraints (from relation). |
||||
603 | * If we still have one clause, we return the compiled sql |
||||
604 | * Otherwise we start the loop of clauses. |
||||
605 | */ |
||||
606 | 209 | if (count($clauses) === 1) { |
|||
607 | 205 | return $result; |
|||
608 | } |
||||
609 | |||||
610 | // Add the nested level |
||||
611 | 4 | $sql[] = '('.$result.')'; |
|||
612 | 4 | $i = 1; |
|||
613 | } |
||||
614 | |||||
615 | 529 | $clauses[0]['glue'] = null; |
|||
616 | |||||
617 | //Cannot use foreach because where expression can add new relations with constraints |
||||
618 | 529 | for (; isset($clauses[$i]); ++$i) { |
|||
619 | 529 | $part = $clauses[$i]; |
|||
620 | |||||
621 | 529 | if ($part['glue'] !== null) { |
|||
622 | 157 | $part['glue'] .= ' '; |
|||
623 | } |
||||
624 | |||||
625 | 529 | $part = $query->preprocessor()->expression($part); |
|||
626 | |||||
627 | 529 | if (isset($part['nested'])) { |
|||
628 | 106 | $sql[] = $part['glue'].'('.$this->compileCompilableClauses($query, $part['nested']).')'; |
|||
629 | 529 | } elseif (!isset($part['raw'])) { |
|||
630 | 519 | $sql[] = $part['glue'].$this->compileExpression($query, $part['column'], $part['operator'], $part['value'], $part['converted'] ?? false); |
|||
631 | } else { |
||||
632 | 11 | $sql[] = $part['glue'].$this->compileRawValue($query, $part['raw']); |
|||
633 | } |
||||
634 | } |
||||
635 | |||||
636 | 529 | return implode(' ', $sql); |
|||
637 | } |
||||
638 | |||||
639 | /** |
||||
640 | * Determine which operator to use based on custom and standard syntax |
||||
641 | * |
||||
642 | * @param SqlQueryInterface&CompilableClause $query |
||||
643 | * @param string $column |
||||
644 | * @param string $operator |
||||
645 | * @param mixed $value |
||||
646 | * @param bool $converted |
||||
647 | * |
||||
648 | * @return string operator found |
||||
649 | * |
||||
650 | * @throws UnexpectedValueException |
||||
651 | * @throws PrimeException |
||||
652 | */ |
||||
653 | 519 | protected function compileExpression(CompilableClause $query, string $column, string $operator, $value, bool $converted): string |
|||
654 | { |
||||
655 | 519 | if ($value instanceof ExpressionTransformerInterface) { |
|||
656 | /** @psalm-suppress InvalidArgument */ |
||||
657 | 6 | $value->setContext($this, $column, $operator); |
|||
658 | |||||
659 | 6 | $column = $value->getColumn(); |
|||
660 | 6 | $operator = $value->getOperator(); |
|||
661 | 6 | $value = $value->getValue(); |
|||
662 | 6 | $converted = true; |
|||
663 | } |
||||
664 | |||||
665 | switch ($operator) { |
||||
666 | 519 | case '<': |
|||
667 | 519 | case ':lt': |
|||
668 | 2 | if (is_array($value)) { |
|||
669 | return $this->compileIntoExpression($query, $value, $column, '<', $converted); |
||||
670 | } |
||||
671 | 2 | return $this->quoteIdentifier($query, $column).' < '.$this->compileExpressionValue($query, $value, $converted); |
|||
672 | |||||
673 | 519 | case '<=': |
|||
674 | 519 | case ':lte': |
|||
675 | if (is_array($value)) { |
||||
676 | return $this->compileIntoExpression($query, $value, $column, '<=', $converted); |
||||
677 | } |
||||
678 | return $this->quoteIdentifier($query, $column).' <= '.$this->compileExpressionValue($query, $value, $converted); |
||||
679 | |||||
680 | 519 | case '>': |
|||
681 | 504 | case ':gt': |
|||
682 | 18 | if (is_array($value)) { |
|||
683 | return $this->compileIntoExpression($query, $value, $column, '>', $converted); |
||||
684 | } |
||||
685 | 18 | return $this->quoteIdentifier($query, $column).' > '.$this->compileExpressionValue($query, $value, $converted); |
|||
686 | |||||
687 | 504 | case '>=': |
|||
688 | 504 | case ':gte': |
|||
689 | 2 | if (is_array($value)) { |
|||
690 | return $this->compileIntoExpression($query, $value, $column, '>=', $converted); |
||||
691 | } |
||||
692 | 2 | return $this->quoteIdentifier($query, $column).' >= '.$this->compileExpressionValue($query, $value, $converted); |
|||
693 | |||||
694 | 504 | case '~=': |
|||
695 | 504 | case '=~': |
|||
696 | 504 | case ':regex': |
|||
697 | if (is_array($value)) { |
||||
698 | return $this->compileIntoExpression($query, $value, $column, 'REGEXP', $converted); |
||||
699 | } |
||||
700 | return $this->quoteIdentifier($query, $column).' REGEXP '.$this->compileExpressionValue($query, (string)$value, $converted); |
||||
701 | |||||
702 | 504 | case ':like': |
|||
703 | 27 | if (is_array($value)) { |
|||
704 | 4 | return $this->compileIntoExpression($query, $value, $column, 'LIKE', $converted); |
|||
705 | } |
||||
706 | 23 | return $this->quoteIdentifier($query, $column).' LIKE '.$this->compileExpressionValue($query, $value, $converted); |
|||
707 | |||||
708 | 491 | case ':notlike': |
|||
709 | 491 | case '!like': |
|||
710 | 1 | if (is_array($value)) { |
|||
711 | 1 | return $this->compileIntoExpression($query, $value, $column, 'NOT LIKE', $converted, CompositeExpression::TYPE_AND); |
|||
712 | } |
||||
713 | 1 | return $this->quoteIdentifier($query, $column).' NOT LIKE '.$this->compileExpressionValue($query, $value, $converted); |
|||
714 | |||||
715 | 490 | case 'in': |
|||
716 | 482 | case ':in': |
|||
717 | 181 | if (empty($value)) { |
|||
718 | 2 | return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column)); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...::getIsNullExpression() has been deprecated: Use IS NULL in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
719 | } |
||||
720 | 180 | return $this->compileInExpression($query, $value, $column, 'IN', $converted); |
|||
0 ignored issues
–
show
It seems like
$value can also be of type string ; however, parameter $values of Bdf\Prime\Query\Compiler...::compileInExpression() does only seem to accept Bdf\Prime\Query\Expressi...ry\QueryInterface|array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
721 | |||||
722 | 476 | case 'notin': |
|||
723 | 476 | case '!in': |
|||
724 | 475 | case ':notin': |
|||
725 | 4 | if (empty($value)) { |
|||
726 | 2 | return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column)); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...etIsNotNullExpression() has been deprecated: Use IS NOT NULL in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
727 | } |
||||
728 | 3 | return $this->compileInExpression($query, $value, $column, 'NOT IN', $converted); |
|||
729 | |||||
730 | 474 | case 'between': |
|||
731 | 474 | case ':between': |
|||
732 | 6 | if (is_array($value)) { |
|||
733 | 6 | return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), $this->compileExpressionValue($query, $value[0], $converted), $this->compileExpressionValue($query, $value[1], $converted)); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...:getBetweenExpression() has been deprecated: Use BETWEEN in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
734 | } |
||||
735 | return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), '0', $this->compileExpressionValue($query, $value, $converted)); |
||||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...:getBetweenExpression() has been deprecated: Use BETWEEN in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
736 | |||||
737 | 469 | case '!between': |
|||
738 | 469 | case ':notbetween': |
|||
739 | 1 | return $this->platform()->grammar()->getNotExpression($this->compileExpression($query, $column, ':between', $value, $converted)); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...orm::getNotExpression() has been deprecated: Use NOT() in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
740 | |||||
741 | 468 | case '<>': |
|||
742 | 468 | case '!=': |
|||
743 | 463 | case ':ne': |
|||
744 | 463 | case ':not': |
|||
745 | 17 | if (is_null($value)) { |
|||
746 | 6 | return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column)); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...etIsNotNullExpression() has been deprecated: Use IS NOT NULL in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
747 | } |
||||
748 | 11 | if (is_array($value)) { |
|||
749 | 1 | return $this->compileExpression($query, $column, ':notin', $value, $converted); |
|||
750 | } |
||||
751 | 10 | return $this->quoteIdentifier($query, $column).' != '.$this->compileExpressionValue($query, $value, $converted); |
|||
752 | |||||
753 | 463 | case '=': |
|||
754 | case ':eq': |
||||
755 | 463 | if (is_null($value)) { |
|||
756 | 17 | return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column)); |
|||
0 ignored issues
–
show
The function
Doctrine\DBAL\Platforms\...::getIsNullExpression() has been deprecated: Use IS NULL in SQL instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
757 | } |
||||
758 | 456 | if (is_array($value)) { |
|||
759 | 163 | return $this->compileExpression($query, $column, ':in', $value, $converted); |
|||
760 | } |
||||
761 | 391 | return $this->quoteIdentifier($query, $column).' = '.$this->compileExpressionValue($query, $value, $converted); |
|||
762 | |||||
763 | default: |
||||
764 | throw new UnexpectedValueException("Unsupported operator '" . $operator . "' in WHERE clause"); |
||||
765 | } |
||||
766 | } |
||||
767 | |||||
768 | /** |
||||
769 | * Compile expression value |
||||
770 | * |
||||
771 | * @param SqlQueryInterface&CompilableClause $query |
||||
772 | * @param mixed $value |
||||
773 | * @param bool $converted Does the value is already converted to database ? |
||||
774 | * |
||||
775 | * @return string |
||||
776 | * @throws PrimeException |
||||
777 | */ |
||||
778 | 434 | protected function compileExpressionValue(CompilableClause $query, $value, bool $converted) |
|||
779 | { |
||||
780 | 434 | if ($value instanceof QueryInterface) { |
|||
781 | return $this->compileSubQuery($query, $value); |
||||
782 | } |
||||
783 | |||||
784 | 434 | if ($value instanceof ExpressionInterface) { |
|||
785 | 103 | return $value->build($query, $this); |
|||
786 | } |
||||
787 | |||||
788 | 419 | return $converted ? $this->bindRaw($query, $value) : $this->bindTyped($query, $value, null); |
|||
789 | } |
||||
790 | |||||
791 | /** |
||||
792 | * Compile expression value with type |
||||
793 | * |
||||
794 | * @param SqlQueryInterface&CompilableClause $query |
||||
795 | * @param mixed $value |
||||
796 | * @param TypeInterface|null $type The type. If null it will be resolved from value |
||||
797 | * |
||||
798 | * @return string |
||||
799 | * @throws PrimeException |
||||
800 | */ |
||||
801 | 675 | protected function compileTypedValue(CompilableClause $query, $value, ?TypeInterface $type) |
|||
802 | { |
||||
803 | 675 | if ($value instanceof QueryInterface) { |
|||
804 | return $this->compileSubQuery($query, $value); |
||||
805 | } |
||||
806 | |||||
807 | 675 | if ($value instanceof ExpressionInterface) { |
|||
808 | 1 | return $value->build($query, $this); |
|||
809 | } |
||||
810 | |||||
811 | 675 | return $this->bindTyped($query, $value, $type); |
|||
812 | } |
||||
813 | |||||
814 | /** |
||||
815 | * Compile raw expression value |
||||
816 | * |
||||
817 | * @param SqlQueryInterface&CompilableClause $query |
||||
818 | * @param mixed $value |
||||
819 | * |
||||
820 | * @return string |
||||
821 | * @throws PrimeException |
||||
822 | */ |
||||
823 | 11 | protected function compileRawValue(CompilableClause $query, $value) |
|||
824 | { |
||||
825 | 11 | if ($value instanceof QueryInterface) { |
|||
826 | return $this->compileSubQuery($query, $value); |
||||
827 | } |
||||
828 | |||||
829 | 11 | if ($value instanceof ExpressionInterface) { |
|||
830 | 2 | return $value->build($query, $this); |
|||
831 | } |
||||
832 | |||||
833 | 9 | return $value; |
|||
834 | } |
||||
835 | |||||
836 | /** |
||||
837 | * Add sub query bindings. |
||||
838 | * |
||||
839 | * @param SqlQueryInterface&CompilableClause $clause |
||||
840 | * @param QueryInterface $query The sub query. |
||||
841 | * @param string $alias |
||||
842 | * |
||||
843 | * @return string The sub query sql |
||||
844 | * @throws PrimeException |
||||
845 | */ |
||||
846 | 6 | protected function compileSubQuery(CompilableClause $clause, QueryInterface $query, $alias = null) |
|||
847 | { |
||||
848 | //TODO les alias peuvent etre les memes. Ne gene pas MySQL, voir à regénérer ceux de la subquery |
||||
849 | 6 | $sql = '('.$this->compileSelect($query).')'; |
|||
850 | |||||
851 | 6 | if ($alias) { |
|||
852 | 4 | $sql = $sql . ' as ' . $this->quoteIdentifier($clause, $alias); |
|||
853 | } |
||||
854 | |||||
855 | 6 | $this->addQueryBindings($clause, $query); |
|||
856 | |||||
857 | 6 | return $sql; |
|||
858 | } |
||||
859 | |||||
860 | /** |
||||
861 | * Compile IN or NOT IN expression |
||||
862 | * |
||||
863 | * @param SqlQueryInterface&CompilableClause $query |
||||
864 | * @param array|QueryInterface|ExpressionInterface $values |
||||
865 | * @param string $column |
||||
866 | * @param string $operator |
||||
867 | * @param boolean $converted |
||||
868 | * |
||||
869 | * @return string |
||||
870 | * @throws PrimeException |
||||
871 | */ |
||||
872 | 183 | protected function compileInExpression(CompilableClause $query, $values, string $column, string $operator = 'IN', bool $converted = false) |
|||
873 | { |
||||
874 | 183 | if (is_array($values)) { |
|||
875 | 180 | $hasNullValue = null; |
|||
876 | 180 | foreach ($values as $index => &$value) { |
|||
877 | 180 | if ($value === null) { |
|||
878 | 2 | unset($values[$index]); |
|||
879 | 2 | $hasNullValue = true; |
|||
880 | } else { |
||||
881 | 180 | $value = $converted ? $this->bindRaw($query, $value) : $this->bindTyped($query, $value, null); |
|||
882 | } |
||||
883 | } |
||||
884 | |||||
885 | // If the collection has a null value we add the null expression |
||||
886 | 180 | if ($hasNullValue) { |
|||
887 | 2 | if ($values) { |
|||
0 ignored issues
–
show
The expression
$values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
888 | 2 | $expression = '('.$this->quoteIdentifier($query, $column).' '.$operator.' ('.implode(',', $values).')'; |
|||
889 | |||||
890 | 2 | if ($operator === 'IN') { |
|||
891 | 1 | return $expression.' OR '.$this->compileExpression($query, $column, 'in', null, $converted).')'; |
|||
892 | } else { |
||||
893 | 1 | return $expression.' AND '.$this->compileExpression($query, $column, '!in', null, $converted).')'; |
|||
894 | } |
||||
895 | } |
||||
896 | |||||
897 | 2 | return $this->compileExpression($query, $column, $operator === 'IN' ? 'in' : '!in', null, $converted); |
|||
898 | } |
||||
899 | |||||
900 | 180 | $values = '('.implode(',', $values).')'; |
|||
901 | 3 | } elseif ($values instanceof QueryInterface) { |
|||
902 | 2 | $values = $this->compileSubQuery($query, $values); |
|||
903 | 1 | } elseif ($values instanceof ExpressionInterface) { |
|||
0 ignored issues
–
show
|
|||||
904 | $values = '('.$values->build($query, $this).')'; |
||||
905 | } else { |
||||
906 | 1 | $values = $converted ? $this->bindRaw($query, $values) : $this->bindTyped($query, $values, null); |
|||
907 | 1 | $values = '('.$values.')'; // @todo utile ? |
|||
908 | } |
||||
909 | |||||
910 | 183 | return $this->quoteIdentifier($query, $column).' '.$operator.' '.$values; |
|||
911 | } |
||||
912 | |||||
913 | /** |
||||
914 | * Compile into expression |
||||
915 | * Multiple OR expression |
||||
916 | * |
||||
917 | * @param SqlQueryInterface&CompilableClause $query |
||||
918 | * @param array $values |
||||
919 | * @param string $column |
||||
920 | * @param string $operator |
||||
921 | * @param bool $converted True if the value is already converted, or false to convert into bind() |
||||
922 | * @param string $separator The expressions separators. By default set to OR, but should be AND on negative (NOT) expressions. See CompositeExpression |
||||
923 | * |
||||
924 | * @return string |
||||
925 | * @throws PrimeException |
||||
926 | */ |
||||
927 | 5 | public function compileIntoExpression(CompilableClause $query, array $values, $column, $operator, $converted, $separator = CompositeExpression::TYPE_OR) |
|||
928 | { |
||||
929 | 5 | $into = []; |
|||
930 | |||||
931 | 5 | $column = $this->quoteIdentifier($query, $column); |
|||
932 | |||||
933 | 5 | foreach ($values as $value) { |
|||
934 | 5 | $into[] = $column.' '.$operator.' '.$this->compileExpressionValue($query, $value, $converted); |
|||
935 | } |
||||
936 | |||||
937 | 5 | return '('.implode(' '.$separator.' ', $into).')'; |
|||
938 | } |
||||
939 | |||||
940 | /** |
||||
941 | * Compile group by expression |
||||
942 | * |
||||
943 | * @param SqlQueryInterface&CompilableClause $query |
||||
944 | * |
||||
945 | * @return string |
||||
946 | * @throws PrimeException |
||||
947 | */ |
||||
948 | 864 | protected function compileGroup(CompilableClause $query) |
|||
949 | { |
||||
950 | 864 | if (empty($query->statements['groups'])) { |
|||
951 | 863 | return ''; |
|||
952 | } |
||||
953 | |||||
954 | 3 | $fields = array_map(fn (string $group) => $query->preprocessor()->field($group), $query->statements['groups']); |
|||
955 | |||||
956 | 3 | if ($query->isQuoteIdentifier()) { |
|||
957 | $fields = $this->quoteIdentifiers($query, $fields); |
||||
958 | } |
||||
959 | |||||
960 | 3 | return ' GROUP BY '.implode(', ', $fields); |
|||
961 | } |
||||
962 | |||||
963 | /** |
||||
964 | * Compile order by expression |
||||
965 | * |
||||
966 | * @param SqlQueryInterface&CompilableClause $query |
||||
967 | * |
||||
968 | * @return string |
||||
969 | * @throws PrimeException |
||||
970 | */ |
||||
971 | 864 | protected function compileOrder(CompilableClause $query) |
|||
972 | { |
||||
973 | 864 | if (empty($query->statements['orders'])) { |
|||
974 | 808 | return ''; |
|||
975 | } |
||||
976 | |||||
977 | 62 | $sql = []; |
|||
978 | |||||
979 | 62 | foreach ($query->statements['orders'] as $part) { |
|||
980 | 62 | if ($part['sort'] instanceof ExpressionInterface) { |
|||
981 | $part['sort'] = $part['sort']->build($query, $this); |
||||
982 | } else { |
||||
983 | 62 | $part['sort'] = $this->quoteIdentifier($query, $query->preprocessor()->field($part['sort'])); |
|||
984 | } |
||||
985 | |||||
986 | 62 | $sql[] = $part['sort'].' '.$part['order']; |
|||
987 | } |
||||
988 | |||||
989 | 62 | return ' ORDER BY '.implode(', ', $sql); |
|||
990 | } |
||||
991 | |||||
992 | /** |
||||
993 | * Compile the lock expression |
||||
994 | * |
||||
995 | * Does not support system that use hint like SqlServer |
||||
996 | * |
||||
997 | * @param SqlQueryInterface&CompilableClause $query |
||||
998 | * |
||||
999 | * @return string |
||||
1000 | * @throws PrimeException |
||||
1001 | */ |
||||
1002 | 864 | protected function compileLock(CompilableClause $query) |
|||
1003 | { |
||||
1004 | 864 | $lock = $query->statements['lock']; |
|||
1005 | |||||
1006 | // The lock should not be applied on aggregate function |
||||
1007 | 864 | if ($lock !== null && !$query->statements['aggregate']) { |
|||
1008 | // Lock for update |
||||
1009 | 2 | if ($lock === LockMode::PESSIMISTIC_WRITE) { |
|||
1010 | 1 | return ' ' . $this->platform()->grammar()->getWriteLockSQL(); |
|||
1011 | } |
||||
1012 | |||||
1013 | // Shared Lock: other process can read the row but not update it. |
||||
1014 | 1 | if ($lock === LockMode::PESSIMISTIC_READ) { |
|||
1015 | 1 | return ' ' . $this->platform()->grammar()->getReadLockSQL(); |
|||
1016 | } |
||||
1017 | } |
||||
1018 | |||||
1019 | 862 | return ''; |
|||
1020 | } |
||||
1021 | |||||
1022 | /** |
||||
1023 | * Add sub query bindings. |
||||
1024 | * |
||||
1025 | * @param SqlQueryInterface&CompilableClause $clause The main query |
||||
1026 | * @param Compilable $subQuery The sub query. |
||||
1027 | * |
||||
1028 | * @return $this This compiler instance. |
||||
1029 | * @throws PrimeException |
||||
1030 | */ |
||||
1031 | 13 | protected function addQueryBindings(CompilableClause $clause, Compilable $subQuery) |
|||
1032 | { |
||||
1033 | 13 | foreach ($subQuery->getBindings() as $binding) { |
|||
1034 | 5 | $this->bindRaw($clause, $binding); // Types are already converted on compilation |
|||
1035 | } |
||||
1036 | |||||
1037 | 13 | return $this; |
|||
1038 | } |
||||
1039 | |||||
1040 | /** |
||||
1041 | * Creates a new positional parameter and bind the given value to it. |
||||
1042 | * The value will be converted according to the given type. If the type is not defined, it will be resolved from PHP value. |
||||
1043 | * |
||||
1044 | * Attention: If you are using positional parameters with the query builder you have |
||||
1045 | * to be very careful to bind all parameters in the order they appear in the SQL |
||||
1046 | * statement , otherwise they get bound in the wrong order which can lead to serious |
||||
1047 | * bugs in your code. |
||||
1048 | * |
||||
1049 | * @param SqlQueryInterface&CompilableClause $query |
||||
1050 | * @param mixed $value |
||||
1051 | * @param TypeInterface|null $type The type to bind, or null to resolve |
||||
1052 | * |
||||
1053 | * @return string |
||||
1054 | * @throws PrimeException |
||||
1055 | */ |
||||
1056 | 709 | protected function bindTyped(CompilableClause $query, $value, ?TypeInterface $type) |
|||
1057 | { |
||||
1058 | 709 | return $this->bindRaw($query, $this->platform()->types()->toDatabase($value, $type)); |
|||
1059 | } |
||||
1060 | |||||
1061 | /** |
||||
1062 | * Creates a new positional parameter and bind the given value to it. |
||||
1063 | * The value will not be converted here |
||||
1064 | * |
||||
1065 | * Attention: If you are using positional parameters with the query builder you have |
||||
1066 | * to be very careful to bind all parameters in the order they appear in the SQL |
||||
1067 | * statement , otherwise they get bound in the wrong order which can lead to serious |
||||
1068 | * bugs in your code. |
||||
1069 | * |
||||
1070 | * @param SqlQueryInterface&CompilableClause $query |
||||
1071 | * @param mixed $value Raw database value : must be converted before |
||||
1072 | * |
||||
1073 | * @return string |
||||
1074 | */ |
||||
1075 | 800 | protected function bindRaw(CompilableClause $query, $value) |
|||
1076 | { |
||||
1077 | 800 | $query->state()->bind($value); |
|||
1078 | |||||
1079 | 800 | return '?'; |
|||
1080 | } |
||||
1081 | |||||
1082 | /** |
||||
1083 | * {@inheritdoc} |
||||
1084 | */ |
||||
1085 | 839 | public function getBindings(CompilableClause $query): array |
|||
1086 | { |
||||
1087 | 839 | return $this->mergeBindings($query->state()->bindings); |
|||
1088 | } |
||||
1089 | |||||
1090 | /** |
||||
1091 | * Merge algo for bindings and binding types |
||||
1092 | * |
||||
1093 | * @param array $bindings |
||||
1094 | * |
||||
1095 | * @return array |
||||
1096 | */ |
||||
1097 | 839 | protected function mergeBindings($bindings) |
|||
1098 | { |
||||
1099 | 839 | $mergedBindings = []; |
|||
1100 | |||||
1101 | 839 | if (isset($bindings[0])) { |
|||
1102 | 672 | $mergedBindings = $bindings[0]; |
|||
1103 | } else { |
||||
1104 | 784 | foreach (['columns', 'joins', 'where', 'groups', 'having', 'orders'] as $part) { |
|||
1105 | 784 | if (isset($bindings[$part])) { |
|||
1106 | 433 | $mergedBindings = array_merge($mergedBindings, $bindings[$part]); |
|||
1107 | } |
||||
1108 | } |
||||
1109 | } |
||||
1110 | |||||
1111 | 839 | return $mergedBindings; |
|||
1112 | } |
||||
1113 | } |
||||
1114 |