Completed
Push — master ( bc5307...59c2a6 )
by Arjay
10:42
created

OracleGrammar::wrap()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 5.667

Importance

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