Subversion-Projekte lars-tiefland.laravel_shop

Revision

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