Passed
Pull Request — master (#29)
by Bas
06:20
created

Grammar::compileAvg()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 7
rs 10
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
12
/*
13
 * Provides AQL syntax functions
14
 */
15
16
class Grammar extends FluentAqlGrammar
17
{
18
    use Macroable;
19
20
    public $name;
21
22
    /**
23
     * The grammar table prefix.
24
     *
25
     * @var string
26
     */
27
    protected $tablePrefix = '';
28
29
    /**
30
     * The grammar table prefix.
31
     *
32
     * @var null|int
33
     */
34
    protected $offset = null;
35
36
    /**
37
     * The components that make up a select clause.
38
     *
39
     * @var array
40
     */
41
    protected $selectComponents = [
42
        'from',
43
        'joins',
44
        'wheres',
45
        'groups',
46
        'aggregate',
47
        'havings',
48
        'orders',
49
        'offset',
50
        'limit',
51
        'columns',
52
    ];
53
54
    protected $operatorTranslations = [
55
        '=' => '==',
56
        '<>' => '!=',
57
        '<=>' => '==',
58
        'rlike' => '=~',
59
        'not rlike' => '!~',
60
        'regexp' => '=~',
61
        'not regexp' => '!~',
62
    ];
63
64
    protected $whereTypeOperators = [
65
        'In' => 'IN',
66
        'NotIn' => 'NOT IN',
67
    ];
68
    /**
69
     * Get the format for database stored dates.
70
     *
71
     * @return string
72
     */
73
    public function getDateFormat()
74
    {
75
        return 'Y-m-d\TH:i:s.v\Z';
76
    }
77
78
    /**
79
     * Get the grammar specific operators.
80
     *
81
     * @return array
82
     */
83
    public function getOperators()
84
    {
85
        return $this->comparisonOperators;
86
    }
87
88
    /**
89
     * @param Builder $builder
90
     * @param $table
91
     * @param string $postfix
92
     * @return mixed
93
     */
94
    protected function generateTableAlias($builder, $table, $postfix = 'Doc')
95
    {
96
        $builder->registerAlias($table, Str::singular($table) . $postfix);
97
98
        return $builder;
99
    }
100
101
    protected function prefixTable($table)
102
    {
103
        return $this->tablePrefix . $table;
104
    }
105
106
    /**
107
     * Compile an insert statement into AQL.
108
     *
109
     * @param Builder $builder
110
     * @param array $values
111
     * @return Builder
112
     * @throws BindException
113
     */
114
    public function compileInsert(Builder $builder, array $values)
115
    {
116
        if (Arr::isAssoc($values)) {
117
            $values = [$values];
118
        }
119
        $table = $this->prefixTable($builder->from);
120
121
        if (empty($values)) {
122
            $builder->aqb = $builder->aqb->insert('{}', $table)->get();
123
124
            return $builder;
125
        }
126
127
        $builder->aqb = $builder->aqb->let('values', $values)
128
            ->for('value', 'values')
129
            ->insert('value', $table)
130
            ->return('NEW._key')
131
            ->get();
132
133
        return $builder;
134
    }
135
136
    /**
137
     * Compile an insert and get ID statement into SQL.
138
     *
139
     * @param Builder $builder
140
     * @param array $values
141
     * @return Builder
142
     * @throws BindException
143
     */
144
    public function compileInsertGetId(Builder $builder, $values)
145
    {
146
        return $this->compileInsert($builder, $values);
147
    }
148
149
    /**
150
     * Compile a select query into AQL.
151
     *
152
     * @param  Builder  $builder
153
     * @return Builder
154
     */
155
    public function compileSelect(Builder $builder)
156
    {
157
//        if ($builder->unions && $builder->aggregate) {
158
//            return $this->compileUnionAggregate($builder);
159
//        }
160
161
        // To compile the query, we'll spin through each component of the query and
162
        // see if that component exists. If it does we'll just call the compiler
163
        // function for the component which is responsible for making the SQL.
164
165
        $builder = $this->compileComponents($builder);
166
167
//        if ($builder->unions) {
168
//            $sql = $this->wrapUnion($sql).' '.$this->compileUnions($builder);
169
//        }
170
171
        $builder->aqb = $builder->aqb->get();
172
173
        return $builder;
174
    }
175
176
    /**
177
     * Compile the components necessary for a select clause.
178
     *
179
     * @param Builder $builder
180
     * @return Builder
181
     */
182
    protected function compileComponents(Builder $builder)
183
    {
184
        foreach ($this->selectComponents as $component) {
185
            // To compile the query, we'll spin through each component of the query and
186
            // see if that component exists. If it does we'll just call the compiler
187
            // function for the component which is responsible for making the SQL.
188
189
            if (isset($builder->$component) && ! is_null($builder->$component)) {
190
                $method = 'compile' . ucfirst($component);
191
192
                $builder = $this->$method($builder, $builder->$component);
193
            }
194
        }
195
196
        return $builder;
197
    }
198
199
    /**
200
     * Compile the "from" portion of the query -> FOR in AQL.
201
     *
202
     * @param Builder $builder
203
     * @param string $table
204
     * @return Builder
205
     */
206
    protected function compileFrom(Builder $builder, $table)
207
    {
208
        $table = $this->prefixTable($table);
209
        $builder = $this->generateTableAlias($builder, $table);
210
        $tableAlias = $builder->getAlias($table);
211
212
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
213
214
        return $builder;
215
    }
216
217
    /**
218
     * Compile the "join" portions of the query.
219
     *
220
     * @param  Builder  $query
221
     * @param  array  $joins
222
     * @return string
223
     */
224
    protected function compileJoins(Builder $query, $joins)
225
    {
226
        return collect($joins)->map(function ($join) use ($query) {
227
            $table = $this->wrapTable($join->table);
0 ignored issues
show
Bug introduced by
The method wrapTable() does not exist on LaravelFreelancerNL\Aranguent\Query\Grammar. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

227
            /** @scrutinizer ignore-call */ 
228
            $table = $this->wrapTable($join->table);
Loading history...
228
229
            $nestedJoins = is_null($join->joins) ? '' : ' ' . $this->compileJoins($query, $join->joins);
230
231
            $tableAndNestedJoins = is_null($join->joins) ? $table : '(' . $nestedJoins . ')';
232
233
            return trim("{$join->type} join {$tableAndNestedJoins} {$this->compileWheres($join)}");
234
        })->implode(' ');
235
    }
236
237
238
    /**
239
     * Compile the "where" portions of the query.
240
     *
241
     * @param Builder $builder
242
     * @return Builder
243
     */
244
    protected function compileWheres(Builder $builder)
245
    {
246
        // Each type of where clauses has its own compiler function which is responsible
247
        // for actually creating the where clauses SQL. This helps keep the code nice
248
        // and maintainable since each clause has a very small method that it uses.
249
        if (is_null($builder->wheres)) {
0 ignored issues
show
introduced by
The condition is_null($builder->wheres) is always false.
Loading history...
250
            return $builder;
251
        }
252
253
        if (count($predicates = $this->compileWheresToArray($builder)) > 0) {
254
            $builder->aqb = $builder->aqb->filter($predicates);
0 ignored issues
show
Bug introduced by
$predicates of type array is incompatible with the type string expected by parameter $attribute of LaravelFreelancerNL\Flue...\QueryBuilder::filter(). ( Ignorable by Annotation )

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

254
            $builder->aqb = $builder->aqb->filter(/** @scrutinizer ignore-type */ $predicates);
Loading history...
255
256
            return $builder;
257
        }
258
259
        return $builder;
260
    }
261
262
    /**
263
     * Get an array of all the where clauses for the query.
264
     *
265
     * @param  \Illuminate\Database\Query\Builder  $builder
266
     * @return array
267
     */
268
    protected function compileWheresToArray($builder)
269
    {
270
        $result = collect($builder->wheres)->map(function ($where) use ($builder) {
271
            if (isset($where['operator'])) {
272
                $where['operator'] = $this->translateOperator($where['operator']);
273
            } else {
274
                $where['operator'] = $this->getOperatorByWhereType($where['type']);
275
            }
276
277
            //Prefix table alias on the column
278
            $where['column'] = $this->prefixAlias($builder, $builder->from, $where['column']);
279
280
            $cleanWhere = [];
281
            $cleanWhere[0] = $where['column'];
282
            $cleanWhere[1] = $where['operator'];
283
            $cleanWhere[2] = null;
284
            if (isset($where['value'])) {
285
                $cleanWhere[2] = $where['value'];
286
            }
287
            if (isset($where['values'])) {
288
                $cleanWhere[2] = $where['values'];
289
            }
290
            $cleanWhere[3] = $where['boolean'];
291
292
            return $cleanWhere;
293
        })->all();
294
295
        return $result;
296
    }
297
298
    /**
299
     * Compile an aggregated select clause.
300
     *
301
     * @param  Builder  $builder
302
     * @param  array  $aggregate
303
     * @return Builder
304
     */
305
    protected function compileAggregate(Builder $builder, $aggregate)
306
    {
307
        $method = 'compile' . ucfirst($aggregate['function']);
308
309
        return $this->$method($builder, $aggregate);
310
    }
311
312
    /**
313
     * Compile AQL for count aggregate.
314
     * @param Builder $builder
315
     * @param $aggregate
316
     * @return Builder
317
     */
318
    protected function compileCount(Builder $builder, $aggregate)
319
    {
320
        $builder->aqb = $builder->aqb->collect()->withCount('aggregateResult');
321
322
        return $builder;
323
    }
324
325
    /**
326
     * Compile AQL for max aggregate.
327
     *
328
     * @param Builder $builder
329
     * @param $aggregate
330
     * @return Builder
331
     */
332
    protected function compileMax(Builder $builder, $aggregate)
333
    {
334
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
335
336
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->max($column));
337
338
        return $builder;
339
    }
340
341
    /**
342
     * Compile AQL for min aggregate.
343
     *
344
     * @param Builder $builder
345
     * @param $aggregate
346
     * @return Builder
347
     */
348
    protected function compileMin(Builder $builder, $aggregate)
349
    {
350
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
351
352
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->min($column));
353
354
        return $builder;
355
    }
356
357
    /**
358
     * Compile AQL for average aggregate.
359
     *
360
     * @param Builder $builder
361
     * @param $aggregate
362
     * @return Builder
363
     */
364
    protected function compileAvg(Builder $builder, $aggregate)
365
    {
366
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
367
368
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->average($column));
369
370
        return $builder;
371
    }
372
373
    /**
374
     * Compile AQL for sum aggregate.
375
     *
376
     * @param Builder $builder
377
     * @param $aggregate
378
     * @return Builder
379
     */
380
    protected function compileSum(Builder $builder, $aggregate)
381
    {
382
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
383
384
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->sum($column));
385
386
        return $builder;
387
    }
388
389
    /**
390
     * Compile the "order by" portions of the query.
391
     *
392
     * @param Builder $builder
393
     * @param array $orders
394
     * @return Builder
395
     */
396
    protected function compileOrders(Builder $builder, $orders)
397
    {
398
        if (! empty($orders)) {
399
            $builder->aqb = $builder->aqb->sort($this->compileOrdersToArray($builder, $orders));
400
401
            return $builder;
402
        }
403
404
        return $builder;
405
    }
406
407
    /**
408
     * Compile the query orders to an array.
409
     *
410
     * @param  Builder  $builder
411
     * @param  array  $orders
412
     * @return array
413
     */
414
    protected function compileOrdersToArray(Builder $builder, $orders)
415
    {
416
        return array_map(function ($order) use ($builder) {
417
            if (! isset($order['type']) || $order['type'] != 'Raw') {
418
                $order['column'] = $this->prefixAlias($builder, $builder->from, $order['column']);
419
            }
420
            unset($order['type']);
421
422
            return array_values($order);
423
        }, $orders);
424
    }
425
426
    /**
427
     * Compile the "offset" portions of the query.
428
     * We are handling this first by saving the offset which will be used by the FluentAQL's limit function.
429
     *
430
     * @param Builder $builder
431
     * @param int $offset
432
     * @return Builder
433
     */
434
    protected function compileOffset(Builder $builder, $offset)
435
    {
436
        $this->offset = (int) $offset;
437
438
        return $builder;
439
    }
440
441
    /**
442
     * Compile the "limit" portions of the query.
443
     *
444
     * @param Builder $builder
445
     * @param int $limit
446
     * @return Builder
447
     */
448
    protected function compileLimit(Builder $builder, $limit)
449
    {
450
        if ($this->offset !== null) {
451
            $builder->aqb = $builder->aqb->limit((int) $this->offset, (int) $limit);
452
453
            return $builder;
454
        }
455
        $builder->aqb = $builder->aqb->limit((int) $limit);
456
457
        return $builder;
458
    }
459
460
    /**
461
     * Compile the "RETURN" portion of the query.
462
     *
463
     * @param Builder $builder
464
     * @param array $columns
465
     * @return Builder
466
     */
467
    protected function compileColumns(Builder $builder, array $columns): Builder
468
    {
469
        $values = [];
470
471
        $doc = $builder->getAlias($builder->from);
472
        foreach ($columns as $column) {
473
            if ($column != null && $column != '*') {
474
                $values[$column] = $doc . '.' . $column;
475
            }
476
        }
477
        if ($builder->aggregate !== null) {
478
            $values = ['aggregate' => 'aggregateResult'];
479
        }
480
        if (empty($values)) {
481
            $values = $doc;
482
        }
483
484
        $builder->aqb = $builder->aqb->return($values, (bool) $builder->distinct);
485
486
        return $builder;
487
    }
488
489
    /**
490
     * Compile an update statement into SQL.
491
     *
492
     * @param Builder $builder
493
     * @param array $values
494
     * @return Builder
495
     */
496
    public function compileUpdate(Builder $builder, array $values)
497
    {
498
        $table = $this->prefixTable($builder->from);
499
        $builder = $this->generateTableAlias($builder, $table);
500
        $tableAlias = $builder->getAlias($table);
501
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
502
503
        //Fixme: joins?
504
        $builder = $this->compileWheres($builder);
505
506
        $builder->aqb = $builder->aqb->update($tableAlias, $values, $table)->get();
507
508
        return $builder;
509
    }
510
511
    /**
512
     * Compile a delete statement into SQL.
513
     *
514
     * @param Builder $builder
515
     * @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...
516
     * @return Builder
517
     */
518
    public function compileDelete(Builder $builder, $_key = null)
519
    {
520
        $table = $this->prefixTable($builder->from);
521
        $builder = $this->generateTableAlias($builder, $table);
522
        $tableAlias = $builder->getAlias($table);
523
524
        if (! is_null($_key)) {
0 ignored issues
show
introduced by
The condition is_null($_key) is always true.
Loading history...
525
            $builder->aqb = $builder->aqb->remove((string) $_key, $table)->get();
526
527
            return $builder;
528
        }
529
530
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
531
532
        //Fixme: joins?
533
        $builder = $this->compileWheres($builder);
534
535
        $builder->aqb = $builder->aqb->remove($tableAlias, $table)->get();
536
537
        return $builder;
538
    }
539
540
    /**
541
     * Compile the random statement into SQL.
542
     *
543
     * @param Builder $builder
544
     * @return FunctionExpression;
545
     */
546
    public function compileRandom(Builder $builder)
547
    {
548
        return $builder->aqb->rand();
549
    }
550
551
    /**
552
     * Translate sql operators to their AQL equivalent where possible.
553
     *
554
     * @param string $operator
555
     * @return mixed|string
556
     */
557
    private function translateOperator(string $operator)
558
    {
559
        if (isset($this->operatorTranslations[strtolower($operator)])) {
560
            $operator = $this->operatorTranslations[$operator];
561
        }
562
563
        return $operator;
564
    }
565
566
    protected function getOperatorByWhereType($type)
567
    {
568
        if (isset($this->whereTypeOperators[$type])) {
569
            return $this->whereTypeOperators[$type];
570
        }
571
        return '==';
572
    }
573
574
575
    /**
576
     * @param Builder $builder
577
     * @param string $target
578
     * @param string $value
579
     * @return Builder
580
     */
581
    protected function prefixAlias(Builder $builder, string $target, string $value): string
582
    {
583
        $alias = $builder->getAlias($target);
584
585
        if (Str::startsWith($value, $alias . '.')) {
586
            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...
587
        }
588
589
        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...
590
    }
591
}
592