Passed
Pull Request — master (#18)
by Vincent
07:19
created

SqlCompiler::compileTableAndAliasClause()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

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

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

280
        if ($query->/** @scrutinizer ignore-call */ isLimitQuery()) {
Loading history...
281 179
            $sql = $this->platform()->grammar()->modifyLimitQuery($sql, $query->statements['limit'], $query->statements['offset']);
282
        }
283
284 616
        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...
285
    }
286
287
    /**
288
     * Check if the the query is an aggregate which requires to execute the query as temporary table
289
     * 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...
290
     *
291
     * @param CompilableClause $query
292
     *
293
     * @return bool
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
294
     */
295 616
    protected function isComplexAggregate(CompilableClause $query)
296
    {
297 616
        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...
298
    }
299
300
    /**
301
     * Compile the complexe aggregate query
302
     * 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...
303
     *
304
     * @param CompilableClause $query
305
     *
306
     * @return string
307
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
308
     */
309 3
    protected function compileComplexAggregate(CompilableClause $query)
310
    {
311 3
        list($function, $column) = $query->statements['aggregate'];
312
313 3
        $query->statements['aggregate'] = null;
314 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...
315
316 3
        return 'SELECT '.$this->compileAggregate($query, $function, '*', false).' FROM ('.$this->doCompileSelect($query).') as derived_query';
317
    }
318
319
    /**
320
     * @param CompilableClause $query
321
     *
322
     * @return string
323
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
324
     */
325 616
    protected function compileColumns(CompilableClause $query)
326
    {
327 616
        if (!empty($query->statements['aggregate'])) {
328 368
            return 'SELECT '.$this->compileAggregate($query, $query->statements['aggregate'][0], $query->statements['aggregate'][1], $query->statements['distinct']);
329
        }
330
331 447
        if ($query->statements['distinct'] && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
332 8
            $select = 'SELECT DISTINCT ';
333
        } else {
334 440
            $select = 'SELECT ';
335
        }
336
        
337 447
        if (empty($query->statements['columns'])) {
338 389
            $root = $query->preprocessor()->root();
339
340 389
            if ($root) {
341 293
                $select .= $this->quoteIdentifier($query, $root).'.';
342
            }
343
344 389
            return $select.'*';
345
        }
346
347 67
        $sql = [];
348
        
349 67
        foreach ($query->statements['columns'] as $column) {
350 67
            $sql[] = $this->compileExpressionColumn($query, $column['column'], $column['alias']);
351
        }
352
        
353 67
        return $select.implode(', ', $sql);
354
    }
355
356
    /**
357
     * Compile a SQL function
358
     *
359
     * @param CompilableClause $query
360
     * @param string $function  The sql function
361
     * @param string $column    The column to aggregate
362
     * @param bool   $distinct  The distinct status
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
363
     *
364
     * @return string
365
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
366
     */
367 370
    protected function compileAggregate(CompilableClause $query, $function, $column, $distinct)
368
    {
369 370
        if ($column !== '*') {
370 14
            $column = $query->preprocessor()->field($column);
371 14
            $column = $this->quoteIdentifier($query, $column);
372
373 14
            if ($distinct && $this->platform()->grammar()->getReservedKeywordsList()->isKeyword('DISTINCT')) {
374
                // Le count ne compte pas les fields qui ont une valeur NULL.
375
                // Pour une pagination, il est important de compter les valeurs null sachant qu'elles seront sélectionnées.
376
                // La pagination utilise une column que pour le distinct.
377 12
                if ($function === 'pagination') {
378 4
                    $column = 'IFNULL('.$column.',"___null___")';
379
                }
380
381 12
                $column = 'DISTINCT '.$column;
382
            }
383
        }
384
385
        switch ($function) {
386 370
            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...
387 368
            case 'count':      return $this->platform()->grammar()->getCountExpression($column).' AS aggregate';
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

Loading history...
388 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...
389 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...
390 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...
391 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...
392
393
            default:
394 1
                $method = 'get'.ucfirst($function).'Expression';
395 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...
396
        }
397
    }
398
399
    /**
400
     * Compile expression column
401
     *
402
     * @param CompilableClause $query
403
     * @param mixed $column
404
     * @param string $alias
405
     * 
406
     * @return string
407
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
408
     */
409 67
    protected function compileExpressionColumn(CompilableClause $query, $column, $alias = null)
410
    {
411 67
        if ($column instanceof QueryInterface) {
412 1
            return $this->compileSubQuery($query, $column, $alias);
413
        }
414
        
415 67
        if ($column instanceof ExpressionInterface) {
416 3
            return $alias !== null
417 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...
418 3
                : $column->build($query, $this)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
419
            ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
420
        }
421
422 66
        $column = $query->preprocessor()->field($column);
423
        
424 66
        if (strpos($column, '*') !== false) {
425 1
            return $column;
426
        }
427
        
428 66
        return $alias !== null
429 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...
430 66
            : $this->quoteIdentifier($query, $column)
0 ignored issues
show
Coding Style introduced by
Space after closing parenthesis of function call prohibited
Loading history...
431
        ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
432
    }
433
    
434
    /**
435
     * @param CompilableClause $query
436
     *
437
     * @return string
438
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
439
     */
440 616
    protected function compileFrom(CompilableClause $query)
441
    {
442 616
        $sql = ' FROM ';
443 616
        $isFirst = true;
444
445
        // Loop through all FROM clauses
446 616
        foreach ($this->compileTableAndAliasClause($query, $query->statements['tables']) as $from) {
447 616
            if (!$isFirst) {
448 6
                $sql .= ', ';
449
            } else {
450 616
                $isFirst = false;
451
            }
452
453 616
            $sql .= $from['sql'];
454
        }
455
456 616
        return $sql;
457
    }
458
459
    /**
460
     * @param CompilableClause $query
461
     *
462
     * @return string
463
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
464
     */
465 616
    protected function compileJoins(CompilableClause $query)
466
    {
467 616
        if (empty($query->statements['joins'])) {
468 597
            return '';
469
        }
470
471 50
        $sql = '';
472
473 50
        foreach ($this->compileTableAndAliasClause($query, $query->statements['joins']) as $join) {
474 50
            $sql .= ' '.$join['type'].' JOIN '.$join['sql'].' ON '.$this->compileCompilableClauses($query, $join['on']);
475
        }
476
477 50
        return $sql;
478
    }
479
480
    /**
481
     * Compile clauses with 'table' and 'alias' keys
482
     *
483
     * The table name will be resolved, quoted, and generate the alias if present
484
     * 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...
485
     *
486
     * The compiled table expression will be returned into the 'sql' key
487
     * All input parameter will be kept on the return value
488
     *
489
     * @param CompilableClause $query
490
     * @param array $clauses
491
     *
492
     * @return array
493
     *
494
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
495
     */
496 616
    protected function compileTableAndAliasClause(CompilableClause $query, array $clauses): array
497
    {
498 616
        $databasePrefix = $this->getDatabaseNamePrefix($query);
499 616
        $compiled = [];
500
501 616
        foreach ($clauses as $from) {
502 616
            if ($from['table'] instanceof QueryInterface) {
503 3
                $from['sql'] = $this->compileSubQuery($query, $from['table'], $from['alias']);
504 3
                $compiled[] = $from;
505
            } else {
506 616
                $from = $query->preprocessor()->table($from);
507
508 616
                if ($from['alias'] === null) {
509 464
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']);
510 464
                    $compiled[$from['table']] = $from;
511
                } else {
512 360
                    $from['sql'] = $this->quoteIdentifier($query, $databasePrefix.$from['table']) . ' ' . $this->quoteIdentifier($query, $from['alias']);
513 616
                    $compiled[$from['alias']] = $from;
514
                }
515
            }
516
        }
517
518 616
        return $compiled;
519
    }
520
521
    /**
522
     * Adding database prefix for sub query x-db
523
     *
524
     * @param CompilableClause $query
525
     *
526
     * @return string
527
     */
528 616
    protected function getDatabaseNamePrefix(CompilableClause $query): string
529
    {
530 616
        if ($query instanceof CommandInterface && $query->compiler() !== $this && $query->connection()->getDatabase() !== $this->connection->getDatabase()) {
531 2
            return $query->connection()->getDatabase().'.';
532
        }
533
534 615
        return '';
535
    }
536
537
    /**
538
     * Compile Where sql
539
     * 
540
     * @param CompilableClause $query
541
     * 
542
     * @return string
543
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
544
     */
545 627
    protected function compileWhere(CompilableClause $query)
546
    {
547 627
        if (empty($query->statements['where'])) {
548 498
            return '';
549
        }
550
        
551 331
        return ' WHERE '.$this->compileCompilableClauses($query, $query->statements['where']);
552
    }
553
554
    /**
555
     * Compile having sql
556
     * 
557
     * @param CompilableClause $query
558
     * 
559
     * @return string
560
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
561
     */
562 616
    protected function compileHaving(CompilableClause $query)
563
    {
564 616
        if (empty($query->statements['having'])) {
565 607
            return '';
566
        }
567
        
568 9
        return ' HAVING '.$this->compileCompilableClauses($query, $query->statements['having']);
569
    }
570
571
    /**
572
     * @param CompilableClause $query
573
     * @param array $clauses
574
     *
575
     * @return string
576
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
577
     */
578 343
    protected function compileCompilableClauses(CompilableClause $query, array &$clauses)
579
    {
580 343
        $sql = [];
581 343
        $i = 0;
582
583
        // Permet de retirer le niveau du nested
584 343
        if (count($clauses) === 1 && isset($clauses[0]['nested'])) {
585 149
            $result = $this->compileCompilableClauses($query, $clauses[0]['nested']);
586
            /*
0 ignored issues
show
Coding Style introduced by
Empty line required before block comment
Loading history...
587
             * 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...
588
             * If we still have one clause, we return the compiled sql
589
             * Otherwise we start the loop of clauses.
590
             */
0 ignored issues
show
Coding Style introduced by
Empty line required after block comment
Loading history...
591 149
            if (count($clauses) === 1) {
592 147
                return $result;
593
            }
594
595
            // Add the nested level
596 2
            $sql[] = '('.$result.')';
597 2
            $i = 1;
598
        }
599
600 343
        $clauses[0]['glue'] = null;
601
602
        //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...
603 343
        for (; isset($clauses[$i]); ++$i) {
604 343
            $part = $clauses[$i];
605
606 343
            if ($part['glue'] !== null) {
607 100
                $part['glue'] .= ' ';
608
            }
609
610 343
            $part = $query->preprocessor()->expression($part);
611
            
612 343
            if (isset($part['nested'])) {
613 64
                $sql[] = $part['glue'].'('.$this->compileCompilableClauses($query, $part['nested']).')';
614 343
            } elseif (!isset($part['raw'])) {
0 ignored issues
show
Coding Style introduced by
Usage of ELSEIF not allowed; use ELSE IF instead
Loading history...
615 333
                $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...
616
            } else {
617 10
                $sql[] = $part['glue'].$this->compileRawValue($query, $part['raw']);
618
            }
619
        }
620
        
621 343
        return implode(' ', $sql);
622
    }
623
624
    /**
625
     * Determine which operator to use based on custom and standard syntax
626
     *
627
     * @param CompilableClause $query
628
     * @param string $column
629
     * @param string $operator
630
     * @param mixed  $value
631
     * @param bool $converted
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for parameter type
Loading history...
632
     * 
633
     * @return string  operator found
634
     * 
635
     * @throws UnexpectedValueException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
636
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
637
     */
0 ignored issues
show
Coding Style introduced by
Expected 1 @throws tag(s) in function comment; 2 found
Loading history...
638 333
    protected function compileExpression(CompilableClause $query, $column, $operator, $value, $converted)
639
    {
640 333
        if ($value instanceof ExpressionTransformerInterface) {
641 6
            $value->setContext($this, $column, $operator);
642
643 6
            $column    = $value->getColumn();
644 6
            $operator  = $value->getOperator();
645 6
            $value     = $value->getValue();
646 6
            $converted = true;
647
        }
648
649
        switch ($operator) {
650 333
            case '<':
651 333
            case ':lt':
652 1
                if (is_array($value)) {
653
                    return $this->compileIntoExpression($query, $value, $column, '<', $converted);
654
                }
655 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...
656
657 333
            case '<=':
658 333
            case ':lte':
659
                if (is_array($value)) {
660
                    return $this->compileIntoExpression($query, $value, $column, '<=', $converted);
661
                }
662
                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...
663
664 333
            case '>':
665 328
            case ':gt':
666 7
                if (is_array($value)) {
667
                    return $this->compileIntoExpression($query, $value, $column, '>', $converted);
668
                }
669 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...
670
671 328
            case '>=':
672 328
            case ':gte':
673 2
                if (is_array($value)) {
674
                    return $this->compileIntoExpression($query, $value, $column, '>=', $converted);
675
                }
676 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...
677
678
            // REGEX matching
679 328
            case '~=':
680 328
            case '=~':
681 328
            case ':regex':
682
                if (is_array($value)) {
683
                    return $this->compileIntoExpression($query, $value, $column, 'REGEXP', $converted);
684
                }
685
                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...
686
687
            // LIKE
688 328
            case ':like':
689 24
                if (is_array($value)) {
690 4
                    return $this->compileIntoExpression($query, $value, $column, 'LIKE', $converted);
691
                }
692 20
                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...
693
694
            // NOT LIKE
695 316
            case ':notlike':
696 316
            case '!like':
697 1
                if (is_array($value)) {
698 1
                    return $this->compileIntoExpression($query, $value, $column, 'NOT LIKE', $converted, CompositeExpression::TYPE_AND);
699
                }
700 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...
701
702
            // In
703 315
            case 'in':
704 307
            case ':in':
705 94
                if (empty($value)) {
706 2
                    return $this->platform()->grammar()->getIsNullExpression($this->quoteIdentifier($query, $column));
707
                }
708 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

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