Passed
Pull Request — next (#84)
by Bas
15:17 queued 11:12
created

Grammar   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 438
Duplicated Lines 0 %

Test Coverage

Coverage 96.88%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 122
c 7
b 0
f 0
dl 0
loc 438
ccs 93
cts 96
cp 0.9688
rs 9.28
wmc 39

21 Methods

Rating   Name   Duplication   Size   Complexity  
A compileSearch() 0 11 2
A compileOffset() 0 5 1
A getBitwiseOperators() 0 3 1
A getDateFormat() 0 3 1
A parameter() 0 4 2
A compileVariables() 0 13 3
A prefixTable() 0 3 1
A compileOrders() 0 7 2
A isExpression() 0 3 1
A compileSelect() 0 37 4
A compilePreIterationVariables() 0 3 1
A compileLimit() 0 7 2
A compileOrdersToArray() 0 18 5
A prepareBindingsForDelete() 0 4 1
A translateOperator() 0 7 2
A getOperators() 0 3 1
A compileFrom() 0 12 1
A compileComponents() 0 17 4
A compilePostIterationVariables() 0 3 1
A compileRandom() 0 5 1
A getValue() 0 7 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Query;
6
7
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
8
use Illuminate\Database\Query\Expression;
9
use Illuminate\Database\Query\Grammars\Grammar as IlluminateQueryGrammar;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Traits\Macroable;
12
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesAggregates;
13
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesColumns;
14
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesFilters;
15
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesGroups;
16
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesDataManipulations;
17
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesJoins;
18
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesUnions;
19
use LaravelFreelancerNL\Aranguent\Query\Concerns\ConvertsIdToKey;
20
use LaravelFreelancerNL\Aranguent\Query\Concerns\HandlesAqlGrammar;
21
22
class Grammar extends IlluminateQueryGrammar
23
{
24
    use CompilesAggregates;
0 ignored issues
show
Bug introduced by
The trait LaravelFreelancerNL\Aran...erns\CompilesAggregates requires the property $from which is not provided by LaravelFreelancerNL\Aranguent\Query\Grammar.
Loading history...
25
    use CompilesColumns;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...oncerns\CompilesColumns requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Grammar: $groups, $returnSingleValue, $distinct, $from, $joins, $table, $tableAliases
Loading history...
26
    use CompilesFilters;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...oncerns\CompilesFilters requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Grammar: $havings, $wheres
Loading history...
27
    use CompilesDataManipulations;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...mpilesDataManipulations requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Grammar: $from, $joins
Loading history...
28
    use CompilesJoins;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...\Concerns\CompilesJoins requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Grammar: $table, $grammar, $type
Loading history...
29
    use CompilesGroups;
30
    use CompilesUnions;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...Concerns\CompilesUnions requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Grammar: $unions, $unionOrders, $unionOffset, $unionLimit
Loading history...
31
    use ConvertsIdToKey;
32
    use HandlesAqlGrammar;
33
    use Macroable;
34
35
    public string $name;
36
37
    /**
38
     * The grammar table prefix.
39
     *
40
     * @var string
41
     */
42
    protected $tablePrefix = '';
43
44
    /**
45
     * The grammar table prefix.
46
     *
47
     * @var null|int
48
     */
49
    protected $offset = null;
50
51
    /**
52
     * The grammar specific operators.
53
     *
54
     * @var array<string>
55
     */
56
    protected $operators = [
57
        '==', '!=', '<', '>', '<=', '>=',
58
        'LIKE', '~', '!~',
59
        'IN', 'NOT IN',
60
        'ALL ==', 'ALL !=', 'ALL <', 'ALL >', 'ALL <=', 'ALL >=', 'ALL IN',
61
        'ANY ==', 'ANY !=', 'ANY <', 'ANY >', 'ANY <=', 'ANY >=', 'ANY IN',
62
        'NONE ==', 'NONE !=', 'NONE <', 'NONE >', 'NONE <=', 'NONE >=', 'NONE IN',
63
    ];
64
65
    /**
66
     * The components that make up a select clause.
67
     *
68
     * @var array<string>
69
     */
70
    protected $selectComponents = [
71
        'preIterationVariables',
72
        'from',
73
        'search',
74
        'joins',
75
        'postIterationVariables',
76
        'wheres',
77
        'groups',
78
        'aggregate',
79
        'havings',
80
        'orders',
81
        'offset',
82
        'limit',
83
        'columns',
84
    ];
85
86
    /**
87
     * @var array<string, string>
88
     */
89
    protected array $operatorTranslations = [
90
        '='          => '==',
91
        '<>'         => '!=',
92
        '<=>'        => '==',
93
        'rlike'      => '=~',
94
        'not rlike'  => '!~',
95
        'regexp'     => '=~',
96
        'not regexp' => '!~',
97
    ];
98
99
    /**
100
     * @var array<string, string>
101
     */
102
    protected array $whereTypeOperators = [
103
        'In'    => 'IN',
104
        'NotIn' => 'NOT IN',
105
    ];
106
107
    /**
108
     * The grammar specific bitwise operators.
109
     *
110
     * @var array<string>
111
     */
112
    public $bitwiseOperators = [
113
        '&', '|', '^', '<<', '>>', '~',
114
    ];
115
116
    /**
117
     * Get the format for database stored dates.
118
     *
119
     * @return string
120
     */
121 26
    public function getDateFormat()
122
    {
123 26
        return 'Y-m-d\TH:i:s.vp';
124
    }
125
126
    /**
127
     * Get the grammar specific operators.
128
     *
129
     * @return array<string>
130
     */
131 92
    public function getOperators()
132
    {
133 92
        return $this->operators;
134
    }
135
136
137 155
    public function translateOperator(string $operator): string
138
    {
139 155
        if (array_key_exists($operator, $this->operatorTranslations)) {
140 94
            return $this->operatorTranslations[$operator];
141
        }
142
143 102
        return $operator;
144
    }
145
146 254
    protected function prefixTable(string $table): string
147
    {
148 254
        return $this->tablePrefix . $table;
149
    }
150
151
152
    /**
153
     * Get the appropriate query parameter place-holder for a value.
154
     *
155
     * @param  mixed  $value
156
     * @return string
157
     */
158 150
    public function parameter($value)
159
    {
160
161 150
        return $this->isExpression($value) ? $this->getValue($value) : $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->isExpressi...tValue($value) : $value also could return the type boolean which is incompatible with the documented return type string.
Loading history...
162
    }
163
164
    /**
165
     * Compile the components necessary for a select clause.
166
     *
167
     * @param  IlluminateQueryBuilder  $query
168
     * @return array<string, string>
169
     */
170 253
    protected function compileComponents(IlluminateQueryBuilder $query)
171
    {
172 253
        $aql = [];
173
174 253
        foreach ($this->selectComponents as $component) {
175 253
            if ($component === 'unions') {
176
                continue;
177
            }
178
179 253
            if (isset($query->$component)) {
180 253
                $method = 'compile' . ucfirst($component);
181
182 253
                $aql[$component] = $this->$method($query, $query->$component);
183
            }
184
        }
185
186 253
        return $aql;
187
    }
188
189
190
191
    /**
192
     * Compile a select query into SQL.
193
     *
194
     * @param IlluminateQueryBuilder $query
195
     * @return string
196
     */
197 253
    public function compileSelect(IlluminateQueryBuilder $query)
198
    {
199
        assert($query instanceof Builder);
200
201
        // If the query does not have any columns set, we'll set the columns to the
202
        // * character to just get all of the columns from the database. Then we
203
        // can build the query and concatenate all the pieces together as one.
204 253
        $original = $query->columns;
205
206 253
        if (empty($query->columns)) {
207 25
            $query->columns = ['*'];
208
        }
209
210
        // To compile the query, we'll spin through each component of the query and
211
        // see if that component exists. If it does we'll just call the compiler
212
        // function for the component which is responsible for making the SQL.
213
214 253
        $aql = trim(
215 253
            $this->concatenate(
216 253
                $this->compileComponents($query)
217 253
            )
218 253
        );
219
220
        //        if ($query->unions && $query->aggregate) {
221
        //            return $this->compileUnionAggregate($query);
222
        //        }
223 253
        if ($query->unions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query->unions 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...
224 5
            return $this->compileUnions($query, $aql);
225
        }
226
227 253
        $query->columns = $original;
228
229 253
        if ($query->groupVariables !== null) {
230 10
            $query->cleanGroupVariables();
231
        }
232
233 253
        return $aql;
234
    }
235
236
    /**
237
     * Compile the "from" portion of the query -> FOR in AQL.
238
     *
239
     * @param IlluminateQueryBuilder $query
240
     * @param string  $table
241
     *
242
     * @return string
243
     */
244 253
    protected function compileFrom(IlluminateQueryBuilder $query, $table)
245
    {
246
        assert($query instanceof Builder);
247
248
        // FIXME: wrapping/quoting
249 253
        $table = $this->prefixTable($table);
250
251
        //FIXME: register given alias (x AS y in SQL)
252 253
        $alias = $query->registerTableAlias($table);
253
254
255 253
        return "FOR $alias IN $table";
256
    }
257
258
    /**
259
     * @param IlluminateQueryBuilder $query
260
     * @param array<string, mixed> $variables
261
     * @return string
262
     */
263 253
    protected function compilePreIterationVariables(IlluminateQueryBuilder $query, array $variables): string
264
    {
265 253
        return $this->compileVariables($query, $variables);
266
    }
267
268
    /**
269
     * @param IlluminateQueryBuilder $query
270
     * @param array<string, mixed> $variables
271
     * @return string
272
     */
273 253
    protected function compilePostIterationVariables(IlluminateQueryBuilder $query, array $variables): string
274
    {
275 253
        return $this->compileVariables($query, $variables);
276
    }
277
278
279
    /**
280
     * @param IlluminateQueryBuilder $query
281
     * @param array<string, mixed> $variables
282
     * @return string
283
     *
284
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
285
     */
286 253
    protected function compileVariables(IlluminateQueryBuilder $query, array $variables): string
287
    {
288 253
        $aql = '';
289
290 253
        foreach ($variables as $variable => $value) {
291 7
            if ($value instanceof Expression) {
292
                $value = $value->getValue($this);
293
            }
294
295 7
            $aql .= ' LET ' . $variable . ' = ' . $value;
296
        }
297
298 253
        return trim($aql);
299
    }
300
301
    /**
302
     * Compile the "order by" portions of the query.
303
     *
304
     * @param Builder $query
305
     * @param array<mixed> $orders
306
     * @param null|string $table
307
     * @return string
308
     */
309 33
    protected function compileOrders(IlluminateQueryBuilder $query, $orders, $table = null)
310
    {
311 33
        if (!empty($orders)) {
312 33
            return 'SORT ' . implode(', ', $this->compileOrdersToArray($query, $orders, $table));
313
        }
314
315
        return '';
316
    }
317
318
    /**
319
     * Compile the query orders to an array.
320
     *
321
     * @param Builder $query
322
     * @param array<mixed> $orders
323
     * @param null|string $table
324
     * @return array<string>
325
     * @throws \Exception
326
     */
327 33
    protected function compileOrdersToArray(IlluminateQueryBuilder $query, $orders, $table = null)
328
    {
329 33
        return array_map(function ($order) use ($query, $table) {
330 33
            $key = 'column';
331 33
            if (array_key_exists('sql', $order)) {
332 2
                $key = 'sql';
333
            }
334
335 33
            if (!$order[$key] instanceof Expression) {
336 29
                $order[$key] = $this->normalizeColumn($query, $order[$key], $table);
337
            }
338
339 33
            if ($order[$key] instanceof Expression) {
340 4
                $order[$key] = $order[$key]->getValue($this);
341
            }
342
343 33
            return array_key_exists('direction', $order) ? $order[$key] . ' ' . $order['direction'] : $order[$key];
344 33
        }, $orders);
345
    }
346
347
    /**
348
     * Compile the "offset" portions of the query.
349
     *
350
     * @param  \Illuminate\Database\Query\Builder  $query
351
     * @param  int  $offset
352
     * @return string
353
     *
354
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
355
     */
356 10
    protected function compileOffset(IlluminateQueryBuilder $query, $offset)
357
    {
358 10
        $this->offset = (int) $offset;
359
360 10
        return "";
361
    }
362
363
    /**
364
     * Compile the "limit" portions of the query.
365
     *
366
     * @param  \Illuminate\Database\Query\Builder  $query
367
     * @param  int  $limit
368
     * @return string
369
     *
370
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
371
     */
372 135
    protected function compileLimit(IlluminateQueryBuilder $query, $limit)
373
    {
374 135
        if ($this->offset !== null) {
375 10
            return "LIMIT " . (int) $this->offset . ", " . (int) $limit;
376
        }
377
378 125
        return "LIMIT " . (int) $limit;
379
    }
380
381
    /**
382
     * Compile the random statement into SQL.
383
     *
384
     * @param  string|int|null  $seed
385
     * @return string
386
     */
387 1
    public function compileRandom($seed = null)
388
    {
389 1
        unset($seed);
390
391 1
        return "RAND()";
392
    }
393
394
    /**
395
     * @param IlluminateQueryBuilder $query
396
     * @param array<mixed> $search
397
     * @return string
398
     * @throws \Exception
399
     */
400 4
    public function compileSearch(IlluminateQueryBuilder $query, array $search)
401
    {
402 4
        $predicates = [];
403 4
        foreach($search['fields'] as $field) {
404 4
            $predicates[] = $this->normalizeColumn($query, $field)
405 4
                . ' IN TOKENS(' . $search['searchText'] . ', "text_en")';
406
        }
407
408 4
        return 'SEARCH ANALYZER('
409 4
            . implode(' OR ', $predicates)
410 4
            . ', "text_en")';
411
    }
412
413
    /**
414
     * Get the value of a raw expression.
415
     *
416
     * @param bool|float|Expression|int|string|null $expression
417
     * @return bool|float|int|string|null
418
     */
419 210
    public function getValue($expression)
420
    {
421 210
        if ($expression instanceof Expression) {
422 1
            return $expression->getValue($this);
423
        }
424
425 210
        return $expression;
426
    }
427
428
    /**
429
     * Get the grammar specific bit operators.
430
     *
431
     * @return array<string>
432
     */
433 138
    public function getBitwiseOperators()
434
    {
435 138
        return $this->bitwiseOperators;
436
    }
437
438
    /**
439
     * Prepare the bindings for a delete statement.
440
     *
441
     * @param  array<mixed>  $bindings
442
     * @return array<mixed>
443
     */
444 21
    public function prepareBindingsForDelete(array $bindings)
445
    {
446 21
        return Arr::collapse(
447 21
            Arr::except($bindings, 'select')
448 21
        );
449
    }
450
451
    /**
452
     * Determine if the given value is a raw expression.
453
     *
454
     * @param  mixed  $value
455
     * @return bool
456
     */
457 150
    public function isExpression($value)
458
    {
459 150
        return $value instanceof Expression;
460
    }
461
}
462