Failed Conditions
Push — refactor/improve-static-analys... ( d71351...8065ea )
by Bas
05:41
created

Grammar::getDateFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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\CompilesInserts;
17
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesJoins;
18
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesUnions;
19
use LaravelFreelancerNL\Aranguent\Query\Concerns\CompilesWheres;
20
use LaravelFreelancerNL\Aranguent\Query\Concerns\ConvertsIdToKey;
21
use LaravelFreelancerNL\Aranguent\Query\Concerns\HandlesAqlGrammar;
22
23
class Grammar extends IlluminateQueryGrammar
24
{
25
    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...
26
    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...
27
    use CompilesFilters;
0 ignored issues
show
Bug introduced by
The trait LaravelFreelancerNL\Aran...oncerns\CompilesFilters requires the property $havings which is not provided by LaravelFreelancerNL\Aranguent\Query\Grammar.
Loading history...
28
    use CompilesInserts;
0 ignored issues
show
Bug introduced by
The trait LaravelFreelancerNL\Aran...oncerns\CompilesInserts requires the property $from which is not provided by LaravelFreelancerNL\Aranguent\Query\Grammar.
Loading history...
29
    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: $type, $table, $grammar
Loading history...
30
    use CompilesGroups;
31
    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...
32
    use CompilesWheres;
33
    use ConvertsIdToKey;
34
    use HandlesAqlGrammar;
35
    use Macroable;
36
37
    public $name;
38
39
    /**
40
     * The grammar table prefix.
41
     *
42
     * @var string
43
     */
44
    protected $tablePrefix = '';
45
46
    /**
47
     * The grammar table prefix.
48
     *
49
     * @var null|int
50
     */
51
    protected $offset = null;
52
53
    /**
54
     * The grammar specific operators.
55
     *
56
     * @var array
57
     */
58
    protected $operators = [
59
        '==', '!=', '<', '>', '<=', '>=',
60
        'LIKE', '~', '!~',
61
        'IN', 'NOT IN',
62
        'ALL ==', 'ALL !=', 'ALL <', 'ALL >', 'ALL <=', 'ALL >=', 'ALL IN',
63
        'ANY ==', 'ANY !=', 'ANY <', 'ANY >', 'ANY <=', 'ANY >=', 'ANY IN',
64
        'NONE ==', 'NONE !=', 'NONE <', 'NONE >', 'NONE <=', 'NONE >=', 'NONE IN',
65
    ];
66
67
    /**
68
     * The components that make up a select clause.
69
     *
70
     * @var array<string>
71
     */
72
    protected $selectComponents = [
73
        'preIterationVariables',
74
        'from',
75
        'search',
76
        'joins',
77
        'postIterationVariables',
78
        'wheres',
79
        'groups',
80
        'aggregate',
81
        'havings',
82
        'orders',
83
        'offset',
84
        'limit',
85
        'columns',
86 49
    ];
87
88 49
    protected $operatorTranslations = [
89
        '='          => '==',
90
        '<>'         => '!=',
91
        '<=>'        => '==',
92
        'rlike'      => '=~',
93
        'not rlike'  => '!~',
94
        'regexp'     => '=~',
95
        'not regexp' => '!~',
96 12
    ];
97
98 12
    protected $whereTypeOperators = [
99
        'In'    => 'IN',
100
        'NotIn' => 'NOT IN',
101 162
    ];
102
103 162
    /**
104
     * The grammar specific bitwise operators.
105
     *
106
     * @var array
107
     */
108
    public $bitwiseOperators = [
109
        '&', '|', '^', '<<', '>>', '~',
110
    ];
111
112
    /**
113
     * Get the format for database stored dates.
114
     *
115
     * @return string
116 44
     */
117
    public function getDateFormat()
118 44
    {
119 1
        return 'Y-m-d\TH:i:s.vp';
120
    }
121 44
122
    /**
123 44
     * Get the grammar specific operators.
124
     *
125
     * @return array
126
     */
127
    public function getOperators()
128
    {
129
        return $this->operators;
130 44
    }
131 44
132
133
    public function translateOperator(string $operator): string
134 44
    {
135 44
        if (array_key_exists($operator, $this->operatorTranslations)) {
136 44
            return $this->operatorTranslations[$operator];
137 44
        }
138
139 44
        return $operator;
140
    }
141
142
    protected function prefixTable($table)
143
    {
144
        return $this->tablePrefix . $table;
145
    }
146
147 10
148
    /**
149 10
     * Get the appropriate query parameter place-holder for a value.
150 10
     *
151
     * @param  mixed  $value
152 10
     * @return string
153
     */
154 10
    public function parameter($value)
155 9
    {
156
157
        return $this->isExpression($value) ? $this->getValue($value) : $value;
158 10
    }
159
160
    /**
161
     * Compile the components necessary for a select clause.
162
     *
163
     * @param  IlluminateQueryBuilder  $query
164
     * @return array
165
     */
166 10
    protected function compileComponents(IlluminateQueryBuilder $query)
167 10
    {
168
        $aql = [];
169
170 10
        foreach ($this->selectComponents as $component) {
171 10
            if ($component === 'unions') {
172 10
                continue;
173 10
            }
174
175 10
            if (isset($query->$component)) {
176
                $method = 'compile' . ucfirst($component);
177
178
                $aql[$component] = $this->$method($query, $query->$component);
179
            }
180
        }
181
182
        return $aql;
183
    }
184
185 98
186
187 98
    /**
188 98
     * Compile a select query into SQL.
189
     *
190 98
     * @param IlluminateQueryBuilder $query
191
     * @return string
192 98
     */
193
    public function compileSelect(IlluminateQueryBuilder $query)
194
    {
195
        assert($query instanceof Builder);
196
197
        // If the query does not have any columns set, we'll set the columns to the
198
        // * character to just get all of the columns from the database. Then we
199 98
        // can build the query and concatenate all the pieces together as one.
200 98
        $original = $query->columns;
201
202
        if (empty($query->columns)) {
203 98
            $query->columns = ['*'];
204 98
        }
205 98
206 98
        // To compile the query, we'll spin through each component of the query and
207 98
        // see if that component exists. If it does we'll just call the compiler
208
        // function for the component which is responsible for making the SQL.
209 98
210
        $aql = trim(
211
            $this->concatenate(
212
                $this->compileComponents($query)
213
            )
214
        );
215
216
        //        if ($query->unions && $query->aggregate) {
217
        //            return $this->compileUnionAggregate($query);
218
        //        }
219
        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...
220 146
            return $this->compileUnions($query, $aql);
221
        }
222
223
        $query->columns = $original;
224
225
        if ($query->groupVariables !== null) {
226
            $query->cleanGroupVariables();
227
        }
228
229
        return $aql;
230 146
    }
231
232
    /**
233
     * Compile a truncate table statement into SQL.
234
     *
235
     * @param  IlluminateQueryBuilder  $query
236 146
     * @return array
237
     */
238
    public function compileTruncate(IlluminateQueryBuilder $query)
239
    {
240
        return [$this->compileDelete($query) => []];
241
    }
242
243
    /**
244
     * Compile the "from" portion of the query -> FOR in AQL.
245 1
     *
246
     * @param IlluminateQueryBuilder $query
247
     * @param string  $table
248 1
     *
249 1
     * @return string
250 1
     */
251
    protected function compileFrom(IlluminateQueryBuilder $query, $table)
252
    {
253
        assert($query instanceof Builder);
254
255
        // FIXME: wrapping/quoting
256
        $table = $this->prefixTable($table);
257
258
        //FIXME: register given alias (x AS y in SQL)
259
        $alias = $query->registerTableAlias($table);
260 146
261
262 146
        return "FOR $alias IN $table";
263
    }
264
265
    /**
266
     * @param IlluminateQueryBuilder $query
267 146
     * @param array $variables
268 146
     * @return string
269
     */
270 146
    protected function compilePreIterationVariables(IlluminateQueryBuilder $query, array $variables): string
271
    {
272
        return $this->compileVariables($query, $variables);
273
    }
274 146
275
    /**
276
     * @param IlluminateQueryBuilder $query
277
     * @param array $variables
278
     * @return string
279
     */
280
    protected function compilePostIterationVariables(IlluminateQueryBuilder $query, array $variables): string
281
    {
282
        return $this->compileVariables($query, $variables);
283
    }
284
285 146
286
    /**
287 146
     * @param IlluminateQueryBuilder $query
288 146
     * @param array $variables
289
     * @return string
290 146
     *
291
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
292 146
     */
293
    protected function compileVariables(IlluminateQueryBuilder $query, array $variables): string
294
    {
295
        $aql = '';
296
297
        foreach ($variables as $variable => $value) {
298
            if ($value instanceof Expression) {
299
                $value = $value->getValue($this);
300 146
            }
301
302 146
            $aql .= ' LET ' . $variable . ' = ' . $value;
303 1
        }
304 1
305
        return trim($aql);
306
    }
307
308 146
    /**
309
     * Compile the "order by" portions of the query.
310
     *
311
     * @param  \Illuminate\Database\Query\Builder  $query
312
     * @param  array  $orders
313
     * @return string
314
     */
315
    protected function compileOrders(IlluminateQueryBuilder $query, $orders, $table = null)
316
    {
317
        if (!empty($orders)) {
318
            return 'SORT ' . implode(', ', $this->compileOrdersToArray($query, $orders, $table));
319 3
        }
320
321 3
        return '';
322 3
    }
323 3
324
    /**
325 3
     * Compile the query orders to an array.
326
     *
327
     * @param  \Illuminate\Database\Query\Builder  $query
328
     * @param  array  $orders
329
     * @return array
330
     */
331
    protected function compileOrdersToArray(IlluminateQueryBuilder $query, $orders, $table = null)
332
    {
333
        return array_map(function ($order) use ($query, $table) {
334
            $key = 'column';
335
            if (array_key_exists('sql', $order)) {
336
                $key = 'sql';
337
            }
338
339 3
            if ($order[$key] instanceof Expression) {
340
                $order[$key] = $order[$key]->getValue($this);
341 3
            } else {
342
                $order[$key] = $this->normalizeColumn($query, $order[$key], $table);
343 3
            }
344 3
345 2
            return array_key_exists('direction', $order) ? $order[$key] . ' ' . $order['direction'] : $order[$key];
346
        }, $orders);
347
    }
348 3
349
    /**
350 3
     * Compile the "offset" portions of the query.
351 2
     *
352
     * @param  \Illuminate\Database\Query\Builder  $query
353
     * @param  int  $offset
354
     * @return string
355 3
     *
356
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
357
     */
358
    protected function compileOffset(IlluminateQueryBuilder $query, $offset)
359
    {
360
        $this->offset = (int) $offset;
361
362
        return "";
363
    }
364
365
    /**
366
     * Compile the "limit" portions of the query.
367 4
     *
368
     * @param  \Illuminate\Database\Query\Builder  $query
369 4
     * @param  int  $limit
370
     * @return string
371 4
     *
372
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
373
     */
374
    protected function compileLimit(IlluminateQueryBuilder $query, $limit)
375
    {
376
        if ($this->offset !== null) {
377
            return "LIMIT " . (int) $this->offset . ", " . (int) $limit;
378
        }
379
380
        return "LIMIT " . (int) $limit;
381
    }
382 72
383
    protected function createUpdateObject($values)
384 72
    {
385 4
        $valueStrings = [];
386
        foreach($values as $key => $value) {
387 4
            if (is_array($value)) {
388
                $valueStrings[] = $key . ': ' . $this->createUpdateObject($value);
389 68
            } else {
390
                $valueStrings[] = $key . ': ' . $value;
391 68
            }
392
        }
393
394
        return '{ ' . implode(', ', $valueStrings) . ' }';
395
    }
396
397
    /**
398
     * Compile an update statement into AQL.
399
     *
400
     * @param  \Illuminate\Database\Query\Builder  $query
401
     * @param  array  $values
402
     * @return string
403 23
     */
404
    public function compileUpdate(IlluminateQueryBuilder $query, array|string $values)
405
    {
406 23
        assert($query instanceof Builder);
407 23
408
        $table = $query->from;
409 23
        $alias = $query->getTableAlias($query->from);
410
411
        if (!is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
412 23
            $values = Arr::wrap($values);
413
        }
414 23
415
        $updateValues = $this->generateAqlObject($values);
416 23
417
        $aqlElements = [];
418
        $aqlElements[] = $this->compileFrom($query, $query->from);
419
420
        if (!empty($query->joins)) {
421
            $aqlElements[] = $this->compileJoins($query, $query->joins);
422
        }
423
424
        $aqlElements[] = $this->compileWheres($query);
425
426
        $aqlElements[] = 'UPDATE ' . $alias . ' WITH ' . $updateValues . ' IN ' . $table;
427
428
        return implode(' ', $aqlElements);
429
    }
430 1
431
    /**
432
     * Compile an "upsert" statement into AQL.
433 1
     *
434 1
     * @param  \Illuminate\Database\Query\Builder  $query
435
     * @param  array  $values
436
     * @param  array  $uniqueBy
437 1
     * @param  array  $update
438 1
     * @return string
439
     */
440
    public function compileUpsert(IlluminateQueryBuilder $query, array $values, array $uniqueBy, array $update)
441 1
    {
442 1
        $searchFields = [];
443
        foreach($uniqueBy as $key => $field) {
444
            $searchFields[$field] = 'doc.' . $field;
445
        }
446 1
        $searchObject = $this->generateAqlObject($searchFields);
447 1
448 1
        $updateFields = [];
449 1
        foreach($update as $key => $field) {
450 1
            $updateFields[$field] = 'doc.' . $field;
451 1
        }
452
        $updateObject = $this->generateAqlObject($updateFields);
453 1
454
        $valueObjects = [];
455
        foreach($values as $data) {
456
            $valueObjects[] = $this->generateAqlObject($data);
457
        }
458
459
        return 'LET docs = [' . implode(', ', $valueObjects) . ']'
460
            . ' FOR doc IN docs'
461
            . ' UPSERT ' . $searchObject
462
            . ' INSERT doc'
463
            . ' UPDATE ' . $updateObject
464
            . ' IN ' . $query->from;
465
    }
466
467 12
    /**
468
     * Compile a delete statement into SQL.
469 12
     *
470 12
     * @param  \Illuminate\Database\Query\Builder  $query
471
     * @return string
472
     */
473 12
    public function compileDelete(IlluminateQueryBuilder $query)
474 1
    {
475
        $table = $query->from;
476 1
477
        $where = $this->compileWheres($query);
478
479 12
        return trim(
480
            !empty($query->joins)
481
                ? $this->compileDeleteWithJoins($query, $table, $where)
482 12
                : $this->compileDeleteWithoutJoins($query, $table, $where)
483
        );
484 12
    }
485
486 12
487
    /**
488
     * Compile a delete statement without joins into SQL.
489
     *
490
     * @param  \Illuminate\Database\Query\Builder  $query
491
     * @param  string  $table
492
     * @param  string  $where
493
     * @return string
494
     */
495
    protected function compileDeleteWithoutJoins(IlluminateQueryBuilder $query, $table, $where)
496 1
    {
497
        assert($query instanceof Builder);
498 1
499
        $alias = $this->normalizeColumn($query, $query->registerTableAlias($table));
500
501
        $table = $this->wrapTable($this->prefixTable($table));
502
503
        return "FOR {$alias} IN {$table} {$where} REMOVE {$alias} IN {$table}";
504
    }
505 7
506
    /**
507 7
     * Compile the random statement into SQL.
508
     *
509 7
     * @param  string|int|null  $seed
510 3
     * @return string
511
     */
512
    public function compileRandom($seed = null)
513 7
    {
514
        unset($seed);
515
516
        return "RAND()";
517
    }
518
519
    /**
520
     * @param IlluminateQueryBuilder $query
521
     * @return string
522 1
     * @throws \Exception
523
     */
524 1
    public function compileSearch(IlluminateQueryBuilder $query, array $search)
525
    {
526
        $predicates = [];
527
        foreach($search['fields'] as $field) {
528
            $predicates[] = $this->normalizeColumn($query, $field)
529
                . ' IN TOKENS(' . $search['searchText'] . ', "text_en")';
530
        }
531
532
        return 'SEARCH ANALYZER('
533
            . implode(' OR ', $predicates)
534
            . ', "text_en")';
535
    }
536
537
    /**
538
     * Get the value of a raw expression.
539
     *
540
     * @param Expression $expression
541
     * @return string
542
     */
543
    public function getValue($expression)
544
    {
545
        if (!$expression instanceof Expression) {
0 ignored issues
show
introduced by
$expression is always a sub-type of Illuminate\Database\Query\Expression.
Loading history...
546
            return $expression;
547
        }
548
549
        return $expression->getValue($this);
550
    }
551
552
    /**
553
     * Get the grammar specific bit operators.
554
     *
555
     * @return array
556
     */
557
    public function getBitwiseOperators()
558
    {
559
        return $this->bitwiseOperators;
560
    }
561
562
    /**
563
     * Prepare the bindings for a delete statement.
564
     *
565
     * @param  array  $bindings
566
     * @return array
567
     */
568
    public function prepareBindingsForDelete(array $bindings)
569
    {
570
        return Arr::collapse(
571
            Arr::except($bindings, 'select')
572
        );
573
    }
574
575
    /**
576
     * Determine if the given string is a JSON selector.
577
     *
578
     * @param  string  $value
579
     * @return bool
580
     */
581
    public function isJsonSelector($value)
582
    {
583
        if(!is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
584
            return false;
585
        }
586
587
        return str_contains($value, '->');
588
    }
589
590
    public function convertJsonFields($data): mixed
591
    {
592
        if (!is_array($data) && !is_string($data)) {
593
            return $data;
594
        }
595
596
        if (is_string($data)) {
597
            return str_replace('->', '.', $data);
598
        }
599
600
        if (array_is_list($data)) {
601
            return $this->convertJsonValuesToDotNotation($data);
602
        }
603
604
        return $this->convertJsonKeysToDotNotation($data);
605
    }
606
607
    public function convertJsonValuesToDotNotation(array $fields): array
608
    {
609
        foreach($fields as $key => $value) {
610
            if ($this->isJsonSelector($value)) {
611
                $fields[$key] = str_replace('->', '.', $value);
612
            }
613
        }
614
        return $fields;
615
    }
616
617
    public function convertJsonKeysToDotNotation(array $fields): array
618
    {
619
        foreach($fields as $key => $value) {
620
            if ($this->isJsonSelector($key)) {
621
                $fields[str_replace('->', '.', $key)] = $value;
622
                unset($fields[$key]);
623
            }
624
        }
625
        return $fields;
626
    }
627
}
628