OracleGrammar   B
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 441
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 84.56%

Importance

Changes 0
Metric Value
dl 0
loc 441
ccs 126
cts 149
cp 0.8456
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 2
cbo 4

20 Methods

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