Issues (590)

src/Query/Compiler/SqlCompiler.php (18 issues)

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
The method quote() does not exist on Bdf\Prime\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Connection\ConnectionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

34
        return $this->connection->/** @scrutinizer ignore-call */ quote((string) $this->autoConvertValue($value));
Loading history...
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 ignore-call  annotation

295
        if ($query->/** @scrutinizer ignore-call */ isLimitQuery()) {
Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

401
            case 'avg'  :      return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getAvgExpression($column).' AS aggregate';

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.

Loading history...
402 588
            case 'count':      return $this->platform()->grammar()->getCountExpression($column).' AS aggregate';
0 ignored issues
show
Deprecated Code introduced by
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 ignore-deprecated  annotation

402
            case 'count':      return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getCountExpression($column).' AS aggregate';

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.

Loading history...
403 23
            case 'max'  :      return $this->platform()->grammar()->getMaxExpression($column).' AS aggregate';
0 ignored issues
show
Deprecated Code introduced by
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 ignore-deprecated  annotation

403
            case 'max'  :      return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getMaxExpression($column).' AS aggregate';

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.

Loading history...
404 20
            case 'min'  :      return $this->platform()->grammar()->getMinExpression($column).' AS aggregate';
0 ignored issues
show
Deprecated Code introduced by
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 ignore-deprecated  annotation

404
            case 'min'  :      return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getMinExpression($column).' AS aggregate';

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.

Loading history...
405 18
            case 'pagination': return $this->platform()->grammar()->getCountExpression($column).' AS aggregate';
0 ignored issues
show
Deprecated Code introduced by
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 ignore-deprecated  annotation

405
            case 'pagination': return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getCountExpression($column).' AS aggregate';

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.

Loading history...
406 3
            case 'sum'  :      return $this->platform()->grammar()->getSumExpression($column).' AS aggregate';
0 ignored issues
show
Deprecated Code introduced by
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 ignore-deprecated  annotation

406
            case 'sum'  :      return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getSumExpression($column).' AS aggregate';

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.

Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

718
                    return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));

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.

Loading history...
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 ignore-type  annotation

720
                return $this->compileInExpression($query, /** @scrutinizer ignore-type */ $value, $column, 'IN', $converted);
Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

726
                    return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));

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.

Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

733
                    return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), $this->compileExpressionValue($query, $value[0], $converted), $this->compileExpressionValue($query, $value[1], $converted));

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.

Loading history...
734
                }
735
                return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), '0', $this->compileExpressionValue($query, $value, $converted));
0 ignored issues
show
Deprecated Code introduced by
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 ignore-deprecated  annotation

735
                return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), '0', $this->compileExpressionValue($query, $value, $converted));

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.

Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

739
                return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getNotExpression($this->compileExpression($query, $column, ':between', $value, $converted));

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.

Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

746
                    return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));

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.

Loading history...
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
Deprecated Code introduced by
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 ignore-deprecated  annotation

756
                    return /** @scrutinizer ignore-deprecated */ $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));

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.

Loading history...
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
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
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
$values is always a sub-type of Bdf\Prime\Query\Expression\ExpressionInterface.
Loading history...
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