Completed
Push — master ( 6df8f5...4829ab )
by Arjay
12:03
created

OracleGrammar   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 467
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 84.56%

Importance

Changes 0
Metric Value
dl 0
loc 467
ccs 126
cts 149
cp 0.8456
rs 3.6
c 0
b 0
f 0
wmc 60
lcom 2
cbo 5

21 Methods

Rating   Name   Duplication   Size   Complexity  
A compileExists() 0 9 1
A compileSelect() 0 17 3
A isPaginationable() 0 4 3
A compileAnsiOffset() 0 23 3
A compileRowConstraint() 0 15 5
A compileTableExpression() 0 8 3
A compileTruncate() 0 4 1
A wrap() 0 8 3
A wrapTable() 0 18 4
A getSchemaPrefix() 0 4 2
A setSchemaPrefix() 0 4 1
A wrapValue() 0 10 2
A compileInsertGetId() 0 17 6
A compileInsert() 0 34 4
A compileInsertLob() 0 31 5
B compileUpdateLob() 0 48 7
A compileLock() 0 12 3
A compileLimit() 0 4 1
A compileOffset() 0 4 1
A whereDate() 0 6 1
A dateBasedWhere() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like OracleGrammar often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OracleGrammar, and based on these observations, apply Extract Interface, too.

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