Test Failed
Pull Request — master (#4)
by
unknown
03:29
created

Grammar::compileInsert()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 8
nop 2
dl 0
loc 32
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: admin
5
 * Date: 21.01.2018
6
 * Time: 14:35
7
 */
8
9
namespace sonrac\Arango\Query\Grammars;
10
11
use Illuminate\Database\Query\Builder;
12
use Illuminate\Database\Query\Grammars\Grammar as IlluminateGrammar;
13
use function sonrac\Arango\Helpers\getEntityName;
14
use function sonrac\Arango\Helpers\getEntityNameFromColumn;
15
16
class Grammar extends IlluminateGrammar
17
{
18
    /**
19
     * The components that make up a select clause.
20
     *
21
     * @var array
22
     */
23
    protected $selectComponents = [
24
        'aggregate',
25
        'from',
26
        'joins',
27
        'wheres',
28
        'groups',
29
        'havings',
30
        'orders',
31
        'limit',
32
        'offset',
33
        'columns',
34
        'unions',
35
        'lock',
36
    ];
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function compileInsertGetId(Builder $query, $values, $sequence)
42
    {
43
        return $this->compileInsert($query, $values) . ' RETURN NEW';
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function compileInsert(Builder $query, array $values)
50
    {
51
        // Essentially we will force every insert to be treated as a batch insert which
52
        // simply makes creating the SQL easier for us since we can utilize the same
53
        // basic routine regardless of an amount of records given to us to insert.
54
        $collection = $this->wrapTable($query->from);
55
56
        $entityName = getEntityName($query->from);
57
58
        if (! is_array(reset($values))) {
59
            $values = [$values];
60
        }
61
62
        $columns = array_keys(reset($values));
63
64
        $parameters = [];
65
66
        foreach ($values as $record) {
67
            $bindValuesTmp = [];
68
            foreach ($columns as $column) {
69
                if (!isset($record[$column])) continue;
70
                $bindValuesTmp[$column] = $record[$column];
71
            }
72
            $parameters[] =  $bindValuesTmp;
73
        }
74
        $parameters = json_encode($parameters);
75
        $parameters = preg_replace('/"(\@B\w+)"/', '$1', $parameters);
76
77
        $aql =  "FOR ".$entityName." IN ".$parameters." INSERT ".$entityName." INTO ".$collection;
78
        //var_dump($aql);
79
        return $aql;
80
    }
81
82
    /**
83
     * Prepare column before use in AQL request
84
     * Add entityName and wrap it if needed.
85
     * Add alias for string like (column as other_name)
86
     *
87
     * @param $collection
88
     * @param $column
89
     * @param bool $withCollection
90
     * @return string
91
     */
92
    public function wrapColumn($column, $collection = null, $withCollection = true)
93
    {
94
95
        $entityName = getEntityNameFromColumn($column);
96
97
        $clearColumn = $this->getClearColumnName($column);
98
99
        $alias = $this->getAliasNameFromColumn($column);
100
101
        if (is_null($entityName) && !is_null($collection)) {
102
            $entityName =  getEntityName($collection);
103
        }
104
105
        if ($clearColumn !== '_key') {
106
            $clearColumn = trim($clearColumn, '`');
107
            $clearColumn = '`'.$clearColumn.'`';
108
        }
109
        if ($withCollection) {
110
            $column = $entityName.'.'.$clearColumn;
111
        }
112
        if ($alias) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $alias of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
113
            $column = $alias.':'.$column;
114
        }
115
        return $column;
116
    }
117
118
    /**
119
     * Return collection name
120
     * @return string
121
     */
122
    public function wrapTable($table)
123
    {
124
        return $this->wrapCollection($table);
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function columnize(array $columns)
131
    {
132
        $resultColumns = [];
133
        foreach ($columns as $column) {
134
            if (strpos($column, ':') !== false) {
135
                $resultColumns[] = $column;
136
                continue;
137
            }
138
139
            list($entityName, $column) = explode(".", $column);
140
            if ($column === '`*`') {
141
                $resultColumns[] = $entityName . ': '.$entityName;
142
                continue;
143
            }
144
            $resultColumns[] =  $column . ': ' . $entityName.'.'.$column;
145
        }
146
        return implode(',', $resultColumns);
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function compileUpdate(Builder $query, $values)
153
    {
154
        $table = $this->wrapTable($query->from);
155
156
        // Each one of the columns in the update statements needs to be wrapped in the
157
        // keyword identifiers, also a place-holder needs to be created for each of
158
        // the values in the list of bindings so we can make the sets statements.
159
        $columns = collect($values)->map(function ($value, $key) {
160
            return $key.' : '.$value;
161
        })->implode(', ');
162
163
        $joins = '';
164
165
        if (isset($query->joins)) {
166
            $joins = $this->compileJoins($query, $query->joins).' ';
167
        }
168
169
        // Of course, update queries may also be constrained by where clauses so we'll
170
        // need to compile the where clauses and attach it to the query so only the
171
        // intended records are updated by the SQL statements we generate to run.
172
        $wheres = $this->compileWheres($query);
173
174
        $entityName = getEntityName($table);
175
176
        $aql = $joins."FOR ".$entityName." IN ".$table." ".$wheres.
177
            " UPDATE ".$entityName." WITH { ".$columns." } IN ".$table;
178
179
        var_dump($aql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($aql); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
180
        return $aql;
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function compileDelete(Builder $query)
187
    {
188
        $wheres = is_array($query->wheres) ? $this->compileWheres($query) : '';
189
190
        $collection = $this->wrapTable($query->from);
191
        $entityName = getEntityName($collection);
192
        $aql = "FOR {$entityName} in {$collection} {$wheres} REMOVE {$entityName} IN {$collection}";
193
        var_dump($aql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($aql); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
194
        return $aql;
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function compileSelect(Builder $query)
201
    {
202
        // If the query does not have any columns set, we'll set the columns to the
203
        // * character to just get all of the columns from the database. Then we
204
        // can build the query and concatenate all the pieces together as one.
205
        $original = $query->columns;
206
207
        if (is_null($query->columns)) {
208
            $query->columns = ['*'];
209
        }
210
211
        // To compile the query, we'll spin through each component of the query and
212
        // see if that component exists. If it does we'll just call the compiler
213
        // function for the component which is responsible for making the AQL.
214
        $aql = trim($this->concatenate(
215
            $this->compileComponents($query))
216
        );
217
218
        if (isset($query->joins)) {
219
            $aql = $this->compileJoins($query, $query->joins).' '.$aql;
220
        }
221
222
223
224
225
        if (!is_null($query->aggregate)) {
226
            $aql = $this->compileAggregateExtended($query, $query->aggregate, $aql);
227
        }
228
        $query->columns = $original;
229
230
        return $aql;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    protected function compileJoins(Builder $query, $joins)
237
    {
238
239
        return collect($joins)->map(function ($join) use(&$aql) {
240
            $table = $this->wrapTable($join->table);
241
            $entityName = getEntityName($join->table);
242
            return 'FOR '.$entityName.' IN '.$table;
243
        })->implode(' ');
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    protected function compileComponents(Builder $query)
250
    {
251
        $aql = [];
252
253
        foreach ($this->selectComponents as $component) {
254
            // To compile the query, we'll spin through each component of the query and
255
            // see if that component exists. If it does we'll just call the compiler
256
            // function for the component which is responsible for making the SQL.
257
            if (! is_null($query->$component)) {
258
                if ($component === 'aggregate' ||
259
                   $component === 'joins') {
260
                    continue;
261
                }
262
                $method = 'compile'.ucfirst($component);
263
264
                $aql[$component] = $this->$method($query, $query->$component);
265
            }
266
        }
267
268
        return $aql;
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274
    protected function compileFrom(Builder $query, $collection)
275
    {
276
        return 'FOR '.getEntityName($collection).' IN '.$this->wrapCollection($collection);
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    protected function compileColumns(Builder $query, $columns)
283
    {
284
        if (count($columns) === 1 && $columns[0] === "*") {
285
            return 'RETURN '.getEntityName($query->from);
286
        }
287
288
289
        return "RETURN { " . $this->columnize($columns) . " }";
290
    }
291
292
    /**
293
     * Return string for aggregate some column from AQL request
294
     *
295
     * @param Builder $query
296
     * @param $aggregate
297
     * @param $aql
298
     * @return string
299
     */
300
    protected function compileAggregateExtended(Builder $query, $aggregate, $aql)
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
301
    {
302
        return "RETURN {\"aggregate\":".$aggregate['function']."(".$aql.")}";
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    protected function compileWheres(Builder $query)
309
    {
310
        // Each type of where clauses has its own compiler function which is responsible
311
        // for actually creating the where clauses SQL. This helps keep the code nice
312
        // and maintainable since each clause has a very small method that it uses.
313
        if (is_null($query->wheres)) {
314
            return '';
315
        }
316
317
        // If we actually have some where clauses, we will strip off the first boolean
318
        // operator, which is added by the query builders for convenience so we can
319
        // avoid checking for the first clauses in each of the compilers methods.
320
        if (count($sql = $this->compileWheresToArray($query)) > 0) {
321
            return $this->concatenateWhereClauses($query, $sql);
322
        }
323
324
        return '';
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330
    protected function concatenateWhereClauses($query, $sql)
331
    {
332
        return 'FILTER '.$this->removeLeadingBoolean(implode(' ', $sql));
333
    }
334
335
    /**
336
     * {@inheritdoc}
337
     */
338
    protected function compileWheresToArray($query)
339
    {
340
        return collect($query->wheres)->map(function ($where) use ($query) {
341
            return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where);
342
        })->all();
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348
    protected function whereBasic(Builder $query, $where)
349
    {
350
        return $where['column'].' '.$where['operator'].' '.$where['value'];
351
    }
352
353
    /**
354
     * {@inheritdoc}
355
     */
356 View Code Duplication
    protected function whereIn(Builder $query, $where)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
357
    {
358
        if (! empty($where['values'])) {
359
            $column = $this->wrapColumn($where['column'], $query->from);
360
            return '['.implode(",", $where['values']).'] ANY == '.$column;
361
        }
362
363
        return '0 = 1';
364
    }
365
366
    /**
367
     * {@inheritdoc}
368
     */
369 View Code Duplication
    protected function whereNotIn(Builder $query, $where)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
370
    {
371
        if (! empty($where['values'])) {
372
            $column = $this->wrapColumn($where['table'],$where['column']);
373
            return '['.implode(",", $where['values']).'] NONE == '.$column;
374
        }
375
376
        return '0 = 1';
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382
    protected function whereNull(Builder $query, $where)
383
    {
384
        return $this->wrapColumn($where['column'], $query->from).' == NULL';
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390
    protected function whereNotNull(Builder $query, $where)
391
    {
392
        return $this->wrapColumn($where['column'], $query->from).' != NULL';
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398
    protected function whereColumn(Builder $query, $where)
399
    {
400
        $firstWrapColumn = $this->wrapColumn($where['first'], $query->from);
401
        $secondWrapColumn = $this->wrapColumn($where['second'], $query->from);
402
        return $firstWrapColumn.' '.$where['operator'].' '.$secondWrapColumn;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408
    protected function compileOrders(Builder $query, $orders)
409
    {
410
        if (! empty($orders)) {
411
            return 'SORT '.implode(', ', $this->compileOrdersToArray($query, $orders));
412
        }
413
414
        return '';
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420
    protected function compileOrdersToArray(Builder $query, $orders)
421
    {
422
        return array_map(function ($order) {
423
            return ! isset($order['sql'])
424
                ? $order['column'].' '.$order['direction']
425
                : $order['sql'];
426
        }, $orders);
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432
    protected function compileLimit(Builder $query, $limit)
433
    {
434
        $result = 'LIMIT ';
435
436
        if (isset($query->offset)) {
437
            $result .= (int)$query->offset.', ';
438
        }
439
        $result .= (int)$limit;
440
441
        return $result;
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447
    protected function compileOffset(Builder $query, $offset)
448
    {
449
        if (!isset($query->limit)) {
450
            throw new \Exception("You can't set offset without limit for arangodb");
451
        }
452
        return '';
453
    }
454
455
    /**
456
     * Wrap collection name
457
     * @param string $collection
458
     * @return string
459
     */
460
    protected function wrapCollection($collection)
461
    {
462
        return "`".trim($collection,'`')."`";
463
    }
464
465
    protected function getClearColumnName($column)
466
    {
467
        $parts = explode('.', $column);
468
        if (count($parts) > 1) {
469
            $column = $parts[1];
470
        }
471
        $column = explode('as', $column)[0];
472
473
        return trim($column, '` ');
474
    }
475
476
    protected function getAliasNameFromColumn($column)
477
    {
478
        $parts = explode('as', $column);
479
        if (count($parts) < 2) {
480
            return null;
481
        }
482
483
        return '`'.trim($parts[1], '` ').'`';
484
    }
485
}