Completed
Push — devops/catch-breaking-changes-... ( 88c9a6 )
by Bas
28s queued 18s
created

Builder::aggregate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 13
ccs 5
cts 6
cp 0.8333
rs 10
cc 3
nc 2
nop 2
crap 3.0416
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Closure;
6
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
7
use Illuminate\Database\Query\Expression;
8
use InvalidArgumentException;
9
use LaravelFreelancerNL\Aranguent\Connection;
10
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsJoinClauses;
11
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSubqueries;
12
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsWhereClauses;
13
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
14
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface as ExpressionInterface;
15
use LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression;
16
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
17
18
class Builder extends IlluminateQueryBuilder
19
{
20
    use BuildsJoinClauses;
21
    use BuildsSubqueries;
22
    use BuildsWhereClauses;
23
24
    /**
25
     * @var QueryBuilder|FunctionExpression
26
     */
27
    public QueryBuilder|FunctionExpression $aqb;
28
29
    /**
30
     * @var Connection
31
     */
32
    public $connection;
33
34
    /**
35
     * @var Grammar
36
     */
37
    public $grammar;
38
39
    /**
40
     * The current query value bindings.
41
     *
42
     * @var null|array{predicates: array<mixed>, options: array<string, string>}
43
     */
44
    public ?array $search = null;
45
46
    /**
47
     * The query variables that should be set.
48
     *
49
     * @var array<mixed>
50
     */
51
    public $variables = [];
52
53
    /**
54
     * @override
55
     * Create a new query builder instance.
56
     */
57 158
    public function __construct(
58
        Connection $connection,
59
        Grammar $grammar = null,
60
        Processor $processor = null,
61
        QueryBuilder $aqb = null
62
    ) {
63 158
        $this->connection = $connection;
64 158
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
0 ignored issues
show
Documentation Bug introduced by
It seems like $grammar ?: $connection->getQueryGrammar() can also be of type Illuminate\Database\Query\Grammars\Grammar. However, the property $grammar is declared as type LaravelFreelancerNL\Aranguent\Query\Grammar. 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...
65 158
        $this->processor = $processor ?: $connection->getPostProcessor();
66 158
        if (!$aqb instanceof QueryBuilder) {
67 158
            $aqb = new QueryBuilder();
68
        }
69 158
        $this->aqb = $aqb;
70
    }
71
72
    /**
73
     * Delete a record from the database.
74
     *
75
     * @param mixed $id
76
     *
77
     * @return int
78
     */
79 10
    public function delete($id = null)
80
    {
81 10
        $this->aqb = new QueryBuilder();
82 10
        $this->grammar->compileDelete($this, $id)->setAql();
83 10
        return $this->connection->delete($this->aqb);
84
    }
85
86
    /**
87
     * Set the table which the query is targeting.
88
     *
89
     * @param  \Closure|IlluminateQueryBuilder|string  $table
90
     * @param  string|null  $as
91
     * @return IlluminateQueryBuilder
92
     */
93 158
    public function from($table, $as = null)
94
    {
95 158
        if ($this->isQueryable($table)) {
96
            return $this->fromSub($table, $as);
97
        }
98 158
        $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

98
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
99
100 158
        $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...
101
102 158
        return $this;
103
    }
104
105
    /**
106
     * Run the query as a "select" statement against the connection.
107
     *
108
     * @return array
109
     */
110 117
    protected function runSelect()
111
    {
112 117
        $this->aqb = new QueryBuilder();
113 117
        $this->grammar->compileSelect($this)->setAql();
114 117
        $results = $this->connection->select($this->aqb);
115 116
        $this->aqb = new QueryBuilder();
116 116
        return $results;
117
    }
118
119
    /**
120
     * Run a pagination count query.
121
     *
122
     * @param array $columns
123
     *
124
     * @return array
125
     */
126 5
    protected function runPaginationCountQuery($columns = ['*'])
127
    {
128 5
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
129
130 5
        $closeResults = $this->cloneWithout($without)
131 5
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
132 5
            ->setAggregate('count', $this->withoutSelectAliases($columns))
133
            ->get()->all();
134
135 5
        return $closeResults;
136
    }
137
138
    /**
139
     * Set the columns to be selected.
140
     *
141
     * @param  array|mixed  $columns
142
     * @return IlluminateQueryBuilder
143
     */
144 53
    public function select($columns = ['*'])
145
    {
146 53
        $this->columns = [];
147 53
        $this->bindings['select'] = [];
148 53
        $columns = is_array($columns) ? $columns : func_get_args();
149
150 53
        $this->addColumns($columns);
151
152 53
        return $this;
153
    }
154
155
    /**
156
     * Add a new select column to the query.
157
     *
158
     * @param  array|mixed  $column
159
     * @return $this
160
     */
161 13
    public function addSelect($column)
162
    {
163 13
        $columns = is_array($column) ? $column : func_get_args();
164
165 13
        $this->addColumns($columns);
166
167 13
        return $this;
168
    }
169
170
    /**
171
     * @param array<mixed> $columns
172
     */
173 66
    protected function addColumns(array $columns): void
174
    {
175 66
        foreach ($columns as $as => $column) {
176 66
            if (is_string($as) && $this->isQueryable($column)) {
177
                if (is_null($this->columns)) {
178
                    $this->select($this->from . '.*');
179
                }
180
181
                $this->selectSub($column, $as);
182
183
                continue;
184
            }
185
186 66
            if (! is_string($as) || ! $this->isQueryable($column)) {
187 66
                $this->columns[$as] = $column;
188
189 66
                continue;
190
            }
191
192
            $this->columns[] = $column;
193
        }
194
    }
195
196
    /**
197
     * Get the SQL representation of the query.
198
     *
199
     * @return string
200
     */
201 40
    public function toSql()
202
    {
203 40
        $this->grammar->compileSelect($this)->setAql();
204 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...
205
    }
206
207
    /**
208
     * Insert a new record into the database.
209
     *
210
     * @param array $values
211
     *
212
     * @throws BindException
213
     *
214
     * @return bool
215
     */
216 9
    public function insert(array $values): bool
217
    {
218 9
        $this->grammar->compileInsert($this, $values)->setAql();
219 9
        $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

219
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
220 9
        $this->aqb = new QueryBuilder();
221 9
        return $results;
222
    }
223
224
    /**
225
     * Insert a new record and get the value of the primary key.
226
     *
227
     * @param array<mixed> $values
228
     */
229 13
    public function insertGetId(array $values, $sequence = null)
230
    {
231 13
        $this->grammar->compileInsertGetId($this, $values, $sequence)->setAql();
232 13
        $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

232
        $response = $this->getConnection()->execute(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
233 13
        $this->aqb = new QueryBuilder();
234
235 13
        return (is_array($response)) ? end($response) : $response;
236
    }
237
238
    /**
239
     * Insert a new record into the database.
240
     *
241
     * @param array $values
242
     *
243
     * @throws BindException
244
     *
245
     * @return bool
246
     */
247 4
    public function insertOrIgnore(array $values): bool
248
    {
249 4
        $this->grammar->compileInsertOrIgnore($this, $values)->setAql();
250 4
        $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

250
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
251 4
        $this->aqb = new QueryBuilder();
252
253 4
        return $results;
254
    }
255
256
    /**
257
    /**
258
     * Get the current query value bindings in a flattened array.
259
     *
260
     * @return array
261
     */
262 10
    public function getBindings()
263
    {
264 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...
265
    }
266
267 156
    protected function setAql()
268
    {
269 156
        $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

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