1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Donii Sergii <[email protected]> |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace sonrac\Arango\Query; |
7
|
|
|
|
8
|
|
|
use ArangoDBClient\Exception; |
9
|
|
|
use Illuminate\Contracts\Support\Arrayable; |
10
|
|
|
use Illuminate\Database\Eloquent\Builder; |
11
|
|
|
use Illuminate\Database\Query\Builder as IlluminateBuilder; |
12
|
|
|
use Illuminate\Database\Query\Expression; |
13
|
|
|
use Illuminate\Support\Arr; |
14
|
|
|
use Illuminate\Support\Str; |
15
|
|
|
use sonrac\Arango\Connection; |
16
|
|
|
use sonrac\Arango\Query\Grammars\Grammar; |
17
|
|
|
use function sonrac\Arango\Helpers\getEntityName; |
18
|
|
|
use function sonrac\Arango\Helpers\getEntityNameFromColumn; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Class QueryBuilder. |
22
|
|
|
* |
23
|
|
|
* @author Donii Sergii <[email protected]> |
24
|
|
|
*/ |
25
|
|
|
class QueryBuilder extends IlluminateBuilder |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* @var Grammar |
29
|
|
|
*/ |
30
|
|
|
public $grammar; |
31
|
|
|
|
32
|
|
|
public $bindings = []; |
33
|
|
|
|
34
|
|
|
public $operators = [ |
35
|
|
|
'==', //equality |
36
|
|
|
'!=', //inequality |
37
|
|
|
'<', //less than |
38
|
|
|
'<=', //less or equal |
39
|
|
|
'>', //greater than |
40
|
|
|
'>=', //greater or equal |
41
|
|
|
'IN', //test if a value is contained in an array |
42
|
|
|
'NOT IN', //test if a value is not contained in an array |
43
|
|
|
'LIKE', //tests if a string value matches a pattern |
44
|
|
|
'=~', //tests if a string value matches a regular expression |
45
|
|
|
'!~', //tests if a string value does not match a regular expression |
46
|
|
|
]; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* {@inheritdoc} |
50
|
|
|
*/ |
51
|
|
|
public function pluck($column, $key = null) |
52
|
|
|
{ |
53
|
|
|
$column = $this->prepareColumn($column); |
54
|
|
|
if (!is_null($key)) { |
55
|
|
|
$key = $this->prepareColumn($key); |
56
|
|
|
} |
57
|
|
|
$results = $this->get(is_null($key) ? [$column] : [$column, $key]); |
58
|
|
|
|
59
|
|
|
// If the columns are qualified with a table or have an alias, we cannot use |
60
|
|
|
// those directly in the "pluck" operations since the results from the DB |
61
|
|
|
// are only keyed by the column itself. We'll strip the table out here. |
62
|
|
|
return $results->pluck( |
63
|
|
|
$this->stripTableForPluck($column), |
64
|
|
|
$this->stripTableForPluck($key) |
65
|
|
|
); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* {@inheritdoc} |
70
|
|
|
*/ |
71
|
|
|
public function addSelect($column) |
72
|
|
|
{ |
73
|
|
|
$column = is_array($column) ? $column : func_get_args(); |
74
|
|
|
|
75
|
|
|
$column = collect($column)->map(function ($column) { |
76
|
|
|
return $this->prepareColumn($column); |
77
|
|
|
})->toArray(); |
78
|
|
|
|
79
|
|
|
$this->columns = array_merge((array) $this->columns, $column); |
80
|
|
|
|
81
|
|
|
return $this; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* {@inheritdoc} |
86
|
|
|
*/ |
87
|
|
|
public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) |
88
|
|
|
{ |
89
|
|
|
$join = new JoinClause($this, $type, $table); |
90
|
|
|
|
91
|
|
|
// If the first "column" of the join is really a Closure instance the developer |
92
|
|
|
// is trying to build a join with a complex "on" clause containing more than |
93
|
|
|
// one condition, so we'll add the join and call a Closure with the query. |
94
|
|
|
if ($first instanceof \Closure) { |
95
|
|
|
call_user_func($first, $join); |
96
|
|
|
|
97
|
|
|
$this->joins[] = $join; |
98
|
|
|
|
99
|
|
|
$this->addBinding($join->getBindings(), 'join'); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// If the column is simply a string, we can assume the join simply has a basic |
103
|
|
|
// "on" clause with a single condition. So we will just build the join with |
104
|
|
|
// this simple join clauses attached to it. There is not a join callback. |
105
|
|
|
else { |
106
|
|
|
$method = $where ? 'where' : 'on'; |
107
|
|
|
|
108
|
|
|
$this->joins[] = $join->$method($first, $operator, $second); |
109
|
|
|
|
110
|
|
|
$this->addBinding($join->getBindings(), 'join'); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
//Move wheres from join to main query (arangoDB don't have "on" method) |
114
|
|
|
foreach ($join->wheres as $where) { |
115
|
|
|
$this->wheres[] = $where; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$join->wheres = []; |
119
|
|
|
|
120
|
|
|
return $this; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* {@inheritdoc} |
125
|
|
|
*/ |
126
|
|
|
public function orderBy($column, $direction = 'asc') |
127
|
|
|
{ |
128
|
|
|
$column = $this->prepareColumn($column); |
129
|
|
|
$this->{$this->unions ? 'unionOrders' : 'orders'}[] = [ |
130
|
|
|
'column' => $column, |
131
|
|
|
'direction' => strtolower($direction) == 'asc' ? 'asc' : 'desc', |
132
|
|
|
]; |
133
|
|
|
|
134
|
|
|
return $this; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* {@inheritdoc} |
139
|
|
|
*/ |
140
|
|
|
public function whereIn($column, $values, $boolean = 'and', $not = false) |
141
|
|
|
{ |
142
|
|
|
$type = $not ? 'NotIn' : 'In'; |
143
|
|
|
|
144
|
|
|
if ($values instanceof Builder) { |
145
|
|
|
$values = $values->getQuery(); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
// If the value is a query builder instance we will assume the developer wants to |
149
|
|
|
// look for any values that exists within this given query. So we will add the |
150
|
|
|
// query accordingly so that this query is properly executed when it is run. |
151
|
|
|
if ($values instanceof self) { |
152
|
|
|
return $this->whereInExistingQuery( |
153
|
|
|
$column, $values, $boolean, $not |
154
|
|
|
); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
// If the value of the where in clause is actually a Closure, we will assume that |
158
|
|
|
// the developer is using a full sub-select for this "in" statement, and will |
159
|
|
|
// execute those Closures, then we can re-construct the entire sub-selects. |
160
|
|
|
if ($values instanceof \Closure) { |
161
|
|
|
return $this->whereInSub($column, $values, $boolean, $not); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
// Next, if the value is Arrayable we need to cast it to its raw array form so we |
165
|
|
|
// have the underlying array value instead of an Arrayable object which is not |
166
|
|
|
// able to be added as a binding, etc. We will then add to the wheres array. |
167
|
|
|
if ($values instanceof Arrayable) { |
168
|
|
|
$values = $values->toArray(); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
// Finally we'll add a binding for each values unless that value is an expression |
172
|
|
|
// in which case we will just skip over it since it will be the query as a raw |
173
|
|
|
// string and not as a parameterized place-holder to be replaced by the PDO. |
174
|
|
View Code Duplication |
foreach ($values as $index => $value) { |
|
|
|
|
175
|
|
|
if (!$value instanceof Expression) { |
176
|
|
|
$this->addBinding($value, 'where'); |
177
|
|
|
$values[$index] = $this->getLastBindingKey(); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
$this->wheres[] = compact('type', 'column', 'values', 'boolean'); |
182
|
|
|
|
183
|
|
|
return $this; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* You can get last binding key from getLastBindingKey |
188
|
|
|
* {@inheritdoc} |
189
|
|
|
*/ |
190
|
|
|
public function addBinding($value, $type = 'where') |
191
|
|
|
{ |
192
|
|
|
if (is_array($value)) { |
193
|
|
|
foreach ($value as $variable) { |
194
|
|
|
$this->bindings[$this->getBindingVariableName()] = $variable; |
195
|
|
|
} |
196
|
|
|
} else { |
197
|
|
|
$this->bindings[$this->getBindingVariableName()] = $value; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
return $this; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Return last binding key |
205
|
|
|
* |
206
|
|
|
* @return string |
207
|
|
|
*/ |
208
|
|
|
public function getLastBindingKey() |
209
|
|
|
{ |
210
|
|
|
$keys = array_keys($this->getBindings()); |
211
|
|
|
return '@' . array_pop($keys); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* {@inheritdoc} |
216
|
|
|
*/ |
217
|
|
|
public function getBindings() |
218
|
|
|
{ |
219
|
|
|
return $this->bindings; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* {@inheritdoc} |
224
|
|
|
*/ |
225
|
|
|
public function whereBetween($column, array $values, $boolean = 'and', $not = false) |
226
|
|
|
{ |
227
|
|
|
$this->where(function (QueryBuilder $query) use ($column, $values, $boolean, $not) { |
228
|
|
|
list($from, $to) = $values; |
229
|
|
|
if (!$not) { |
230
|
|
|
$query->where($column, '>', $from); |
231
|
|
|
$query->where($column, '<', $to); |
232
|
|
|
} else { |
233
|
|
|
$query->where($column, '<=', $from); |
234
|
|
|
$query->orWhere($column, '>=', $to); |
235
|
|
|
} |
236
|
|
|
}, $boolean); |
237
|
|
|
|
238
|
|
|
return $this; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* {@inheritdoc} |
243
|
|
|
*/ |
244
|
|
|
public function where($column, $operator = null, $value = null, $boolean = 'and') |
245
|
|
|
{ |
246
|
|
|
$column = $this->prepareColumn($column); |
247
|
|
|
|
248
|
|
|
//For compatibility with internal framework functions |
249
|
|
|
if ($operator === '=') { |
250
|
|
|
$operator = '=='; |
251
|
|
|
} |
252
|
|
|
// If the column is an array, we will assume it is an array of key-value pairs |
253
|
|
|
// and can add them each as a where clause. We will maintain the boolean we |
254
|
|
|
// received when the method was called and pass it into the nested where. |
255
|
|
|
if (is_array($column)) { |
256
|
|
|
return $this->addArrayOfWheres($column, $boolean); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
// Here we will make some assumptions about the operator. If only 2 values are |
260
|
|
|
// passed to the method, we will assume that the operator is an equals sign |
261
|
|
|
// and keep going. Otherwise, we'll require the operator to be passed in. |
262
|
|
|
list($value, $operator) = $this->prepareValueAndOperator( |
263
|
|
|
$value, $operator, func_num_args() == 2 |
264
|
|
|
); |
265
|
|
|
|
266
|
|
|
// If the columns is actually a Closure instance, we will assume the developer |
267
|
|
|
// wants to begin a nested where statement which is wrapped in parenthesis. |
268
|
|
|
// We'll add that Closure to the query then return back out immediately. |
269
|
|
|
if ($column instanceof \Closure) { |
270
|
|
|
return $this->whereNested($column, $boolean); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
// If the given operator is not found in the list of valid operators we will |
274
|
|
|
// assume that the developer is just short-cutting the '=' operators and |
275
|
|
|
// we will set the operators to '=' and set the values appropriately. |
276
|
|
|
if ($this->invalidOperator($operator)) { |
277
|
|
|
list($value, $operator) = [$operator, '==']; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// If the value is a Closure, it means the developer is performing an entire |
281
|
|
|
// sub-select within the query and we will need to compile the sub-select |
282
|
|
|
// within the where clause to get the appropriate query record results. |
283
|
|
|
if ($value instanceof \Closure) { |
284
|
|
|
return $this->whereSub($column, $operator, $value, $boolean); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
// If the value is "null", we will just assume the developer wants to add a |
288
|
|
|
// where null clause to the query. So, we will allow a short-cut here to |
289
|
|
|
// that method for convenience so the developer doesn't have to check. |
290
|
|
|
if (is_null($value)) { |
291
|
|
|
return $this->whereNull($column, $boolean, $operator !== '=='); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
// If the column is making a JSON reference we'll check to see if the value |
295
|
|
|
// is a boolean. If it is, we'll add the raw boolean string as an actual |
296
|
|
|
// value to the query to ensure this is properly handled by the query. |
297
|
|
|
if (Str::contains($column, '->') && is_bool($value)) { |
298
|
|
|
$value = new Expression($value ? 'true' : 'false'); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
// Now that we are working with just a simple query we can put the elements |
302
|
|
|
// in our array and add the query binding to our array of bindings that |
303
|
|
|
// will be bound to each SQL statements when it is finally executed. |
304
|
|
|
$type = 'Basic'; |
305
|
|
|
|
306
|
|
|
if (!$value instanceof Expression) { |
307
|
|
|
$this->addBinding($value, 'where'); |
308
|
|
|
$value = $this->getLastBindingKey(); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$this->wheres[] = compact( |
312
|
|
|
'type', 'column', 'operator', 'value', 'boolean' |
313
|
|
|
); |
314
|
|
|
|
315
|
|
|
return $this; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* {@inheritdoc} |
320
|
|
|
*/ |
321
|
|
|
public function whereColumn($first, $operator = null, $second = null, $boolean = 'and') |
322
|
|
|
{ |
323
|
|
|
if ($operator === '=') { |
324
|
|
|
$operator = '=='; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
// If the column is an array, we will assume it is an array of key-value pairs |
328
|
|
|
// and can add them each as a where clause. We will maintain the boolean we |
329
|
|
|
// received when the method was called and pass it into the nested where. |
330
|
|
|
if (is_array($first)) { |
331
|
|
|
return $this->addArrayOfWheres($first, $boolean, 'whereColumn'); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
// If the given operator is not found in the list of valid operators we will |
335
|
|
|
// assume that the developer is just short-cutting the '=' operators and |
336
|
|
|
// we will set the operators to '=' and set the values appropriately. |
337
|
|
|
if ($this->invalidOperator($operator)) { |
338
|
|
|
list($second, $operator) = [$operator, '==']; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
// Finally, we will add this where clause into this array of clauses that we |
342
|
|
|
// are building for the query. All of them will be compiled via a grammar |
343
|
|
|
// once the query is about to be executed and run against the database. |
344
|
|
|
$type = 'Column'; |
345
|
|
|
|
346
|
|
|
$this->wheres[] = compact( |
347
|
|
|
'type', 'first', 'operator', 'second', 'boolean' |
348
|
|
|
); |
349
|
|
|
|
350
|
|
|
return $this; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* {@inheritdoc} |
355
|
|
|
*/ |
356
|
|
|
public function whereNull($column, $boolean = 'and', $not = false) |
357
|
|
|
{ |
358
|
|
|
$column = $this->prepareColumn($column); |
359
|
|
|
|
360
|
|
|
$type = $not ? 'NotNull' : 'Null'; |
361
|
|
|
|
362
|
|
|
$this->wheres[] = compact('type', 'column', 'boolean'); |
363
|
|
|
|
364
|
|
|
return $this; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* {@inheritdoc} |
369
|
|
|
*/ |
370
|
|
View Code Duplication |
public function increment($column, $amount = 1, array $extra = []) |
|
|
|
|
371
|
|
|
{ |
372
|
|
|
if (!is_numeric($amount)) { |
373
|
|
|
throw new \InvalidArgumentException('Non-numeric value passed to increment method.'); |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
$wrapped = $this->prepareColumn($column); |
377
|
|
|
|
378
|
|
|
$columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra); |
379
|
|
|
|
380
|
|
|
return $this->update($columns); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* {@inheritdoc} |
385
|
|
|
*/ |
386
|
|
View Code Duplication |
public function decrement($column, $amount = 1, array $extra = []) |
|
|
|
|
387
|
|
|
{ |
388
|
|
|
if (!is_numeric($amount)) { |
389
|
|
|
throw new \InvalidArgumentException('Non-numeric value passed to decrement method.'); |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
$wrapped = $this->prepareColumn($column); |
393
|
|
|
|
394
|
|
|
$columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra); |
395
|
|
|
|
396
|
|
|
return $this->update($columns); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* {@inheritdoc} |
401
|
|
|
*/ |
402
|
|
|
public function update(array $values) |
403
|
|
|
{ |
404
|
|
View Code Duplication |
foreach ($values as $index => $value) { |
|
|
|
|
405
|
|
|
if (!$value instanceof Expression) { |
406
|
|
|
$this->addBinding($value, 'update'); |
407
|
|
|
$values[$index] = $this->getLastBindingKey(); |
408
|
|
|
} |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
$aql = $this->grammar->compileUpdate($this, $values); |
412
|
|
|
|
413
|
|
|
return $this->connection->update($aql, $this->getBindings()); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* {@inheritdoc} |
418
|
|
|
*/ |
419
|
|
|
public function delete($id = null) |
420
|
|
|
{ |
421
|
|
|
// If an ID is passed to the method, we will set the where clause to check the |
422
|
|
|
// ID to let developers to simply and quickly remove a single row from this |
423
|
|
|
// database without manually specifying the "where" clauses on the query. |
424
|
|
|
if (!is_null($id)) { |
425
|
|
|
$this->where($this->from.'.id', '=', $id); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
return $this->connection->delete( |
429
|
|
|
$this->grammar->compileDelete($this), $this->getBindings() |
430
|
|
|
); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* {@inheritdoc} |
435
|
|
|
*/ |
436
|
|
|
public function truncate() |
437
|
|
|
{ |
438
|
|
|
$connection = $this->getConnection(); |
439
|
|
|
/** |
440
|
|
|
* @var Connection |
441
|
|
|
*/ |
442
|
|
|
$arangoDB = $connection->getArangoDB(); |
443
|
|
|
$arangoDB->truncate($this->from); |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* {@inheritdoc} |
448
|
|
|
*/ |
449
|
|
|
public function find($id, $columns = ['*']) |
450
|
|
|
{ |
451
|
|
|
$column = $this->prepareColumn('_key'); |
452
|
|
|
return $this->where($column, '==', $id)->limit(1)->first($columns); |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
/** |
456
|
|
|
* {@inheritdoc} |
457
|
|
|
*/ |
458
|
|
|
public function insertGetId(array $values, $sequence = null) |
459
|
|
|
{ |
460
|
|
|
if (!is_array(reset($values))) { |
461
|
|
|
$values = [$values]; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
View Code Duplication |
foreach ($values as $i => $record) { |
|
|
|
|
465
|
|
|
foreach ($record as $j => $value) { |
466
|
|
|
$this->addBinding($value, 'insert'); |
467
|
|
|
$values[$i][$j] = $this->getLastBindingKey(); |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
$sql = $this->grammar->compileInsertGetId($this, $values, $sequence); |
472
|
|
|
|
473
|
|
|
return $this->processor->processInsertGetId($this, $sql, $this->getBindings(), $sequence); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* {@inheritdoc} |
478
|
|
|
*/ |
479
|
|
|
public function sum($columns = '*') |
480
|
|
|
{ |
481
|
|
|
return (int) $this->aggregate(strtoupper(__FUNCTION__), Arr::wrap($columns)); |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* {@inheritdoc} |
486
|
|
|
*/ |
487
|
|
|
public function count($columns = '*') |
488
|
|
|
{ |
489
|
|
|
return (int) $this->aggregate(strtoupper(__FUNCTION__), Arr::wrap($columns)); |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* {@inheritdoc} |
494
|
|
|
*/ |
495
|
|
|
public function aggregate($function, $columns = ['*']) |
496
|
|
|
{ |
497
|
|
|
$results = $this->cloneWithout(['columns']) |
498
|
|
|
->cloneWithoutBindings(['select']) |
499
|
|
|
->setAggregate($function, $columns) |
500
|
|
|
->get($columns); |
501
|
|
|
|
502
|
|
|
if (!$results->isEmpty()) { |
503
|
|
|
return array_change_key_case((array) $results[0])['aggregate']; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
return null; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
/** |
510
|
|
|
* {@inheritdoc} |
511
|
|
|
*/ |
512
|
|
|
public function cloneWithoutBindings(array $except) |
513
|
|
|
{ |
514
|
|
|
return tap(clone $this, function ($clone) use ($except) { |
515
|
|
|
foreach ($except as $type) { |
516
|
|
|
unset($clone->bindings[$type]); |
517
|
|
|
} |
518
|
|
|
}); |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* {@inheritdoc} |
523
|
|
|
*/ |
524
|
|
|
public function insert(array $values) |
525
|
|
|
{ |
526
|
|
|
// Since every insert gets treated like a batch insert, we will make sure the |
527
|
|
|
// bindings are structured in a way that is convenient when building these |
528
|
|
|
// inserts statements by verifying these elements are actually an array. |
529
|
|
|
if (empty($values)) { |
530
|
|
|
return true; |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
if (!is_array(reset($values))) { |
534
|
|
|
$values = [$values]; |
535
|
|
|
} |
536
|
|
|
|
537
|
|
|
// Here, we will sort the insert keys for every record so that each insert is |
538
|
|
|
// in the same order for the record. We need to make sure this is the case |
539
|
|
|
// so there are not any errors or problems when inserting these records. |
540
|
|
|
else { |
541
|
|
|
foreach ($values as $key => $value) { |
542
|
|
|
ksort($value); |
543
|
|
|
$values[$key] = $value; |
544
|
|
|
} |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
$values = $this->prepareColumns($values); |
548
|
|
|
|
549
|
|
View Code Duplication |
foreach ($values as $i => $record) { |
|
|
|
|
550
|
|
|
foreach ($record as $j => $value) { |
551
|
|
|
$this->addBinding($value, 'insert'); |
552
|
|
|
$values[$i][$j] = $this->getLastBindingKey(); |
553
|
|
|
} |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
$aql = $this->grammar->compileInsert($this, $values); |
557
|
|
|
|
558
|
|
|
// Finally, we will run this query against the database connection and return |
559
|
|
|
// the results. We will need to also flatten these bindings before running |
560
|
|
|
// the query so they are all in one huge, flattened array for execution. |
561
|
|
|
return $this->connection->insert( |
562
|
|
|
$aql, |
563
|
|
|
$this->getBindings() |
564
|
|
|
); |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
/** |
568
|
|
|
* Compile select to Aql format |
569
|
|
|
* @return string |
570
|
|
|
*/ |
571
|
|
|
public function toAql() |
572
|
|
|
{ |
573
|
|
|
$aql = $this->grammar->compileSelect($this); |
574
|
|
|
return $aql; |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* {@inheritdoc} |
579
|
|
|
*/ |
580
|
|
|
protected function addDynamic($segment, $connector, $parameters, $index) |
581
|
|
|
{ |
582
|
|
|
// Once we have parsed out the columns and formatted the boolean operators we |
583
|
|
|
// are ready to add it to this query as a where clause just like any other |
584
|
|
|
// clause on the query. Then we'll increment the parameter index values. |
585
|
|
|
$bool = strtolower($connector); |
586
|
|
|
|
587
|
|
|
$this->where(Str::snake($segment), '==', $parameters[$index], $bool); |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
/** |
591
|
|
|
* {@inheritdoc} |
592
|
|
|
*/ |
593
|
|
|
protected function runSelect() |
594
|
|
|
{ |
595
|
|
|
return $this->connection->select( |
596
|
|
|
$this->toAql(), $this->getBindings() |
597
|
|
|
); |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
/** |
601
|
|
|
* {@inheritdoc} |
602
|
|
|
*/ |
603
|
|
|
protected function setAggregate($function, $columns) |
604
|
|
|
{ |
605
|
|
|
$this->aggregate = compact('function', 'columns'); |
606
|
|
|
|
607
|
|
|
if (empty($this->groups)) { |
608
|
|
|
$this->orders = null; |
|
|
|
|
609
|
|
|
|
610
|
|
|
unset($this->bindings['order']); |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
return $this; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* {@inheritdoc} |
618
|
|
|
*/ |
619
|
|
|
protected function invalidOperatorAndValue($operator, $value) |
620
|
|
|
{ |
621
|
|
|
return is_null($value) && in_array($operator, $this->operators) && |
622
|
|
|
!in_array($operator, ['==', '!=']); |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* {@inheritdoc} |
627
|
|
|
*/ |
628
|
|
|
protected function prepareValueAndOperator($value, $operator, $useDefault = false) |
629
|
|
|
{ |
630
|
|
|
if ($useDefault) { |
631
|
|
|
return [$operator, '==']; |
632
|
|
|
} elseif ($this->invalidOperatorAndValue($operator, $value)) { |
633
|
|
|
throw new \InvalidArgumentException('Illegal operator and value combination.'); |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
return [$value, $operator]; |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Check exist entity name in joins or it base entity. Throw exception if didn't find. |
641
|
|
|
* @param $column |
642
|
|
|
* @throws Exception |
643
|
|
|
*/ |
644
|
|
|
protected function checkColumnIfJoin($column) |
645
|
|
|
{ |
646
|
|
|
if (empty($this->joins)) { |
647
|
|
|
return; |
648
|
|
|
} |
649
|
|
|
$columnEntityName = getEntityNameFromColumn($column); |
650
|
|
|
|
651
|
|
|
if (is_null($columnEntityName)) { |
652
|
|
|
throw new Exception('You can\'t use column '.$column.' without entity name, with join.'); |
653
|
|
|
} |
654
|
|
|
|
655
|
|
|
if ($columnEntityName === getEntityName($this->from)) { |
656
|
|
|
return; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
foreach ($this->joins as $join) { |
660
|
|
|
$joinEntityName = getEntityName($join->table); |
661
|
|
|
if ($columnEntityName === $joinEntityName) { |
662
|
|
|
return; |
663
|
|
|
} |
664
|
|
|
} |
665
|
|
|
throw new Exception('You can\'t use column '.$column.' with this joins.'); |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
/** |
669
|
|
|
* Prepate columns from values array |
670
|
|
|
* @param $values |
671
|
|
|
* @return array |
672
|
|
|
* @throws \Exception |
673
|
|
|
*/ |
674
|
|
|
protected function prepareColumns($values) |
675
|
|
|
{ |
676
|
|
|
$res = []; |
677
|
|
|
foreach ($values as $key => $value) { |
678
|
|
|
$column = $this->prepareColumn($key); |
679
|
|
|
$res[$column] = $value; |
680
|
|
|
} |
681
|
|
|
return $res; |
682
|
|
|
} |
683
|
|
|
|
684
|
|
|
/** |
685
|
|
|
* Check column for joins and wrap column (add table name and wrap in ``) |
686
|
|
|
* |
687
|
|
|
* @param $column |
688
|
|
|
* @return string |
689
|
|
|
* @throws Exception |
690
|
|
|
*/ |
691
|
|
|
protected function prepareColumn($column) |
692
|
|
|
{ |
693
|
|
|
$this->checkColumnIfJoin($column); |
694
|
|
|
|
695
|
|
|
$column = $this->grammar->wrapColumn($column, $this->from); |
696
|
|
|
|
697
|
|
|
return $column; |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
/** |
701
|
|
|
* Get next binding variable name |
702
|
|
|
* |
703
|
|
|
* @return string |
704
|
|
|
*/ |
705
|
|
|
protected function getBindingVariableName() |
706
|
|
|
{ |
707
|
|
|
return 'B' . (count($this->bindings) + 1); |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Return table from prepared column or null |
712
|
|
|
* |
713
|
|
|
* @param string $column |
714
|
|
|
* @return null|string |
715
|
|
|
*/ |
716
|
|
|
protected function stripTableForPluck($column) |
717
|
|
|
{ |
718
|
|
|
if (is_null($column)) { |
719
|
|
|
return null; |
720
|
|
|
} |
721
|
|
|
$column = explode('.', $column)[1]; |
722
|
|
|
|
723
|
|
|
return trim($column, '`'); |
724
|
|
|
} |
725
|
|
|
} |
726
|
|
|
|
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.