Passed
Push — master ( efd9d8...e473b2 )
by Sébastien
02:47 queued 13s
created

SqlCompiler::bindRaw()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Bdf\Prime\Query\Compiler;
4
5
use Bdf\Prime\Query\CommandInterface;
6
use Bdf\Prime\Query\CompilableClause;
7
use Bdf\Prime\Query\Expression\ExpressionInterface;
8
use Bdf\Prime\Query\Expression\ExpressionTransformerInterface;
9
use Bdf\Prime\Query\QueryInterface;
10
use Bdf\Prime\Types\TypeInterface;
11
use Doctrine\DBAL\LockMode;
12
use Doctrine\DBAL\Query\Expression\CompositeExpression;
13
use UnexpectedValueException;
14
15
/**
16
 * SqlCompiler
17
 *
18
 * @author seb
0 ignored issues
show
Coding Style Documentation introduced by
@author tag is not allowed in class comment
Loading history...
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 1
Loading history...
19
 * @package Bdf\Prime\Query\Compiler
0 ignored issues
show
Coding Style Documentation introduced by
@package tag is not allowed in class comment
Loading history...
20
 */
21
class SqlCompiler extends AbstractCompiler
22
{
23
    /**
24
     * Quote a value
25
     * 
26
     * @param string $value
27
     *
28
     * @return string
29
     */
30 3
    public function quote($value)
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line before function; 0 found
Loading history...
31
    {
32 3
        return $this->connection->quote($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

32
        return $this->connection->/** @scrutinizer ignore-call */ quote($this->autoConvertValue($value));
Loading history...
33
    }
34
    
35
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
36
     * {@inheritdoc}
37
     */
38 676
    public function quoteIdentifier(CompilableClause $query, $column)
39
    {
40 676
        if (!$query->isQuoteIdentifier()) {
41 670
            return $column;
42
        }
43
44 8
        return $this->platform()->grammar()->quoteIdentifier($column);
45
    }
46
    
47
    /**
48
     * Quote a identifier on multiple columns
49
     *
50
     * @param CompilableClause $query
51
     * @param array $columns
52
     *
53
     * @return array
54
     */
55 1
    public function quoteIdentifiers(CompilableClause $query, array $columns)
56
    {
57 1
        if (!$query->isQuoteIdentifier()) {
58 1
            return $columns;
59
        }
60
61 1
        return array_map([$this->platform()->grammar(), 'quoteIdentifier'], $columns);
62
    }
63
    
64
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
65
     * {@inheritdoc}
66
     */
67 447
    protected function doCompileInsert(CompilableClause $query)
68
    {
69 447
        $query->state()->currentPart = 0;
70
71 447
        if ($query->statements['ignore'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('IGNORE')) {
72 1
            if ($this->platform()->grammar()->getName() === 'sqlite') {
73 1
                $insert = 'INSERT OR IGNORE INTO ';
74
            } else {
75 1
                $insert = 'INSERT IGNORE INTO ';
76
            }
77 446
        } elseif ($query->statements['replace'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('REPLACE')) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
78 2
            $insert = 'REPLACE INTO ';
79
        } else {
80 445
            $insert = 'INSERT INTO ';
81
        }
82
83 447
        return $query->state()->compiled = $insert.$this->quoteIdentifier($query, $query->statements['tables'][0]['table']).$this->compileInsertData($query);
0 ignored issues
show
Coding Style introduced by
Assignments must be the first block of code on a line
Loading history...
84
    }
85
86
    /**
87
     * Compile the data part of the insert query
88
     *
89
     * @param CompilableClause $query
90
     *
91
     * @return string
92
     */
93 447
    protected function compileInsertData(CompilableClause $query)
94
    {
95
        // @todo Do not use QueryInterface
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
96 447
        if ($query->statements['values']['data'] instanceof QueryInterface) {
97 7
            return $this->compileInsertSelect($query);
98
        }
99
100 444
        list($columns, $values) = $this->compileInsertValues($query);
101
102 444
        return ' ('.implode(', ', $columns).') VALUES('.implode(', ', $values).')';
103
    }
104
105
    /**
106
     * Compile an INSERT INTO ... SELECT ... query
107
     *
108
     * @param CompilableClause $query
109
     *
110
     * @return string
111
     */
112 7
    protected function compileInsertSelect(CompilableClause $query)
113
    {
114 7
        $select = clone $query->statements['values']['data']; // Clone the query for ensure that it'll not be modified
0 ignored issues
show
Coding Style introduced by
Comments may not appear after statements
Loading history...
115 7
        $columns = [];
116
117
        // Columns are defined on the select query
118
        // Alias of the selected columns will be concidered as the INSERT table columns
119 7
        if ($select->statements['columns'] && $select->statements['columns'][0]['column'] !== '*') {
120 3
            foreach ($select->statements['columns'] as &$column) {
121 3
                $alias = $query->preprocessor()->field($column['alias'] ?? $column['column']);
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
122
123
                // Modify the column alias to match with the INSERT column
124 3
                $column['alias'] = $alias;
125
126 3
                $columns[] = $this->quoteIdentifier($query, $alias);
127
            }
128
        }
129
130 7
        $sql = ' '.$this->compileSelect($select); // @todo Ensure that the query is sql compilable
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
Coding Style introduced by
Comments may not appear after statements
Loading history...
131 7
        $this->addQueryBindings($query, $select);
132
133 7
        return empty($columns) ? $sql : ' ('.implode(', ', $columns).')'.$sql;
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
134
    }
135
136
    /**
137
     * Compile columns and values to insert
138
     *
139
     * @param CompilableClause $query
140
     *
141
     * @return array
142
     */
143 444
    protected function compileInsertValues(CompilableClause $query)
144
    {
145 444
        $data = $query->statements['values'];
146
147 444
        $columns = [];
148 444
        $values = [];
149
150 444
        foreach ($data['data'] as $column => $value) {
151 444
            $type = $data['types'][$column] ?? true;
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
152 444
            $column = $query->preprocessor()->field($column, $type);
153
154
            // The type cannot be resolved by preprocessor
155 444
            if ($type === true) {
156 1
                $type = null;
157
            }
158
159 444
            $columns[] = $this->quoteIdentifier($query, $column);
160 444
            $values[]  = $this->compileTypedValue($query, $value, $type);
161
        }
162
163 444
        return [$columns, $values];
164
    }
165
166
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
167
     * {@inheritdoc}
168
     */
169 23
    protected function doCompileUpdate(CompilableClause $query)
170
    {
171 23
        $query->state()->currentPart = 0;
172
173 23
        $values = $this->compileUpdateValues($query);
174
175 23
        return $query->state()->compiled = 'UPDATE '
0 ignored issues
show
Coding Style introduced by
Assignments must be the first block of code on a line
Loading history...
176 23
            . $this->quoteIdentifier($query, $query->statements['tables'][0]['table'])
177 23
            . ' SET ' . implode(', ', $values)
178 23
            . $this->compileWhere($query);
179
    }
180
181
    /**
182
     * Compile columns and values to update
183
     *
184
     * @param CompilableClause $query
185
     *
186
     * @return array
187
     */
188 23
    protected function compileUpdateValues(CompilableClause $query)
189
    {
190 23
        $data = $query->statements['values'];
191 23
        $values = [];
192
193 23
        foreach ($data['data'] as $column => $value) {
194 23
            $type = $data['types'][$column] ?? true;
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
195 23
            $column = $query->preprocessor()->field($column, $type);
196
197 23
            $values[] = $this->quoteIdentifier($query, $column)
198 23
                . ' = '
199 23
                . $this->compileTypedValue($query, $value, $type);
200
        }
201
202 23
        return $values;
203
    }
204
205
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
206
     * {@inheritdoc}
207
     */
208 28
    protected function doCompileDelete(CompilableClause $query)
209
    {
210 28
        $query->state()->currentPart = 0;
211
212 28
        return $query->state()->compiled = 'DELETE FROM '
0 ignored issues
show
Coding Style introduced by
Assignments must be the first block of code on a line
Loading history...
213 28
            . $this->quoteIdentifier($query, $query->statements['tables'][0]['table'])
214 28
            . $this->compileWhere($query);
215
    }
216
    
217
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
218
     * {@inheritdoc}
219
     */
220 614
    protected function doCompileSelect(CompilableClause $query)
221
    {
222 614
        if ($this->isComplexAggregate($query)) {
223 3
            return $query->state()->compiled = $this->compileComplexAggregate($query);
0 ignored issues
show
Coding Style introduced by
Assignments must be the first block of code on a line
Loading history...
224
        }
225
226 614
        if (!isset($query->state()->compiledParts['columns'])) {
227 614
            $query->state()->currentPart = 'columns';
228 614
            $query->state()->compiledParts['columns'] = $this->compileColumns($query);
229
        }
230
231 614
        if (!isset($query->state()->compiledParts['from'])) {
232 614
            $query->state()->compiledParts['from'] = $this->compileFrom($query);
233
        }
234
235 614
        if (!isset($query->state()->compiledParts['groups'])) {
236 614
            $query->state()->currentPart = 'groups';
237 614
            $query->state()->compiledParts['groups'] = $this->compileGroup($query);
238
        }
239
240 614
        if (!isset($query->state()->compiledParts['having'])) {
241 614
            $query->state()->currentPart = 'having';
242 614
            $query->state()->compiledParts['having'] = $this->compileHaving($query);
243
        }
244
245 614
        if (!isset($query->state()->compiledParts['orders'])) {
246 614
            $query->state()->currentPart = 'orders';
247 614
            $query->state()->compiledParts['orders'] = $this->compileOrder($query);
248
        }
249
250 614
        if (!isset($query->state()->compiledParts['where'])) {
251 614
            $query->state()->currentPart = 'where';
252 614
            $query->state()->compiledParts['where'] = $this->compileWhere($query);
253
        }
254
255 614
        if (!isset($query->state()->compiledParts['joins'])) {
256 614
            $query->state()->currentPart = 'joins';
257 614
            $query->state()->compiledParts['joins'] = $this->compileJoins($query);
258
        }
259
260 614
        if (!isset($query->state()->compiledParts['lock'])) {
261 614
            $query->state()->currentPart = 'lock';
262 614
            $query->state()->compiledParts['lock'] = $this->compileLock($query);
263
        }
264
265 614
        $sql = $query->state()->compiledParts['columns']
266 614
                .$query->state()->compiledParts['from']
267 614
                .$query->state()->compiledParts['joins']
268 614
                .$query->state()->compiledParts['where']
269 614
                .$query->state()->compiledParts['groups']
270 614
                .$query->state()->compiledParts['having']
271 614
                .$query->state()->compiledParts['orders'];
272
273 614
        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

273
        if ($query->/** @scrutinizer ignore-call */ isLimitQuery()) {
Loading history...
274 179
            $sql = $this->platform()->grammar()->modifyLimitQuery($sql, $query->statements['limit'], $query->statements['offset']);
275
        }
276
277 614
        return $query->state()->compiled = $sql.$query->state()->compiledParts['lock'];
0 ignored issues
show
Coding Style introduced by
Assignments must be the first block of code on a line
Loading history...
278
    }
279
280
    /**
281
     * Check if the the query is an aggregate which requires to execute the query as temporary table
282
     * A temporary table is required for DISTINT aggregate with wildcard "*" column
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
283
     *
284
     * @param CompilableClause $query
285
     *
286
     * @return bool
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
287
     */
288 614
    protected function isComplexAggregate(CompilableClause $query)
289
    {
290 614
        return isset($query->statements['aggregate']) && $query->statements['aggregate'][1] === '*' && $query->statements['distinct'];
0 ignored issues
show
Coding Style introduced by
Boolean operators are not allowed outside of control structure conditions
Loading history...
291
    }
292
293
    /**
294
     * Compile the complexe aggregate query
295
     * Will generate a query in form : "SELECT [aggregate](*) FROM ([query])"
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
296
     *
297
     * @param CompilableClause $query
298
     *
299
     * @return string
300
     */
301 3
    protected function compileComplexAggregate(CompilableClause $query)
302
    {
303 3
        list($function, $column) = $query->statements['aggregate'];
304
305 3
        $query->statements['aggregate'] = null;
306 3
        $query->statements['columns'] = $column === '*' ? [] : [['column' => $column, 'alias' => null]];
0 ignored issues
show
Coding Style introduced by
The value of a comparison must not be assigned to a variable
Loading history...
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
307
308 3
        return 'SELECT '.$this->compileAggregate($query, $function, '*', false).' FROM ('.$this->doCompileSelect($query).') as derived_query';
309
    }
310
311
    /**
312
     * @param CompilableClause $query
313
     *
314
     * @return string
315
     */
316 614
    protected function compileColumns(CompilableClause $query)
317
    {
318 614
        if (!empty($query->statements['aggregate'])) {
319 368
            return 'SELECT '.$this->compileAggregate($query, $query->statements['aggregate'][0], $query->statements['aggregate'][1], $query->statements['distinct']);
320
        }
321
322 445
        if ($query->statements['distinct'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
323 8
            $select = 'SELECT DISTINCT ';
324
        } else {
325 438
            $select = 'SELECT ';
326
        }
327
        
328 445
        if (empty($query->statements['columns'])) {
329 388
            $root = $query->preprocessor()->root();
330
331 388
            if ($root) {
332 292
                $select .= $this->quoteIdentifier($query, $root).'.';
333
            }
334
335 388
            return $select.'*';
336
        }
337
338 66
        $sql = [];
339
        
340 66
        foreach ($query->statements['columns'] as $column) {
341 66
            $sql[] = $this->compileExpressionColumn($query, $column['column'], $column['alias']);
342
        }
343
        
344 66
        return $select.implode(', ', $sql);
345
    }
346
347
    /**
348
     * Compile a SQL function
349
     *
350
     * @param CompilableClause $query
351
     * @param string $function  The sql function
352
     * @param string $column    The column to aggregate
353
     * @param bool   $distinct  The distinct status
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
354
     *
355
     * @return string
356
     */
357 370
    protected function compileAggregate(CompilableClause $query, $function, $column, $distinct)
358
    {
359 370
        if ($column !== '*') {
360 14
            $column = $query->preprocessor()->field($column);
361 14
            $column = $this->quoteIdentifier($query, $column);
362
363 14
            if ($distinct && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
364
                // Le count ne compte pas les fields qui ont une valeur NULL.
365
                // Pour une pagination, il est important de compter les valeurs null sachant qu'elles seront sélectionnées.
366
                // La pagination utilise une column que pour le distinct.
367 12
                if ($function === 'pagination') {
368 4
                    $column = 'IFNULL('.$column.',"___null___")';
369
                }
370
371 12
                $column = 'DISTINCT '.$column;
372
            }
373
        }
374
375
        switch ($function) {
376 370
            case 'avg'  :      return $this->platform()->grammar()->getAvgExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Case breaking statements must be followed by a single blank line
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
There must be no space before the colon in a CASE statement
Loading history...
377 368
            case 'count':      return $this->platform()->grammar()->getCountExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Case breaking statements must be followed by a single blank line
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
378 21
            case 'max'  :      return $this->platform()->grammar()->getMaxExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement
Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Case breaking statements must be followed by a single blank line
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
379 18
            case 'min'  :      return $this->platform()->grammar()->getMinExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Case breaking statements must be followed by a single blank line
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
There must be no space before the colon in a CASE statement
Loading history...
380 16
            case 'pagination': return $this->platform()->grammar()->getCountExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Case breaking statements must be followed by a single blank line
Loading history...
381 3
            case 'sum'  :      return $this->platform()->grammar()->getSumExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement
Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
382
383
            default:
384 1
                $method = 'get'.ucfirst($function).'Expression';
385 1
                return $this->platform()->grammar()->{$method}($column).' AS aggregate';
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
386
        }
387
    }
388
389
    /**
390
     * Compile expression column
391
     *
392
     * @param CompilableClause $query
393
     * @param mixed $column
394
     * @param string $alias
395
     * 
396
     * @return string
397
     */
398 66
    protected function compileExpressionColumn(CompilableClause $query, $column, $alias = null)
399
    {
400 66
        if ($column instanceof QueryInterface) {
401 1
            return $this->compileSubQuery($query, $column, $alias);
402
        }
403
        
404 66
        if ($column instanceof ExpressionInterface) {
405 2
            return $alias !== null
406
                ? $column->build($query, $this).' as '.$this->quoteIdentifier($query, $alias)
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement must be declared on a single line
Loading history...
407 2
                : $column->build($query, $this)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
408
            ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
409
        }
410
411 65
        $column = $query->preprocessor()->field($column);
412
        
413 65
        if (strpos($column, '*') !== false) {
414
            return $column;
415
        }
416
        
417 65
        return $alias !== null
418 5
            ? $this->quoteIdentifier($query, $column).' as '.$this->quoteIdentifier($query, $alias)
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement must be declared on a single line
Loading history...
419 65
            : $this->quoteIdentifier($query, $column)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
420
        ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
421
    }
422
    
423
    /**
424
     * @param CompilableClause $query
425
     *
426
     * @return string
427
     */
428 614
    protected function compileFrom(CompilableClause $query)
429
    {
430 614
        $sql = [];
431 614
        $databasePrefix = $this->getDatabaseNamePrefix($query);
432
433
        // Loop through all FROM clauses
434 614
        foreach ($query->statements['tables'] as $from) {
435 614
            if ($from['table'] instanceof QueryInterface) {
436 2
                $sql[] = $this->compileSubQuery($query, $from['table'], $from['alias']);
437
            } else {
438 614
                $from = $query->preprocessor()->table($from);
439
440 614
                if ($from['alias'] === null) {
441 464
                    $sql[$from['table']] = $this->quoteIdentifier($query, $databasePrefix.$from['table']);
442
                } else {
443 614
                    $sql[$from['alias']] = $this->quoteIdentifier($query, $databasePrefix.$from['table']) . ' ' . $this->quoteIdentifier($query, $from['alias']);
444
                }
445
            }
446
        }
447
448 614
        return ' FROM '.implode(', ', $sql);
449
    }
450
451
    /**
452
     * @param CompilableClause $query
453
     *
454
     * @return string
455
     */
456 614
    protected function compileJoins(CompilableClause $query)
457
    {
458 614
        if (empty($query->statements['joins'])) {
459 594
            return '';
460
        }
461
        
462 50
        $sql = [];
463 50
        $databasePrefix = $this->getDatabaseNamePrefix($query);
464
465 50
        foreach ($query->statements['joins'] as $join) {
466 50
            $join = $query->preprocessor()->table($join);
467
468 50
            if ($join['alias'] === null) {
469
                $sql[$join['table']] = $join['type']
470
                    . ' JOIN ' . $this->quoteIdentifier($query, $databasePrefix.$join['table'])
471
                    . ' ON '   . $this->compileCompilableClauses($query, $join['on']);
472
            } else {
473 50
                $sql[$join['alias']] = $join['type']
474 50
                    . ' JOIN ' . $this->quoteIdentifier($query, $databasePrefix.$join['table']).' '.$this->quoteIdentifier($query, $join['alias'])
475 50
                    . ' ON '   . $this->compileCompilableClauses($query, $join['on']);
476
            }
477
        }
478
479 50
        return ' '.implode(' ', $sql);
480
    }
481
482
    /**
483
     * Adding database prefix for sub query x-db
484
     *
485
     * @param CompilableClause $query
486
     *
487
     * @return string
488
     */
489 614
    protected function getDatabaseNamePrefix(CompilableClause $query): string
490
    {
491 614
        if ($query instanceof CommandInterface && $query->compiler() !== $this && $query->connection()->getDatabase() !== $this->connection->getDatabase()) {
492 2
            return $query->connection()->getDatabase().'.';
493
        }
494
495 613
        return '';
496
    }
497
498
    /**
499
     * Compile Where sql
500
     * 
501
     * @param CompilableClause $query
502
     * 
503
     * @return string
504
     */
505 625
    protected function compileWhere(CompilableClause $query)
506
    {
507 625
        if (empty($query->statements['where'])) {
508 498
            return '';
509
        }
510
        
511 329
        return ' WHERE '.$this->compileCompilableClauses($query, $query->statements['where']);
512
    }
513
514
    /**
515
     * Compile having sql
516
     * 
517
     * @param CompilableClause $query
518
     * 
519
     * @return string
520
     */
521 614
    protected function compileHaving(CompilableClause $query)
522
    {
523 614
        if (empty($query->statements['having'])) {
524 605
            return '';
525
        }
526
        
527 9
        return ' HAVING '.$this->compileCompilableClauses($query, $query->statements['having']);
528
    }
529
530
    /**
531
     * @param CompilableClause $query
532
     * @param array $clauses
533
     *
534
     * @return string
535
     */
536 341
    protected function compileCompilableClauses(CompilableClause $query, array &$clauses)
537
    {
538 341
        $sql = [];
539 341
        $i = 0;
540
541
        // Permet de retirer le niveau du nested
542 341
        if (count($clauses) === 1 && isset($clauses[0]['nested'])) {
543 149
            $result = $this->compileCompilableClauses($query, $clauses[0]['nested']);
544
            /*
0 ignored issues
show
Coding Style introduced by
Empty line required before block comment
Loading history...
545
             * We check he if where expression has added constraints (from relation).
0 ignored issues
show
introduced by
Unnecessarily gendered language in a comment
Loading history...
546
             * If we still have one clause, we return the compiled sql
547
             * Otherwise we start the loop of clauses.
548
             */
0 ignored issues
show
Coding Style introduced by
Empty line required after block comment
Loading history...
549 149
            if (count($clauses) === 1) {
550 147
                return $result;
551
            }
552
553
            // Add the nested level
554 2
            $sql[] = '('.$result.')';
555 2
            $i = 1;
556
        }
557
558 341
        $clauses[0]['glue'] = null;
559
560
        //Cannot use foreach because where expression can add new relations with constraints
0 ignored issues
show
Coding Style introduced by
No space found before comment text; expected "// Cannot use foreach because where expression can add new relations with constraints" but found "//Cannot use foreach because where expression can add new relations with constraints"
Loading history...
561 341
        for (; isset($clauses[$i]); ++$i) {
562 341
            $part = $clauses[$i];
563
564 341
            if ($part['glue'] !== null) {
565 100
                $part['glue'] .= ' ';
566
            }
567
568 341
            $part = $query->preprocessor()->expression($part);
569
            
570 341
            if (isset($part['nested'])) {
571 64
                $sql[] = $part['glue'].'('.$this->compileCompilableClauses($query, $part['nested']).')';
572 341
            } elseif (!isset($part['raw'])) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
573 331
                $sql[] = $part['glue'].$this->compileExpression($query, $part['column'], $part['operator'], $part['value'], $part['converted'] ?? false);
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
574
            } else {
575 10
                $sql[] = $part['glue'].$this->compileRawValue($query, $part['raw']);
576
            }
577
        }
578
        
579 341
        return implode(' ', $sql);
580
    }
581
582
    /**
583
     * Determine which operator to use based on custom and standard syntax
584
     *
585
     * @param CompilableClause $query
586
     * @param string $column
587
     * @param string $operator
588
     * @param mixed  $value
589
     * @param bool $converted
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
590
     * 
591
     * @return string  operator found
592
     * 
593
     * @throws UnexpectedValueException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
594
     */
595 331
    protected function compileExpression(CompilableClause $query, $column, $operator, $value, $converted)
596
    {
597 331
        if ($value instanceof ExpressionTransformerInterface) {
598 6
            $value->setContext($this, $column, $operator);
599
600 6
            $column    = $value->getColumn();
601 6
            $operator  = $value->getOperator();
602 6
            $value     = $value->getValue();
603 6
            $converted = true;
604
        }
605
606
        switch ($operator) {
607 331
            case '<':
608 331
            case ':lt':
609 1
                if (is_array($value)) {
610
                    return $this->compileIntoExpression($query, $value, $column, '<', $converted);
611
                }
612 1
                return $this->quoteIdentifier($query, $column).' < '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
613
614 331
            case '<=':
615 331
            case ':lte':
616
                if (is_array($value)) {
617
                    return $this->compileIntoExpression($query, $value, $column, '<=', $converted);
618
                }
619
                return $this->quoteIdentifier($query, $column).' <= '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
620
621 331
            case '>':
622 326
            case ':gt':
623 6
                if (is_array($value)) {
624
                    return $this->compileIntoExpression($query, $value, $column, '>', $converted);
625
                }
626 6
                return $this->quoteIdentifier($query, $column).' > '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
627
628 326
            case '>=':
629 326
            case ':gte':
630 2
                if (is_array($value)) {
631
                    return $this->compileIntoExpression($query, $value, $column, '>=', $converted);
632
                }
633 2
                return $this->quoteIdentifier($query, $column).' >= '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
634
635
            // REGEX matching
636 326
            case '~=':
637 326
            case '=~':
638 326
            case ':regex':
639
                if (is_array($value)) {
640
                    return $this->compileIntoExpression($query, $value, $column, 'REGEXP', $converted);
641
                }
642
                return $this->quoteIdentifier($query, $column).' REGEXP '.$this->compileExpressionValue($query, (string)$value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
Coding Style introduced by
Expected 1 space(s) after cast statement; 0 found
Loading history...
643
644
            // LIKE
645 326
            case ':like':
646 22
                if (is_array($value)) {
647 4
                    return $this->compileIntoExpression($query, $value, $column, 'LIKE', $converted);
648
                }
649 18
                return $this->quoteIdentifier($query, $column).' LIKE '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
650
651
            // NOT LIKE
652 315
            case ':notlike':
653 315
            case '!like':
654 1
                if (is_array($value)) {
655 1
                    return $this->compileIntoExpression($query, $value, $column, 'NOT LIKE', $converted, CompositeExpression::TYPE_AND);
656
                }
657 1
                return $this->quoteIdentifier($query, $column).' NOT LIKE '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
658
659
            // In
660 314
            case 'in':
661 306
            case ':in':
662 94
                if (empty($value)) {
663 2
                    return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));
664
                }
665 93
                return $this->compileInExpression($query, $value, $column, 'IN', $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
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

665
                return $this->compileInExpression($query, /** @scrutinizer ignore-type */ $value, $column, 'IN', $converted);
Loading history...
666
667
            // Not in
668 300
            case 'notin':
669 300
            case '!in':
670 299
            case ':notin':
671 4
                if (empty($value)) {
672 2
                    return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));
673
                }
674 3
                return $this->compileInExpression($query, $value, $column, 'NOT IN', $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
675
676
            // Between
677 298
            case 'between':
678 298
            case ':between':
679 4
                if (is_array($value)) {
680 4
                    return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), $this->compileExpressionValue($query, $value[0], $converted), $this->compileExpressionValue($query, $value[1], $converted));
681
                }
682
                return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), 0, $this->compileExpressionValue($query, $value, $converted));
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
683
684
            // Not between
685 295
            case '!between':
686 295
            case ':notbetween':
687 1
                return $this->platform()->grammar()->getNotExpression($this->compileExpression($query, $column, ':between', $value, $converted));
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
688
689
            // Not equal
690 294
            case '<>':
691 294
            case '!=':
692 289
            case ':ne':
693 289
            case ':not':
694 15
                if (is_null($value)) {
695 6
                    return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));
696
                }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
697 9
                if (is_array($value)) {
698 1
                    return $this->compileExpression($query, $column, ':notin', $value, $converted);
699
                }
700 8
                return $this->quoteIdentifier($query, $column).' != '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
701
702
            // Equals
703 289
            case '=':
704
            case ':eq':
705 289
                if (is_null($value)) {
706 16
                    return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));
707
                }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
708 282
                if (is_array($value)) {
709 76
                    return $this->compileExpression($query, $column, ':in', $value, $converted);
710
                }
711 251
                return $this->quoteIdentifier($query, $column).' = '.$this->compileExpressionValue($query, $value, $converted);
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
712
                
713
            // Unsupported operator
714
            default:
715
                throw new UnexpectedValueException("Unsupported operator '" . $operator . "' in WHERE clause");
0 ignored issues
show
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
716
        }
0 ignored issues
show
Coding Style introduced by
End comment for long condition not found; expected "//end switch"
Loading history...
717
    }
718
719
    /**
720
     * Compile expression value
721
     *
722
     * @param CompilableClause $query
723
     * @param mixed $value
724
     * @param bool $converted Does the value is already converted to database ?
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
725
     * 
726
     * @return string
727
     */
728 278
    protected function compileExpressionValue(CompilableClause $query, $value, bool $converted)
729
    {
730 278
        if ($value instanceof QueryInterface) {
731
            return $this->compileSubQuery($query, $value);
732
        }
733
734 278
        if ($value instanceof ExpressionInterface) {
735 54
            return $value->build($query, $this);
736
        }
737
738 265
        return $converted ? $this->bindRaw($query, $value) : $this->bindTyped($query, $value, null);
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
739
    }
740
741
    /**
742
     * Compile expression value with type
743
     *
744
     * @param CompilableClause $query
745
     * @param mixed $value
746
     * @param TypeInterface|null $type The type. If null it will be resolved from value
747
     *
748
     * @return string
749
     */
750 454
    protected function compileTypedValue(CompilableClause $query, $value, ?TypeInterface $type)
751
    {
752 454
        if ($value instanceof QueryInterface) {
753
            return $this->compileSubQuery($query, $value);
754
        }
755
756 454
        if ($value instanceof ExpressionInterface) {
757 1
            return $value->build($query, $this);
758
        }
759
760 454
        return $this->bindTyped($query, $value, $type);
761
    }
762
763
    /**
764
     * Compile raw expression value
765
     *
766
     * @param CompilableClause $query
767
     * @param mixed $value
768
     * 
769
     * @return string
770
     */
771 10
    protected function compileRawValue(CompilableClause $query, $value)
772
    {
773 10
        if ($value instanceof QueryInterface) {
774
            return $this->compileSubQuery($query, $value);
775
        }
776
        
777 10
        if ($value instanceof ExpressionInterface) {
778 2
            return $value->build($query, $this);
779
        }
780
            
781 8
        return $value;
782
    }
783
784
    /**
785
     * Add sub query bindings.
786
     *
787
     * @param CompilableClause $clause
788
     * @param QueryInterface $query The sub query.
789
     * @param string $alias
790
     *
791
     * @return string  The sub query sql
792
     */
793 5
    protected function compileSubQuery(CompilableClause $clause, QueryInterface $query, $alias = null)
794
    {
795
        //TODO les alias peuvent etre les memes. Ne gene pas MySQL, voir à regénérer ceux de la subquery
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
Coding Style introduced by
No space found before comment text; expected "// TODO les alias peuvent etre les memes. Ne gene pas MySQL, voir à regénérer ceux de la subquery" but found "//TODO les alias peuvent etre les memes. Ne gene pas MySQL, voir à regénérer ceux de la subquery"
Loading history...
796 5
        $sql = '('.$this->compileSelect($query).')';
797
        
798 5
        if ($alias) {
799 3
            $sql = $sql . ' as ' . $this->quoteIdentifier($clause, $alias);
800
        }
801
        
802 5
        $this->addQueryBindings($clause, $query);
803
804 5
        return $sql;
805
    }
806
    
807
    /**
808
     * Compile IN or NOT IN expression
809
     *
810
     * @param CompilableClause $query
811
     * @param array|QueryInterface|ExpressionInterface  $values
812
     * @param string $column
813
     * @param string $operator
814
     * @param boolean $converted
0 ignored issues
show
introduced by
Expected "bool" but found "boolean" for parameter type
Loading history...
815
     * 
816
     * @return string
817
     */
818 96
    protected function compileInExpression(CompilableClause $query, $values, string $column, string $operator = 'IN', bool $converted = false)
819
    {
820 96
        if (is_array($values)) {
821 93
            $hasNullValue = null;
822 93
            foreach ($values as $index => &$value) {
823 93
                if ($value === null) {
824 2
                    unset($values[$index]);
825 2
                    $hasNullValue = true;
826
                } else {
827 93
                    $value = $converted ? $this->bindRaw($query, $value) : $this->bindTyped($query, $value, null);
0 ignored issues
show
Coding Style introduced by
The value of a comparison must not be assigned to a variable
Loading history...
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
828
                }
829
            }
830
831
            // If the collection has a null value we add the null expression
832 93
            if ($hasNullValue) {
833 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...
834 2
                    $expression = '('.$this->quoteIdentifier($query, $column).' '.$operator.' ('.implode(',', $values).')';
835
836 2
                    if ($operator === 'IN') {
837 1
                        return $expression.' OR '.$this->compileExpression($query, $column, 'in', null, $converted).')';
838
                    } else {
839 1
                        return $expression.' AND '.$this->compileExpression($query, $column, '!in', null, $converted).')';
840
                    }
841
                }
842
843 2
                return $this->compileExpression($query, $column, $operator === 'IN' ? 'in' : '!in', null, $converted);
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
844
            }
845
846 93
            $values = '('.implode(',', $values).')';
847 3
        } elseif ($values instanceof QueryInterface) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
848 2
            $values = $this->compileSubQuery($query, $values);
849 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...
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
850
            $values = '('.$values->build($query, $this).')';
851
        } else {
852 1
            $values = $converted ? $this->bindRaw($query, $values) : $this->bindTyped($query, $values, null);
0 ignored issues
show
Coding Style introduced by
The value of a comparison must not be assigned to a variable
Loading history...
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
853 1
            $values = '('.$values.')'; // @todo utile ?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
Coding Style introduced by
Comments may not appear after statements
Loading history...
854
        }
0 ignored issues
show
Coding Style introduced by
End comment for long condition not found; expected "//end if"
Loading history...
855
856 96
        return $this->quoteIdentifier($query, $column).' '.$operator.' '.$values;
857
    }
858
859
    /**
860
     * Compile into expression
861
     * Multiple OR expression
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
862
     *
863
     * @param CompilableClause $query
864
     * @param array $values
865
     * @param string $column
866
     * @param string $operator
867
     * @param bool $converted True if the value is already converted, or false to convert into bind()
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
868
     * @param string $separator The expressions separators. By default set to OR, but should be AND on negative (NOT) expressions. See CompositeExpression
869
     *
870
     * @return string
871
     */
872 5
    public function compileIntoExpression(CompilableClause $query, array $values, $column, $operator, $converted, $separator = CompositeExpression::TYPE_OR)
873
    {
874 5
        $into = [];
875
876 5
        $column = $this->quoteIdentifier($query, $column);
877
878 5
        foreach ($values as $value) {
879 5
            $into[] = $column.' '.$operator.' '.$this->compileExpressionValue($query, $value, $converted);
880
        }
881
882 5
        return '('.implode(' '.$separator.' ', $into).')';
883
    }
884
885
    /**
886
     * Compile group by expression
887
     * 
888
     * @param CompilableClause $query
889
     * 
890
     * @return string
891
     */
892 614
    protected function compileGroup(CompilableClause $query)
893
    {
894 614
        if (empty($query->statements['groups'])) {
895 613
            return '';
896
        }
897
898 3
        $fields = array_map([$query->preprocessor(), 'field'], $query->statements['groups']);
899
900 3
        if ($query->isQuoteIdentifier()) {
901
            $fields = $this->quoteIdentifiers($query, $fields);
902
        }
903
        
904 3
        return ' GROUP BY '.implode(', ', $fields);
905
    }
906
907
    /**
908
     * Compile order by expression
909
     * 
910
     * @param CompilableClause $query
911
     * 
912
     * @return string
913
     */
914 614
    protected function compileOrder(CompilableClause $query)
915
    {
916 614
        if (empty($query->statements['orders'])) {
917 569
            return '';
918
        }
919
        
920 47
        $sql = [];
921
922 47
        foreach ($query->statements['orders'] as $part) {
923 47
            if ($part['sort'] instanceof ExpressionInterface) {
924
                $part['sort'] = $part['sort']->build($query, $this);
925
            } else {
926 47
                $part['sort'] = $this->quoteIdentifier($query, $query->preprocessor()->field($part['sort']));
927
            }
928
            
929 47
            $sql[] = $part['sort'].' '.$part['order'];
930
        }
931
932 47
        return ' ORDER BY '.implode(', ', $sql);
933
    }
934
935
    /**
936
     * Compile the lock expression
937
     *
938
     * Does not support system that use hint like SqlServer
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
939
     *
940
     * @param CompilableClause $query
941
     *
942
     * @return string
943
     */
944 614
    protected function compileLock(CompilableClause $query)
945
    {
946 614
        $lock = $query->statements['lock'];
947
948
        // The lock should not be applied on aggregate function
949 614
        if ($lock !== null && !$query->statements['aggregate']) {
950
            // Lock for update
951 2
            if ($lock === LockMode::PESSIMISTIC_WRITE) {
952 1
                return ' ' . $this->platform()->grammar()->getWriteLockSQL();
953
            }
954
955
            // Shared Lock: other process can read the row but not update it.
956 1
            if ($lock === LockMode::PESSIMISTIC_READ) {
957 1
                return ' ' . $this->platform()->grammar()->getReadLockSQL();
958
            }
959
        }
960
961 612
        return '';
962
    }
963
964
    /**
965
     * Add sub query bindings.
966
     *
967
     * @param CompilableClause $clause The main query
968
     * @param QueryInterface $subQuery The sub query.
969
     *
970
     * @return $this This compiler instance.
971
     */
972 12
    protected function addQueryBindings(CompilableClause $clause, $subQuery)
0 ignored issues
show
introduced by
Type hint "QueryInterface" missing for $subQuery
Loading history...
973
    {
974 12
        foreach ($subQuery->getBindings() as $binding) {
975 4
            $this->bindRaw($clause, $binding); // Types are already converted on compilation
0 ignored issues
show
Coding Style introduced by
Comments may not appear after statements
Loading history...
976
        }
977
978 12
        return $this;
979
    }
980
981
    /**
982
     * Creates a new positional parameter and bind the given value to it.
983
     * The value will be converted according to the given type. If the type is not defined, it will be resolved from PHP value.
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
984
     *
985
     * Attention: If you are using positional parameters with the query builder you have
986
     * to be very careful to bind all parameters in the order they appear in the SQL
987
     * statement , otherwise they get bound in the wrong order which can lead to serious
988
     * bugs in your code.
989
     *
990
     * @param CompilableClause $query
991
     * @param mixed $value
992
     * @param TypeInterface|null $type The type to bind, or null to resolve
993
     *
994
     * @return string
995
     */
996 485
    protected function bindTyped(CompilableClause $query, $value, ?TypeInterface $type)
997
    {
998 485
        return $this->bindRaw($query, $this->platform()->types()->toDatabase($value, $type));
999
    }
1000
1001
    /**
1002
     * Creates a new positional parameter and bind the given value to it.
1003
     * The value will not be converted here
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
1004
     *
1005
     * Attention: If you are using positional parameters with the query builder you have
1006
     * to be very careful to bind all parameters in the order they appear in the SQL
1007
     * statement , otherwise they get bound in the wrong order which can lead to serious
1008
     * bugs in your code.
1009
     *
1010
     * @param CompilableClause $query
1011
     * @param mixed $value Raw database value : must be converted before
1012
     *
1013
     * @return string
1014
     */
1015 563
    protected function bindRaw(CompilableClause $query, $value)
1016
    {
1017 563
        $query->state()->bind($value);
1018
1019 563
        return '?';
1020
    }
1021
1022
    /**
1023
     * @param CompilableClause $query
1024
     *
1025
     * @return array
1026
     */
1027 595
    public function getBindings(CompilableClause $query)
1028
    {
1029 595
        return $this->mergeBindings($query->state()->bindings);
1030
    }
1031
1032
    /**
1033
     * Merge algo for bindings and binding types
1034
     * 
1035
     * @param array $bindings
1036
     *
1037
     * @return array
1038
     */
1039 595
    protected function mergeBindings($bindings)
0 ignored issues
show
introduced by
Type hint "array" missing for $bindings
Loading history...
1040
    {
1041 595
        $mergedBindings = [];
1042
1043 595
        if (isset($bindings[0])) {
1044 453
            $mergedBindings = $bindings[0];
1045
        } else {
1046 542
            foreach (['columns', 'joins', 'where', 'groups', 'having', 'orders'] as $part) {
1047 542
                if (isset($bindings[$part])) {
1048 542
                    $mergedBindings = array_merge($mergedBindings, $bindings[$part]);
1049
                }
1050
            }
1051
        }
1052
1053 595
        return $mergedBindings;
1054
    }
1055
}
1056