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
![]() |
|||||
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 | 395 | 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 | 395 | parent::__construct($connection, $grammar, $processor); |
|||
126 | |||||
127 | 395 | if (!$aqb instanceof AQB) { |
|||
128 | 395 | $aqb = new AQB(); |
|||
129 | } |
||||
130 | 395 | $this->aqb = $aqb; |
|||
131 | |||||
132 | 395 | $this->queryId = spl_object_id($this); |
|||
133 | } |
||||
134 | |||||
135 | 39 | public function getQueryId(): int |
|||
136 | { |
||||
137 | 39 | return $this->queryId; |
|||
138 | } |
||||
139 | |||||
140 | /** |
||||
141 | * Get the current query value bindings in a flattened array. |
||||
142 | * |
||||
143 | * @return array<mixed> |
||||
144 | */ |
||||
145 | 356 | public function getBindings() |
|||
146 | { |
||||
147 | 356 | $extractedBindings = []; |
|||
148 | 356 | foreach ($this->bindings as $typeBinds) { |
|||
149 | 356 | foreach ($typeBinds as $key => $value) { |
|||
150 | 270 | $extractedBindings[$key] = $value; |
|||
151 | } |
||||
152 | } |
||||
153 | |||||
154 | 356 | return $extractedBindings; |
|||
155 | } |
||||
156 | |||||
157 | /** |
||||
158 | * Run a pagination count query. |
||||
159 | * |
||||
160 | * @param array<mixed> $columns |
||||
161 | * @return array<mixed> |
||||
162 | */ |
||||
163 | 3 | protected function runPaginationCountQuery($columns = ['*']) |
|||
164 | { |
||||
165 | 3 | $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; |
|||
166 | |||||
167 | 3 | $closeResults = $this->cloneWithout($without) |
|||
168 | 3 | ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order']) |
|||
169 | 3 | ->setAggregate('count', $this->withoutSelectAliases($columns)) |
|||
0 ignored issues
–
show
$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
![]() |
|||||
170 | 3 | ->get()->all(); |
|||
171 | |||||
172 | 3 | return $closeResults; |
|||
173 | } |
||||
174 | |||||
175 | /** |
||||
176 | * Delete records from the database. |
||||
177 | * |
||||
178 | * @param mixed $id |
||||
179 | * @return int |
||||
180 | */ |
||||
181 | 35 | public function delete($id = null) |
|||
182 | { |
||||
183 | 35 | $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 | 35 | if (!is_null($id)) { |
|||
189 | 1 | $this->where($table . '._key', '=', $id); |
|||
190 | } |
||||
191 | |||||
192 | 35 | $this->applyBeforeQueryCallbacks(); |
|||
193 | |||||
194 | 35 | return $this->connection->delete( |
|||
195 | 35 | $this->grammar->compileDelete($this), |
|||
196 | 35 | $this->cleanBindings( |
|||
197 | 35 | $this->grammar->prepareBindingsForDelete($this->bindings), |
|||
198 | 35 | ), |
|||
199 | 35 | ); |
|||
200 | } |
||||
201 | |||||
202 | /** |
||||
203 | * Determine if any rows exist for the current query. |
||||
204 | * |
||||
205 | * @return bool |
||||
206 | */ |
||||
207 | 18 | public function exists() |
|||
208 | { |
||||
209 | 18 | $this->applyBeforeQueryCallbacks(); |
|||
210 | |||||
211 | 18 | $results = $this->connection->select( |
|||
212 | 18 | $this->grammar->compileExists($this), |
|||
213 | 18 | $this->getBindings(), |
|||
214 | 18 | !$this->useWritePdo, |
|||
215 | 18 | ); |
|||
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 | 18 | if (isset($results[0])) { |
|||
221 | 18 | $results = (array) $results[0]; |
|||
222 | |||||
223 | 18 | 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 | 79 | public function aggregate($function, $columns = ['*']) |
|||
238 | { |
||||
239 | 79 | $results = $this->cloneWithout($this->unions ? [] : ['columns']) |
|||
240 | 79 | ->setAggregate($function, $columns) |
|||
241 | 79 | ->get($columns); |
|||
242 | |||||
243 | 79 | if (!$results->isEmpty()) { |
|||
244 | 79 | 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 | 7 | public function set( |
|||
258 | string $variable, |
||||
259 | IlluminateQueryBuilder|Expression|array|Boolean|Int|Float|String $value, |
||||
260 | VariablePosition|string $variablePosition = VariablePosition::preIterations, |
||||
261 | ): Builder { |
||||
262 | 7 | if (is_string($variablePosition)) { |
|||
263 | 3 | $variablePosition = VariablePosition::tryFrom($variablePosition) ?? VariablePosition::preIterations; |
|||
264 | } |
||||
265 | 7 | if ($value instanceof Expression) { |
|||
0 ignored issues
–
show
|
|||||
266 | 5 | $this->{$variablePosition->value}[$variable] = $value->getValue($this->grammar); |
|||
267 | |||||
268 | 5 | return $this; |
|||
269 | } |
||||
270 | |||||
271 | 3 | if ($value instanceof Builder) { |
|||
0 ignored issues
–
show
|
|||||
272 | |||||
273 | 1 | [$subquery] = $this->createSub($value); |
|||
274 | |||||
275 | 1 | $this->{$variablePosition->value}[$variable] = $subquery; |
|||
276 | |||||
277 | 1 | return $this; |
|||
278 | } |
||||
279 | 2 | $this->{$variablePosition->value}[$variable] = $this->bindValue($value, $variablePosition->value); |
|||
280 | |||||
281 | 2 | return $this; |
|||
282 | } |
||||
283 | |||||
284 | 336 | public function isVariable(string $value): bool |
|||
285 | { |
||||
286 | if ( |
||||
287 | 336 | key_exists($value, $this->preIterationVariables) |
|||
288 | 336 | || key_exists($value, $this->postIterationVariables) |
|||
289 | ) { |
||||
290 | 1 | return true; |
|||
291 | } |
||||
292 | |||||
293 | 336 | return false; |
|||
294 | } |
||||
295 | |||||
296 | /** |
||||
297 | * @param mixed $value |
||||
298 | * @param array<mixed> $variables |
||||
299 | * @return bool |
||||
300 | */ |
||||
301 | 364 | public function isReference(mixed $value, array $variables = []): bool |
|||
302 | { |
||||
303 | 364 | if (!is_string($value) || empty($value)) { |
|||
304 | 177 | return false; |
|||
305 | } |
||||
306 | |||||
307 | 356 | if (empty($variables)) { |
|||
308 | 356 | $variables = array_merge( |
|||
309 | 356 | array_keys($this->preIterationVariables), |
|||
310 | 356 | array_keys($this->postIterationVariables), |
|||
311 | 356 | $this->tableAliases, |
|||
312 | 356 | ); |
|||
313 | } |
||||
314 | |||||
315 | 356 | $variablesRegex = implode('|', $variables); |
|||
316 | |||||
317 | 356 | return (bool) preg_match( |
|||
318 | 356 | '/^\`?(' |
|||
319 | 356 | . $variablesRegex |
|||
320 | 356 | . '|CURRENT|NEW|OLD)\`?(\[\`.+\`\]|\[[\d\w\*]*\])*(\.(\`.+\`|@?[\d\w]*)(\[\`.+\`\]|\[[\d\w\*]*\])*)*$/', |
|||
321 | 356 | $value, |
|||
322 | 356 | ); |
|||
323 | } |
||||
324 | |||||
325 | |||||
326 | /** |
||||
327 | * Get the database connection instance. |
||||
328 | * |
||||
329 | * @return Connection |
||||
330 | */ |
||||
331 | 207 | public function getConnection() |
|||
332 | { |
||||
333 | 207 | 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 | 3 | public function toAql(): string |
|||
361 | { |
||||
362 | 3 | return $this->toSql(); |
|||
363 | } |
||||
364 | } |
||||
365 |