Passed
Pull Request — 2.1 (#71)
by Vincent
13:18 queued 06:40
created

SqlCompiler::doCompileDelete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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
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 938
    public function quoteIdentifier(CompilableClause $query, string $column): string
41
    {
42 938
        if (!$query->isQuoteIdentifier()) {
43 931
            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 669
    protected function doCompileInsert(CompilableClause $query)
71
    {
72 669
        $query->state()->currentPart = 0;
73
74 669
        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 668
        } elseif ($query->statements['replace'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('REPLACE')) {
81 2
            $insert = 'REPLACE INTO ';
82
        } else {
83 667
            $insert = 'INSERT INTO ';
84
        }
85
86 669
        foreach ($query->statements['tables'] as $table) {
87 668
            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 668
    protected function compileInsertData(CompilableClause $query)
102
    {
103
        // @todo Do not use QueryInterface
104 668
        if ($query->statements['values']['data'] instanceof QueryInterface) {
105 7
            return $this->compileInsertSelect($query);
106
        }
107
108 665
        list($columns, $values) = $this->compileInsertValues($query);
109
110 665
        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 665
    protected function compileInsertValues(CompilableClause $query)
155
    {
156 665
        $data = $query->statements['values'];
157
158 665
        $columns = [];
159 665
        $values = [];
160
161 665
        foreach ($data['data'] as $column => $value) {
162 665
            $type = $data['types'][$column] ?? true;
163 665
            $column = $query->preprocessor()->field($column, $type);
164
165
            // The type cannot be resolved by preprocessor
166 665
            if ($type === true) {
167 1
                $type = null;
168
            }
169
170 665
            $columns[] = $this->quoteIdentifier($query, $column);
171 665
            $values[]  = $this->compileTypedValue($query, $value, $type);
172
        }
173
174 665
        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 873
    protected function doCompileSelect(CompilableClause $query)
243
    {
244 873
        if ($this->isComplexAggregate($query)) {
245 3
            return $query->state()->compiled = $this->compileComplexAggregate($query);
246
        }
247
248 873
        if (!isset($query->state()->compiledParts['columns'])) {
249 873
            $query->state()->currentPart = 'columns';
250 873
            $query->state()->compiledParts['columns'] = $this->compileColumns($query);
251
        }
252
253 873
        if (!isset($query->state()->compiledParts['from'])) {
254 873
            $query->state()->compiledParts['from'] = $this->compileFrom($query);
255
        }
256
257 873
        if (!isset($query->state()->compiledParts['groups'])) {
258 873
            $query->state()->currentPart = 'groups';
259 873
            $query->state()->compiledParts['groups'] = $this->compileGroup($query);
260
        }
261
262 873
        if (!isset($query->state()->compiledParts['having'])) {
263 873
            $query->state()->currentPart = 'having';
264 873
            $query->state()->compiledParts['having'] = $this->compileHaving($query);
265
        }
266
267 873
        if (!isset($query->state()->compiledParts['orders'])) {
268 873
            $query->state()->currentPart = 'orders';
269 873
            $query->state()->compiledParts['orders'] = $this->compileOrder($query);
270
        }
271
272 873
        if (!isset($query->state()->compiledParts['where'])) {
273 873
            $query->state()->currentPart = 'where';
274 873
            $query->state()->compiledParts['where'] = $this->compileWhere($query);
275
        }
276
277 873
        if (!isset($query->state()->compiledParts['joins'])) {
278 873
            $query->state()->currentPart = 'joins';
279 873
            $query->state()->compiledParts['joins'] = $this->compileJoins($query);
280
        }
281
282 873
        if (!isset($query->state()->compiledParts['lock'])) {
283 873
            $query->state()->currentPart = 'lock';
284 873
            $query->state()->compiledParts['lock'] = $this->compileLock($query);
285
        }
286
287 873
        $sql = $query->state()->compiledParts['columns']
288 873
                .$query->state()->compiledParts['from']
289 873
                .$query->state()->compiledParts['joins']
290 873
                .$query->state()->compiledParts['where']
291 873
                .$query->state()->compiledParts['groups']
292 873
                .$query->state()->compiledParts['having']
293 873
                .$query->state()->compiledParts['orders'];
294
295 873
        if ($query->isLimitQuery()) {
0 ignored issues
show
Bug introduced by
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 285
            $sql = $this->platform()->grammar()->modifyLimitQuery($sql, $query->statements['limit'], $query->statements['offset']);
297
        }
298
299 873
        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 873
    protected function isComplexAggregate(CompilableClause $query)
311
    {
312 873
        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 873
    protected function compileColumns(CompilableClause $query)
341
    {
342 873
        if (!empty($query->statements['aggregate'])) {
343 589
            return 'SELECT '.$this->compileAggregate($query, $query->statements['aggregate'][0], $query->statements['aggregate'][1], $query->statements['distinct']);
344
        }
345
346 638
        if ($query->statements['distinct'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
347 8
            $select = 'SELECT DISTINCT ';
348
        } else {
349 631
            $select = 'SELECT ';
350
        }
351
352 638
        if (empty($query->statements['columns'])) {
353 572
            $root = $query->preprocessor()->root();
354
355 572
            if ($root) {
356 469
                $select .= $this->quoteIdentifier($query, $root).'.';
357
            }
358
359 572
            return $select.'*';
360
        }
361
362 77
        $sql = [];
363
364 77
        foreach ($query->statements['columns'] as $column) {
365 77
            $sql[] = $this->compileExpressionColumn($query, $column['column'], $column['alias']);
366
        }
367
368 77
        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 591
    protected function compileAggregate(CompilableClause $query, $function, $column, $distinct)
383
    {
384 591
        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 591
            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 589
            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 77
    protected function compileExpressionColumn(CompilableClause $query, $column, $alias = null)
425
    {
426 77
        if ($column instanceof QueryInterface) {
427 1
            return $this->compileSubQuery($query, $column, $alias);
428
        }
429
430 77
        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 70
        $column = $query->preprocessor()->field($column);
438
439 70
        if ($column[-1] === '*') {
440 1
            return $column;
441
        }
442
443 70
        return $alias !== null
444 9
            ? $this->quoteIdentifier($query, $column).' as '.$this->quoteIdentifier($query, $alias)
445 70
            : $this->quoteIdentifier($query, $column)
446 70
        ;
447
    }
448
449
    /**
450
     * @param SqlQueryInterface&CompilableClause $query
451
     *
452
     * @return string
453
     * @throws PrimeException
454
     */
455 873
    protected function compileFrom(CompilableClause $query)
456
    {
457 873
        $sql = ' FROM ';
458 873
        $isFirst = true;
459
460
        // Loop through all FROM clauses
461 873
        foreach ($this->compileTableAndAliasClause($query, $query->statements['tables']) as $from) {
462 873
            if (!$isFirst) {
463 8
                $sql .= ', ';
464
            } else {
465 873
                $isFirst = false;
466
            }
467
468 873
            $sql .= $from['sql'];
469
        }
470
471 873
        return $sql;
472
    }
473
474
    /**
475
     * @param SqlQueryInterface&CompilableClause $query
476
     *
477
     * @return string
478
     * @throws PrimeException
479
     */
480 873
    protected function compileJoins(CompilableClause $query)
481
    {
482 873
        if (empty($query->statements['joins'])) {
483 852
            return '';
484
        }
485
486 100
        $sql = '';
487
488 100
        foreach ($this->compileTableAndAliasClause($query, $query->statements['joins']) as $join) {
489 100
            $sql .= ' '.$join['type'].' JOIN '.$join['sql'].' ON '.$this->compileCompilableClauses($query, $join['on']);
490
        }
491
492 100
        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 873
    protected function compileTableAndAliasClause(CompilableClause $query, array $clauses): array
512
    {
513 873
        $databasePrefix = $this->getDatabaseNamePrefix($query);
514 873
        $compiled = [];
515
516 873
        foreach ($clauses as $from) {
517 873
            if ($from['table'] instanceof QueryInterface) {
518 3
                $from['sql'] = $this->compileSubQuery($query, $from['table'], $from['alias']);
519 3
                $compiled[] = $from;
520
            } else {
521 873
                $from = $query->preprocessor()->table($from);
522
523 873
                if ($from['alias'] === null) {
524 690
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']);
525 690
                    $compiled[$from['table']] = $from;
526
                } else {
527 569
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']) . ' ' . $this->quoteIdentifier($query, $from['alias']);
528 569
                    $compiled[$from['alias']] = $from;
529
                }
530
            }
531
        }
532
533 873
        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 873
    protected function getDatabaseNamePrefix(CompilableClause $query): string
544
    {
545 873
        if ($query instanceof CommandInterface && $query->compiler() !== $this && $query->connection()->getDatabase() !== $this->connection->getDatabase()) {
546 2
            return $query->connection()->getDatabase().'.';
547
        }
548
549 872
        return '';
550
    }
551
552
    /**
553
     * Compile Where sql
554
     *
555
     * @param SqlQueryInterface&CompilableClause $query
556
     *
557
     * @return string
558
     * @throws PrimeException
559
     */
560 884
    protected function compileWhere(CompilableClause $query)
561
    {
562 884
        if (empty($query->statements['where'])) {
563 736
            return '';
564
        }
565
566 524
        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 873
    protected function compileHaving(CompilableClause $query)
578
    {
579 873
        if (empty($query->statements['having'])) {
580 864
            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 538
    protected function compileCompilableClauses(CompilableClause $query, array &$clauses)
594
    {
595 538
        $sql = [];
596 538
        $i = 0;
597
598
        // Permet de retirer le niveau du nested
599 538
        if (count($clauses) === 1 && isset($clauses[0]['nested'])) {
600 217
            $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 217
            if (count($clauses) === 1) {
607 213
                return $result;
608
            }
609
610
            // Add the nested level
611 4
            $sql[] = '('.$result.')';
612 4
            $i = 1;
613
        }
614
615 538
        $clauses[0]['glue'] = null;
616
617
        //Cannot use foreach because where expression can add new relations with constraints
618 538
        for (; isset($clauses[$i]); ++$i) {
619 538
            $part = $clauses[$i];
620
621 538
            if ($part['glue'] !== null) {
622 161
                $part['glue'] .= ' ';
623
            }
624
625 538
            $part = $query->preprocessor()->expression($part);
626
627 538
            if (isset($part['nested'])) {
628 108
                $sql[] = $part['glue'].'('.$this->compileCompilableClauses($query, $part['nested']).')';
629 538
            } elseif (!isset($part['raw'])) {
630 528
                $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 538
        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 528
    protected function compileExpression(CompilableClause $query, string $column, string $operator, $value, bool $converted): string
654
    {
655 528
        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 528
            case '<':
667 528
            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 528
            case '<=':
674 528
            case ':lte':
675 1
                if (is_array($value)) {
676
                    return $this->compileIntoExpression($query, $value, $column, '<=', $converted);
677
                }
678 1
                return $this->quoteIdentifier($query, $column).' <= '.$this->compileExpressionValue($query, $value, $converted);
679
680 528
            case '>':
681 513
            case ':gt':
682 19
                if (is_array($value)) {
683
                    return $this->compileIntoExpression($query, $value, $column, '>', $converted);
684
                }
685 19
                return $this->quoteIdentifier($query, $column).' > '.$this->compileExpressionValue($query, $value, $converted);
686
687 513
            case '>=':
688 512
            case ':gte':
689 3
                if (is_array($value)) {
690
                    return $this->compileIntoExpression($query, $value, $column, '>=', $converted);
691
                }
692 3
                return $this->quoteIdentifier($query, $column).' >= '.$this->compileExpressionValue($query, $value, $converted);
693
694 512
            case '~=':
695 512
            case '=~':
696 512
            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 512
            case ':like':
703 31
                if (is_array($value)) {
704 4
                    return $this->compileIntoExpression($query, $value, $column, 'LIKE', $converted);
705
                }
706 27
                return $this->quoteIdentifier($query, $column).' LIKE '.$this->compileExpressionValue($query, $value, $converted);
707
708 499
            case ':notlike':
709 499
            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 498
            case 'in':
716 490
            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
Bug introduced by
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 484
            case 'notin':
723 484
            case '!in':
724 483
            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 482
            case 'between':
731 482
            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 477
            case '!between':
738 477
            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 476
            case '<>':
742 476
            case '!=':
743 471
            case ':ne':
744 471
            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 471
            case '=':
754
            case ':eq':
755 471
                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 464
                if (is_array($value)) {
759 163
                    return $this->compileExpression($query, $column, ':in', $value, $converted);
760
                }
761 399
                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 443
    protected function compileExpressionValue(CompilableClause $query, $value, bool $converted)
779
    {
780 443
        if ($value instanceof QueryInterface) {
781
            return $this->compileSubQuery($query, $value);
782
        }
783
784 443
        if ($value instanceof ExpressionInterface) {
785 104
            return $value->build($query, $this);
786
        }
787
788 428
        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 676
    protected function compileTypedValue(CompilableClause $query, $value, ?TypeInterface $type)
802
    {
803 676
        if ($value instanceof QueryInterface) {
804
            return $this->compileSubQuery($query, $value);
805
        }
806
807 676
        if ($value instanceof ExpressionInterface) {
808 1
            return $value->build($query, $this);
809
        }
810
811 676
        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
introduced by
$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 873
    protected function compileGroup(CompilableClause $query)
949
    {
950 873
        if (empty($query->statements['groups'])) {
951 872
            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 873
    protected function compileOrder(CompilableClause $query)
972
    {
973 873
        if (empty($query->statements['orders'])) {
974 817
            return '';
975
        }
976
977 63
        $sql = [];
978
979 63
        foreach ($query->statements['orders'] as $part) {
980 63
            if ($part['sort'] instanceof ExpressionInterface) {
981
                $part['sort'] = $part['sort']->build($query, $this);
982
            } else {
983 63
                $part['sort'] = $this->quoteIdentifier($query, $query->preprocessor()->field($part['sort']));
984
            }
985
986 63
            $sql[] = $part['sort'].' '.$part['order'];
987
        }
988
989 63
        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 873
    protected function compileLock(CompilableClause $query)
1003
    {
1004 873
        $lock = $query->statements['lock'];
1005
1006
        // The lock should not be applied on aggregate function
1007 873
        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 871
        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 710
    protected function bindTyped(CompilableClause $query, $value, ?TypeInterface $type)
1057
    {
1058 710
        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 809
    protected function bindRaw(CompilableClause $query, $value)
1076
    {
1077 809
        $query->state()->bind($value);
1078
1079 809
        return '?';
1080
    }
1081
1082
    /**
1083
     * {@inheritdoc}
1084
     */
1085 848
    public function getBindings(CompilableClause $query): array
1086
    {
1087 848
        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 848
    protected function mergeBindings($bindings)
1098
    {
1099 848
        $mergedBindings = [];
1100
1101 848
        if (isset($bindings[0])) {
1102 673
            $mergedBindings = $bindings[0];
1103
        } else {
1104 793
            foreach (['columns', 'joins', 'where', 'groups', 'having', 'orders'] as $part) {
1105 793
                if (isset($bindings[$part])) {
1106 442
                    $mergedBindings = array_merge($mergedBindings, $bindings[$part]);
1107
                }
1108
            }
1109
        }
1110
1111 848
        return $mergedBindings;
1112
    }
1113
}
1114