Test Failed
Pull Request — master (#21)
by Vincent
06:04
created

SqlCompiler::compileInsertValues()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 21
ccs 9
cts 9
cp 1
rs 9.9
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
namespace Bdf\Prime\Query\Compiler;
4
5
use Bdf\Prime\Exception\PrimeException;
6
use Bdf\Prime\Exception\QueryException;
7
use Bdf\Prime\Query\CommandInterface;
8
use Bdf\Prime\Query\CompilableClause;
9
use Bdf\Prime\Query\Expression\ExpressionInterface;
10
use Bdf\Prime\Query\Expression\ExpressionTransformerInterface;
11
use Bdf\Prime\Query\QueryInterface;
12
use Bdf\Prime\Types\TypeInterface;
13
use Doctrine\DBAL\LockMode;
14
use Doctrine\DBAL\Query\Expression\CompositeExpression;
15
use UnexpectedValueException;
16
17
/**
18
 * SqlCompiler
19
 *
20
 * @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...
21
 * @package Bdf\Prime\Query\Compiler
0 ignored issues
show
Coding Style Documentation introduced by
@package tag is not allowed in class comment
Loading history...
22
 */
23
class SqlCompiler extends AbstractCompiler
24
{
25
    /**
26
     * Quote a value
27
     * 
28
     * @param mixed $value
29
     *
30
     * @return string
31
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
32 3
     */
33
    public function quote($value)
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line before function; 0 found
Loading history...
34 3
    {
35
        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

35
        return $this->connection->/** @scrutinizer ignore-call */ quote($this->autoConvertValue($value));
Loading history...
36
    }
37
    
38
    /**
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...
39
     * {@inheritdoc}
40 678
     */
41
    public function quoteIdentifier(CompilableClause $query, $column)
42 678
    {
43 672
        if (!$query->isQuoteIdentifier()) {
44
            return $column;
45
        }
46 8
47
        return $this->platform()->grammar()->quoteIdentifier($column);
48
    }
49
    
50
    /**
51
     * Quote a identifier on multiple columns
52
     *
53
     * @param CompilableClause $query
54
     * @param array $columns
55
     *
56
     * @return array
57
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
58 1
     */
59
    public function quoteIdentifiers(CompilableClause $query, array $columns)
60 1
    {
61 1
        if (!$query->isQuoteIdentifier()) {
62
            return $columns;
63
        }
64 1
65
        return array_map([$this->platform()->grammar(), 'quoteIdentifier'], $columns);
66
    }
67
    
68
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
69
     * {@inheritdoc}
70 447
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
71
    protected function doCompileInsert(CompilableClause $query)
72 447
    {
73
        $query->state()->currentPart = 0;
74 447
75 1
        if ($query->statements['ignore'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('IGNORE')) {
76 1
            if ($this->platform()->grammar()->getName() === 'sqlite') {
77
                $insert = 'INSERT OR IGNORE INTO ';
78 1
            } else {
79
                $insert = 'INSERT IGNORE INTO ';
80 446
            }
81 2
        } 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...
82
            $insert = 'REPLACE INTO ';
83 445
        } else {
84
            $insert = 'INSERT INTO ';
85
        }
86 447
87
        foreach ($query->statements['tables'] as $table) {
88
            return $query->state()->compiled = $insert.$this->quoteIdentifier($query, $table['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...
89
        }
90
91
        throw new QueryException('The insert table name is missing');
92
    }
93
94
    /**
95
     * Compile the data part of the insert query
96
     *
97 447
     * @param CompilableClause $query
98
     *
99
     * @return string
100 447
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
101 7
     */
102
    protected function compileInsertData(CompilableClause $query)
103
    {
104 444
        // @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...
105
        if ($query->statements['values']['data'] instanceof QueryInterface) {
106 444
            return $this->compileInsertSelect($query);
107
        }
108
109
        list($columns, $values) = $this->compileInsertValues($query);
110
111
        return ' ('.implode(', ', $columns).') VALUES('.implode(', ', $values).')';
112
    }
113
114
    /**
115
     * Compile an INSERT INTO ... SELECT ... query
116
     *
117 7
     * @param CompilableClause $query
118
     *
119 7
     * @return string
120 7
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
121
     */
122
    protected function compileInsertSelect(CompilableClause $query)
123
    {
124 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...
125 3
        $columns = [];
126 3
127
        // Columns are defined on the select query
128
        // Alias of the selected columns will be concidered as the INSERT table columns
129 3
        if ($select->statements['columns'] && $select->statements['columns'][0]['column'] !== '*') {
130
            foreach ($select->statements['columns'] as &$column) {
131 3
                $alias = $query->preprocessor()->field($column['alias'] ?? $column['column']);
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
132
133
                // Modify the column alias to match with the INSERT column
134
                $column['alias'] = $alias;
135 7
136 7
                $columns[] = $this->quoteIdentifier($query, $alias);
137
            }
138 7
        }
139
140
        $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...
141
        $this->addQueryBindings($query, $select);
142
143
        return empty($columns) ? $sql : ' ('.implode(', ', $columns).')'.$sql;
144
    }
145
146
    /**
147
     * Compile columns and values to insert
148
     *
149 444
     * @param CompilableClause $query
150
     *
151 444
     * @return array
152
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
153 444
     */
154 444
    protected function compileInsertValues(CompilableClause $query)
155
    {
156 444
        $data = $query->statements['values'];
157 444
158 444
        $columns = [];
159
        $values = [];
160
161 444
        foreach ($data['data'] as $column => $value) {
162 1
            $type = $data['types'][$column] ?? true;
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
163
            $column = $query->preprocessor()->field($column, $type);
164
165 444
            // The type cannot be resolved by preprocessor
166 444
            if ($type === true) {
167
                $type = null;
168
            }
169 444
170
            $columns[] = $this->quoteIdentifier($query, $column);
171
            $values[]  = $this->compileTypedValue($query, $value, $type);
172
        }
173
174
        return [$columns, $values];
175 23
    }
176
177 23
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
178
     * {@inheritdoc}
179 23
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
180
    protected function doCompileUpdate(CompilableClause $query)
181 23
    {
182 23
        $query->state()->currentPart = 0;
183 23
184 23
        $values = $this->compileUpdateValues($query);
185
186
        foreach ($query->statements['tables'] as $table) {
187
            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...
188
                . $this->quoteIdentifier($query, $table['table'])
189
                . ' SET ' . implode(', ', $values)
190
                . $this->compileWhere($query)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
191
            ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
192
        }
193
194
        throw new QueryException('The update table name is missing');
195 23
    }
196
197 23
    /**
198 23
     * Compile columns and values to update
199
     *
200 23
     * @param CompilableClause $query
201 23
     *
202 23
     * @return array
203
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
204 23
     */
205 23
    protected function compileUpdateValues(CompilableClause $query)
206 23
    {
207
        $data = $query->statements['values'];
208
        $values = [];
209 23
210
        foreach ($data['data'] as $column => $value) {
211
            $type = $data['types'][$column] ?? true;
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
212
            $column = $query->preprocessor()->field($column, $type);
213
214
            $values[] = $this->quoteIdentifier($query, $column)
215 28
                . ' = '
216
                . $this->compileTypedValue($query, $value, $type);
217 28
        }
218
219 28
        return $values;
220 28
    }
221 28
222
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
223
     * {@inheritdoc}
224
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
225
    protected function doCompileDelete(CompilableClause $query)
226
    {
227 616
        $query->state()->currentPart = 0;
228
229 616
        foreach ($query->statements['tables'] as $table) {
230 3
            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...
231
                . $this->quoteIdentifier($query, $table['table'])
232
                . $this->compileWhere($query)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
233 616
            ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
234 616
        }
235 616
236
        throw new QueryException('The delete table name is missing');
237
    }
238 616
    
239 616
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
240
     * {@inheritdoc}
241
     */
242 616
    protected function doCompileSelect(CompilableClause $query)
243 616
    {
244 616
        if ($this->isComplexAggregate($query)) {
245
            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...
246
        }
247 616
248 616
        if (!isset($query->state()->compiledParts['columns'])) {
249 616
            $query->state()->currentPart = 'columns';
250
            $query->state()->compiledParts['columns'] = $this->compileColumns($query);
251
        }
252 616
253 616
        if (!isset($query->state()->compiledParts['from'])) {
254 616
            $query->state()->compiledParts['from'] = $this->compileFrom($query);
255
        }
256
257 616
        if (!isset($query->state()->compiledParts['groups'])) {
258 616
            $query->state()->currentPart = 'groups';
259 616
            $query->state()->compiledParts['groups'] = $this->compileGroup($query);
260
        }
261
262 616
        if (!isset($query->state()->compiledParts['having'])) {
263 616
            $query->state()->currentPart = 'having';
264 616
            $query->state()->compiledParts['having'] = $this->compileHaving($query);
265
        }
266
267 616
        if (!isset($query->state()->compiledParts['orders'])) {
268 616
            $query->state()->currentPart = 'orders';
269 616
            $query->state()->compiledParts['orders'] = $this->compileOrder($query);
270
        }
271
272 616
        if (!isset($query->state()->compiledParts['where'])) {
273 616
            $query->state()->currentPart = 'where';
274 616
            $query->state()->compiledParts['where'] = $this->compileWhere($query);
275 616
        }
276 616
277 616
        if (!isset($query->state()->compiledParts['joins'])) {
278 616
            $query->state()->currentPart = 'joins';
279
            $query->state()->compiledParts['joins'] = $this->compileJoins($query);
280 616
        }
281 179
282
        if (!isset($query->state()->compiledParts['lock'])) {
283
            $query->state()->currentPart = 'lock';
284 616
            $query->state()->compiledParts['lock'] = $this->compileLock($query);
285
        }
286
287
        $sql = $query->state()->compiledParts['columns']
288
                .$query->state()->compiledParts['from']
289
                .$query->state()->compiledParts['joins']
290
                .$query->state()->compiledParts['where']
291
                .$query->state()->compiledParts['groups']
292
                .$query->state()->compiledParts['having']
293
                .$query->state()->compiledParts['orders'];
294
295 616
        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
            $sql = $this->platform()->grammar()->modifyLimitQuery($sql, $query->statements['limit'], $query->statements['offset']);
297 616
        }
298
299
        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...
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
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...
305
     *
306
     * @param CompilableClause $query
307
     *
308
     * @return bool
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
309 3
     */
310
    protected function isComplexAggregate(CompilableClause $query)
311 3
    {
312
        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...
313 3
    }
314 3
315
    /**
316 3
     * Compile the complexe aggregate query
317
     * 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...
318
     *
319
     * @param CompilableClause $query
320
     *
321
     * @return string
322
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
323
     */
324
    protected function compileComplexAggregate(CompilableClause $query)
325 616
    {
326
        list($function, $column) = $query->statements['aggregate'];
327 616
328 368
        $query->statements['aggregate'] = null;
329
        $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 shorthand IF statement requires brackets around comparison
Loading history...
330
331 447
        return 'SELECT '.$this->compileAggregate($query, $function, '*', false).' FROM ('.$this->doCompileSelect($query).') as derived_query';
332 8
    }
333
334 440
    /**
335
     * @param CompilableClause $query
336
     *
337 447
     * @return string
338 389
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
339
     */
340 389
    protected function compileColumns(CompilableClause $query)
341 293
    {
342
        if (!empty($query->statements['aggregate'])) {
343
            return 'SELECT '.$this->compileAggregate($query, $query->statements['aggregate'][0], $query->statements['aggregate'][1], $query->statements['distinct']);
344 389
        }
345
346
        if ($query->statements['distinct'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
347 67
            $select = 'SELECT DISTINCT ';
348
        } else {
349 67
            $select = 'SELECT ';
350 67
        }
351
        
352
        if (empty($query->statements['columns'])) {
353 67
            $root = $query->preprocessor()->root();
354
355
            if ($root) {
356
                $select .= $this->quoteIdentifier($query, $root).'.';
357
            }
358
359
            return $select.'*';
360
        }
361
362
        $sql = [];
363
        
364
        foreach ($query->statements['columns'] as $column) {
365
            $sql[] = $this->compileExpressionColumn($query, $column['column'], $column['alias']);
366
        }
367 370
        
368
        return $select.implode(', ', $sql);
369 370
    }
370 14
371 14
    /**
372
     * Compile a SQL function
373 14
     *
374
     * @param CompilableClause $query
375
     * @param string $function  The sql function
376
     * @param string $column    The column to aggregate
377 12
     * @param bool   $distinct  The distinct status
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
378 4
     *
379
     * @return string
380
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
381 12
     */
382
    protected function compileAggregate(CompilableClause $query, $function, $column, $distinct)
383
    {
384
        if ($column !== '*') {
385
            $column = $query->preprocessor()->field($column);
386 370
            $column = $this->quoteIdentifier($query, $column);
387 368
388 21
            if ($distinct && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
389 18
                // Le count ne compte pas les fields qui ont une valeur NULL.
390 16
                // Pour une pagination, il est important de compter les valeurs null sachant qu'elles seront sélectionnées.
391 3
                // La pagination utilise une column que pour le distinct.
392
                if ($function === 'pagination') {
393
                    $column = 'IFNULL('.$column.',"___null___")';
394 1
                }
395 1
396
                $column = 'DISTINCT '.$column;
397
            }
398
        }
399
400
        switch ($function) {
401
            case 'avg'  :      return $this->platform()->grammar()->getAvgExpression($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...
402
            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...
403
            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...
404
            case 'min'  :      return $this->platform()->grammar()->getMinExpression($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...
405
            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
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...
406
            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...
407
408
            default:
409 67
                $method = 'get'.ucfirst($function).'Expression';
410
                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...
411 67
        }
412 1
    }
413
414
    /**
415 67
     * Compile expression column
416 3
     *
417 1
     * @param CompilableClause $query
418 3
     * @param mixed $column
419
     * @param string $alias
420
     * 
421
     * @return string
422 66
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
423
     */
424 66
    protected function compileExpressionColumn(CompilableClause $query, $column, $alias = null)
425 1
    {
426
        if ($column instanceof QueryInterface) {
427
            return $this->compileSubQuery($query, $column, $alias);
428 66
        }
429 6
        
430 66
        if ($column instanceof ExpressionInterface) {
431
            return $alias !== null
432
                ? $column->build($query, $this).' as '.$this->quoteIdentifier($query, $alias)
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement must be declared on a single line
Loading history...
433
                : $column->build($query, $this)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
434
            ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
435
        }
436
437
        $column = $query->preprocessor()->field($column);
438
        
439
        if (strpos($column, '*') !== false) {
440 616
            return $column;
441
        }
442 616
        
443 616
        return $alias !== null
444
            ? $this->quoteIdentifier($query, $column).' as '.$this->quoteIdentifier($query, $alias)
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement must be declared on a single line
Loading history...
445
            : $this->quoteIdentifier($query, $column)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
446 616
        ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
447 616
    }
448 6
    
449
    /**
450 616
     * @param CompilableClause $query
451
     *
452
     * @return string
453 616
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
454
     */
455
    protected function compileFrom(CompilableClause $query)
456 616
    {
457
        $sql = ' FROM ';
458
        $isFirst = true;
459
460
        // Loop through all FROM clauses
461
        foreach ($this->compileTableAndAliasClause($query, $query->statements['tables']) as $from) {
462
            if (!$isFirst) {
463
                $sql .= ', ';
464
            } else {
465 616
                $isFirst = false;
466
            }
467 616
468 597
            $sql .= $from['sql'];
469
        }
470
471 50
        return $sql;
472
    }
473 50
474 50
    /**
475
     * @param CompilableClause $query
476
     *
477 50
     * @return string
478
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
479
     */
480
    protected function compileJoins(CompilableClause $query)
481
    {
482
        if (empty($query->statements['joins'])) {
483
            return '';
484
        }
485
486
        $sql = '';
487
488
        foreach ($this->compileTableAndAliasClause($query, $query->statements['joins']) as $join) {
489
            $sql .= ' '.$join['type'].' JOIN '.$join['sql'].' ON '.$this->compileCompilableClauses($query, $join['on']);
490
        }
491
492
        return $sql;
493
    }
494
495
    /**
496 616
     * Compile clauses with 'table' and 'alias' keys
497
     *
498 616
     * The table name will be resolved, quoted, and generate the alias if present
499 616
     * Duplicate table name or aliases will also be removed from result
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
500
     *
501 616
     * The compiled table expression will be returned into the 'sql' key
502 616
     * All input parameter will be kept on the return value
503 3
     *
504 3
     * @param CompilableClause $query
505
     * @param array $clauses
506 616
     *
507
     * @return array
508 616
     *
509 464
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
510 464
     */
511
    protected function compileTableAndAliasClause(CompilableClause $query, array $clauses): array
512 360
    {
513 616
        $databasePrefix = $this->getDatabaseNamePrefix($query);
514
        $compiled = [];
515
516
        foreach ($clauses as $from) {
517
            if ($from['table'] instanceof QueryInterface) {
518 616
                $from['sql'] = $this->compileSubQuery($query, $from['table'], $from['alias']);
519
                $compiled[] = $from;
520
            } else {
521
                $from = $query->preprocessor()->table($from);
522
523
                if ($from['alias'] === null) {
524
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']);
525
                    $compiled[$from['table']] = $from;
526
                } else {
527
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']) . ' ' . $this->quoteIdentifier($query, $from['alias']);
528 616
                    $compiled[$from['alias']] = $from;
529
                }
530 616
            }
531 2
        }
532
533
        return $compiled;
534 615
    }
535
536
    /**
537
     * Adding database prefix for sub query x-db
538
     *
539
     * @param CompilableClause $query
540
     *
541
     * @return string
542
     */
543
    protected function getDatabaseNamePrefix(CompilableClause $query): string
544
    {
545 627
        if ($query instanceof CommandInterface && $query->compiler() !== $this && $query->connection()->getDatabase() !== $this->connection->getDatabase()) {
546
            return $query->connection()->getDatabase().'.';
547 627
        }
548 498
549
        return '';
550
    }
551 331
552
    /**
553
     * Compile Where sql
554
     * 
555
     * @param CompilableClause $query
556
     * 
557
     * @return string
558
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
559
     */
560
    protected function compileWhere(CompilableClause $query)
561
    {
562 616
        if (empty($query->statements['where'])) {
563
            return '';
564 616
        }
565 607
        
566
        return ' WHERE '.$this->compileCompilableClauses($query, $query->statements['where']);
567
    }
568 9
569
    /**
570
     * Compile having sql
571
     * 
572
     * @param CompilableClause $query
573
     * 
574
     * @return string
575
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
576
     */
577
    protected function compileHaving(CompilableClause $query)
578 343
    {
579
        if (empty($query->statements['having'])) {
580 343
            return '';
581 343
        }
582
        
583
        return ' HAVING '.$this->compileCompilableClauses($query, $query->statements['having']);
584 343
    }
585 149
586
    /**
587
     * @param CompilableClause $query
588
     * @param array $clauses
589
     *
590
     * @return string
591 149
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
592 147
     */
593
    protected function compileCompilableClauses(CompilableClause $query, array &$clauses)
594
    {
595
        $sql = [];
596 2
        $i = 0;
597 2
598
        // Permet de retirer le niveau du nested
599
        if (count($clauses) === 1 && isset($clauses[0]['nested'])) {
600 343
            $result = $this->compileCompilableClauses($query, $clauses[0]['nested']);
601
            /*
0 ignored issues
show
Coding Style introduced by
Empty line required before block comment
Loading history...
602
             * 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...
603 343
             * If we still have one clause, we return the compiled sql
604 343
             * Otherwise we start the loop of clauses.
605
             */
0 ignored issues
show
Coding Style introduced by
Empty line required after block comment
Loading history...
606 343
            if (count($clauses) === 1) {
607 100
                return $result;
608
            }
609
610 343
            // Add the nested level
611
            $sql[] = '('.$result.')';
612 343
            $i = 1;
613 64
        }
614 343
615 333
        $clauses[0]['glue'] = null;
616
617 10
        //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...
618
        for (; isset($clauses[$i]); ++$i) {
619
            $part = $clauses[$i];
620
621 343
            if ($part['glue'] !== null) {
622
                $part['glue'] .= ' ';
623
            }
624
625
            $part = $query->preprocessor()->expression($part);
626
            
627
            if (isset($part['nested'])) {
628
                $sql[] = $part['glue'].'('.$this->compileCompilableClauses($query, $part['nested']).')';
629
            } elseif (!isset($part['raw'])) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
630
                $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...
631
            } else {
632
                $sql[] = $part['glue'].$this->compileRawValue($query, $part['raw']);
633
            }
634
        }
635
        
636
        return implode(' ', $sql);
637
    }
638 333
639
    /**
640 333
     * Determine which operator to use based on custom and standard syntax
641 6
     *
642
     * @param CompilableClause $query
643 6
     * @param string $column
644 6
     * @param string $operator
645 6
     * @param mixed  $value
646 6
     * @param bool $converted
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
647
     * 
648
     * @return string  operator found
649
     * 
650 333
     * @throws UnexpectedValueException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
651 333
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
652 1
     */
0 ignored issues
show
Coding Style introduced by
Expected 1 @throws tag(s) in function comment; 2 found
Loading history...
653
    protected function compileExpression(CompilableClause $query, $column, $operator, $value, $converted)
654
    {
655 1
        if ($value instanceof ExpressionTransformerInterface) {
656
            $value->setContext($this, $column, $operator);
657 333
658 333
            $column    = $value->getColumn();
659
            $operator  = $value->getOperator();
660
            $value     = $value->getValue();
661
            $converted = true;
662
        }
663
664 333
        switch ($operator) {
665 328
            case '<':
666 7
            case ':lt':
667
                if (is_array($value)) {
668
                    return $this->compileIntoExpression($query, $value, $column, '<', $converted);
669 7
                }
670
                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...
671 328
672 328
            case '<=':
673 2
            case ':lte':
674
                if (is_array($value)) {
675
                    return $this->compileIntoExpression($query, $value, $column, '<=', $converted);
676 2
                }
677
                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...
678
679 328
            case '>':
680 328
            case ':gt':
681 328
                if (is_array($value)) {
682
                    return $this->compileIntoExpression($query, $value, $column, '>', $converted);
683
                }
684
                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...
685
686
            case '>=':
687
            case ':gte':
688 328
                if (is_array($value)) {
689 24
                    return $this->compileIntoExpression($query, $value, $column, '>=', $converted);
690 4
                }
691
                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...
692 20
693
            // REGEX matching
694
            case '~=':
695 316
            case '=~':
696 316
            case ':regex':
697 1
                if (is_array($value)) {
698 1
                    return $this->compileIntoExpression($query, $value, $column, 'REGEXP', $converted);
699
                }
700 1
                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...
701
702
            // LIKE
703 315
            case ':like':
704 307
                if (is_array($value)) {
705 94
                    return $this->compileIntoExpression($query, $value, $column, 'LIKE', $converted);
706 2
                }
707
                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...
708 93
709
            // NOT LIKE
710
            case ':notlike':
711 301
            case '!like':
712 301
                if (is_array($value)) {
713 300
                    return $this->compileIntoExpression($query, $value, $column, 'NOT LIKE', $converted, CompositeExpression::TYPE_AND);
714 4
                }
715 2
                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...
716
717 3
            // In
718
            case 'in':
719
            case ':in':
720 299
                if (empty($value)) {
721 299
                    return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));
722 4
                }
723 4
                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

723
                return $this->compileInExpression($query, /** @scrutinizer ignore-type */ $value, $column, 'IN', $converted);
Loading history...
introduced by
Case breaking statement indented incorrectly; expected 14 spaces, found 16
Loading history...
724
725
            // Not in
726
            case 'notin':
727
            case '!in':
728 296
            case ':notin':
729 296
                if (empty($value)) {
730 1
                    return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));
731
                }
732
                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...
733 295
734 295
            // Between
735 290
            case 'between':
736 290
            case ':between':
737 15
                if (is_array($value)) {
738 6
                    return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), $this->compileExpressionValue($query, $value[0], $converted), $this->compileExpressionValue($query, $value[1], $converted));
739
                }
740 9
                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...
741 1
742
            // Not between
743 8
            case '!between':
744
            case ':notbetween':
745
                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...
746 290
747
            // Not equal
748 290
            case '<>':
749 16
            case '!=':
750
            case ':ne':
751 283
            case ':not':
752 76
                if (is_null($value)) {
753
                    return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));
754 252
                }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
755
                if (is_array($value)) {
756
                    return $this->compileExpression($query, $column, ':notin', $value, $converted);
757
                }
758
                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...
759
760
            // Equals
761
            case '=':
762
            case ':eq':
763
                if (is_null($value)) {
764
                    return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));
765
                }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
766
                if (is_array($value)) {
767
                    return $this->compileExpression($query, $column, ':in', $value, $converted);
768
                }
769
                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...
770
                
771
            // Unsupported operator
772 280
            default:
773
                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...
774 280
        }
0 ignored issues
show
Coding Style introduced by
End comment for long condition not found; expected "//end switch"
Loading history...
775
    }
776
777
    /**
778 280
     * Compile expression value
779 54
     *
780
     * @param CompilableClause $query
781
     * @param mixed $value
782 267
     * @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...
783
     * 
784
     * @return string
785
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
786
     */
787
    protected function compileExpressionValue(CompilableClause $query, $value, bool $converted)
788
    {
789
        if ($value instanceof QueryInterface) {
790
            return $this->compileSubQuery($query, $value);
791
        }
792
793
        if ($value instanceof ExpressionInterface) {
794
            return $value->build($query, $this);
795 454
        }
796
797 454
        return $converted ? $this->bindRaw($query, $value) : $this->bindTyped($query, $value, null);
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
798
    }
799
800
    /**
801 454
     * Compile expression value with type
802 1
     *
803
     * @param CompilableClause $query
804
     * @param mixed $value
805 454
     * @param TypeInterface|null $type The type. If null it will be resolved from value
806
     *
807
     * @return string
808
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
809
     */
810
    protected function compileTypedValue(CompilableClause $query, $value, ?TypeInterface $type)
811
    {
812
        if ($value instanceof QueryInterface) {
813
            return $this->compileSubQuery($query, $value);
814
        }
815
816
        if ($value instanceof ExpressionInterface) {
817 10
            return $value->build($query, $this);
818
        }
819 10
820
        return $this->bindTyped($query, $value, $type);
821
    }
822
823 10
    /**
824 2
     * Compile raw expression value
825
     *
826
     * @param CompilableClause $query
827 8
     * @param mixed $value
828
     * 
829
     * @return string
830
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
831
     */
832
    protected function compileRawValue(CompilableClause $query, $value)
833
    {
834
        if ($value instanceof QueryInterface) {
835
            return $this->compileSubQuery($query, $value);
836
        }
837
        
838
        if ($value instanceof ExpressionInterface) {
839
            return $value->build($query, $this);
840 6
        }
841
            
842
        return $value;
843 6
    }
844
845 6
    /**
846 4
     * Add sub query bindings.
847
     *
848
     * @param CompilableClause $clause
849 6
     * @param QueryInterface $query The sub query.
850
     * @param string $alias
851 6
     *
852
     * @return string  The sub query sql
853
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
854
     */
855
    protected function compileSubQuery(CompilableClause $clause, QueryInterface $query, $alias = null)
856
    {
857
        //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...
858
        $sql = '('.$this->compileSelect($query).')';
859
        
860
        if ($alias) {
861
            $sql = $sql . ' as ' . $this->quoteIdentifier($clause, $alias);
862
        }
863
        
864
        $this->addQueryBindings($clause, $query);
865
866 96
        return $sql;
867
    }
868 96
    
869 93
    /**
870 93
     * Compile IN or NOT IN expression
871 93
     *
872 2
     * @param CompilableClause $query
873 2
     * @param array|QueryInterface|ExpressionInterface  $values
874
     * @param string $column
875 93
     * @param string $operator
876
     * @param boolean $converted
0 ignored issues
show
introduced by
Expected "bool" but found "boolean" for parameter type
Loading history...
877
     * 
878
     * @return string
879
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
880 93
     */
881 2
    protected function compileInExpression(CompilableClause $query, $values, string $column, string $operator = 'IN', bool $converted = false)
882 2
    {
883
        if (is_array($values)) {
884 2
            $hasNullValue = null;
885 1
            foreach ($values as $index => &$value) {
886
                if ($value === null) {
887 1
                    unset($values[$index]);
888
                    $hasNullValue = true;
889
                } else {
890
                    $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 shorthand IF statement requires brackets around comparison
Loading history...
891 2
                }
892
            }
893
894 93
            // If the collection has a null value we add the null expression
895 3
            if ($hasNullValue) {
896 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...
897 1
                    $expression = '('.$this->quoteIdentifier($query, $column).' '.$operator.' ('.implode(',', $values).')';
898
899
                    if ($operator === 'IN') {
900 1
                        return $expression.' OR '.$this->compileExpression($query, $column, 'in', null, $converted).')';
901 1
                    } else {
902
                        return $expression.' AND '.$this->compileExpression($query, $column, '!in', null, $converted).')';
903
                    }
904 96
                }
905
906
                return $this->compileExpression($query, $column, $operator === 'IN' ? 'in' : '!in', null, $converted);
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
907
            }
908
909
            $values = '('.implode(',', $values).')';
910
        } elseif ($values instanceof QueryInterface) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
911
            $values = $this->compileSubQuery($query, $values);
912
        } 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...
913
            $values = '('.$values->build($query, $this).')';
914
        } else {
915
            $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 shorthand IF statement requires brackets around comparison
Loading history...
916
            $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...
917
        }
0 ignored issues
show
Coding Style introduced by
End comment for long condition not found; expected "//end if"
Loading history...
918
919
        return $this->quoteIdentifier($query, $column).' '.$operator.' '.$values;
920
    }
921 5
922
    /**
923 5
     * Compile into expression
924
     * 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...
925 5
     *
926
     * @param CompilableClause $query
927 5
     * @param array $values
928 5
     * @param string $column
929
     * @param string $operator
930
     * @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...
931 5
     * @param string $separator The expressions separators. By default set to OR, but should be AND on negative (NOT) expressions. See CompositeExpression
932
     *
933
     * @return string
934
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
935
     */
936
    public function compileIntoExpression(CompilableClause $query, array $values, $column, $operator, $converted, $separator = CompositeExpression::TYPE_OR)
937
    {
938
        $into = [];
939
940
        $column = $this->quoteIdentifier($query, $column);
941
942 616
        foreach ($values as $value) {
943
            $into[] = $column.' '.$operator.' '.$this->compileExpressionValue($query, $value, $converted);
944 616
        }
945 615
946
        return '('.implode(' '.$separator.' ', $into).')';
947
    }
948 3
949
    /**
950 3
     * Compile group by expression
951
     * 
952
     * @param CompilableClause $query
953
     * 
954 3
     * @return string
955
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
956
     */
957
    protected function compileGroup(CompilableClause $query)
958
    {
959
        if (empty($query->statements['groups'])) {
960
            return '';
961
        }
962
963
        $fields = array_map([$query->preprocessor(), 'field'], $query->statements['groups']);
964
965 616
        if ($query->isQuoteIdentifier()) {
966
            $fields = $this->quoteIdentifiers($query, $fields);
967 616
        }
968 571
        
969
        return ' GROUP BY '.implode(', ', $fields);
970
    }
971 47
972
    /**
973 47
     * Compile order by expression
974 47
     * 
975
     * @param CompilableClause $query
976
     * 
977 47
     * @return string
978
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
979
     */
980 47
    protected function compileOrder(CompilableClause $query)
981
    {
982
        if (empty($query->statements['orders'])) {
983 47
            return '';
984
        }
985
        
986
        $sql = [];
987
988
        foreach ($query->statements['orders'] as $part) {
989
            if ($part['sort'] instanceof ExpressionInterface) {
990
                $part['sort'] = $part['sort']->build($query, $this);
991
            } else {
992
                $part['sort'] = $this->quoteIdentifier($query, $query->preprocessor()->field($part['sort']));
993
            }
994
            
995
            $sql[] = $part['sort'].' '.$part['order'];
996 616
        }
997
998 616
        return ' ORDER BY '.implode(', ', $sql);
999
    }
1000
1001 616
    /**
1002
     * Compile the lock expression
1003 2
     *
1004 1
     * 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...
1005
     *
1006
     * @param CompilableClause $query
1007
     *
1008 1
     * @return string
1009 1
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
1010
     */
1011
    protected function compileLock(CompilableClause $query)
1012
    {
1013 614
        $lock = $query->statements['lock'];
1014
1015
        // The lock should not be applied on aggregate function
1016
        if ($lock !== null && !$query->statements['aggregate']) {
1017
            // Lock for update
1018
            if ($lock === LockMode::PESSIMISTIC_WRITE) {
1019
                return ' ' . $this->platform()->grammar()->getWriteLockSQL();
1020
            }
1021
1022
            // Shared Lock: other process can read the row but not update it.
1023
            if ($lock === LockMode::PESSIMISTIC_READ) {
1024
                return ' ' . $this->platform()->grammar()->getReadLockSQL();
1025 13
            }
1026
        }
1027 13
1028 5
        return '';
1029
    }
1030
1031 13
    /**
1032
     * Add sub query bindings.
1033
     *
1034
     * @param CompilableClause $clause The main query
1035
     * @param QueryInterface $subQuery The sub query.
1036
     *
1037
     * @return $this This compiler instance.
1038
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
1039
     */
1040
    protected function addQueryBindings(CompilableClause $clause, $subQuery)
0 ignored issues
show
introduced by
Type hint "QueryInterface" missing for $subQuery
Loading history...
1041
    {
1042
        foreach ($subQuery->getBindings() as $binding) {
1043
            $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...
1044
        }
1045
1046
        return $this;
1047
    }
1048
1049
    /**
1050 485
     * Creates a new positional parameter and bind the given value to it.
1051
     * 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...
1052 485
     *
1053
     * Attention: If you are using positional parameters with the query builder you have
1054
     * to be very careful to bind all parameters in the order they appear in the SQL
1055
     * statement , otherwise they get bound in the wrong order which can lead to serious
1056
     * bugs in your code.
1057
     *
1058
     * @param CompilableClause $query
1059
     * @param mixed $value
1060
     * @param TypeInterface|null $type The type to bind, or null to resolve
1061
     *
1062
     * @return string
1063
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
1064
     */
1065
    protected function bindTyped(CompilableClause $query, $value, ?TypeInterface $type)
1066
    {
1067
        return $this->bindRaw($query, $this->platform()->types()->toDatabase($value, $type));
1068
    }
1069 565
1070
    /**
1071 565
     * Creates a new positional parameter and bind the given value to it.
1072
     * 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...
1073 565
     *
1074
     * Attention: If you are using positional parameters with the query builder you have
1075
     * to be very careful to bind all parameters in the order they appear in the SQL
1076
     * statement , otherwise they get bound in the wrong order which can lead to serious
1077
     * bugs in your code.
1078
     *
1079
     * @param CompilableClause $query
1080
     * @param mixed $value Raw database value : must be converted before
1081 596
     *
1082
     * @return string
1083 596
     */
1084
    protected function bindRaw(CompilableClause $query, $value)
1085
    {
1086
        $query->state()->bind($value);
1087
1088
        return '?';
1089
    }
1090
1091
    /**
1092
     * @param CompilableClause $query
1093 596
     *
1094
     * @return array
1095 596
     */
1096
    public function getBindings(CompilableClause $query)
1097 596
    {
1098 453
        return $this->mergeBindings($query->state()->bindings);
1099
    }
1100 543
1101 543
    /**
1102 543
     * Merge algo for bindings and binding types
1103
     * 
1104
     * @param array $bindings
1105
     *
1106
     * @return array
1107 596
     */
1108
    protected function mergeBindings($bindings)
0 ignored issues
show
introduced by
Type hint "array" missing for $bindings
Loading history...
1109
    {
1110
        $mergedBindings = [];
1111
1112
        if (isset($bindings[0])) {
1113
            $mergedBindings = $bindings[0];
1114
        } else {
1115
            foreach (['columns', 'joins', 'where', 'groups', 'having', 'orders'] as $part) {
1116
                if (isset($bindings[$part])) {
1117
                    $mergedBindings = array_merge($mergedBindings, $bindings[$part]);
1118
                }
1119
            }
1120
        }
1121
1122
        return $mergedBindings;
1123
    }
1124
}
1125