Builder   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 336
Duplicated Lines 0 %

Importance

Changes 7
Bugs 3 Features 0
Metric Value
eloc 115
dl 0
loc 336
rs 9.76
c 7
b 3
f 0
wmc 33

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getConnection() 0 3 1
A isReference() 0 21 4
A set() 0 25 4
A getBindings() 0 10 3
A prependDatabaseNameIfCrossDatabaseQuery() 0 12 4
A exists() 0 20 2
A __construct() 0 16 2
A runPaginationCountQuery() 0 10 3
A toAql() 0 3 1
A isVariable() 0 10 3
A delete() 0 17 2
A aggregate() 0 11 3
A getQueryId() 0 3 1
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Exception;
6
use Illuminate\Database\ConnectionInterface as IlluminateConnectionInterface;
7
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
8
use Illuminate\Database\Query\Expression;
9
use Illuminate\Database\Query\Grammars\Grammar as IlluminateQueryGrammar;
10
use Illuminate\Database\Query\Processors\Processor as IlluminateProcessor;
11
use LaravelFreelancerNL\Aranguent\Connection;
12
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsGroups;
13
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSearches;
14
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsInserts;
15
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsJoins;
16
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSelects;
17
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSubqueries;
18
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsUpdates;
19
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsWheres;
20
use LaravelFreelancerNL\Aranguent\Query\Concerns\ConvertsIdToKey;
21
use LaravelFreelancerNL\Aranguent\Query\Concerns\HandlesAliases;
22
use LaravelFreelancerNL\Aranguent\Query\Concerns\HandlesBindings;
23
use LaravelFreelancerNL\Aranguent\Query\Enums\VariablePosition;
24
use LaravelFreelancerNL\FluentAQL\QueryBuilder as AQB;
25
use phpDocumentor\Reflection\Types\Boolean;
26
27
class Builder extends IlluminateQueryBuilder
28
{
29
    use BuildsGroups;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...y\Concerns\BuildsGroups requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Builder: $end, $start
Loading history...
30
    use BuildsInserts;
31
    use BuildsJoins;
32
    use BuildsSearches;
33
    use BuildsSelects;
34
    use BuildsSubqueries;
35
    use BuildsUpdates;
36
    use BuildsWheres;
37
    use ConvertsIdToKey;
38
    use HandlesAliases;
39
    use HandlesBindings;
40
    public AQB $aqb;
41
42
    /**
43
     * The current query value bindings.
44
     *
45
     * @var array<mixed>
46
     */
47
    public $bindings = [
48
        'preIterationVariables' => [],
49
        'from' => [],
50
        'fromOptions' => [],
51
        'search' => [],
52
        'join' => [],
53
        'postIterationVariables' => [],
54
        'where' => [],
55
        'groupBy' => [],
56
        'having' => [],
57
        'order' => [],
58
        'union' => [],
59
        'unionOrder' => [],
60
        'select' => [],
61
        'insert' => [],
62
        'update' => [],
63
        'upsert' => [],
64
    ];
65
66
67
    /**
68
     * @var Connection
69
     */
70
    public $connection;
71
72
    /**
73
     * @var IlluminateQueryGrammar
74
     */
75
    public $grammar;
76
77
    /**
78
     * 'from' options.
79
     *
80
     * @var array<mixed>
81
     */
82
    public $fromOptions = [];
83
84
    /**
85
     * The current query value bindings.
86
     *
87
     * @var null|array<mixed>
88
     */
89
    public ?array $search = null;
90
91
    /**
92
     * The query variables that should be set before traversals (for/joins).
93
     *
94
     * @var array<mixed>
95
     */
96
    public $preIterationVariables = [];
97
98
    /**
99
     * The query variables that should be set after traversals (for/joins).
100
     *
101
     * @var array<mixed>
102
     */
103
    public $postIterationVariables = [];
104
105
    /**
106
     * ID of the query
107
     * Used as prefix for automatically generated bindings.
108
     *
109
     * @var int
110
     */
111
    protected $queryId = 1;
112
113
    /**
114
     * @override
115
     * Create a new query builder instance.
116
     */
117
    public function __construct(
118
        IlluminateConnectionInterface  $connection,
119
        ?IlluminateQueryGrammar        $grammar = null,
120
        ?IlluminateProcessor           $processor = null,
121
        ?AQB                           $aqb = null,
122
    ) {
123
        assert($processor instanceof IlluminateProcessor);
124
125
        parent::__construct($connection, $grammar, $processor);
126
127
        if (!$aqb instanceof AQB) {
128
            $aqb = new AQB();
129
        }
130
        $this->aqb = $aqb;
131
132
        $this->queryId = spl_object_id($this);
133
    }
134
135
    public function getQueryId(): int
136
    {
137
        return $this->queryId;
138
    }
139
140
    /**
141
     * Get the current query value bindings in a flattened array.
142
     *
143
     * @return array<mixed>
144
     */
145
    public function getBindings()
146
    {
147
        $extractedBindings = [];
148
        foreach ($this->bindings as $typeBinds) {
149
            foreach ($typeBinds as $key => $value) {
150
                $extractedBindings[$key] = $value;
151
            }
152
        }
153
154
        return $extractedBindings;
155
    }
156
157
    /**
158
     * Run a pagination count query.
159
     *
160
     * @param array<mixed> $columns
161
     * @return array<mixed>
162
     */
163
    protected function runPaginationCountQuery($columns = ['*'])
164
    {
165
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
166
167
        $closeResults = $this->cloneWithout($without)
168
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
169
            ->setAggregate('count', $this->withoutSelectAliases($columns))
0 ignored issues
show
Bug introduced by
$this->withoutSelectAliases($columns) of type array<mixed,Illuminate\C...uery\Expression|string> is incompatible with the type array<mixed,Illuminate\C...uery\Expression|string> expected by parameter $columns of Illuminate\Database\Query\Builder::setAggregate(). ( Ignorable by Annotation )

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

169
            ->setAggregate('count', /** @scrutinizer ignore-type */ $this->withoutSelectAliases($columns))
Loading history...
170
            ->get()->all();
171
172
        return $closeResults;
173
    }
174
175
    /**
176
     * Delete records from the database.
177
     *
178
     * @param mixed $id
179
     * @return int
180
     */
181
    public function delete($id = null)
182
    {
183
        $table = (string) $this->grammar->getValue($this->from);
184
185
        // If an ID is passed to the method, we will set the where clause to check the
186
        // ID to let developers to simply and quickly remove a single row from this
187
        // database without manually specifying the "where" clauses on the query.
188
        if (!is_null($id)) {
189
            $this->where($table . '._key', '=', $id);
190
        }
191
192
        $this->applyBeforeQueryCallbacks();
193
194
        return $this->connection->delete(
195
            $this->grammar->compileDelete($this),
196
            $this->cleanBindings(
197
                $this->grammar->prepareBindingsForDelete($this->bindings),
198
            ),
199
        );
200
    }
201
202
    /**
203
     * Determine if any rows exist for the current query.
204
     *
205
     * @return bool
206
     */
207
    public function exists()
208
    {
209
        $this->applyBeforeQueryCallbacks();
210
211
        $results = $this->connection->select(
212
            $this->grammar->compileExists($this),
213
            $this->getBindings(),
214
            !$this->useWritePdo,
215
        );
216
217
        // If the results have rows, we will get the row and see if the exists column is a
218
        // boolean true. If there are no results for this query we will return false as
219
        // there are no rows for this query at all, and we can return that info here.
220
        if (isset($results[0])) {
221
            $results = (array) $results[0];
222
223
            return (bool) $results['exists'];
224
        }
225
226
        return false;
227
    }
228
229
230
    /**
231
     * Execute an aggregate function on the database.
232
     *
233
     * @param string $function
234
     * @param array<mixed> $columns
235
     * @return mixed
236
     */
237
    public function aggregate($function, $columns = ['*'])
238
    {
239
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
240
            ->setAggregate($function, $columns)
241
            ->get($columns);
242
243
        if (!$results->isEmpty()) {
244
            return ($results->first())->aggregate;
245
        }
246
247
        return false;
248
    }
249
250
    /**
251
     * Set a variable
252
     * @param string $variable
253
     * @param IlluminateQueryBuilder|Expression|array<mixed>|Int|Float|String|Boolean $value
254
     * @param string|VariablePosition $variablePosition
255
     * @return Builder
256
     */
257
    public function set(
258
        string $variable,
259
        IlluminateQueryBuilder|Expression|array|Boolean|Int|Float|String $value,
260
        VariablePosition|string $variablePosition = VariablePosition::preIterations,
261
    ): Builder {
262
        if (is_string($variablePosition)) {
263
            $variablePosition = VariablePosition::tryFrom($variablePosition) ?? VariablePosition::preIterations;
264
        }
265
        if ($value instanceof Expression) {
0 ignored issues
show
introduced by
$value is never a sub-type of Illuminate\Database\Query\Expression.
Loading history...
266
            $this->{$variablePosition->value}[$variable] = $value->getValue($this->grammar);
267
268
            return $this;
269
        }
270
271
        if ($value instanceof Builder) {
0 ignored issues
show
introduced by
$value is never a sub-type of LaravelFreelancerNL\Aranguent\Query\Builder.
Loading history...
272
273
            [$subquery] = $this->createSub($value);
274
275
            $this->{$variablePosition->value}[$variable] = $subquery;
276
277
            return $this;
278
        }
279
        $this->{$variablePosition->value}[$variable] = $this->bindValue($value, $variablePosition->value);
280
281
        return $this;
282
    }
283
284
    public function isVariable(string $value): bool
285
    {
286
        if (
287
            key_exists($value, $this->preIterationVariables)
288
            ||  key_exists($value, $this->postIterationVariables)
289
        ) {
290
            return true;
291
        }
292
293
        return false;
294
    }
295
296
    /**
297
     * @param mixed $value
298
     * @param array<mixed> $variables
299
     * @return bool
300
     */
301
    public function isReference(mixed $value, array $variables = []): bool
302
    {
303
        if (!is_string($value) || empty($value)) {
304
            return false;
305
        }
306
307
        if (empty($variables)) {
308
            $variables = array_merge(
309
                array_keys($this->preIterationVariables),
310
                array_keys($this->postIterationVariables),
311
                $this->tableAliases,
312
            );
313
        }
314
315
        $variablesRegex = implode('|', $variables);
316
317
        return (bool) preg_match(
318
            '/^\`?('
319
            . $variablesRegex
320
            . '|CURRENT|NEW|OLD)\`?(\[\`.+\`\]|\[[\d\w\*]*\])*(\.(\`.+\`|@?[\d\w]*)(\[\`.+\`\]|\[[\d\w\*]*\])*)*$/',
321
            $value,
322
        );
323
    }
324
325
326
    /**
327
     * Get the database connection instance.
328
     *
329
     * @return Connection
330
     */
331
    public function getConnection()
332
    {
333
        return $this->connection;
334
    }
335
336
    /**
337
     * Prepend the database name if the given query is on another database.
338
     *
339
     * @param mixed $query
340
     * @return mixed
341
     * @throws Exception
342
     */
343
    protected function prependDatabaseNameIfCrossDatabaseQuery($query)
344
    {
345
        if ($query->getConnection()->getDatabaseName() !==
346
            $this->getConnection()->getDatabaseName()) {
347
            $databaseName = $query->getConnection()->getDatabaseName();
348
349
            if (!str_starts_with($query->from, $databaseName) && !str_contains($query->from, '.')) {
350
                throw new Exception(message: 'ArangoDB does not support cross database queries.');
351
            }
352
        }
353
354
        return $query;
355
    }
356
357
    /**
358
     * Get the AQL representation of the query.
359
     */
360
    public function toAql(): string
361
    {
362
        return $this->toSql();
363
    }
364
}
365