Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen 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 SqlServerGrammar extends Grammar
10
{
11
    /**
12
     * All of the available clause operators.
13
     *
14
     * @var string[]
15
     */
16
    protected $operators = [
17
        '=', '<', '>', '<=', '>=', '!<', '!>', '<>', '!=',
18
        'like', 'not like', 'ilike',
19
        '&', '&=', '|', '|=', '^', '^=',
20
    ];
21
 
22
    /**
23
     * Compile a select query into SQL.
24
     *
25
     * @param  \Illuminate\Database\Query\Builder  $query
26
     * @return string
27
     */
28
    public function compileSelect(Builder $query)
29
    {
30
        if (! $query->offset) {
31
            return parent::compileSelect($query);
32
        }
33
 
34
        if (is_null($query->columns)) {
35
            $query->columns = ['*'];
36
        }
37
 
38
        $components = $this->compileComponents($query);
39
 
40
        if (! empty($components['orders'])) {
41
            return parent::compileSelect($query)." offset {$query->offset} rows fetch next {$query->limit} rows only";
42
        }
43
 
44
        // If an offset is present on the query, we will need to wrap the query in
45
        // a big "ANSI" offset syntax block. This is very nasty compared to the
46
        // other database systems but is necessary for implementing features.
47
        return $this->compileAnsiOffset(
48
            $query, $components
49
        );
50
    }
51
 
52
    /**
53
     * Compile the "select *" portion of the query.
54
     *
55
     * @param  \Illuminate\Database\Query\Builder  $query
56
     * @param  array  $columns
57
     * @return string|null
58
     */
59
    protected function compileColumns(Builder $query, $columns)
60
    {
61
        if (! is_null($query->aggregate)) {
62
            return;
63
        }
64
 
65
        $select = $query->distinct ? 'select distinct ' : 'select ';
66
 
67
        // If there is a limit on the query, but not an offset, we will add the top
68
        // clause to the query, which serves as a "limit" type clause within the
69
        // SQL Server system similar to the limit keywords available in MySQL.
70
        if (is_numeric($query->limit) && $query->limit > 0 && $query->offset <= 0) {
71
            $select .= 'top '.((int) $query->limit).' ';
72
        }
73
 
74
        return $select.$this->columnize($columns);
75
    }
76
 
77
    /**
78
     * Compile the "from" portion of the query.
79
     *
80
     * @param  \Illuminate\Database\Query\Builder  $query
81
     * @param  string  $table
82
     * @return string
83
     */
84
    protected function compileFrom(Builder $query, $table)
85
    {
86
        $from = parent::compileFrom($query, $table);
87
 
88
        if (is_string($query->lock)) {
89
            return $from.' '.$query->lock;
90
        }
91
 
92
        if (! is_null($query->lock)) {
93
            return $from.' with(rowlock,'.($query->lock ? 'updlock,' : '').'holdlock)';
94
        }
95
 
96
        return $from;
97
    }
98
 
99
    /**
100
     * {@inheritdoc}
101
     *
102
     * @param  \Illuminate\Database\Query\Builder  $query
103
     * @param  array  $where
104
     * @return string
105
     */
106
    protected function whereBitwise(Builder $query, $where)
107
    {
108
        $value = $this->parameter($where['value']);
109
 
110
        $operator = str_replace('?', '??', $where['operator']);
111
 
112
        return '('.$this->wrap($where['column']).' '.$operator.' '.$value.') != 0';
113
    }
114
 
115
    /**
116
     * Compile a "where date" clause.
117
     *
118
     * @param  \Illuminate\Database\Query\Builder  $query
119
     * @param  array  $where
120
     * @return string
121
     */
122
    protected function whereDate(Builder $query, $where)
123
    {
124
        $value = $this->parameter($where['value']);
125
 
126
        return 'cast('.$this->wrap($where['column']).' as date) '.$where['operator'].' '.$value;
127
    }
128
 
129
    /**
130
     * Compile a "where time" clause.
131
     *
132
     * @param  \Illuminate\Database\Query\Builder  $query
133
     * @param  array  $where
134
     * @return string
135
     */
136
    protected function whereTime(Builder $query, $where)
137
    {
138
        $value = $this->parameter($where['value']);
139
 
140
        return 'cast('.$this->wrap($where['column']).' as time) '.$where['operator'].' '.$value;
141
    }
142
 
143
    /**
144
     * Compile a "JSON contains" statement into SQL.
145
     *
146
     * @param  string  $column
147
     * @param  string  $value
148
     * @return string
149
     */
150
    protected function compileJsonContains($column, $value)
151
    {
152
        [$field, $path] = $this->wrapJsonFieldAndPath($column);
153
 
154
        return $value.' in (select [value] from openjson('.$field.$path.'))';
155
    }
156
 
157
    /**
158
     * Prepare the binding for a "JSON contains" statement.
159
     *
160
     * @param  mixed  $binding
161
     * @return string
162
     */
163
    public function prepareBindingForJsonContains($binding)
164
    {
165
        return is_bool($binding) ? json_encode($binding) : $binding;
166
    }
167
 
168
    /**
169
     * Compile a "JSON contains key" statement into SQL.
170
     *
171
     * @param  string  $column
172
     * @return string
173
     */
174
    protected function compileJsonContainsKey($column)
175
    {
176
        $segments = explode('->', $column);
177
 
178
        $lastSegment = array_pop($segments);
179
 
180
        if (preg_match('/\[([0-9]+)\]$/', $lastSegment, $matches)) {
181
            $segments[] = Str::beforeLast($lastSegment, $matches[0]);
182
 
183
            $key = $matches[1];
184
        } else {
185
            $key = "'".str_replace("'", "''", $lastSegment)."'";
186
        }
187
 
188
        [$field, $path] = $this->wrapJsonFieldAndPath(implode('->', $segments));
189
 
190
        return $key.' in (select [key] from openjson('.$field.$path.'))';
191
    }
192
 
193
    /**
194
     * Compile a "JSON length" statement into SQL.
195
     *
196
     * @param  string  $column
197
     * @param  string  $operator
198
     * @param  string  $value
199
     * @return string
200
     */
201
    protected function compileJsonLength($column, $operator, $value)
202
    {
203
        [$field, $path] = $this->wrapJsonFieldAndPath($column);
204
 
205
        return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value;
206
    }
207
 
208
    /**
209
     * Compile a "JSON value cast" statement into SQL.
210
     *
211
     * @param  string  $value
212
     * @return string
213
     */
214
    public function compileJsonValueCast($value)
215
    {
216
        return 'json_query('.$value.')';
217
    }
218
 
219
    /**
220
     * Compile a single having clause.
221
     *
222
     * @param  array  $having
223
     * @return string
224
     */
225
    protected function compileHaving(array $having)
226
    {
227
        if ($having['type'] === 'Bitwise') {
228
            return $this->compileHavingBitwise($having);
229
        }
230
 
231
        return parent::compileHaving($having);
232
    }
233
 
234
    /**
235
     * Compile a having clause involving a bitwise operator.
236
     *
237
     * @param  array  $having
238
     * @return string
239
     */
240
    protected function compileHavingBitwise($having)
241
    {
242
        $column = $this->wrap($having['column']);
243
 
244
        $parameter = $this->parameter($having['value']);
245
 
246
        return '('.$column.' '.$having['operator'].' '.$parameter.') != 0';
247
    }
248
 
249
    /**
250
     * Create a full ANSI offset clause for the query.
251
     *
252
     * @param  \Illuminate\Database\Query\Builder  $query
253
     * @param  array  $components
254
     * @return string
255
     */
256
    protected function compileAnsiOffset(Builder $query, $components)
257
    {
258
        // An ORDER BY clause is required to make this offset query work, so if one does
259
        // not exist we'll just create a dummy clause to trick the database and so it
260
        // does not complain about the queries for not having an "order by" clause.
261
        if (empty($components['orders'])) {
262
            $components['orders'] = 'order by (select 0)';
263
        }
264
 
265
        // We need to add the row number to the query so we can compare it to the offset
266
        // and limit values given for the statements. So we will add an expression to
267
        // the "select" that will give back the row numbers on each of the records.
268
        $components['columns'] .= $this->compileOver($components['orders']);
269
 
270
        unset($components['orders']);
271
 
272
        if ($this->queryOrderContainsSubquery($query)) {
273
            $query->bindings = $this->sortBindingsForSubqueryOrderBy($query);
274
        }
275
 
276
        // Next we need to calculate the constraints that should be placed on the query
277
        // to get the right offset and limit from our query but if there is no limit
278
        // set we will just handle the offset only since that is all that matters.
279
        $sql = $this->concatenate($components);
280
 
281
        return $this->compileTableExpression($sql, $query);
282
    }
283
 
284
    /**
285
     * Compile the over statement for a table expression.
286
     *
287
     * @param  string  $orderings
288
     * @return string
289
     */
290
    protected function compileOver($orderings)
291
    {
292
        return ", row_number() over ({$orderings}) as row_num";
293
    }
294
 
295
    /**
296
     * Determine if the query's order by clauses contain a subquery.
297
     *
298
     * @param  \Illuminate\Database\Query\Builder  $query
299
     * @return bool
300
     */
301
    protected function queryOrderContainsSubquery($query)
302
    {
303
        if (! is_array($query->orders)) {
304
            return false;
305
        }
306
 
307
        return Arr::first($query->orders, function ($value) {
308
            return $this->isExpression($value['column'] ?? null);
309
        }, false) !== false;
310
    }
311
 
312
    /**
313
     * Move the order bindings to be after the "select" statement to account for an order by subquery.
314
     *
315
     * @param  \Illuminate\Database\Query\Builder  $query
316
     * @return array
317
     */
318
    protected function sortBindingsForSubqueryOrderBy($query)
319
    {
320
        return Arr::sort($query->bindings, function ($bindings, $key) {
321
            return array_search($key, ['select', 'order', 'from', 'join', 'where', 'groupBy', 'having', 'union', 'unionOrder']);
322
        });
323
    }
324
 
325
    /**
326
     * Compile a common table expression for a query.
327
     *
328
     * @param  string  $sql
329
     * @param  \Illuminate\Database\Query\Builder  $query
330
     * @return string
331
     */
332
    protected function compileTableExpression($sql, $query)
333
    {
334
        $constraint = $this->compileRowConstraint($query);
335
 
336
        return "select * from ({$sql}) as temp_table where row_num {$constraint} order by row_num";
337
    }
338
 
339
    /**
340
     * Compile the limit / offset row constraint for a query.
341
     *
342
     * @param  \Illuminate\Database\Query\Builder  $query
343
     * @return string
344
     */
345
    protected function compileRowConstraint($query)
346
    {
347
        $start = (int) $query->offset + 1;
348
 
349
        if ($query->limit > 0) {
350
            $finish = (int) $query->offset + (int) $query->limit;
351
 
352
            return "between {$start} and {$finish}";
353
        }
354
 
355
        return ">= {$start}";
356
    }
357
 
358
    /**
359
     * Compile a delete statement without joins into SQL.
360
     *
361
     * @param  \Illuminate\Database\Query\Builder  $query
362
     * @param  string  $table
363
     * @param  string  $where
364
     * @return string
365
     */
366
    protected function compileDeleteWithoutJoins(Builder $query, $table, $where)
367
    {
368
        $sql = parent::compileDeleteWithoutJoins($query, $table, $where);
369
 
370
        return ! is_null($query->limit) && $query->limit > 0 && $query->offset <= 0
371
                        ? Str::replaceFirst('delete', 'delete top ('.$query->limit.')', $sql)
372
                        : $sql;
373
    }
374
 
375
    /**
376
     * Compile the random statement into SQL.
377
     *
378
     * @param  string|int  $seed
379
     * @return string
380
     */
381
    public function compileRandom($seed)
382
    {
383
        return 'NEWID()';
384
    }
385
 
386
    /**
387
     * Compile the "limit" portions of the query.
388
     *
389
     * @param  \Illuminate\Database\Query\Builder  $query
390
     * @param  int  $limit
391
     * @return string
392
     */
393
    protected function compileLimit(Builder $query, $limit)
394
    {
395
        return '';
396
    }
397
 
398
    /**
399
     * Compile the "offset" portions of the query.
400
     *
401
     * @param  \Illuminate\Database\Query\Builder  $query
402
     * @param  int  $offset
403
     * @return string
404
     */
405
    protected function compileOffset(Builder $query, $offset)
406
    {
407
        return '';
408
    }
409
 
410
    /**
411
     * Compile the lock into SQL.
412
     *
413
     * @param  \Illuminate\Database\Query\Builder  $query
414
     * @param  bool|string  $value
415
     * @return string
416
     */
417
    protected function compileLock(Builder $query, $value)
418
    {
419
        return '';
420
    }
421
 
422
    /**
423
     * Wrap a union subquery in parentheses.
424
     *
425
     * @param  string  $sql
426
     * @return string
427
     */
428
    protected function wrapUnion($sql)
429
    {
430
        return 'select * from ('.$sql.') as '.$this->wrapTable('temp_table');
431
    }
432
 
433
    /**
434
     * Compile an exists statement into SQL.
435
     *
436
     * @param  \Illuminate\Database\Query\Builder  $query
437
     * @return string
438
     */
439
    public function compileExists(Builder $query)
440
    {
441
        $existsQuery = clone $query;
442
 
443
        $existsQuery->columns = [];
444
 
445
        return $this->compileSelect($existsQuery->selectRaw('1 [exists]')->limit(1));
446
    }
447
 
448
    /**
449
     * Compile an update statement with joins into SQL.
450
     *
451
     * @param  \Illuminate\Database\Query\Builder  $query
452
     * @param  string  $table
453
     * @param  string  $columns
454
     * @param  string  $where
455
     * @return string
456
     */
457
    protected function compileUpdateWithJoins(Builder $query, $table, $columns, $where)
458
    {
459
        $alias = last(explode(' as ', $table));
460
 
461
        $joins = $this->compileJoins($query, $query->joins);
462
 
463
        return "update {$alias} set {$columns} from {$table} {$joins} {$where}";
464
    }
465
 
466
    /**
467
     * Compile an "upsert" statement into SQL.
468
     *
469
     * @param  \Illuminate\Database\Query\Builder  $query
470
     * @param  array  $values
471
     * @param  array  $uniqueBy
472
     * @param  array  $update
473
     * @return string
474
     */
475
    public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
476
    {
477
        $columns = $this->columnize(array_keys(reset($values)));
478
 
479
        $sql = 'merge '.$this->wrapTable($query->from).' ';
480
 
481
        $parameters = collect($values)->map(function ($record) {
482
            return '('.$this->parameterize($record).')';
483
        })->implode(', ');
484
 
485
        $sql .= 'using (values '.$parameters.') '.$this->wrapTable('laravel_source').' ('.$columns.') ';
486
 
487
        $on = collect($uniqueBy)->map(function ($column) use ($query) {
488
            return $this->wrap('laravel_source.'.$column).' = '.$this->wrap($query->from.'.'.$column);
489
        })->implode(' and ');
490
 
491
        $sql .= 'on '.$on.' ';
492
 
493
        if ($update) {
494
            $update = collect($update)->map(function ($value, $key) {
495
                return is_numeric($key)
496
                    ? $this->wrap($value).' = '.$this->wrap('laravel_source.'.$value)
497
                    : $this->wrap($key).' = '.$this->parameter($value);
498
            })->implode(', ');
499
 
500
            $sql .= 'when matched then update set '.$update.' ';
501
        }
502
 
503
        $sql .= 'when not matched then insert ('.$columns.') values ('.$columns.');';
504
 
505
        return $sql;
506
    }
507
 
508
    /**
509
     * Prepare the bindings for an update statement.
510
     *
511
     * @param  array  $bindings
512
     * @param  array  $values
513
     * @return array
514
     */
515
    public function prepareBindingsForUpdate(array $bindings, array $values)
516
    {
517
        $cleanBindings = Arr::except($bindings, 'select');
518
 
519
        return array_values(
520
            array_merge($values, Arr::flatten($cleanBindings))
521
        );
522
    }
523
 
524
    /**
525
     * Compile the SQL statement to define a savepoint.
526
     *
527
     * @param  string  $name
528
     * @return string
529
     */
530
    public function compileSavepoint($name)
531
    {
532
        return 'SAVE TRANSACTION '.$name;
533
    }
534
 
535
    /**
536
     * Compile the SQL statement to execute a savepoint rollback.
537
     *
538
     * @param  string  $name
539
     * @return string
540
     */
541
    public function compileSavepointRollBack($name)
542
    {
543
        return 'ROLLBACK TRANSACTION '.$name;
544
    }
545
 
546
    /**
547
     * Get the format for database stored dates.
548
     *
549
     * @return string
550
     */
551
    public function getDateFormat()
552
    {
553
        return 'Y-m-d H:i:s.v';
554
    }
555
 
556
    /**
557
     * Wrap a single string in keyword identifiers.
558
     *
559
     * @param  string  $value
560
     * @return string
561
     */
562
    protected function wrapValue($value)
563
    {
564
        return $value === '*' ? $value : '['.str_replace(']', ']]', $value).']';
565
    }
566
 
567
    /**
568
     * Wrap the given JSON selector.
569
     *
570
     * @param  string  $value
571
     * @return string
572
     */
573
    protected function wrapJsonSelector($value)
574
    {
575
        [$field, $path] = $this->wrapJsonFieldAndPath($value);
576
 
577
        return 'json_value('.$field.$path.')';
578
    }
579
 
580
    /**
581
     * Wrap the given JSON boolean value.
582
     *
583
     * @param  string  $value
584
     * @return string
585
     */
586
    protected function wrapJsonBooleanValue($value)
587
    {
588
        return "'".$value."'";
589
    }
590
 
591
    /**
592
     * Wrap a table in keyword identifiers.
593
     *
594
     * @param  \Illuminate\Database\Query\Expression|string  $table
595
     * @return string
596
     */
597
    public function wrapTable($table)
598
    {
599
        if (! $this->isExpression($table)) {
600
            return $this->wrapTableValuedFunction(parent::wrapTable($table));
601
        }
602
 
603
        return $this->getValue($table);
604
    }
605
 
606
    /**
607
     * Wrap a table in keyword identifiers.
608
     *
609
     * @param  string  $table
610
     * @return string
611
     */
612
    protected function wrapTableValuedFunction($table)
613
    {
614
        if (preg_match('/^(.+?)(\(.*?\))]$/', $table, $matches) === 1) {
615
            $table = $matches[1].']'.$matches[2];
616
        }
617
 
618
        return $table;
619
    }
620
}