Test Failed
Push — master ( 324ce1...c0782f )
by Sébastien
09:01
created

SqlCompiler::getDatabaseNamePrefix()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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

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

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

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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