Passed
Push — experiment/id-key-conversion ( cbc737...bb5ee1 )
by Bas
03:11
created

Builder::insertOrIgnore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Closure;
6
use Illuminate\Database\ConnectionInterface;
7
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
8
use Illuminate\Database\Query\Expression;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Collection;
11
use Illuminate\Support\Facades\DB;
12
use InvalidArgumentException;
13
use LaravelFreelancerNL\Aranguent\Connection;
14
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsJoinClauses;
15
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSubqueries;
16
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsWhereClauses;
17
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
18
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface as ExpressionInterface;
19
use LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression;
20
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
21
22
class Builder extends IlluminateQueryBuilder
23
{
24
    use BuildsJoinClauses;
25
    use BuildsSubqueries;
26
    use BuildsWhereClauses;
27
28
    /**
29
     * @var QueryBuilder|FunctionExpression
30
     */
31
    public QueryBuilder|FunctionExpression $aqb;
32
33
    /**
34
     * @var Connection
35
     */
36
    public $connection;
37
38
    /**
39
     * @var Grammar
40
     */
41
    public $grammar;
42
43
    /**
44
     * The current query value bindings.
45
     *
46
     * @var null|array{predicates: array<mixed>, options: array<string, string>}
47
     */
48
    public ?array $search = null;
49
50
    /**
51
     * The query variables that should be set.
52
     *
53
     * @var array<mixed>
54
     */
55
    public $variables = [];
56
57
    /**
58
     * @override
59
     * Create a new query builder instance.
60
     *
61
     * @param  ConnectionInterface  $connection
62
     * @param  Grammar|null  $grammar
63
     * @param  Processor|null  $processor
64
     * @param  QueryBuilder|null  $aqb
65
     */
66 162
    public function __construct(
67
        ConnectionInterface $connection,
68
        Grammar $grammar = null,
69
        Processor $processor = null,
70
        QueryBuilder $aqb = null
71
    ) {
72 162
        $this->connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
$connection is of type Illuminate\Database\ConnectionInterface, but the property $connection was declared to be of type LaravelFreelancerNL\Aranguent\Connection. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
73 162
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
74 162
        $this->processor = $processor ?: $connection->getPostProcessor();
75 162
        if (!$aqb instanceof QueryBuilder) {
76 162
            $aqb = new QueryBuilder();
77
        }
78 162
        $this->aqb = $aqb;
79 162
    }
80
81
    /**
82
     * Delete a record from the database.
83
     *
84
     * @param mixed $id
85
     *
86
     * @return int
87
     */
88 12
    public function delete($id = null)
89
    {
90 12
        $this->aqb = new QueryBuilder();
91 12
        $this->grammar->compileDelete($this, $id)->setAql();
92 12
        return $this->connection->delete($this->aqb);
93
    }
94
95
    /**
96
     * Set the table which the query is targeting.
97
     *
98
     * @param  \Closure|IlluminateQueryBuilder|string  $table
99
     * @param  string|null  $as
100
     * @return IlluminateQueryBuilder
101
     */
102 162
    public function from($table, $as = null)
103
    {
104 162
        if ($this->isQueryable($table)) {
105
            return $this->fromSub($table, $as);
106
        }
107 162
        $this->grammar->registerTableAlias($table, $as);
0 ignored issues
show
Bug introduced by
It seems like $table can also be of type Closure and Illuminate\Database\Query\Builder; however, parameter $table of LaravelFreelancerNL\Aran...r::registerTableAlias() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

107
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
108
109 162
        $this->from = $table;
0 ignored issues
show
Documentation Bug introduced by
It seems like $table can also be of type Closure or Illuminate\Database\Query\Builder. However, the property $from is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
110
111 162
        return $this;
112
    }
113
114
    /**
115
     * Run the query as a "select" statement against the connection.
116
     *
117
     * @return array
118
     */
119 112
    protected function runSelect()
120
    {
121 112
        $this->aqb = new QueryBuilder();
122 112
        $this->grammar->compileSelect($this)->setAql();
123 112
        $results = $this->connection->select($this->aqb);
124 111
        $this->aqb = new QueryBuilder();
125 111
        return $results;
126
    }
127
128
    /**
129
     * Run a pagination count query.
130
     *
131
     * @param array $columns
132
     *
133
     * @return array
134
     */
135 3
    protected function runPaginationCountQuery($columns = ['*'])
136
    {
137 3
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
138
139 3
        $closeResults = $this->cloneWithout($without)
140 3
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
141 3
            ->setAggregate('count', $this->withoutSelectAliases($columns))
142 3
            ->get()->all();
143
144 3
        return $closeResults;
145
    }
146
147
    /**
148
     * Set the columns to be selected.
149
     *
150
     * @param  array|mixed  $columns
151
     * @return IlluminateQueryBuilder
152
     */
153 53
    public function select($columns = ['*'])
154
    {
155 53
        $this->columns = [];
156 53
        $this->bindings['select'] = [];
157 53
        $columns = is_array($columns) ? $columns : func_get_args();
158
159 53
        $this->addColumns($columns);
160
161 53
        return $this;
162
    }
163
164
    /**
165
     * Add a new select column to the query.
166
     *
167
     * @param  array|mixed  $column
168
     * @return $this
169
     */
170 11
    public function addSelect($column)
171
    {
172 11
        $columns = is_array($column) ? $column : func_get_args();
173
174 11
        $this->addColumns($columns);
175
176 11
        return $this;
177
    }
178
179
    /**
180
     * @param array<mixed> $columns
181
     */
182 64
    protected function addColumns(array $columns): void
183
    {
184 64
        foreach ($columns as $as => $column) {
185 64
            if (is_string($as) && $this->isQueryable($column)) {
186
                if (is_null($this->columns)) {
187
                    $this->select($this->from . '.*');
188
                }
189
190
                $this->selectSub($column, $as);
191
192
                continue;
193
            }
194
195 64
            if (! is_string($as) || ! $this->isQueryable($column)) {
196 64
                $this->columns[$as] = $column;
197
198 64
                continue;
199
            }
200
201
            $this->columns[] = $column;
202
        }
203 64
    }
204
205
    /**
206
     * Get the SQL representation of the query.
207
     *
208
     * @return string
209
     */
210 40
    public function toSql()
211
    {
212 40
        $this->grammar->compileSelect($this)->setAql();
213 40
        return $this->aqb->query;
0 ignored issues
show
Bug introduced by
The property query does not seem to exist on LaravelFreelancerNL\Flue...ions\FunctionExpression.
Loading history...
214
    }
215
216
    /**
217
     * Insert a new record into the database.
218
     *
219
     * @param array $values
220
     *
221
     * @throws BindException
222
     *
223
     * @return bool
224
     */
225 44
    public function insert(array $values): bool
226
    {
227 44
        $this->grammar->compileInsert($this, $values)->setAql();
228 44
        $results = $this->getConnection()->insert($this->aqb);
0 ignored issues
show
Bug introduced by
It seems like $this->aqb can also be of type LaravelFreelancerNL\Flue...ions\FunctionExpression; however, parameter $query of Illuminate\Database\Connection::insert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

228
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
229 44
        $this->aqb = new QueryBuilder();
230 44
        return $results;
231
    }
232
233
    /**
234
     * Insert a new record and get the value of the primary key.
235
     *
236
     * @param array<mixed> $values
237
     */
238 10
    public function insertGetId(array $values, $sequence = null)
239
    {
240 10
        $this->grammar->compileInsertGetId($this, $values, $sequence)->setAql();
241 10
        $response = $this->getConnection()->execute($this->aqb);
0 ignored issues
show
Bug introduced by
It seems like $this->aqb can also be of type LaravelFreelancerNL\Flue...ions\FunctionExpression; however, parameter $query of LaravelFreelancerNL\Aran...t\Connection::execute() does only seem to accept LaravelFreelancerNL\FluentAQL\QueryBuilder|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

241
        $response = $this->getConnection()->execute(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
242 10
        $this->aqb = new QueryBuilder();
243
244 10
        return (is_array($response)) ? end($response) : $response;
245
    }
246
247
    /**
248
     * Insert a new record into the database.
249
     *
250
     * @param array $values
251
     *
252
     * @throws BindException
253
     *
254
     * @return bool
255
     */
256 98
    public function insertOrIgnore(array $values): bool
257
    {
258 98
        $this->grammar->compileInsertOrIgnore($this, $values)->setAql();
259 98
        $results = $this->getConnection()->insert($this->aqb);
0 ignored issues
show
Bug introduced by
It seems like $this->aqb can also be of type LaravelFreelancerNL\Flue...ions\FunctionExpression; however, parameter $query of Illuminate\Database\Connection::insert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

259
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
260 98
        $this->aqb = new QueryBuilder();
261
262 98
        return $results;
263
    }
264
265
    /**
266
    /**
267
     * Get the current query value bindings in a flattened array.
268
     *
269
     * @return array
270
     */
271 10
    public function getBindings()
272
    {
273 10
        return $this->aqb->binds;
0 ignored issues
show
Bug introduced by
The property binds does not seem to exist on LaravelFreelancerNL\Flue...ions\FunctionExpression.
Loading history...
274
    }
275
276 162
    protected function setAql()
277
    {
278 162
        $this->aqb = $this->aqb->get();
0 ignored issues
show
Bug introduced by
The method get() does not exist on LaravelFreelancerNL\Flue...ions\FunctionExpression. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

278
        /** @scrutinizer ignore-call */ 
279
        $this->aqb = $this->aqb->get();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
279
280 162
        return $this;
281
    }
282
283
    /**
284
     * Update a record in the database.
285
     *
286
     * @param array $values
287
     *
288
     * @return int
289
     */
290 23
    public function update(array $values)
291
    {
292 23
        $this->aqb = new QueryBuilder();
293
294 23
        $this->grammar->compileUpdate($this, $values)->setAql();
295 23
        $results = $this->connection->update($this->aqb);
296 23
        $this->aqb = new QueryBuilder();
297 23
        return $results;
298
    }
299
300
301
302
    /**
303
     * Execute an aggregate function on the database.
304
     *
305
     * @param string $function
306
     * @param array  $columns
307
     *
308
     * @return mixed
309
     */
310 21
    public function aggregate($function, $columns = ['*'])
311
    {
312 21
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
313 21
            ->setAggregate($function, $columns)
314 21
            ->get($columns);
315
316 21
        $this->aqb = new QueryBuilder();
317
318 21
        if (!$results->isEmpty()) {
319 21
            return array_change_key_case((array) $results[0])['aggregate'];
320
        }
321
322
        return false;
323
    }
324
325
326
    /**
327
     * Determine if the given operator is supported.
328
     *
329
     * @param string $operator
330
     *
331
     * @return bool
332
     */
333 88
    protected function invalidOperator($operator)
334
    {
335 88
        return !in_array(strtolower($operator), $this->operators, true) &&
336 88
            !isset($this->grammar->getOperators()[strtoupper($operator)]);
337
    }
338
339
340
    /**
341
     * Add an "order by" clause to the query.
342
     *
343
     * @param Closure|IlluminateQueryBuilder|string $column
344
     * @param string                                $direction
345
     *
346
     * @throws InvalidArgumentException
347
     *
348
     * @return $this
349
     */
350 2
    public function orderBy($column, $direction = 'asc')
351
    {
352 2
        if ($this->isQueryable($column)) {
353
            [$query, $bindings] = $this->createSub($column);
354
355
            //fixme: Remove binding when implementing subqueries
356
            $bindings = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
357
358
            $column = new Expression('(' . $query . ')');
359
        }
360
361 2
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
362 2
            'column'    => $column,
363 2
            'direction' => $direction,
364
        ];
365
366 2
        return $this;
367
    }
368
369
    /**
370
     * Add a raw "order by" clause to the query.
371
     *
372
     * @param string|ExpressionInterface $aql
373
     * @param array                      $bindings
374
     *
375
     * @return $this
376
     */
377 1
    public function orderByRaw($aql, $bindings = [])
378
    {
379 1
        $bindings = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
380
381 1
        $type = 'Raw';
382 1
        $column = $aql;
383 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
384
385 1
        return $this;
386
    }
387
388
389
    /**
390
     * Put the query's results in random order.
391
     *
392
     * @param string $seed
393
     *
394
     * @return $this
395
     */
396 1
    public function inRandomOrder($seed = '')
397
    {
398
        // ArangoDB's random function doesn't accept a seed.
399 1
        $seed = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $seed is dead and can be removed.
Loading history...
400
401 1
        return $this->orderByRaw($this->grammar->compileRandom($this));
402
    }
403
404
    /**
405
     * Search an ArangoSearch view.
406
     *
407
     * @param mixed $predicates
408
     * @param array|null $options
409
     * @return Builder
410
     */
411 7
    public function search(mixed $predicates, array $options = null): Builder
412
    {
413 7
        if ($predicates instanceof Closure) {
414 2
            $predicates = $predicates($this->aqb);
415
        }
416
417 7
        if (! is_array($predicates)) {
418 4
            $predicates = [$predicates];
419
        }
420
421 7
        $this->search = [
422 7
            'predicates' => $predicates,
423 7
            'options' => $options
424
        ];
425
426 7
        return $this;
427
    }
428
}
429