Passed
Pull Request — master (#21)
by Vincent
07:14
created

SqlCompiler::doCompileInsert()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 8.8333
cc 7
nc 8
nop 1
crap 7
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
     */
33 3
    public function quote($value)
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line before function; 0 found
Loading history...
34
    {
35 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

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
     */
41 684
    public function quoteIdentifier(CompilableClause $query, $column)
42
    {
43 684
        if (!$query->isQuoteIdentifier()) {
44 678
            return $column;
45
        }
46
47 8
        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
     */
59 1
    public function quoteIdentifiers(CompilableClause $query, array $columns)
60
    {
61 1
        if (!$query->isQuoteIdentifier()) {
62 1
            return $columns;
63
        }
64
65 1
        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
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
71 449
    protected function doCompileInsert(CompilableClause $query)
72
    {
73 449
        $query->state()->currentPart = 0;
74
75 449
        if ($query->statements['ignore'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('IGNORE')) {
76 1
            if ($this->platform()->grammar()->getName() === 'sqlite') {
77 1
                $insert = 'INSERT OR IGNORE INTO ';
78
            } else {
79 1
                $insert = 'INSERT IGNORE INTO ';
80
            }
81 448
        } 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 2
            $insert = 'REPLACE INTO ';
83
        } else {
84 447
            $insert = 'INSERT INTO ';
85
        }
86
87 449
        foreach ($query->statements['tables'] as $table) {
88 448
            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 1
        throw new QueryException('The insert table name is missing');
92
    }
93
94
    /**
95
     * Compile the data part of the insert query
96
     *
97
     * @param CompilableClause $query
98
     *
99
     * @return string
100
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
101
     */
102 448
    protected function compileInsertData(CompilableClause $query)
103
    {
104
        // @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 448
        if ($query->statements['values']['data'] instanceof QueryInterface) {
106 7
            return $this->compileInsertSelect($query);
107
        }
108
109 445
        list($columns, $values) = $this->compileInsertValues($query);
110
111 445
        return ' ('.implode(', ', $columns).') VALUES('.implode(', ', $values).')';
112
    }
113
114
    /**
115
     * Compile an INSERT INTO ... SELECT ... query
116
     *
117
     * @param CompilableClause $query
118
     *
119
     * @return string
120
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
121
     */
122 7
    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 7
        $columns = [];
126
127
        // Columns are defined on the select query
128
        // Alias of the selected columns will be concidered as the INSERT table columns
129 7
        if ($select->statements['columns'] && $select->statements['columns'][0]['column'] !== '*') {
130 3
            foreach ($select->statements['columns'] as &$column) {
131 3
                $alias = $query->preprocessor()->field($column['alias'] ?? $column['column']);
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 3
                $column['alias'] = $alias;
135
136 3
                $columns[] = $this->quoteIdentifier($query, $alias);
137
            }
138
        }
139
140 7
        $sql = ' '.$this->compileSelect($select); // @todo Ensure that the query is sql compilable
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 7
        $this->addQueryBindings($query, $select);
142
143 7
        return empty($columns) ? $sql : ' ('.implode(', ', $columns).')'.$sql;
144
    }
145
146
    /**
147
     * Compile columns and values to insert
148
     *
149
     * @param CompilableClause $query
150
     *
151
     * @return array
152
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
153
     */
154 445
    protected function compileInsertValues(CompilableClause $query)
155
    {
156 445
        $data = $query->statements['values'];
157
158 445
        $columns = [];
159 445
        $values = [];
160
161 445
        foreach ($data['data'] as $column => $value) {
162 445
            $type = $data['types'][$column] ?? true;
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
163 445
            $column = $query->preprocessor()->field($column, $type);
164
165
            // The type cannot be resolved by preprocessor
166 445
            if ($type === true) {
167 1
                $type = null;
168
            }
169
170 445
            $columns[] = $this->quoteIdentifier($query, $column);
171 445
            $values[]  = $this->compileTypedValue($query, $value, $type);
172
        }
173
174 445
        return [$columns, $values];
175
    }
176
177
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $query should have a doc-comment as per coding-style.
Loading history...
178
     * {@inheritdoc}
179
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
180 24
    protected function doCompileUpdate(CompilableClause $query)
181
    {
182 24
        $query->state()->currentPart = 0;
183
184 24
        $values = $this->compileUpdateValues($query);
185
186 24
        foreach ($query->statements['tables'] as $table) {
187 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...
188 23
                . $this->quoteIdentifier($query, $table['table'])
189 23
                . ' SET ' . implode(', ', $values)
190 23
                . $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 1
        throw new QueryException('The update table name is missing');
195
    }
196
197
    /**
198
     * Compile columns and values to update
199
     *
200
     * @param CompilableClause $query
201
     *
202
     * @return array
203
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
204
     */
205 24
    protected function compileUpdateValues(CompilableClause $query)
206
    {
207 24
        $data = $query->statements['values'];
208 24
        $values = [];
209
210 24
        foreach ($data['data'] as $column => $value) {
211 24
            $type = $data['types'][$column] ?? true;
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
212 24
            $column = $query->preprocessor()->field($column, $type);
213
214 24
            $values[] = $this->quoteIdentifier($query, $column)
215 24
                . ' = '
216 24
                . $this->compileTypedValue($query, $value, $type);
217
        }
218
219 24
        return $values;
220
    }
221
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 29
    protected function doCompileDelete(CompilableClause $query)
226
    {
227 29
        $query->state()->currentPart = 0;
228
229 29
        foreach ($query->statements['tables'] as $table) {
230 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...
231 28
                . $this->quoteIdentifier($query, $table['table'])
232 28
                . $this->compileWhere($query)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
233
            ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
234
        }
235
236 1
        throw new QueryException('The delete table name is missing');
237
    }
238
    
239
    /**
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 621
    protected function doCompileSelect(CompilableClause $query)
243
    {
244 621
        if ($this->isComplexAggregate($query)) {
245 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...
246
        }
247
248 621
        if (!isset($query->state()->compiledParts['columns'])) {
249 621
            $query->state()->currentPart = 'columns';
250 621
            $query->state()->compiledParts['columns'] = $this->compileColumns($query);
251
        }
252
253 621
        if (!isset($query->state()->compiledParts['from'])) {
254 621
            $query->state()->compiledParts['from'] = $this->compileFrom($query);
255
        }
256
257 621
        if (!isset($query->state()->compiledParts['groups'])) {
258 621
            $query->state()->currentPart = 'groups';
259 621
            $query->state()->compiledParts['groups'] = $this->compileGroup($query);
260
        }
261
262 621
        if (!isset($query->state()->compiledParts['having'])) {
263 621
            $query->state()->currentPart = 'having';
264 621
            $query->state()->compiledParts['having'] = $this->compileHaving($query);
265
        }
266
267 621
        if (!isset($query->state()->compiledParts['orders'])) {
268 621
            $query->state()->currentPart = 'orders';
269 621
            $query->state()->compiledParts['orders'] = $this->compileOrder($query);
270
        }
271
272 621
        if (!isset($query->state()->compiledParts['where'])) {
273 621
            $query->state()->currentPart = 'where';
274 621
            $query->state()->compiledParts['where'] = $this->compileWhere($query);
275
        }
276
277 621
        if (!isset($query->state()->compiledParts['joins'])) {
278 621
            $query->state()->currentPart = 'joins';
279 621
            $query->state()->compiledParts['joins'] = $this->compileJoins($query);
280
        }
281
282 621
        if (!isset($query->state()->compiledParts['lock'])) {
283 621
            $query->state()->currentPart = 'lock';
284 621
            $query->state()->compiledParts['lock'] = $this->compileLock($query);
285
        }
286
287 621
        $sql = $query->state()->compiledParts['columns']
288 621
                .$query->state()->compiledParts['from']
289 621
                .$query->state()->compiledParts['joins']
290 621
                .$query->state()->compiledParts['where']
291 621
                .$query->state()->compiledParts['groups']
292 621
                .$query->state()->compiledParts['having']
293 621
                .$query->state()->compiledParts['orders'];
294
295 621
        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 179
            $sql = $this->platform()->grammar()->modifyLimitQuery($sql, $query->statements['limit'], $query->statements['offset']);
297
        }
298
299 621
        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
     */
310 621
    protected function isComplexAggregate(CompilableClause $query)
311
    {
312 621
        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
    }
314
315
    /**
316
     * 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 3
    protected function compileComplexAggregate(CompilableClause $query)
325
    {
326 3
        list($function, $column) = $query->statements['aggregate'];
327
328 3
        $query->statements['aggregate'] = null;
329 3
        $query->statements['columns'] = $column === '*' ? [] : [['column' => $column, 'alias' => null]];
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 3
        return 'SELECT '.$this->compileAggregate($query, $function, '*', false).' FROM ('.$this->doCompileSelect($query).') as derived_query';
332
    }
333
334
    /**
335
     * @param CompilableClause $query
336
     *
337
     * @return string
338
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
339
     */
340 621
    protected function compileColumns(CompilableClause $query)
341
    {
342 621
        if (!empty($query->statements['aggregate'])) {
343 369
            return 'SELECT '.$this->compileAggregate($query, $query->statements['aggregate'][0], $query->statements['aggregate'][1], $query->statements['distinct']);
344
        }
345
346 452
        if ($query->statements['distinct'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
347 8
            $select = 'SELECT DISTINCT ';
348
        } else {
349 445
            $select = 'SELECT ';
350
        }
351
        
352 452
        if (empty($query->statements['columns'])) {
353 394
            $root = $query->preprocessor()->root();
354
355 394
            if ($root) {
356 295
                $select .= $this->quoteIdentifier($query, $root).'.';
357
            }
358
359 394
            return $select.'*';
360
        }
361
362 67
        $sql = [];
363
        
364 67
        foreach ($query->statements['columns'] as $column) {
365 67
            $sql[] = $this->compileExpressionColumn($query, $column['column'], $column['alias']);
366
        }
367
        
368 67
        return $select.implode(', ', $sql);
369
    }
370
371
    /**
372
     * Compile a SQL function
373
     *
374
     * @param CompilableClause $query
375
     * @param string $function  The sql function
376
     * @param string $column    The column to aggregate
377
     * @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
     *
379
     * @return string
380
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
381
     */
382 371
    protected function compileAggregate(CompilableClause $query, $function, $column, $distinct)
383
    {
384 371
        if ($column !== '*') {
385 14
            $column = $query->preprocessor()->field($column);
386 14
            $column = $this->quoteIdentifier($query, $column);
387
388 14
            if ($distinct && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
389
                // Le count ne compte pas les fields qui ont une valeur NULL.
390
                // Pour une pagination, il est important de compter les valeurs null sachant qu'elles seront sélectionnées.
391
                // La pagination utilise une column que pour le distinct.
392 12
                if ($function === 'pagination') {
393 4
                    $column = 'IFNULL('.$column.',"___null___")';
394
                }
395
396 12
                $column = 'DISTINCT '.$column;
397
            }
398
        }
399
400
        switch ($function) {
401 371
            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 369
            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 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...
404 18
            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 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
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 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...
407
408
            default:
409 1
                $method = 'get'.ucfirst($function).'Expression';
410 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...
411
        }
412
    }
413
414
    /**
415
     * Compile expression column
416
     *
417
     * @param CompilableClause $query
418
     * @param mixed $column
419
     * @param string $alias
420
     * 
421
     * @return string
422
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
423
     */
424 67
    protected function compileExpressionColumn(CompilableClause $query, $column, $alias = null)
425
    {
426 67
        if ($column instanceof QueryInterface) {
427 1
            return $this->compileSubQuery($query, $column, $alias);
428
        }
429
        
430 67
        if ($column instanceof ExpressionInterface) {
431 3
            return $alias !== null
432 1
                ? $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 3
                : $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 66
        $column = $query->preprocessor()->field($column);
438
        
439 66
        if (strpos($column, '*') !== false) {
440 1
            return $column;
441
        }
442
        
443 66
        return $alias !== null
444 6
            ? $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 66
            : $this->quoteIdentifier($query, $column)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
446
        ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
447
    }
448
    
449
    /**
450
     * @param CompilableClause $query
451
     *
452
     * @return string
453
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
454
     */
455 621
    protected function compileFrom(CompilableClause $query)
456
    {
457 621
        $sql = ' FROM ';
458 621
        $isFirst = true;
459
460
        // Loop through all FROM clauses
461 621
        foreach ($this->compileTableAndAliasClause($query, $query->statements['tables']) as $from) {
462 621
            if (!$isFirst) {
463 8
                $sql .= ', ';
464
            } else {
465 621
                $isFirst = false;
466
            }
467
468 621
            $sql .= $from['sql'];
469
        }
470
471 621
        return $sql;
472
    }
473
474
    /**
475
     * @param CompilableClause $query
476
     *
477
     * @return string
478
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
479
     */
480 621
    protected function compileJoins(CompilableClause $query)
481
    {
482 621
        if (empty($query->statements['joins'])) {
483 602
            return '';
484
        }
485
486 50
        $sql = '';
487
488 50
        foreach ($this->compileTableAndAliasClause($query, $query->statements['joins']) as $join) {
489 50
            $sql .= ' '.$join['type'].' JOIN '.$join['sql'].' ON '.$this->compileCompilableClauses($query, $join['on']);
490
        }
491
492 50
        return $sql;
493
    }
494
495
    /**
496
     * Compile clauses with 'table' and 'alias' keys
497
     *
498
     * The table name will be resolved, quoted, and generate the alias if present
499
     * Duplicate table name or aliases will also be removed from result
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
500
     *
501
     * The compiled table expression will be returned into the 'sql' key
502
     * All input parameter will be kept on the return value
503
     *
504
     * @param CompilableClause $query
505
     * @param array $clauses
506
     *
507
     * @return array
508
     *
509
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
510
     */
511 621
    protected function compileTableAndAliasClause(CompilableClause $query, array $clauses): array
512
    {
513 621
        $databasePrefix = $this->getDatabaseNamePrefix($query);
514 621
        $compiled = [];
515
516 621
        foreach ($clauses as $from) {
517 621
            if ($from['table'] instanceof QueryInterface) {
518 3
                $from['sql'] = $this->compileSubQuery($query, $from['table'], $from['alias']);
519 3
                $compiled[] = $from;
520
            } else {
521 621
                $from = $query->preprocessor()->table($from);
522
523 621
                if ($from['alias'] === null) {
524 467
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']);
525 467
                    $compiled[$from['table']] = $from;
526
                } else {
527 365
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']) . ' ' . $this->quoteIdentifier($query, $from['alias']);
528 621
                    $compiled[$from['alias']] = $from;
529
                }
530
            }
531
        }
532
533 621
        return $compiled;
534
    }
535
536
    /**
537
     * Adding database prefix for sub query x-db
538
     *
539
     * @param CompilableClause $query
540
     *
541
     * @return string
542
     */
543 621
    protected function getDatabaseNamePrefix(CompilableClause $query): string
544
    {
545 621
        if ($query instanceof CommandInterface && $query->compiler() !== $this && $query->connection()->getDatabase() !== $this->connection->getDatabase()) {
546 2
            return $query->connection()->getDatabase().'.';
547
        }
548
549 620
        return '';
550
    }
551
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 632
    protected function compileWhere(CompilableClause $query)
561
    {
562 632
        if (empty($query->statements['where'])) {
563 502
            return '';
564
        }
565
        
566 332
        return ' WHERE '.$this->compileCompilableClauses($query, $query->statements['where']);
567
    }
568
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 621
    protected function compileHaving(CompilableClause $query)
578
    {
579 621
        if (empty($query->statements['having'])) {
580 612
            return '';
581
        }
582
        
583 9
        return ' HAVING '.$this->compileCompilableClauses($query, $query->statements['having']);
584
    }
585
586
    /**
587
     * @param CompilableClause $query
588
     * @param array $clauses
589
     *
590
     * @return string
591
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
592
     */
593 344
    protected function compileCompilableClauses(CompilableClause $query, array &$clauses)
594
    {
595 344
        $sql = [];
596 344
        $i = 0;
597
598
        // Permet de retirer le niveau du nested
599 344
        if (count($clauses) === 1 && isset($clauses[0]['nested'])) {
600 149
            $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
             * If we still have one clause, we return the compiled sql
604
             * 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 149
            if (count($clauses) === 1) {
607 147
                return $result;
608
            }
609
610
            // Add the nested level
611 2
            $sql[] = '('.$result.')';
612 2
            $i = 1;
613
        }
614
615 344
        $clauses[0]['glue'] = null;
616
617
        //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 344
        for (; isset($clauses[$i]); ++$i) {
619 344
            $part = $clauses[$i];
620
621 344
            if ($part['glue'] !== null) {
622 100
                $part['glue'] .= ' ';
623
            }
624
625 344
            $part = $query->preprocessor()->expression($part);
626
            
627 344
            if (isset($part['nested'])) {
628 64
                $sql[] = $part['glue'].'('.$this->compileCompilableClauses($query, $part['nested']).')';
629 344
            } elseif (!isset($part['raw'])) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
630 334
                $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 10
                $sql[] = $part['glue'].$this->compileRawValue($query, $part['raw']);
633
            }
634
        }
635
        
636 344
        return implode(' ', $sql);
637
    }
638
639
    /**
640
     * Determine which operator to use based on custom and standard syntax
641
     *
642
     * @param CompilableClause $query
643
     * @param string $column
644
     * @param string $operator
645
     * @param mixed  $value
646
     * @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
     * @throws UnexpectedValueException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
651
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
652
     */
0 ignored issues
show
Coding Style introduced by
Expected 1 @throws tag(s) in function comment; 2 found
Loading history...
653 334
    protected function compileExpression(CompilableClause $query, $column, $operator, $value, $converted)
654
    {
655 334
        if ($value instanceof ExpressionTransformerInterface) {
656 6
            $value->setContext($this, $column, $operator);
657
658 6
            $column    = $value->getColumn();
659 6
            $operator  = $value->getOperator();
660 6
            $value     = $value->getValue();
661 6
            $converted = true;
662
        }
663
664
        switch ($operator) {
665 334
            case '<':
666 334
            case ':lt':
667 1
                if (is_array($value)) {
668
                    return $this->compileIntoExpression($query, $value, $column, '<', $converted);
669
                }
670 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...
671
672 334
            case '<=':
673 334
            case ':lte':
674
                if (is_array($value)) {
675
                    return $this->compileIntoExpression($query, $value, $column, '<=', $converted);
676
                }
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 334
            case '>':
680 329
            case ':gt':
681 7
                if (is_array($value)) {
682
                    return $this->compileIntoExpression($query, $value, $column, '>', $converted);
683
                }
684 7
                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 329
            case '>=':
687 329
            case ':gte':
688 2
                if (is_array($value)) {
689
                    return $this->compileIntoExpression($query, $value, $column, '>=', $converted);
690
                }
691 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...
692
693
            // REGEX matching
694 329
            case '~=':
695 329
            case '=~':
696 329
            case ':regex':
697
                if (is_array($value)) {
698
                    return $this->compileIntoExpression($query, $value, $column, 'REGEXP', $converted);
699
                }
700
                return $this->quoteIdentifier($query, $column).' REGEXP '.$this->compileExpressionValue($query, (string)$value, $converted);
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 329
            case ':like':
704 25
                if (is_array($value)) {
705 4
                    return $this->compileIntoExpression($query, $value, $column, 'LIKE', $converted);
706
                }
707 21
                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
709
            // NOT LIKE
710 316
            case ':notlike':
711 316
            case '!like':
712 1
                if (is_array($value)) {
713 1
                    return $this->compileIntoExpression($query, $value, $column, 'NOT LIKE', $converted, CompositeExpression::TYPE_AND);
714
                }
715 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...
716
717
            // In
718 315
            case 'in':
719 307
            case ':in':
720 94
                if (empty($value)) {
721 2
                    return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));
722
                }
723 93
                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 301
            case 'notin':
727 301
            case '!in':
728 300
            case ':notin':
729 4
                if (empty($value)) {
730 2
                    return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));
731
                }
732 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...
733
734
            // Between
735 299
            case 'between':
736 299
            case ':between':
737 4
                if (is_array($value)) {
738 4
                    return $this->platform()->grammar()->getBetweenExpression($this->quoteIdentifier($query, $column), $this->compileExpressionValue($query, $value[0], $converted), $this->compileExpressionValue($query, $value[1], $converted));
739
                }
740
                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
742
            // Not between
743 296
            case '!between':
744 296
            case ':notbetween':
745 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...
746
747
            // Not equal
748 295
            case '<>':
749 295
            case '!=':
750 290
            case ':ne':
751 290
            case ':not':
752 15
                if (is_null($value)) {
753 6
                    return $this->platform()->grammar()->getIsNotNullExpression($this->quoteIdentifier($query, $column));
754
                }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
755 9
                if (is_array($value)) {
756 1
                    return $this->compileExpression($query, $column, ':notin', $value, $converted);
757
                }
758 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...
759
760
            // Equals
761 290
            case '=':
762
            case ':eq':
763 290
                if (is_null($value)) {
764 16
                    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 283
                if (is_array($value)) {
767 76
                    return $this->compileExpression($query, $column, ':in', $value, $converted);
768
                }
769 252
                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
            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
        }
0 ignored issues
show
Coding Style introduced by
End comment for long condition not found; expected "//end switch"
Loading history...
775
    }
776
777
    /**
778
     * Compile expression value
779
     *
780
     * @param CompilableClause $query
781
     * @param mixed $value
782
     * @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 281
    protected function compileExpressionValue(CompilableClause $query, $value, bool $converted)
788
    {
789 281
        if ($value instanceof QueryInterface) {
790
            return $this->compileSubQuery($query, $value);
791
        }
792
793 281
        if ($value instanceof ExpressionInterface) {
794 54
            return $value->build($query, $this);
795
        }
796
797 268
        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
     * Compile expression value with type
802
     *
803
     * @param CompilableClause $query
804
     * @param mixed $value
805
     * @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 456
    protected function compileTypedValue(CompilableClause $query, $value, ?TypeInterface $type)
811
    {
812 456
        if ($value instanceof QueryInterface) {
813
            return $this->compileSubQuery($query, $value);
814
        }
815
816 456
        if ($value instanceof ExpressionInterface) {
817 1
            return $value->build($query, $this);
818
        }
819
820 456
        return $this->bindTyped($query, $value, $type);
821
    }
822
823
    /**
824
     * Compile raw expression value
825
     *
826
     * @param CompilableClause $query
827
     * @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 10
    protected function compileRawValue(CompilableClause $query, $value)
833
    {
834 10
        if ($value instanceof QueryInterface) {
835
            return $this->compileSubQuery($query, $value);
836
        }
837
        
838 10
        if ($value instanceof ExpressionInterface) {
839 2
            return $value->build($query, $this);
840
        }
841
            
842 8
        return $value;
843
    }
844
845
    /**
846
     * Add sub query bindings.
847
     *
848
     * @param CompilableClause $clause
849
     * @param QueryInterface $query The sub query.
850
     * @param string $alias
851
     *
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 6
    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 6
        $sql = '('.$this->compileSelect($query).')';
859
        
860 6
        if ($alias) {
861 4
            $sql = $sql . ' as ' . $this->quoteIdentifier($clause, $alias);
862
        }
863
        
864 6
        $this->addQueryBindings($clause, $query);
865
866 6
        return $sql;
867
    }
868
    
869
    /**
870
     * Compile IN or NOT IN expression
871
     *
872
     * @param CompilableClause $query
873
     * @param array|QueryInterface|ExpressionInterface  $values
874
     * @param string $column
875
     * @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
     */
881 96
    protected function compileInExpression(CompilableClause $query, $values, string $column, string $operator = 'IN', bool $converted = false)
882
    {
883 96
        if (is_array($values)) {
884 93
            $hasNullValue = null;
885 93
            foreach ($values as $index => &$value) {
886 93
                if ($value === null) {
887 2
                    unset($values[$index]);
888 2
                    $hasNullValue = true;
889
                } else {
890 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 shorthand IF statement requires brackets around comparison
Loading history...
891
                }
892
            }
893
894
            // If the collection has a null value we add the null expression
895 93
            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 2
                    $expression = '('.$this->quoteIdentifier($query, $column).' '.$operator.' ('.implode(',', $values).')';
898
899 2
                    if ($operator === 'IN') {
900 1
                        return $expression.' OR '.$this->compileExpression($query, $column, 'in', null, $converted).')';
901
                    } else {
902 1
                        return $expression.' AND '.$this->compileExpression($query, $column, '!in', null, $converted).')';
903
                    }
904
                }
905
906 2
                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 93
            $values = '('.implode(',', $values).')';
910 3
        } elseif ($values instanceof QueryInterface) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
911 2
            $values = $this->compileSubQuery($query, $values);
912 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...
913
            $values = '('.$values->build($query, $this).')';
914
        } else {
915 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 shorthand IF statement requires brackets around comparison
Loading history...
916 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...
917
        }
0 ignored issues
show
Coding Style introduced by
End comment for long condition not found; expected "//end if"
Loading history...
918
919 96
        return $this->quoteIdentifier($query, $column).' '.$operator.' '.$values;
920
    }
921
922
    /**
923
     * 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
     *
926
     * @param CompilableClause $query
927
     * @param array $values
928
     * @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
     * @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 5
    public function compileIntoExpression(CompilableClause $query, array $values, $column, $operator, $converted, $separator = CompositeExpression::TYPE_OR)
937
    {
938 5
        $into = [];
939
940 5
        $column = $this->quoteIdentifier($query, $column);
941
942 5
        foreach ($values as $value) {
943 5
            $into[] = $column.' '.$operator.' '.$this->compileExpressionValue($query, $value, $converted);
944
        }
945
946 5
        return '('.implode(' '.$separator.' ', $into).')';
947
    }
948
949
    /**
950
     * Compile group by expression
951
     * 
952
     * @param CompilableClause $query
953
     * 
954
     * @return string
955
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
956
     */
957 621
    protected function compileGroup(CompilableClause $query)
958
    {
959 621
        if (empty($query->statements['groups'])) {
960 620
            return '';
961
        }
962
963 3
        $fields = array_map([$query->preprocessor(), 'field'], $query->statements['groups']);
964
965 3
        if ($query->isQuoteIdentifier()) {
966
            $fields = $this->quoteIdentifiers($query, $fields);
967
        }
968
        
969 3
        return ' GROUP BY '.implode(', ', $fields);
970
    }
971
972
    /**
973
     * Compile order by expression
974
     * 
975
     * @param CompilableClause $query
976
     * 
977
     * @return string
978
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
979
     */
980 621
    protected function compileOrder(CompilableClause $query)
981
    {
982 621
        if (empty($query->statements['orders'])) {
983 576
            return '';
984
        }
985
        
986 47
        $sql = [];
987
988 47
        foreach ($query->statements['orders'] as $part) {
989 47
            if ($part['sort'] instanceof ExpressionInterface) {
990
                $part['sort'] = $part['sort']->build($query, $this);
991
            } else {
992 47
                $part['sort'] = $this->quoteIdentifier($query, $query->preprocessor()->field($part['sort']));
993
            }
994
            
995 47
            $sql[] = $part['sort'].' '.$part['order'];
996
        }
997
998 47
        return ' ORDER BY '.implode(', ', $sql);
999
    }
1000
1001
    /**
1002
     * Compile the lock expression
1003
     *
1004
     * 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
     * @return string
1009
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
1010
     */
1011 621
    protected function compileLock(CompilableClause $query)
1012
    {
1013 621
        $lock = $query->statements['lock'];
1014
1015
        // The lock should not be applied on aggregate function
1016 621
        if ($lock !== null && !$query->statements['aggregate']) {
1017
            // Lock for update
1018 2
            if ($lock === LockMode::PESSIMISTIC_WRITE) {
1019 1
                return ' ' . $this->platform()->grammar()->getWriteLockSQL();
1020
            }
1021
1022
            // Shared Lock: other process can read the row but not update it.
1023 1
            if ($lock === LockMode::PESSIMISTIC_READ) {
1024 1
                return ' ' . $this->platform()->grammar()->getReadLockSQL();
1025
            }
1026
        }
1027
1028 619
        return '';
1029
    }
1030
1031
    /**
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 13
    protected function addQueryBindings(CompilableClause $clause, $subQuery)
0 ignored issues
show
introduced by
Type hint "QueryInterface" missing for $subQuery
Loading history...
1041
    {
1042 13
        foreach ($subQuery->getBindings() as $binding) {
1043 5
            $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 13
        return $this;
1047
    }
1048
1049
    /**
1050
     * 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
     *
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 487
    protected function bindTyped(CompilableClause $query, $value, ?TypeInterface $type)
1066
    {
1067 487
        return $this->bindRaw($query, $this->platform()->types()->toDatabase($value, $type));
1068
    }
1069
1070
    /**
1071
     * 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
     *
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
     *
1082
     * @return string
1083
     */
1084 568
    protected function bindRaw(CompilableClause $query, $value)
1085
    {
1086 568
        $query->state()->bind($value);
1087
1088 568
        return '?';
1089
    }
1090
1091
    /**
1092
     * @param CompilableClause $query
1093
     *
1094
     * @return array
1095
     */
1096 597
    public function getBindings(CompilableClause $query)
1097
    {
1098 597
        return $this->mergeBindings($query->state()->bindings);
1099
    }
1100
1101
    /**
1102
     * Merge algo for bindings and binding types
1103
     * 
1104
     * @param array $bindings
1105
     *
1106
     * @return array
1107
     */
1108 597
    protected function mergeBindings($bindings)
0 ignored issues
show
introduced by
Type hint "array" missing for $bindings
Loading history...
1109
    {
1110 597
        $mergedBindings = [];
1111
1112 597
        if (isset($bindings[0])) {
1113 454
            $mergedBindings = $bindings[0];
1114
        } else {
1115 544
            foreach (['columns', 'joins', 'where', 'groups', 'having', 'orders'] as $part) {
1116 544
                if (isset($bindings[$part])) {
1117 544
                    $mergedBindings = array_merge($mergedBindings, $bindings[$part]);
1118
                }
1119
            }
1120
        }
1121
1122 597
        return $mergedBindings;
1123
    }
1124
}
1125