Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database\Query\Grammars;
4
 
5
use Illuminate\Database\Query\Builder;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
 
9
class PostgresGrammar extends Grammar
10
{
11
    /**
12
     * All of the available clause operators.
13
     *
14
     * @var string[]
15
     */
16
    protected $operators = [
17
        '=', '<', '>', '<=', '>=', '<>', '!=',
18
        'like', 'not like', 'between', 'ilike', 'not ilike',
19
        '~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
20
        '&&', '@>', '<@', '?', '?|', '?&', '||', '-', '@?', '@@', '#-',
21
        'is distinct from', 'is not distinct from',
22
    ];
23
 
24
    /**
25
     * The grammar specific bitwise operators.
26
     *
27
     * @var array
28
     */
29
    protected $bitwiseOperators = [
30
        '~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
31
    ];
32
 
33
    /**
34
     * Compile a basic where clause.
35
     *
36
     * @param  \Illuminate\Database\Query\Builder  $query
37
     * @param  array  $where
38
     * @return string
39
     */
40
    protected function whereBasic(Builder $query, $where)
41
    {
42
        if (str_contains(strtolower($where['operator']), 'like')) {
43
            return sprintf(
44
                '%s::text %s %s',
45
                $this->wrap($where['column']),
46
                $where['operator'],
47
                $this->parameter($where['value'])
48
            );
49
        }
50
 
51
        return parent::whereBasic($query, $where);
52
    }
53
 
54
    /**
55
     * Compile a bitwise operator where clause.
56
     *
57
     * @param  \Illuminate\Database\Query\Builder  $query
58
     * @param  array  $where
59
     * @return string
60
     */
61
    protected function whereBitwise(Builder $query, $where)
62
    {
63
        $value = $this->parameter($where['value']);
64
 
65
        $operator = str_replace('?', '??', $where['operator']);
66
 
67
        return '('.$this->wrap($where['column']).' '.$operator.' '.$value.')::bool';
68
    }
69
 
70
    /**
71
     * Compile a "where date" clause.
72
     *
73
     * @param  \Illuminate\Database\Query\Builder  $query
74
     * @param  array  $where
75
     * @return string
76
     */
77
    protected function whereDate(Builder $query, $where)
78
    {
79
        $value = $this->parameter($where['value']);
80
 
81
        return $this->wrap($where['column']).'::date '.$where['operator'].' '.$value;
82
    }
83
 
84
    /**
85
     * Compile a "where time" clause.
86
     *
87
     * @param  \Illuminate\Database\Query\Builder  $query
88
     * @param  array  $where
89
     * @return string
90
     */
91
    protected function whereTime(Builder $query, $where)
92
    {
93
        $value = $this->parameter($where['value']);
94
 
95
        return $this->wrap($where['column']).'::time '.$where['operator'].' '.$value;
96
    }
97
 
98
    /**
99
     * Compile a date based where clause.
100
     *
101
     * @param  string  $type
102
     * @param  \Illuminate\Database\Query\Builder  $query
103
     * @param  array  $where
104
     * @return string
105
     */
106
    protected function dateBasedWhere($type, Builder $query, $where)
107
    {
108
        $value = $this->parameter($where['value']);
109
 
110
        return 'extract('.$type.' from '.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
111
    }
112
 
113
    /**
114
     * Compile a "where fulltext" clause.
115
     *
116
     * @param  \Illuminate\Database\Query\Builder  $query
117
     * @param  array  $where
118
     * @return string
119
     */
120
    public function whereFullText(Builder $query, $where)
121
    {
122
        $language = $where['options']['language'] ?? 'english';
123
 
124
        if (! in_array($language, $this->validFullTextLanguages())) {
125
            $language = 'english';
126
        }
127
 
128
        $columns = collect($where['columns'])->map(function ($column) use ($language) {
129
            return "to_tsvector('{$language}', {$this->wrap($column)})";
130
        })->implode(' || ');
131
 
132
        $mode = 'plainto_tsquery';
133
 
134
        if (($where['options']['mode'] ?? []) === 'phrase') {
135
            $mode = 'phraseto_tsquery';
136
        }
137
 
138
        if (($where['options']['mode'] ?? []) === 'websearch') {
139
            $mode = 'websearch_to_tsquery';
140
        }
141
 
142
        return "({$columns}) @@ {$mode}('{$language}', {$this->parameter($where['value'])})";
143
    }
144
 
145
    /**
146
     * Get an array of valid full text languages.
147
     *
148
     * @return array
149
     */
150
    protected function validFullTextLanguages()
151
    {
152
        return [
153
            'simple',
154
            'arabic',
155
            'danish',
156
            'dutch',
157
            'english',
158
            'finnish',
159
            'french',
160
            'german',
161
            'hungarian',
162
            'indonesian',
163
            'irish',
164
            'italian',
165
            'lithuanian',
166
            'nepali',
167
            'norwegian',
168
            'portuguese',
169
            'romanian',
170
            'russian',
171
            'spanish',
172
            'swedish',
173
            'tamil',
174
            'turkish',
175
        ];
176
    }
177
 
178
    /**
179
     * Compile the "select *" portion of the query.
180
     *
181
     * @param  \Illuminate\Database\Query\Builder  $query
182
     * @param  array  $columns
183
     * @return string|null
184
     */
185
    protected function compileColumns(Builder $query, $columns)
186
    {
187
        // If the query is actually performing an aggregating select, we will let that
188
        // compiler handle the building of the select clauses, as it will need some
189
        // more syntax that is best handled by that function to keep things neat.
190
        if (! is_null($query->aggregate)) {
191
            return;
192
        }
193
 
194
        if (is_array($query->distinct)) {
195
            $select = 'select distinct on ('.$this->columnize($query->distinct).') ';
196
        } elseif ($query->distinct) {
197
            $select = 'select distinct ';
198
        } else {
199
            $select = 'select ';
200
        }
201
 
202
        return $select.$this->columnize($columns);
203
    }
204
 
205
    /**
206
     * Compile a "JSON contains" statement into SQL.
207
     *
208
     * @param  string  $column
209
     * @param  string  $value
210
     * @return string
211
     */
212
    protected function compileJsonContains($column, $value)
213
    {
214
        $column = str_replace('->>', '->', $this->wrap($column));
215
 
216
        return '('.$column.')::jsonb @> '.$value;
217
    }
218
 
219
    /**
220
     * Compile a "JSON contains key" statement into SQL.
221
     *
222
     * @param  string  $column
223
     * @return string
224
     */
225
    protected function compileJsonContainsKey($column)
226
    {
227
        $segments = explode('->', $column);
228
 
229
        $lastSegment = array_pop($segments);
230
 
231
        if (filter_var($lastSegment, FILTER_VALIDATE_INT) !== false) {
232
            $i = $lastSegment;
233
        } elseif (preg_match('/\[(-?[0-9]+)\]$/', $lastSegment, $matches)) {
234
            $segments[] = Str::beforeLast($lastSegment, $matches[0]);
235
 
236
            $i = $matches[1];
237
        }
238
 
239
        $column = str_replace('->>', '->', $this->wrap(implode('->', $segments)));
240
 
241
        if (isset($i)) {
242
            return vsprintf('case when %s then %s else false end', [
243
                'jsonb_typeof(('.$column.")::jsonb) = 'array'",
244
                'jsonb_array_length(('.$column.')::jsonb) >= '.($i < 0 ? abs($i) : $i + 1),
245
            ]);
246
        }
247
 
248
        $key = "'".str_replace("'", "''", $lastSegment)."'";
249
 
250
        return 'coalesce(('.$column.')::jsonb ?? '.$key.', false)';
251
    }
252
 
253
    /**
254
     * Compile a "JSON length" statement into SQL.
255
     *
256
     * @param  string  $column
257
     * @param  string  $operator
258
     * @param  string  $value
259
     * @return string
260
     */
261
    protected function compileJsonLength($column, $operator, $value)
262
    {
263
        $column = str_replace('->>', '->', $this->wrap($column));
264
 
265
        return 'jsonb_array_length(('.$column.')::jsonb) '.$operator.' '.$value;
266
    }
267
 
268
    /**
269
     * Compile a single having clause.
270
     *
271
     * @param  array  $having
272
     * @return string
273
     */
274
    protected function compileHaving(array $having)
275
    {
276
        if ($having['type'] === 'Bitwise') {
277
            return $this->compileHavingBitwise($having);
278
        }
279
 
280
        return parent::compileHaving($having);
281
    }
282
 
283
    /**
284
     * Compile a having clause involving a bitwise operator.
285
     *
286
     * @param  array  $having
287
     * @return string
288
     */
289
    protected function compileHavingBitwise($having)
290
    {
291
        $column = $this->wrap($having['column']);
292
 
293
        $parameter = $this->parameter($having['value']);
294
 
295
        return '('.$column.' '.$having['operator'].' '.$parameter.')::bool';
296
    }
297
 
298
    /**
299
     * Compile the lock into SQL.
300
     *
301
     * @param  \Illuminate\Database\Query\Builder  $query
302
     * @param  bool|string  $value
303
     * @return string
304
     */
305
    protected function compileLock(Builder $query, $value)
306
    {
307
        if (! is_string($value)) {
308
            return $value ? 'for update' : 'for share';
309
        }
310
 
311
        return $value;
312
    }
313
 
314
    /**
315
     * Compile an insert ignore statement into SQL.
316
     *
317
     * @param  \Illuminate\Database\Query\Builder  $query
318
     * @param  array  $values
319
     * @return string
320
     */
321
    public function compileInsertOrIgnore(Builder $query, array $values)
322
    {
323
        return $this->compileInsert($query, $values).' on conflict do nothing';
324
    }
325
 
326
    /**
327
     * Compile an insert and get ID statement into SQL.
328
     *
329
     * @param  \Illuminate\Database\Query\Builder  $query
330
     * @param  array  $values
331
     * @param  string  $sequence
332
     * @return string
333
     */
334
    public function compileInsertGetId(Builder $query, $values, $sequence)
335
    {
336
        return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence ?: 'id');
337
    }
338
 
339
    /**
340
     * Compile an update statement into SQL.
341
     *
342
     * @param  \Illuminate\Database\Query\Builder  $query
343
     * @param  array  $values
344
     * @return string
345
     */
346
    public function compileUpdate(Builder $query, array $values)
347
    {
348
        if (isset($query->joins) || isset($query->limit)) {
349
            return $this->compileUpdateWithJoinsOrLimit($query, $values);
350
        }
351
 
352
        return parent::compileUpdate($query, $values);
353
    }
354
 
355
    /**
356
     * Compile the columns for an update statement.
357
     *
358
     * @param  \Illuminate\Database\Query\Builder  $query
359
     * @param  array  $values
360
     * @return string
361
     */
362
    protected function compileUpdateColumns(Builder $query, array $values)
363
    {
364
        return collect($values)->map(function ($value, $key) {
365
            $column = last(explode('.', $key));
366
 
367
            if ($this->isJsonSelector($key)) {
368
                return $this->compileJsonUpdateColumn($column, $value);
369
            }
370
 
371
            return $this->wrap($column).' = '.$this->parameter($value);
372
        })->implode(', ');
373
    }
374
 
375
    /**
376
     * Compile an "upsert" statement into SQL.
377
     *
378
     * @param  \Illuminate\Database\Query\Builder  $query
379
     * @param  array  $values
380
     * @param  array  $uniqueBy
381
     * @param  array  $update
382
     * @return string
383
     */
384
    public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
385
    {
386
        $sql = $this->compileInsert($query, $values);
387
 
388
        $sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set ';
389
 
390
        $columns = collect($update)->map(function ($value, $key) {
391
            return is_numeric($key)
392
                ? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value)
393
                : $this->wrap($key).' = '.$this->parameter($value);
394
        })->implode(', ');
395
 
396
        return $sql.$columns;
397
    }
398
 
399
    /**
400
     * Prepares a JSON column being updated using the JSONB_SET function.
401
     *
402
     * @param  string  $key
403
     * @param  mixed  $value
404
     * @return string
405
     */
406
    protected function compileJsonUpdateColumn($key, $value)
407
    {
408
        $segments = explode('->', $key);
409
 
410
        $field = $this->wrap(array_shift($segments));
411
 
412
        $path = "'{".implode(',', $this->wrapJsonPathAttributes($segments, '"'))."}'";
413
 
414
        return "{$field} = jsonb_set({$field}::jsonb, {$path}, {$this->parameter($value)})";
415
    }
416
 
417
    /**
418
     * Compile an update from statement into SQL.
419
     *
420
     * @param  \Illuminate\Database\Query\Builder  $query
421
     * @param  array  $values
422
     * @return string
423
     */
424
    public function compileUpdateFrom(Builder $query, $values)
425
    {
426
        $table = $this->wrapTable($query->from);
427
 
428
        // Each one of the columns in the update statements needs to be wrapped in the
429
        // keyword identifiers, also a place-holder needs to be created for each of
430
        // the values in the list of bindings so we can make the sets statements.
431
        $columns = $this->compileUpdateColumns($query, $values);
432
 
433
        $from = '';
434
 
435
        if (isset($query->joins)) {
436
            // When using Postgres, updates with joins list the joined tables in the from
437
            // clause, which is different than other systems like MySQL. Here, we will
438
            // compile out the tables that are joined and add them to a from clause.
439
            $froms = collect($query->joins)->map(function ($join) {
440
                return $this->wrapTable($join->table);
441
            })->all();
442
 
443
            if (count($froms) > 0) {
444
                $from = ' from '.implode(', ', $froms);
445
            }
446
        }
447
 
448
        $where = $this->compileUpdateWheres($query);
449
 
450
        return trim("update {$table} set {$columns}{$from} {$where}");
451
    }
452
 
453
    /**
454
     * Compile the additional where clauses for updates with joins.
455
     *
456
     * @param  \Illuminate\Database\Query\Builder  $query
457
     * @return string
458
     */
459
    protected function compileUpdateWheres(Builder $query)
460
    {
461
        $baseWheres = $this->compileWheres($query);
462
 
463
        if (! isset($query->joins)) {
464
            return $baseWheres;
465
        }
466
 
467
        // Once we compile the join constraints, we will either use them as the where
468
        // clause or append them to the existing base where clauses. If we need to
469
        // strip the leading boolean we will do so when using as the only where.
470
        $joinWheres = $this->compileUpdateJoinWheres($query);
471
 
472
        if (trim($baseWheres) == '') {
473
            return 'where '.$this->removeLeadingBoolean($joinWheres);
474
        }
475
 
476
        return $baseWheres.' '.$joinWheres;
477
    }
478
 
479
    /**
480
     * Compile the "join" clause where clauses for an update.
481
     *
482
     * @param  \Illuminate\Database\Query\Builder  $query
483
     * @return string
484
     */
485
    protected function compileUpdateJoinWheres(Builder $query)
486
    {
487
        $joinWheres = [];
488
 
489
        // Here we will just loop through all of the join constraints and compile them
490
        // all out then implode them. This should give us "where" like syntax after
491
        // everything has been built and then we will join it to the real wheres.
492
        foreach ($query->joins as $join) {
493
            foreach ($join->wheres as $where) {
494
                $method = "where{$where['type']}";
495
 
496
                $joinWheres[] = $where['boolean'].' '.$this->$method($query, $where);
497
            }
498
        }
499
 
500
        return implode(' ', $joinWheres);
501
    }
502
 
503
    /**
504
     * Prepare the bindings for an update statement.
505
     *
506
     * @param  array  $bindings
507
     * @param  array  $values
508
     * @return array
509
     */
510
    public function prepareBindingsForUpdateFrom(array $bindings, array $values)
511
    {
512
        $values = collect($values)->map(function ($value, $column) {
513
            return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value))
514
                ? json_encode($value)
515
                : $value;
516
        })->all();
517
 
518
        $bindingsWithoutWhere = Arr::except($bindings, ['select', 'where']);
519
 
520
        return array_values(
521
            array_merge($values, $bindings['where'], Arr::flatten($bindingsWithoutWhere))
522
        );
523
    }
524
 
525
    /**
526
     * Compile an update statement with joins or limit into SQL.
527
     *
528
     * @param  \Illuminate\Database\Query\Builder  $query
529
     * @param  array  $values
530
     * @return string
531
     */
532
    protected function compileUpdateWithJoinsOrLimit(Builder $query, array $values)
533
    {
534
        $table = $this->wrapTable($query->from);
535
 
536
        $columns = $this->compileUpdateColumns($query, $values);
537
 
538
        $alias = last(preg_split('/\s+as\s+/i', $query->from));
539
 
540
        $selectSql = $this->compileSelect($query->select($alias.'.ctid'));
541
 
542
        return "update {$table} set {$columns} where {$this->wrap('ctid')} in ({$selectSql})";
543
    }
544
 
545
    /**
546
     * Prepare the bindings for an update statement.
547
     *
548
     * @param  array  $bindings
549
     * @param  array  $values
550
     * @return array
551
     */
552
    public function prepareBindingsForUpdate(array $bindings, array $values)
553
    {
554
        $values = collect($values)->map(function ($value, $column) {
555
            return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value))
556
                ? json_encode($value)
557
                : $value;
558
        })->all();
559
 
560
        $cleanBindings = Arr::except($bindings, 'select');
561
 
562
        return array_values(
563
            array_merge($values, Arr::flatten($cleanBindings))
564
        );
565
    }
566
 
567
    /**
568
     * Compile a delete statement into SQL.
569
     *
570
     * @param  \Illuminate\Database\Query\Builder  $query
571
     * @return string
572
     */
573
    public function compileDelete(Builder $query)
574
    {
575
        if (isset($query->joins) || isset($query->limit)) {
576
            return $this->compileDeleteWithJoinsOrLimit($query);
577
        }
578
 
579
        return parent::compileDelete($query);
580
    }
581
 
582
    /**
583
     * Compile a delete statement with joins or limit into SQL.
584
     *
585
     * @param  \Illuminate\Database\Query\Builder  $query
586
     * @return string
587
     */
588
    protected function compileDeleteWithJoinsOrLimit(Builder $query)
589
    {
590
        $table = $this->wrapTable($query->from);
591
 
592
        $alias = last(preg_split('/\s+as\s+/i', $query->from));
593
 
594
        $selectSql = $this->compileSelect($query->select($alias.'.ctid'));
595
 
596
        return "delete from {$table} where {$this->wrap('ctid')} in ({$selectSql})";
597
    }
598
 
599
    /**
600
     * Compile a truncate table statement into SQL.
601
     *
602
     * @param  \Illuminate\Database\Query\Builder  $query
603
     * @return array
604
     */
605
    public function compileTruncate(Builder $query)
606
    {
607
        return ['truncate '.$this->wrapTable($query->from).' restart identity cascade' => []];
608
    }
609
 
610
    /**
611
     * Wrap the given JSON selector.
612
     *
613
     * @param  string  $value
614
     * @return string
615
     */
616
    protected function wrapJsonSelector($value)
617
    {
618
        $path = explode('->', $value);
619
 
620
        $field = $this->wrapSegments(explode('.', array_shift($path)));
621
 
622
        $wrappedPath = $this->wrapJsonPathAttributes($path);
623
 
624
        $attribute = array_pop($wrappedPath);
625
 
626
        if (! empty($wrappedPath)) {
627
            return $field.'->'.implode('->', $wrappedPath).'->>'.$attribute;
628
        }
629
 
630
        return $field.'->>'.$attribute;
631
    }
632
 
633
    /**
634
     * Wrap the given JSON selector for boolean values.
635
     *
636
     * @param  string  $value
637
     * @return string
638
     */
639
    protected function wrapJsonBooleanSelector($value)
640
    {
641
        $selector = str_replace(
642
            '->>', '->',
643
            $this->wrapJsonSelector($value)
644
        );
645
 
646
        return '('.$selector.')::jsonb';
647
    }
648
 
649
    /**
650
     * Wrap the given JSON boolean value.
651
     *
652
     * @param  string  $value
653
     * @return string
654
     */
655
    protected function wrapJsonBooleanValue($value)
656
    {
657
        return "'".$value."'::jsonb";
658
    }
659
 
660
    /**
661
     * Wrap the attributes of the given JSON path.
662
     *
663
     * @param  array  $path
664
     * @return array
665
     */
666
    protected function wrapJsonPathAttributes($path)
667
    {
668
        $quote = func_num_args() === 2 ? func_get_arg(1) : "'";
669
 
670
        return collect($path)->map(function ($attribute) {
671
            return $this->parseJsonPathArrayKeys($attribute);
672
        })->collapse()->map(function ($attribute) use ($quote) {
673
            return filter_var($attribute, FILTER_VALIDATE_INT) !== false
674
                        ? $attribute
675
                        : $quote.$attribute.$quote;
676
        })->all();
677
    }
678
 
679
    /**
680
     * Parse the given JSON path attribute for array keys.
681
     *
682
     * @param  string  $attribute
683
     * @return array
684
     */
685
    protected function parseJsonPathArrayKeys($attribute)
686
    {
687
        if (preg_match('/(\[[^\]]+\])+$/', $attribute, $parts)) {
688
            $key = Str::beforeLast($attribute, $parts[0]);
689
 
690
            preg_match_all('/\[([^\]]+)\]/', $parts[0], $keys);
691
 
692
            return collect([$key])
693
                ->merge($keys[1])
694
                ->diff('')
695
                ->values()
696
                ->all();
697
        }
698
 
699
        return [$attribute];
700
    }
701
}