Completed
Push — master ( 672c77...3dd004 )
by Arjay
02:02
created

OracleGrammar::compileUpdate()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 31
Code Lines 12

Duplication

Lines 8
Ratio 25.81 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 8
loc 31
rs 8.8571
cc 3
eloc 12
nc 4
nop 2
1
<?php
2
3
namespace Yajra\Oci8\Query\Grammars;
4
5
use Illuminate\Database\Query\Builder;
6
use Illuminate\Database\Query\Grammars\Grammar;
7
use Yajra\Oci8\OracleReservedWords;
8
9
class OracleGrammar extends Grammar
10
{
11
    use OracleReservedWords;
12
13
    /**
14
     * The keyword identifier wrapper format.
15
     *
16
     * @var string
17
     */
18
    protected $wrapper = '%s';
19
20
    /**
21
     * @var string
22
     */
23
    protected $schema_prefix = '';
24
25
    /**
26
     * Compile an exists statement into SQL.
27
     *
28
     * @param \Illuminate\Database\Query\Builder $query
29
     * @return string
30
     */
31
    public function compileExists(Builder $query)
32
    {
33
        $q          = clone $query;
34
        $q->columns = [];
35
        $q->selectRaw('1 as "exists"')
36
          ->whereRaw("rownum = 1");
37
38
        return $this->compileSelect($q);
39
    }
40
41
    /**
42
     * Compile a select query into SQL.
43
     *
44
     * @param  \Illuminate\Database\Query\Builder
45
     * @return string
46
     */
47
    public function compileSelect(Builder $query)
48
    {
49
        if (is_null($query->columns)) {
50
            $query->columns = ['*'];
51
        }
52
53
        $components = $this->compileComponents($query);
54
55
        // If an offset is present on the query, we will need to wrap the query in
56
        // a big "ANSI" offset syntax block. This is very nasty compared to the
57
        // other database systems but is necessary for implementing features.
58
        if ($this->isPaginationable($query, $components)) {
59
            return $this->compileAnsiOffset($query, $components);
60
        }
61
62
        return trim($this->concatenate($components));
63
    }
64
65
    /**
66
     * @param Builder $query
67
     * @param array $components
68
     * @return bool
69
     */
70
    protected function isPaginationable(Builder $query, array $components)
71
    {
72
        return ($query->limit > 0 || $query->offset > 0) && ! array_key_exists('lock', $components);
73
    }
74
75
    /**
76
     * Create a full ANSI offset clause for the query.
77
     *
78
     * @param  \Illuminate\Database\Query\Builder $query
79
     * @param  array $components
80
     * @return string
81
     */
82
    protected function compileAnsiOffset(Builder $query, $components)
83
    {
84
        $constraint = $this->compileRowConstraint($query);
85
86
        $sql = $this->concatenate($components);
87
88
        // We are now ready to build the final SQL query so we'll create a common table
89
        // expression from the query and get the records with row numbers within our
90
        // given limit and offset value that we just put on as a query constraint.
91
        $temp = $this->compileTableExpression($sql, $constraint, $query);
92
93
        return $temp;
94
    }
95
96
    /**
97
     * Compile the limit / offset row constraint for a query.
98
     *
99
     * @param  \Illuminate\Database\Query\Builder $query
100
     * @return string
101
     */
102
    protected function compileRowConstraint($query)
103
    {
104
        $start = $query->offset + 1;
105
106
        if ($query->limit == 1) {
107
            return "= 1";
108
        }
109
110
        if ($query->limit > 1) {
111
            $finish = $query->offset + $query->limit;
112
113
            return "between {$start} and {$finish}";
114
        }
115
116
        return ">= {$start}";
117
    }
118
119
    /**
120
     * Compile a common table expression for a query.
121
     *
122
     * @param  string $sql
123
     * @param  string $constraint
124
     * @param Builder $query
125
     * @return string
126
     */
127
    protected function compileTableExpression($sql, $constraint, $query)
128
    {
129
        if ($query->limit > 1) {
130
            return "select t2.* from ( select rownum AS \"rn\", t1.* from ({$sql}) t1 ) t2 where t2.\"rn\" {$constraint}";
131
        } else {
132
            return "select * from ({$sql}) where rownum {$constraint}";
133
        }
134
    }
135
136
    /**
137
     * Compile a truncate table statement into SQL.
138
     *
139
     * @param  \Illuminate\Database\Query\Builder $query
140
     * @return array
141
     */
142
    public function compileTruncate(Builder $query)
143
    {
144
        return ['truncate table ' . $this->wrapTable($query->from) => []];
145
    }
146
147
    /**
148
     * Compile an insert and get ID statement into SQL.
149
     *
150
     * @param  \Illuminate\Database\Query\Builder $query
151
     * @param  array $values
152
     * @param  string $sequence
153
     * @return string
154
     */
155
    public function compileInsertGetId(Builder $query, $values, $sequence = 'id')
156
    {
157
        if (empty($sequence)) {
158
            $sequence = 'id';
159
        }
160
161
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 4)[3]['object'];
162
163
        if (method_exists($backtrace, 'getModel')) {
164
            $model = $backtrace->getModel();
165
            if ($model->sequence && ! isset($values[$model->getKeyName()]) && $model->incrementing) {
166
                $values[$sequence] = null;
167
            }
168
        }
169
170
        return $this->compileInsert($query, $values) . ' returning ' . $this->wrap($sequence) . ' into ?';
171
    }
172
173
    /**
174
     * Compile an insert statement into SQL.
175
     *
176
     * @param  \Illuminate\Database\Query\Builder $query
177
     * @param  array $values
178
     * @return string
179
     */
180
    public function compileInsert(Builder $query, array $values)
181
    {
182
        // Essentially we will force every insert to be treated as a batch insert which
183
        // simply makes creating the SQL easier for us since we can utilize the same
184
        // basic routine regardless of an amount of records given to us to insert.
185
        $table = $this->wrapTable($query->from);
186
187
        if (! is_array(reset($values))) {
188
            $values = [$values];
189
        }
190
191
        $columns = $this->columnize(array_keys(reset($values)));
192
193
        // We need to build a list of parameter place-holders of values that are bound
194
        // to the query. Each insert should have the exact same amount of parameter
195
        // bindings so we can just go off the first list of values in this array.
196
        $parameters = $this->parameterize(reset($values));
197
198
        $value = array_fill(0, count($values), "($parameters)");
199
200
        if (count($value) > 1) {
201
            $insertQueries = [];
202
            foreach ($value as $parameter) {
203
                $parameter       = (str_replace(['(', ')'], '', $parameter));
204
                $insertQueries[] = "select " . $parameter . " from dual ";
205
            }
206
            $parameters = implode('union all ', $insertQueries);
207
208
            return "insert into $table ($columns) $parameters";
209
        }
210
        $parameters = implode(', ', $value);
211
212
        return "insert into " . $this->getSchemaPrefix() . "$table ($columns) values $parameters";
213
    }
214
215
    /**
216
     * Compile an insert with blob field statement into SQL.
217
     *
218
     * @param  \Illuminate\Database\Query\Builder $query
219
     * @param  array $values
220
     * @param  array $binaries
221
     * @param  string $sequence
222
     * @return string
223
     */
224
    public function compileInsertLob(Builder $query, $values, $binaries, $sequence = 'id')
225
    {
226
        if (empty($sequence)) {
227
            $sequence = 'id';
228
        }
229
230
        $table = $this->wrapTable($query->from);
231
232
        if (! is_array(reset($values))) {
233
            $values = [$values];
234
        }
235
236
        if (! is_array(reset($binaries))) {
237
            $binaries = [$binaries];
238
        }
239
240
        $columns       = $this->columnize(array_keys(reset($values)));
241
        $binaryColumns = $this->columnize(array_keys(reset($binaries)));
242
        $columns .= (empty($columns) ? '' : ', ') . $binaryColumns;
243
244
        $parameters       = $this->parameterize(reset($values));
245
        $binaryParameters = $this->parameterize(reset($binaries));
246
247
        $value       = array_fill(0, count($values), "$parameters");
248
        $binaryValue = array_fill(0, count($binaries), str_replace('?', 'EMPTY_BLOB()', $binaryParameters));
249
250
        $value      = array_merge($value, $binaryValue);
251
        $parameters = implode(', ', array_filter($value));
252
253
        return "insert into " . $this->getSchemaPrefix() . "$table ($columns) values ($parameters) returning " . $binaryColumns . ', ' . $this->wrap($sequence) . ' into ' . $binaryParameters . ', ?';
254
    }
255
256
    /**
257
     * Compile an update statement into SQL.
258
     *
259
     * @param  \Illuminate\Database\Query\Builder $query
260
     * @param  array $values
261
     * @param  array $binaries
262
     * @param  string $sequence
263
     * @return string
264
     */
265
    public function compileUpdateLob(Builder $query, array $values, array $binaries, $sequence = 'id')
266
    {
267
        $table = $this->wrapTable($query->from);
268
269
        // Each one of the columns in the update statements needs to be wrapped in the
270
        // keyword identifiers, also a place-holder needs to be created for each of
271
        // the values in the list of bindings so we can make the sets statements.
272
        $columns = [];
273
274 View Code Duplication
        foreach ($values as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
275
            $columns[] = $this->wrap($key) . ' = ' . $this->parameter($value);
276
        }
277
278
        $columns = implode(', ', $columns);
279
280
        // set blob variables
281
        if (! is_array(reset($binaries))) {
282
            $binaries = [$binaries];
283
        }
284
        $binaryColumns    = $this->columnize(array_keys(reset($binaries)));
285
        $binaryParameters = $this->parameterize(reset($binaries));
286
287
        // create EMPTY_BLOB sql for each binary
288
        $binarySql = [];
289
        foreach ((array) $binaryColumns as $binary) {
290
            $binarySql[] = "$binary = EMPTY_BLOB()";
291
        }
292
293
        // prepare binary SQLs
294
        if (count($binarySql)) {
295
            $binarySql = (empty($columns) ? '' : ', ') . implode(',', $binarySql);
296
        }
297
298
        // If the query has any "join" clauses, we will setup the joins on the builder
299
        // and compile them so we can attach them to this update, as update queries
300
        // can get join statements to attach to other tables when they're needed.
301 View Code Duplication
        if (isset($query->joins)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
302
            $joins = ' ' . $this->compileJoins($query, $query->joins);
303
        } else {
304
            $joins = '';
305
        }
306
307
        // Of course, update queries may also be constrained by where clauses so we'll
308
        // need to compile the where clauses and attach it to the query so only the
309
        // intended records are updated by the SQL statements we generate to run.
310
        $where = $this->compileWheres($query);
311
312
        return "update " . $this->getSchemaPrefix() . "{$table}{$joins} set $columns$binarySql $where returning " . $binaryColumns . ', ' . $this->wrap($sequence) . ' into ' . $binaryParameters . ', ?';
313
    }
314
315
    /**
316
     * Compile a delete statement into SQL.
317
     *
318
     * @param  \Illuminate\Database\Query\Builder $query
319
     * @return string
320
     */
321
    public function compileDelete(Builder $query)
322
    {
323
        $table = $this->wrapTable($query->from);
324
325
        $where = is_array($query->wheres) ? $this->compileWheres($query) : '';
326
327
        return trim("delete from " . $this->getSchemaPrefix() . "$table " . $where);
328
    }
329
330
    /**
331
     * Compile an update statement into SQL.
332
     *
333
     * @param  \Illuminate\Database\Query\Builder $query
334
     * @param  array $values
335
     * @return string
336
     */
337
    public function compileUpdate(Builder $query, $values)
338
    {
339
        $table = $this->wrapTable($query->from);
340
341
        // Each one of the columns in the update statements needs to be wrapped in the
342
        // keyword identifiers, also a place-holder needs to be created for each of
343
        // the values in the list of bindings so we can make the sets statements.
344
        $columns = [];
345
346 View Code Duplication
        foreach ($values as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
347
            $columns[] = $this->wrap($key) . ' = ' . $this->parameter($value);
348
        }
349
350
        $columns = implode(', ', $columns);
351
352
        // If the query has any "join" clauses, we will setup the joins on the builder
353
        // and compile them so we can attach them to this update, as update queries
354
        // can get join statements to attach to other tables when they're needed.
355 View Code Duplication
        if (isset($query->joins)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
356
            $joins = ' ' . $this->compileJoins($query, $query->joins);
357
        } else {
358
            $joins = '';
359
        }
360
361
        // Of course, update queries may also be constrained by where clauses so we'll
362
        // need to compile the where clauses and attach it to the query so only the
363
        // intended records are updated by the SQL statements we generate to run.
364
        $where = $this->compileWheres($query);
365
366
        return trim("update " . $this->getSchemaPrefix() . "{$table}{$joins} set $columns $where");
367
    }
368
369
    /**
370
     * Compile the lock into SQL.
371
     *
372
     * @param  \Illuminate\Database\Query\Builder $query
373
     * @param  bool|string $value
374
     * @return string
375
     */
376
    protected function compileLock(Builder $query, $value)
377
    {
378
        if (is_string($value)) {
379
            return $value;
380
        }
381
382
        if ($value) {
383
            return 'for update';
384
        }
385
386
        return '';
387
    }
388
389
    /**
390
     * Compile the "limit" portions of the query.
391
     *
392
     * @param  \Illuminate\Database\Query\Builder $query
393
     * @param  int $limit
394
     * @return string
395
     */
396
    protected function compileLimit(Builder $query, $limit)
397
    {
398
        return '';
399
    }
400
401
    /**
402
     * Compile the "offset" portions of the query.
403
     *
404
     * @param  \Illuminate\Database\Query\Builder $query
405
     * @param  int $offset
406
     * @return string
407
     */
408
    protected function compileOffset(Builder $query, $offset)
409
    {
410
        return '';
411
    }
412
413
    /**
414
     * Compile a "where date" clause.
415
     *
416
     * @param  \Illuminate\Database\Query\Builder $query
417
     * @param  array $where
418
     * @return string
419
     */
420
    protected function whereDate(Builder $query, $where)
421
    {
422
        $value = $this->parameter($where['value']);
423
424
        return "trunc({$this->wrap($where['column'])}) {$where['operator']} $value";
425
    }
426
427
    /**
428
     * Compile a date based where clause.
429
     *
430
     * @param  string $type
431
     * @param  \Illuminate\Database\Query\Builder $query
432
     * @param  array $where
433
     * @return string
434
     */
435
    protected function dateBasedWhere($type, Builder $query, $where)
436
    {
437
        $value = $this->parameter($where['value']);
438
439
        return "extract ($type from {$this->wrap($where['column'])}) {$where['operator']} $value";
440
    }
441
442
    /**
443
     * Wrap a single string in keyword identifiers.
444
     *
445
     * @param  string $value
446
     * @return string
447
     */
448 View Code Duplication
    protected function wrapValue($value)
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...
449
    {
450
        if ($this->isReserved($value)) {
451
            return parent::wrapValue($value);
452
        }
453
454
        return $value !== '*' ? sprintf($this->wrapper, $value) : $value;
455
    }
456
457
    /**
458
     * Compile the "from" portion of the query.
459
     *
460
     * @param  \Illuminate\Database\Query\Builder $query
461
     * @param  string $table
462
     * @return string
463
     */
464
    protected function compileFrom(Builder $query, $table)
465
    {
466
467
        $query = $this->wrapTable($table);
468
469
        if ($this->isSubQuery($query)) {
470
            return 'from ' . $this->wrapTable($table);
471
        }
472
473
        return 'from ' . $this->getSchemaPrefix() . $this->wrapTable($table);
474
    }
475
476
    /**
477
     * Check if given query is a sub-query.
478
     *
479
     * @param string $query
480
     * @return bool
481
     */
482
    protected function isSubQuery($query)
483
    {
484
        return substr($query, 0, 1) == '(';
485
    }
486
487
    /**
488
     * Return the schema prefix
489
     *
490
     * @return string
491
     */
492
    public function getSchemaPrefix()
493
    {
494
        return ! empty($this->schema_prefix) ? $this->schema_prefix . '.' : '';
495
    }
496
497
    /**
498
     * Set the shema prefix
499
     *
500
     * @param string $prefix
501
     */
502
    public function setSchemaPrefix($prefix)
503
    {
504
        $this->schema_prefix = $prefix;
505
    }
506
}
507