Completed
Push — master ( f71fed...42aa60 )
by Bas
04:43 queued 15s
created

Grammar::compileOrdersToArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

217
            $builder->aqb = $builder->aqb->filter(/** @scrutinizer ignore-type */ $predicates);
Loading history...
218
219
            return $builder;
220
        }
221
222
        return $builder;
223
    }
224
225
    /**
226
     * Get an array of all the where clauses for the query.
227
     *
228
     * @param  \Illuminate\Database\Query\Builder  $builder
229
     * @return array
230
     */
231
    protected function compileWheresToArray($builder)
232
    {
233
        $result = collect($builder->wheres)->map(function ($where) use ($builder) {
234
            // ArangoDB uses a double '=' for comparison
235
            if ($where['operator'] == '=') {
236
                $where['operator'] = '==';
237
            }
238
            $where['operator'] = $this->translateOperator($where['operator']);
239
240
            //Prefix table alias on the column
241
            $where['column'] = $this->prefixAlias($builder, $builder->from, $where['column']);
242
243
            return [
244
                $where['column'],
245
                $where['operator'],
246
                $where['value'],
247
                $where['boolean'],
248
            ];
249
        })->all();
250
251
        return $result;
252
    }
253
254
    /**
255
     * Compile an aggregated select clause.
256
     *
257
     * @param  Builder  $builder
258
     * @param  array  $aggregate
259
     * @return Builder
260
     */
261
    protected function compileAggregate(Builder $builder, $aggregate)
262
    {
263
        $method = 'compile'.ucfirst($aggregate['function']);
264
265
        return $this->$method($builder, $aggregate);
266
    }
267
268
    /**
269
     * Compile AQL for count aggregate.
270
     * @param Builder $builder
271
     * @param $aggregate
272
     * @return Builder
273
     */
274
    protected function compileCount(Builder $builder, $aggregate)
275
    {
276
        $builder->aqb = $builder->aqb->collect()->withCount('aggregateResult');
277
278
        return $builder;
279
    }
280
281
    /**
282
     * Compile AQL for max aggregate.
283
     *
284
     * @param Builder $builder
285
     * @param $aggregate
286
     * @return Builder
287
     */
288
    protected function compileMax(Builder $builder, $aggregate)
289
    {
290
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
291
292
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->max($column));
293
294
        return $builder;
295
    }
296
297
    /**
298
     * Compile AQL for min aggregate.
299
     *
300
     * @param Builder $builder
301
     * @param $aggregate
302
     * @return Builder
303
     */
304
    protected function compileMin(Builder $builder, $aggregate)
305
    {
306
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
307
308
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->min($column));
309
310
        return $builder;
311
    }
312
313
    /**
314
     * Compile AQL for average aggregate.
315
     *
316
     * @param Builder $builder
317
     * @param $aggregate
318
     * @return Builder
319
     */
320
    protected function compileAvg(Builder $builder, $aggregate)
321
    {
322
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
323
324
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->average($column));
325
326
        return $builder;
327
    }
328
329
    /**
330
     * Compile AQL for sum aggregate.
331
     *
332
     * @param Builder $builder
333
     * @param $aggregate
334
     * @return Builder
335
     */
336
    protected function compileSum(Builder $builder, $aggregate)
337
    {
338
        $column = $this->prefixAlias($builder, $builder->from, $aggregate['columns'][0]);
339
340
        $builder->aqb = $builder->aqb->collect()->aggregate('aggregateResult', $builder->aqb->sum($column));
341
342
        return $builder;
343
    }
344
345
    /**
346
     * Compile the "order by" portions of the query.
347
     *
348
     * @param Builder $builder
349
     * @param array $orders
350
     * @return Builder
351
     */
352
    protected function compileOrders(Builder $builder, $orders)
353
    {
354
        if (! empty($orders)) {
355
            $builder->aqb = $builder->aqb->sort($this->compileOrdersToArray($builder, $orders));
356
357
            return $builder;
358
        }
359
360
        return $builder;
361
    }
362
363
    /**
364
     * Compile the query orders to an array.
365
     *
366
     * @param  Builder  $builder
367
     * @param  array  $orders
368
     * @return array
369
     */
370
    protected function compileOrdersToArray(Builder $builder, $orders)
371
    {
372
        return array_map(function ($order) {
373
            return $order['sql'] ?? $this->prefixTable($order['column']).' '.$order['direction'];
374
        }, $orders);
375
    }
376
377
    /**
378
     * Compile the "offset" portions of the query.
379
     * We are handling this first by saving the offset which will be used by the FluentAQL's limit function.
380
     *
381
     * @param Builder $builder
382
     * @param int $offset
383
     * @return Builder
384
     */
385
    protected function compileOffset(Builder $builder, $offset)
386
    {
387
        $this->offset = (int) $offset;
388
389
        return $builder;
390
    }
391
392
    /**
393
     * Compile the "limit" portions of the query.
394
     *
395
     * @param Builder $builder
396
     * @param int $limit
397
     * @return Builder
398
     */
399
    protected function compileLimit(Builder $builder, $limit)
400
    {
401
        if ($this->offset !== null) {
402
            $builder->aqb = $builder->aqb->limit((int) $this->offset, (int) $limit);
403
404
            return $builder;
405
        }
406
        $builder->aqb = $builder->aqb->limit((int) $limit);
407
408
        return $builder;
409
    }
410
411
    /**
412
     * Compile the "RETURN" portion of the query.
413
     *
414
     * @param Builder $builder
415
     * @param array $columns
416
     * @return Builder
417
     */
418
    protected function compileColumns(Builder $builder, array $columns) : Builder
419
    {
420
        // If the query is actually performing an aggregating select, we will let that
421
        // compiler handle the building of the select clauses, as it will need some
422
        // more syntax that is best handled by that function to keep things neat.
423
//        if (! is_null($builder->aggregate)) {
424
//            return;
425
//        }
426
427
        $values = [];
428
429
        $doc = $builder->getAlias($builder->from);
430
        foreach ($columns as $column) {
431
            if ($column != null && $column != '*') {
432
                $values[$column] = $doc.'.'.$column;
433
            }
434
        }
435
        if ($builder->aggregate !== null) {
436
            $values = 'aggregateResult';
437
        }
438
        if (empty($values)) {
439
            $values = $doc;
440
        }
441
442
        $builder->aqb = $builder->aqb->return($values, (bool) $builder->distinct);
443
444
        return $builder;
445
    }
446
447
    /**
448
     * Compile an update statement into SQL.
449
     *
450
     * @param Builder $builder
451
     * @param array $values
452
     * @return Builder
453
     */
454
    public function compileUpdate(Builder $builder, array $values)
455
    {
456
        $table = $this->prefixTable($builder->from);
457
        $builder = $this->generateTableAlias($builder, $table);
458
        $tableAlias = $builder->getAlias($table);
459
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
460
461
        //Fixme: joins?
462
        $builder = $this->compileWheres($builder);
463
464
        $builder->aqb = $builder->aqb->update($tableAlias, $values, $table)->get();
465
466
        return $builder;
467
    }
468
469
    /**
470
     * Compile a delete statement into SQL.
471
     *
472
     * @param Builder $builder
473
     * @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...
474
     * @return Builder
475
     */
476
    public function compileDelete(Builder $builder, $_key = null)
477
    {
478
        $table = $this->prefixTable($builder->from);
479
        $builder = $this->generateTableAlias($builder, $table);
480
        $tableAlias = $builder->getAlias($table);
481
482
        if (! is_null($_key)) {
0 ignored issues
show
introduced by
The condition is_null($_key) is always true.
Loading history...
483
            $builder->aqb = $builder->aqb->remove((string) $_key, $table)->get();
484
485
            return $builder;
486
        }
487
488
        $builder->aqb = $builder->aqb->for($tableAlias, $table);
489
490
        //Fixme: joins?
491
        $builder = $this->compileWheres($builder);
492
493
        $builder->aqb = $builder->aqb->remove($tableAlias, $table)->get();
494
495
        return $builder;
496
    }
497
498
    /**
499
     * Compile the random statement into SQL.
500
     *
501
     * @param Builder $builder
502
     * @return string
503
     */
504
    public function compileRandom(Builder $builder)
505
    {
506
        return (string) $builder->aqb->rand();
507
    }
508
509
    /**
510
     * Translate sql operators to their AQL equivalent where possible.
511
     *
512
     * @param string $operator
513
     * @return mixed|string
514
     */
515
    private function translateOperator(string $operator)
516
    {
517
        if (isset($this->operatorTranslations[strtolower($operator)])) {
518
            $operator = $this->operatorTranslations[$operator];
519
        }
520
521
        return $operator;
522
    }
523
524
    /**
525
     * @param Builder $builder
526
     * @param string $target
527
     * @param string $value
528
     * @return Builder
529
     */
530
    protected function prefixAlias(Builder $builder, string $target, string $value) : string
531
    {
532
        return $builder->getAlias($target).'.'.$value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $builder->getAlias($target) . '.' . $value returns the type string which is incompatible with the documented return type LaravelFreelancerNL\Aranguent\Query\Builder.
Loading history...
533
    }
534
}
535