Passed
Push — master ( 8d031b...eddbb7 )
by Bas
22:43 queued 12s
created

Grammar::compileInsert()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 4
nop 2
dl 0
loc 20
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
use Illuminate\Support\Traits\Macroable;
8
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException as BindException;
9
use LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression;
10
use LaravelFreelancerNL\FluentAQL\Grammar as FluentAqlGrammar;
11
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
12
13
/*
14
 * Provides AQL syntax functions
15
 */
16
17
class Grammar extends FluentAqlGrammar
18
{
19
    use Macroable;
20
21
    public $name;
22
23
    /**
24
     * The grammar table prefix.
25
     *
26
     * @var string
27
     */
28
    protected $tablePrefix = '';
29
30
    /**
31
     * The grammar table prefix.
32
     *
33
     * @var null|int
34
     */
35
    protected $offset = null;
36
37
    /**
38
     * The components that make up a select clause.
39
     *
40
     * @var array
41
     */
42
    protected $selectComponents = [
43
        'from',
44
        'joins',
45
        'wheres',
46
        'groups',
47
        'aggregate',
48
        'havings',
49
        'orders',
50
        'offset',
51
        'limit',
52
        'columns',
53
    ];
54
55
    protected $operatorTranslations = [
56
        '=' => '==',
57
        '<>' => '!=',
58
        '<=>' => '==',
59
        'rlike' => '=~',
60
        'not rlike' => '!~',
61
        'regexp' => '=~',
62
        'not regexp' => '!~',
63
    ];
64
65
    protected $whereTypeOperators = [
66
        'In' => 'IN',
67
        'NotIn' => 'NOT IN',
68
    ];
69
    /**
70
     * Get the format for database stored dates.
71
     *
72
     * @return string
73
     */
74
    public function getDateFormat()
75
    {
76
        return 'Y-m-d\TH:i:s.v\Z';
77
    }
78
79
    /**
80
     * Get the grammar specific operators.
81
     *
82
     * @return array
83
     */
84
    public function getOperators()
85
    {
86
        return $this->comparisonOperators;
87
    }
88
89
    /**
90
     * @param Builder $builder
91
     * @param $table
92
     * @param string $postfix
93
     * @return mixed
94
     */
95
    protected function generateTableAlias($builder, $table, $postfix = 'Doc')
96
    {
97
        $builder->registerAlias($table, Str::singular($table) . $postfix);
98
99
        return $builder;
100
    }
101
102
    protected function prefixTable($table)
103
    {
104
        return $this->tablePrefix . $table;
105
    }
106
107
    /**
108
     * Compile an insert statement into AQL.
109
     *
110
     * @param Builder $builder
111
     * @param array $values
112
     * @return Builder
113
     * @throws BindException
114
     */
115
    public function compileInsert(Builder $builder, array $values)
116
    {
117
        if (Arr::isAssoc($values)) {
118
            $values = [$values];
119
        }
120
        $table = $this->prefixTable($builder->from);
121
122
        if (empty($values)) {
123
            $builder->aqb = $builder->aqb->insert('{}', $table)->get();
124
125
            return $builder;
126
        }
127
128
        $builder->aqb = $builder->aqb->let('values', $values)
129
            ->for('value', 'values')
130
            ->insert('value', $table)
131
            ->return('NEW._key')
132
            ->get();
133
134
        return $builder;
135
    }
136
137
    /**
138
     * Compile an insert and get ID statement into SQL.
139
     *
140
     * @param Builder $builder
141
     * @param array $values
142
     * @return Builder
143
     * @throws BindException
144
     */
145
    public function compileInsertGetId(Builder $builder, $values)
146
    {
147
        return $this->compileInsert($builder, $values);
148
    }
149
150
    /**
151
     * Compile a select query into AQL.
152
     *
153
     * @param  Builder  $builder
154
     * @return Builder
155
     */
156
    public function compileSelect(Builder $builder)
157
    {
158
//        if ($builder->unions && $builder->aggregate) {
159
//            return $this->compileUnionAggregate($builder);
160
//        }
161
162
        // To compile the query, we'll spin through each component of the query and
163
        // see if that component exists. If it does we'll just call the compiler
164
        // function for the component which is responsible for making the SQL.
165
166
        $builder = $this->compileComponents($builder);
167
168
//        if ($builder->unions) {
169
//            $sql = $this->wrapUnion($sql).' '.$this->compileUnions($builder);
170
//        }
171
172
        $builder->aqb = $builder->aqb->get();
173
174
        return $builder;
175
    }
176
177
    /**
178
     * Compile the components necessary for a select clause.
179
     *
180
     * @param Builder $builder
181
     * @return Builder
182
     */
183
    protected function compileComponents(Builder $builder)
184
    {
185
        foreach ($this->selectComponents as $component) {
186
            // To compile the query, we'll spin through each component of the query and
187
            // see if that component exists. If it does we'll just call the compiler
188
            // function for the component which is responsible for making the SQL.
189
190
            if (isset($builder->$component) && ! is_null($builder->$component)) {
191
                $method = 'compile' . ucfirst($component);
192
193
                $builder = $this->$method($builder, $builder->$component);
194
            }
195
        }
196
197
        return $builder;
198
    }
199
200
    /**
201
     * Compile the "from" portion of the query -> FOR in AQL.
202
     *
203
     * @param Builder $builder
204
     * @param string $table
205
     * @return Builder
206
     */
207
    protected function compileFrom(Builder $builder, $table)
208
    {
209
        $table = $this->prefixTable($table);
210
        $builder = $this->generateTableAlias($builder, $table);
211
        $tableAlias = $builder->getAlias($table);
212
213
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
214
215
        return $builder;
216
    }
217
218
    /**
219
     * Compile the "join" portions of the query.
220
     *
221
     * @param  Builder  $builder
222
     * @param  array  $joins
223
     * @return string
224
     */
225
    protected function compileJoins(Builder $builder, $joins)
226
    {
227
        foreach ($joins as $join) {
228
            $compileMethod = 'compile' . ucfirst($join->type) . 'Join';
229
            $builder = $this->$compileMethod($builder, $join);
230
        }
231
232
        return $builder;
233
    }
234
235
    protected function compileInnerJoin(Builder $builder, $join)
236
    {
237
        $table = $join->table;
238
        $alias = $builder->generateTableAlias($table);
239
        $builder->aqb = $builder->aqb->for($alias, $table)
240
            ->filter($this->compileWheresToArray($join));
241
242
        return $builder;
243
    }
244
245
    protected function compileLeftJoin(Builder $builder, $join)
246
    {
247
        $table = $join->table;
248
        $alias = $builder->generateTableAlias($table);
249
250
        $resultsToJoin = (new QueryBuilder())
251
            ->for($alias, $table)
252
            ->filter($this->compileWheresToArray($join))
253
            ->return($alias);
254
255
        $builder->aqb = $builder->aqb->let($table, $resultsToJoin)
256
            ->for(
257
                $alias,
258
                $builder->aqb->if(
259
                    [$builder->aqb->length($table), '>', 0],
260
                    $table,
261
                    '[]'
262
                )
263
            );
264
265
        return $builder;
266
    }
267
268
269
//FOR user IN users
270
//  LET friends = (
271
//    FOR friend IN friends
272
//      FILTER friend.user == user._key
273
//      RETURN friend
274
//  )
275
//  FOR friendToJoin IN (
276
//    LENGTH(friends) > 0 ? friends :
277
//      [ { /* no match exists */ } ]
278
//    )
279
//    RETURN {
280
//      user: user,
281
//      friend: friend
282
//    }
283
284
285
    protected function compileCrossJoin(Builder $builder, $join)
286
    {
287
        $table = $join->table;
288
        $alias = $builder->generateTableAlias($table);
289
        $builder->aqb = $builder->aqb->for($alias, $table);
290
291
        return $builder;
292
    }
293
294
295
296
    /**
297
     * Compile the "where" portions of the query.
298
     *
299
     * @param Builder $builder
300
     * @return Builder
301
     */
302
    protected function compileWheres(Builder $builder)
303
    {
304
        if (is_null($builder->wheres)) {
0 ignored issues
show
introduced by
The condition is_null($builder->wheres) is always false.
Loading history...
305
            return $builder;
306
        }
307
308
        if (count($predicates = $this->compileWheresToArray($builder)) > 0) {
309
            $builder->aqb = $builder->aqb->filter($predicates);
310
311
            return $builder;
312
        }
313
314
        return $builder;
315
    }
316
317
    /**
318
     * Get an array of all the where clauses for the query.
319
     *
320
     * @param  \Illuminate\Database\Query\Builder  $builder
321
     * @return array
322
     */
323
    protected function compileWheresToArray($builder)
324
    {
325
        $result = collect($builder->wheres)->map(function ($where) use ($builder) {
326
327
            if (isset($where['operator'])) {
328
                $where['operator'] = $this->translateOperator($where['operator']);
329
            } else {
330
                $where['operator'] = $this->getOperatorByWhereType($where['type']);
331
            }
332
            $cleanWhere = [];
333
            if (isset($where['column'])) {
334
                if (stripos($where['column'], '.') !== false) {
335
                    $where['column'] = $builder->replaceTableForAlias($where['column']);
336
                } else {
337
                    $where['column'] = $this->prefixAlias($builder, $builder->from, $where['column']);
338
                }
339
                $cleanWhere[0] = $where['column'] ;
340
            }
341
342
            if (isset($where['first'])) {
343
                $cleanWhere[0] = $where['first'];
344
            }
345
346
            $cleanWhere[1] = $where['operator'];
347
            $cleanWhere[2] = null;
348
            if (isset($where['value'])) {
349
                $cleanWhere[2] = $where['value'];
350
            }
351
            if (isset($where['values'])) {
352
                $cleanWhere[2] = $where['values'];
353
            }
354
            if (isset($where['second'])) {
355
                $cleanWhere[2] = $where['second'];
356
            }
357
            $cleanWhere[3] = $where['boolean'];
358
359
            return $cleanWhere;
360
        })->all();
361
362
        return $result;
363
    }
364
365
    /**
366
     * Compile an aggregated select clause.
367
     *
368
     * @param  Builder  $builder
369
     * @param  array  $aggregate
370
     * @return Builder
371
     */
372
    protected function compileAggregate(Builder $builder, $aggregate)
373
    {
374
        $method = 'compile' . ucfirst($aggregate['function']);
375
376
        return $this->$method($builder, $aggregate);
377
    }
378
379
    /**
380
     * Compile AQL for count aggregate.
381
     * @param Builder $builder
382
     * @param $aggregate
383
     * @return Builder
384
     */
385
    protected function compileCount(Builder $builder, $aggregate)
386
    {
387
        $builder->aqb = $builder->aqb->collect()->withCount('aggregateResult');
388
389
        return $builder;
390
    }
391
392
    /**
393
     * Compile AQL for max aggregate.
394
     *
395
     * @param Builder $builder
396
     * @param $aggregate
397
     * @return Builder
398
     */
399
    protected function compileMax(Builder $builder, $aggregate)
400
    {
401
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
402
403
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->max($column));
404
405
        return $builder;
406
    }
407
408
    /**
409
     * Compile AQL for min aggregate.
410
     *
411
     * @param Builder $builder
412
     * @param $aggregate
413
     * @return Builder
414
     */
415
    protected function compileMin(Builder $builder, $aggregate)
416
    {
417
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
418
419
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->min($column));
420
421
        return $builder;
422
    }
423
424
    /**
425
     * Compile AQL for average aggregate.
426
     *
427
     * @param Builder $builder
428
     * @param $aggregate
429
     * @return Builder
430
     */
431
    protected function compileAvg(Builder $builder, $aggregate)
432
    {
433
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
434
435
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->average($column));
436
437
        return $builder;
438
    }
439
440
    /**
441
     * Compile AQL for sum aggregate.
442
     *
443
     * @param Builder $builder
444
     * @param $aggregate
445
     * @return Builder
446
     */
447
    protected function compileSum(Builder $builder, $aggregate)
448
    {
449
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
450
451
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->sum($column));
452
453
        return $builder;
454
    }
455
456
    /**
457
     * Compile the "order by" portions of the query.
458
     *
459
     * @param Builder $builder
460
     * @param array $orders
461
     * @return Builder
462
     */
463
    protected function compileOrders(Builder $builder, $orders)
464
    {
465
        if (! empty($orders)) {
466
            $orders = $this->compileOrdersToFlatArray($builder, $orders);
467
            $builder->aqb = $builder->aqb->sort(...$orders);
468
469
            return $builder;
470
        }
471
472
        return $builder;
473
    }
474
475
    /**
476
     * Compile the query orders to an array.
477
     *
478
     * @param  Builder  $builder
479
     * @param  array  $orders
480
     * @return array
481
     */
482
    protected function compileOrdersToFlatArray(Builder $builder, $orders)
483
    {
484
        $flatOrders = [];
485
486
        foreach ($orders as $order) {
487
            if (! isset($order['type']) || $order['type'] != 'Raw') {
488
                $order['column'] = $this->prefixAlias($builder, $builder->from, $order['column']);
489
            }
490
491
            $flatOrders[] =  $order['column'] ;
492
493
            if (isset($order['direction'])) {
494
                $flatOrders[] =  $order['direction'] ;
495
            }
496
        }
497
498
        return $flatOrders;
499
    }
500
501
    /**
502
     * Compile the "offset" portions of the query.
503
     * We are handling this first by saving the offset which will be used by the FluentAQL's limit function.
504
     *
505
     * @param Builder $builder
506
     * @param int $offset
507
     * @return Builder
508
     */
509
    protected function compileOffset(Builder $builder, $offset)
510
    {
511
        $this->offset = (int) $offset;
512
513
        return $builder;
514
    }
515
516
    /**
517
     * Compile the "limit" portions of the query.
518
     *
519
     * @param Builder $builder
520
     * @param int $limit
521
     * @return Builder
522
     */
523
    protected function compileLimit(Builder $builder, $limit)
524
    {
525
        if ($this->offset !== null) {
526
            $builder->aqb = $builder->aqb->limit((int) $this->offset, (int) $limit);
527
528
            return $builder;
529
        }
530
        $builder->aqb = $builder->aqb->limit((int) $limit);
531
532
        return $builder;
533
    }
534
535
    /**
536
     * Compile the "RETURN" portion of the query.
537
     *
538
     * @param Builder $builder
539
     * @param array $columns
540
     * @return Builder
541
     */
542
    protected function compileColumns(Builder $builder, array $columns): Builder
543
    {
544
        $values = [];
545
546
        $doc = $builder->getAlias($builder->from);
547
        foreach ($columns as $column) {
548
            if ($column != null && $column != '*') {
549
                $values[$column] = $doc . '.' . $column;
550
            }
551
        }
552
        if ($builder->aggregate !== null) {
553
            $values = ['aggregate' => 'aggregateResult'];
554
        }
555
556
        if (empty($values)) {
557
            $values = $doc;
558
            if (is_array($builder->joins) && ! empty($builder->joins)) {
559
                $values = $this->mergeJoinResults($builder, $values);
560
            }
561
        }
562
563
        $builder->aqb = $builder->aqb->return($values, (bool) $builder->distinct);
564
565
        return $builder;
566
    }
567
568
    protected function mergeJoinResults($builder, $baseTable)
569
    {
570
        $tablesToJoin = [];
571
        foreach ($builder->joins as $join) {
572
            $tablesToJoin[] = $builder->getAlias($join->table);
573
        }
574
        $tablesToJoin = array_reverse($tablesToJoin);
575
        $tablesToJoin[] = $baseTable;
576
577
        return $builder->aqb->merge(...$tablesToJoin);
578
    }
579
580
    /**
581
     * Compile an update statement into SQL.
582
     *
583
     * @param Builder $builder
584
     * @param array $values
585
     * @return Builder
586
     */
587
    public function compileUpdate(Builder $builder, array $values)
588
    {
589
        $table = $this->prefixTable($builder->from);
590
        $builder = $this->generateTableAlias($builder, $table);
591
        $tableAlias = $builder->getAlias($table);
592
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
593
594
        //Fixme: joins?
595
        $builder = $this->compileWheres($builder);
596
597
        $builder->aqb = $builder->aqb->update($tableAlias, $values, $table)->get();
598
599
        return $builder;
600
    }
601
602
    /**
603
     * Compile a delete statement into SQL.
604
     *
605
     * @param Builder $builder
606
     * @param null $_key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_key is correct as it would always require null to be passed?
Loading history...
607
     * @return Builder
608
     */
609
    public function compileDelete(Builder $builder, $_key = null)
610
    {
611
        $table = $this->prefixTable($builder->from);
612
        $builder = $this->generateTableAlias($builder, $table);
613
        $tableAlias = $builder->getAlias($table);
614
615
        if (! is_null($_key)) {
0 ignored issues
show
introduced by
The condition is_null($_key) is always true.
Loading history...
616
            $builder->aqb = $builder->aqb->remove((string) $_key, $table)->get();
617
618
            return $builder;
619
        }
620
621
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
622
623
        //Fixme: joins?
624
        $builder = $this->compileWheres($builder);
625
626
        $builder->aqb = $builder->aqb->remove($tableAlias, $table)->get();
627
628
        return $builder;
629
    }
630
631
    /**
632
     * Compile the random statement into SQL.
633
     *
634
     * @param Builder $builder
635
     * @return FunctionExpression;
636
     */
637
    public function compileRandom(Builder $builder)
638
    {
639
        return $builder->aqb->rand();
640
    }
641
642
    /**
643
     * Translate sql operators to their AQL equivalent where possible.
644
     *
645
     * @param string $operator
646
     * @return mixed|string
647
     */
648
    private function translateOperator(string $operator)
649
    {
650
        if (isset($this->operatorTranslations[strtolower($operator)])) {
651
            $operator = $this->operatorTranslations[$operator];
652
        }
653
654
        return $operator;
655
    }
656
657
    protected function getOperatorByWhereType($type)
658
    {
659
        if (isset($this->whereTypeOperators[$type])) {
660
            return $this->whereTypeOperators[$type];
661
        }
662
        return '==';
663
    }
664
665
666
    /**
667
     * @param Builder $builder
668
     * @param string $target
669
     * @param string $value
670
     * @return Builder
671
     */
672
    protected function prefixAlias(Builder $builder, string $target, string $value): string
673
    {
674
        $alias = $builder->getAlias($target);
675
676
        if (Str::startsWith($value, $alias . '.')) {
677
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type string which is incompatible with the documented return type LaravelFreelancerNL\Aranguent\Query\Builder.
Loading history...
678
        }
679
680
        return $alias . '.' . $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $alias . '.' . $value returns the type string which is incompatible with the documented return type LaravelFreelancerNL\Aranguent\Query\Builder.
Loading history...
681
    }
682
}
683