Completed
Push — master ( 1195c4...15a10c )
by Arjay
19s queued 10s
created

OracleGrammar::whereInRaw()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 2
dl 12
loc 12
ccs 0
cts 0
cp 0
crap 20
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Oci8\Query\Grammars;
4
5
use Illuminate\Support\Str;
6
use Yajra\Oci8\OracleReservedWords;
7
use Illuminate\Database\Query\Builder;
8
use Illuminate\Database\Query\Grammars\Grammar;
9
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
10
11
class OracleGrammar extends Grammar
12
{
13
    use OracleReservedWords;
14
15
    /**
16
     * The keyword identifier wrapper format.
17
     *
18
     * @var string
19
     */
20
    protected $wrapper = '%s';
21
22
    /**
23
     * @var string
24
     */
25
    protected $schema_prefix = '';
26
27
    /**
28
     * Compile an exists statement into SQL.
29
     *
30
     * @param \Illuminate\Database\Query\Builder $query
31
     * @return string
32
     */
33 3
    public function compileExists(Builder $query)
34
    {
35 3
        $q          = clone $query;
36 3
        $q->columns = [];
37 3
        $q->selectRaw('1 as "exists"')
38 3
          ->whereRaw('rownum = 1');
39
40 3
        return $this->compileSelect($q);
41
    }
42
43
    /**
44
     * Compile a select query into SQL.
45
     *
46
     * @param  \Illuminate\Database\Query\Builder
47
     * @return string
48
     */
49 159
    public function compileSelect(Builder $query)
50
    {
51 159
        if ($query->unions && $query->aggregate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query->unions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $query->aggregate of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
52
            return $this->compileUnionAggregate($query);
53
        }
54
55 159
        // If the query does not have any columns set, we'll set the columns to the
56
        // * character to just get all of the columns from the database. Then we
57
        // can build the query and concatenate all the pieces together as one.
58
        $original = $query->columns;
59
60 159
        if (is_null($query->columns)) {
61 12
            $query->columns = ['*'];
62
        }
63
64 150
        $components = $this->compileComponents($query);
65
66
        // To compile the query, we'll spin through each component of the query and
67
        // see if that component exists. If it does we'll just call the compiler
68
        // function for the component which is responsible for making the SQL.
69
        $sql = trim($this->concatenate($components));
70
71
        // If an offset is present on the query, we will need to wrap the query in
72 159
        // a big "ANSI" offset syntax block. This is very nasty compared to the
73
        // other database systems but is necessary for implementing features.
74 159
        if ($this->isPaginationable($query, $components)) {
75
            return $this->compileAnsiOffset($query, $components);
76
        }
77
78
        if ($query->unions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query->unions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
79
            $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
80
        }
81
82
        $query->columns = $original;
83
84 12
        return $sql;
85
    }
86 12
87
    /**
88 12
     * @param Builder $query
89
     * @param array $components
90
     * @return bool
91
     */
92
    protected function isPaginationable(Builder $query, array $components)
93 12
    {
94
        return ($query->limit > 0 || $query->offset > 0) && ! array_key_exists('lock', $components);
95 12
    }
96
97
    /**
98
     * Create a full ANSI offset clause for the query.
99
     *
100
     * @param  \Illuminate\Database\Query\Builder $query
101
     * @param  array $components
102
     * @return string
103
     */
104 12
    protected function compileAnsiOffset(Builder $query, $components)
105
    {
106 12
        // Improved response time with FIRST_ROWS(n) hint for ORDER BY queries
107
        if ($query->getConnection()->getConfig('server_version') == '12c') {
108 12
            $components['columns'] = str_replace('select', "select /*+ FIRST_ROWS({$query->limit}) */", $components['columns']);
109 6
            $offset                = $query->offset ?: 0;
110
            $limit                 = $query->limit;
111
            $components['limit']   = "offset $offset rows fetch next $limit rows only";
112 6
113 6
            return $this->concatenate($components);
114
        }
115 6
116
        $constraint = $this->compileRowConstraint($query);
117
118 3
        $sql = $this->concatenate($components);
119
120
        // We are now ready to build the final SQL query so we'll create a common table
121
        // expression from the query and get the records with row numbers within our
122
        // given limit and offset value that we just put on as a query constraint.
123
        $temp = $this->compileTableExpression($sql, $constraint, $query);
124
125
        return $temp;
126
    }
127
128
    /**
129 12
     * Compile the limit / offset row constraint for a query.
130
     *
131 12
     * @param  \Illuminate\Database\Query\Builder $query
132 6
     * @return string
133
     */
134 9
    protected function compileRowConstraint($query)
135
    {
136
        $start  = $query->offset + 1;
137
        $finish = $query->offset + $query->limit;
138
139
        if ($query->limit == 1 && is_null($query->offset)) {
140
            return '= 1';
141
        }
142
143
        if ($query->offset && is_null($query->limit)) {
144 3
            return ">= {$start}";
145
        }
146 3
147
        return "between {$start} and {$finish}";
148
    }
149
150
    /**
151
     * Compile a common table expression for a query.
152
     *
153
     * @param  string $sql
154
     * @param  string $constraint
155 201
     * @param Builder $query
156
     * @return string
157 201
     */
158
    protected function compileTableExpression($sql, $constraint, $query)
159
    {
160
        if ($query->limit == 1 && is_null($query->offset)) {
161 201
            return "select * from ({$sql}) where rownum {$constraint}";
162
        }
163
164
        return "select t2.* from ( select rownum AS \"rn\", t1.* from ({$sql}) t1 ) t2 where t2.\"rn\" {$constraint}";
165 201
    }
166 201
167 201
    /**
168
     * Compile a truncate table statement into SQL.
169
     *
170
     * @param  \Illuminate\Database\Query\Builder $query
171 201
     * @return array
172
     */
173
    public function compileTruncate(Builder $query)
174
    {
175
        return ['truncate table ' . $this->wrapTable($query->from) => []];
176
    }
177
178
    /**
179 201
     * Wrap a value in keyword identifiers.
180
     *
181 201
     * Override due to laravel's stringify integers.
182
     *
183
     * @param  \Illuminate\Database\Query\Expression|string  $value
184
     * @param  bool    $prefixAlias
185
     * @return string
186
     */
187
    public function wrap($value, $prefixAlias = false)
188
    {
189
        if (is_int($value) || is_float($value)) {
190
            return $value;
191
        }
192
193
        return parent::wrap($value, $prefixAlias);
194
    }
195
196
    /**
197
     * Wrap a table in keyword identifiers.
198
     *
199
     * @param  \Illuminate\Database\Query\Expression|string $table
200 201
     * @return string
201
     */
202 201
    public function wrapTable($table)
203 123
    {
204
        if ($this->isExpression($table)) {
205
            return $this->getValue($table);
0 ignored issues
show
Bug introduced by
It seems like $table defined by parameter $table on line 202 can also be of type string; however, Illuminate\Database\Grammar::getValue() does only seem to accept object<Illuminate\Database\Query\Expression>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
206 201
        }
207
208 201
        if (strpos(strtolower($table), ' as ') !== false) {
209
            $table = str_replace(' as ', ' ', strtolower($table));
210
        }
211
212
        $tableName = $this->wrap($this->tablePrefix . $table, true);
213
        $segments  = explode(' ', $table);
214
        if (count($segments) > 1) {
215
            $tableName = $this->wrap($this->tablePrefix . $segments[0]) . ' ' . $segments[1];
216
        }
217
218
        return $this->getSchemaPrefix() . $tableName;
219 6
    }
220
221 6
    /**
222
     * Return the schema prefix.
223
     *
224
     * @return string
225 6
     */
226
    public function getSchemaPrefix()
227 6
    {
228
        return ! empty($this->schema_prefix) ? $this->wrapValue($this->schema_prefix) . '.' : '';
229
    }
230
231
    /**
232
     * Set the schema prefix.
233
     *
234 6
     * @param string $prefix
235
     */
236
    public function setSchemaPrefix($prefix)
237
    {
238
        $this->schema_prefix = $prefix;
239
    }
240
241
    /**
242
     * Wrap a single string in keyword identifiers.
243
     *
244 15
     * @param  string $value
245
     * @return string
246
     */
247
    protected function wrapValue($value)
248
    {
249 15
        if ($value === '*') {
250
            return $value;
251 15
        }
252 6
253 4
        $value = Str::upper($value);
254
255 15
        return '"' . str_replace('"', '""', $value) . '"';
256
    }
257
258
    /**
259
     * Compile an insert and get ID statement into SQL.
260 15
     *
261
     * @param  \Illuminate\Database\Query\Builder $query
262 15
     * @param  array $values
263
     * @param  string $sequence
264 15
     * @return string
265 3
     */
266 3
    public function compileInsertGetId(Builder $query, $values, $sequence = 'id')
267 3
    {
268 3
        if (empty($sequence)) {
269 2
            $sequence = 'id';
270 3
        }
271
272 3
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 4)[2]['object'];
273
274 12
        if ($backtrace instanceof EloquentBuilder) {
275
            $model = $backtrace->getModel();
276 12
            if ($model->sequence && ! isset($values[$model->getKeyName()]) && $model->incrementing) {
0 ignored issues
show
Bug introduced by
The method getKeyName does only exist in Illuminate\Database\Eloquent\Model, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
277
                $values[$sequence] = null;
278
            }
279
        }
280
281
        return $this->compileInsert($query, $values) . ' returning ' . $this->wrap($sequence) . ' into ?';
282
    }
283
284
    /**
285
     * Compile an insert statement into SQL.
286
     *
287
     * @param  \Illuminate\Database\Query\Builder $query
288 6
     * @param  array $values
289
     * @return string
290 6
     */
291
    public function compileInsert(Builder $query, array $values)
292
    {
293
        // Essentially we will force every insert to be treated as a batch insert which
294 6
        // simply makes creating the SQL easier for us since we can utilize the same
295
        // basic routine regardless of an amount of records given to us to insert.
296 6
        $table = $this->wrapTable($query->from);
297 6
298 4
        if (! is_array(reset($values))) {
299
            $values = [$values];
300 6
        }
301 6
302 4
        $columns = $this->columnize(array_keys(reset($values)));
303
304 6
        // We need to build a list of parameter place-holders of values that are bound
305 6
        // to the query. Each insert should have the exact same amount of parameter
306 6
        // bindings so we can just go off the first list of values in this array.
307
        $parameters = $this->parameterize(reset($values));
308 6
309 6
        $value = array_fill(0, count($values), "($parameters)");
310
311 6
        if (count($value) > 1) {
312 6
            $insertQueries = [];
313
            foreach ($value as $parameter) {
314 6
                $parameter       = (str_replace(['(', ')'], '', $parameter));
315 6
                $insertQueries[] = 'select ' . $parameter . ' from dual ';
316
            }
317 6
            $parameters = implode('union all ', $insertQueries);
318
319
            return "insert into $table ($columns) $parameters";
320
        }
321
        $parameters = implode(', ', $value);
322
323
        return "insert into $table ($columns) values $parameters";
324
    }
325
326
    /**
327
     * Compile an insert with blob field statement into SQL.
328
     *
329 6
     * @param  \Illuminate\Database\Query\Builder $query
330
     * @param  array $values
331 6
     * @param  array $binaries
332
     * @param  string $sequence
333
     * @return string
334
     */
335
    public function compileInsertLob(Builder $query, $values, $binaries, $sequence = 'id')
336 6
    {
337
        if (empty($sequence)) {
338 6
            $sequence = 'id';
339 3
        }
340 4
341
        $table = $this->wrapTable($query->from);
342 6
343
        if (! is_array(reset($values))) {
344
            $values = [$values];
345 6
        }
346 6
347 4
        if (! is_array(reset($binaries))) {
348 6
            $binaries = [$binaries];
349 6
        }
350
351
        $columns       = $this->columnize(array_keys(reset($values)));
352 6
        $binaryColumns = $this->columnize(array_keys(reset($binaries)));
353 6
        $columns .= (empty($columns) ? '' : ', ') . $binaryColumns;
354 6
355 4
        $parameters       = $this->parameterize(reset($values));
356
        $binaryParameters = $this->parameterize(reset($binaries));
357
358 6
        $value       = array_fill(0, count($values), "$parameters");
359 6
        $binaryValue = array_fill(0, count($binaries), str_replace('?', 'EMPTY_BLOB()', $binaryParameters));
360 4
361
        $value      = array_merge($value, $binaryValue);
362
        $parameters = implode(', ', array_filter($value));
363
364
        return "insert into $table ($columns) values ($parameters) returning " . $binaryColumns . ', ' . $this->wrap($sequence) . ' into ' . $binaryParameters . ', ?';
365 6
    }
366
367
    /**
368 6
     * Compile an update statement into SQL.
369
     *
370
     * @param  \Illuminate\Database\Query\Builder $query
371
     * @param  array $values
372
     * @param  array $binaries
373
     * @param  string $sequence
374 6
     * @return string
375
     */
376 6
    public function compileUpdateLob(Builder $query, array $values, array $binaries, $sequence = 'id')
377
    {
378
        $table = $this->wrapTable($query->from);
379
380
        // Each one of the columns in the update statements needs to be wrapped in the
381
        // keyword identifiers, also a place-holder needs to be created for each of
382
        // the values in the list of bindings so we can make the sets statements.
383
        $columns = [];
384
385
        foreach ($values as $key => $value) {
386 3
            $columns[] = $this->wrap($key) . ' = ' . $this->parameter($value);
387
        }
388 3
389
        $columns = implode(', ', $columns);
390
391
        // set blob variables
392 3
        if (! is_array(reset($binaries))) {
393 3
            $binaries = [$binaries];
394
        }
395
        $binaryColumns    = $this->columnize(array_keys(reset($binaries)));
396
        $binaryParameters = $this->parameterize(reset($binaries));
397
398
        // create EMPTY_BLOB sql for each binary
399
        $binarySql = [];
400
        foreach ((array) $binaryColumns as $binary) {
401
            $binarySql[] = "$binary = EMPTY_BLOB()";
402
        }
403
404
        // prepare binary SQLs
405
        if (count($binarySql)) {
406 12
            $binarySql = (empty($columns) ? '' : ', ') . implode(',', $binarySql);
407
        }
408 12
409
        // If the query has any "join" clauses, we will setup the joins on the builder
410
        // and compile them so we can attach them to this update, as update queries
411
        // can get join statements to attach to other tables when they're needed.
412
        $joins = '';
413
        if (isset($query->joins)) {
414
            $joins = ' ' . $this->compileJoins($query, $query->joins);
415
        }
416
417
        // Of course, update queries may also be constrained by where clauses so we'll
418 3
        // need to compile the where clauses and attach it to the query so only the
419
        // intended records are updated by the SQL statements we generate to run.
420 3
        $where = $this->compileWheres($query);
421
422
        return "update {$table}{$joins} set $columns$binarySql $where returning " . $binaryColumns . ', ' . $this->wrap($sequence) . ' into ' . $binaryParameters . ', ?';
423
    }
424
425
    /**
426
     * Compile the lock into SQL.
427
     *
428
     * @param  \Illuminate\Database\Query\Builder $query
429
     * @param  bool|string $value
430 3
     * @return string
431
     */
432 3
    protected function compileLock(Builder $query, $value)
433
    {
434 3
        if (is_string($value)) {
435
            return $value;
436
        }
437
438
        if ($value) {
439
            return 'for update';
440
        }
441
442
        return '';
443
    }
444
445 9
    /**
446
     * Compile the "limit" portions of the query.
447 9
     *
448
     * @param  \Illuminate\Database\Query\Builder $query
449 9
     * @param  int $limit
450
     * @return string
451
     */
452
    protected function compileLimit(Builder $query, $limit)
453
    {
454
        return '';
455
    }
456
457
    /**
458
     * Compile the "offset" portions of the query.
459
     *
460
     * @param  \Illuminate\Database\Query\Builder $query
461
     * @param  int $offset
462
     * @return string
463
     */
464
    protected function compileOffset(Builder $query, $offset)
465
    {
466
        return '';
467
    }
468
469
    /**
470
     * Compile a "where date" clause.
471
     *
472
     * @param  \Illuminate\Database\Query\Builder $query
473
     * @param  array $where
474
     * @return string
475
     */
476
    protected function whereDate(Builder $query, $where)
477
    {
478
        $value = $this->parameter($where['value']);
479
480
        return "trunc({$this->wrap($where['column'])}) {$where['operator']} $value";
481
    }
482
483
    /**
484
     * Compile a date based where clause.
485
     *
486
     * @param  string $type
487
     * @param  \Illuminate\Database\Query\Builder $query
488
     * @param  array $where
489
     * @return string
490
     */
491
    protected function dateBasedWhere($type, Builder $query, $where)
492
    {
493
        $value = $this->parameter($where['value']);
494
495
        return "extract ($type from {$this->wrap($where['column'])}) {$where['operator']} $value";
496
    }
497
498
    /**
499
     * Compile a "where not in raw" clause.
500
     *
501
     * For safety, whereIntegerInRaw ensures this method is only used with integer values.
502
     *
503
     * @param  \Illuminate\Database\Query\Builder  $query
504
     * @param  array  $where
505
     * @return string
506
     */
507 View Code Duplication
    protected function whereNotInRaw(Builder $query, $where)
508
    {
509
        if (! empty($where['values'])) {
510
            if (is_array($where['values']) && count($where['values']) > 1000) {
511
                return $this->resolveClause($where['column'], $where['values'], 'not in');
512
            } else {
513
                return $this->wrap($where['column']).' not in ('.implode(', ', $where['values']).')';
514
            }
515
        }
516
517
        return '1 = 1';
518
    }
519
520
    /**
521
     * Compile a "where in raw" clause.
522
     *
523
     * For safety, whereIntegerInRaw ensures this method is only used with integer values.
524
     *
525
     * @param  \Illuminate\Database\Query\Builder  $query
526
     * @param  array  $where
527
     * @return string
528
     */
529 View Code Duplication
    protected function whereInRaw(Builder $query, $where)
530
    {
531
        if (! empty($where['values'])) {
532
            if (is_array($where['values']) && count($where['values']) > 1000) {
533
                return $this->resolveClause($where['column'], $where['values'], 'in');
534
            } else {
535
                return $this->wrap($where['column']).' in ('.implode(', ', $where['values']).')';
536
            }
537
        }
538
539
        return '0 = 1';
540
    }
541
542
    private function resolveClause($column, $values, $type)
543
    {
544
        $chunks = array_chunk($values, 1000);
545
        $whereClause = '';
546
        $i=0;
547
        $type = $this->wrap($column) . ' '.$type.' ';
548
        foreach ($chunks as $ch) {
549
            if ($i > 0) {
550
                $type = ' or '. $this->wrap($column) . ' ' . $type . ' ';
551
            }
552
            $whereClause  .= $type . '('.implode(', ', $ch).')';
553
            $i++;
554
        }
555
556
        return '(' . $whereClause . ')';
557
    }
558
}
559