Passed
Branch master (512d52)
by Bas
08:47
created

Grammar::compileInsert()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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

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