RunsQueries::cursor()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 17
ccs 0
cts 9
cp 0
crap 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Concerns;
6
7
use Closure;
8
use Exception;
9
use LaravelFreelancerNL\Aranguent\Exceptions\NoArangoClientException;
10
use LaravelFreelancerNL\Aranguent\Query\Builder as QueryBuilder;
11
use LaravelFreelancerNL\Aranguent\Exceptions\QueryException;
12
use LaravelFreelancerNL\FluentAQL\QueryBuilder as FluentAqlBuilder;
13
use stdClass;
14
use LaravelFreelancerNL\Aranguent\Exceptions\UniqueConstraintViolationException;
15
16
trait RunsQueries
17
{
18
    /**
19
     * Determine if the given database exception was caused by a unique constraint violation.
20
     *
21
     * @param  \Exception  $exception
22
     * @return bool
23
     */
24 8
    protected function isUniqueConstraintError(Exception $exception)
25
    {
26 8
        return boolval(preg_match('/409 - AQL: unique constraint violated/i', $exception->getMessage()));
27
    }
28
29
    /**
30
     * Run a select statement against the database and returns a generator.
31
     *
32
     * @param  string  $query
33
     * @param  array<mixed>  $bindings
34
     * @param  bool  $useReadPdo
35
     * @return \Generator
36
     * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
37
     */
38
    public function cursor($query, $bindings = [], $useReadPdo = true)
0 ignored issues
show
Unused Code introduced by
The parameter $useReadPdo is not used and could be removed. ( Ignorable by Annotation )

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

38
    public function cursor($query, $bindings = [], /** @scrutinizer ignore-unused */ $useReadPdo = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
39
    {
40
41
        // Usage of a separate DB to read date isn't supported at this time
42
        $useReadPdo = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $useReadPdo is dead and can be removed.
Loading history...
43
44
        return $this->run($query, $bindings, function ($query, $bindings) {
45
            if ($this->pretending()) {
0 ignored issues
show
Bug introduced by
It seems like pretending() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

45
            if ($this->/** @scrutinizer ignore-call */ pretending()) {
Loading history...
46
                return [];
47
            }
48
49
            if ($this->arangoClient === null) {
50
                throw new NoArangoClientException();
51
            }
52
            $statement = $this->arangoClient->prepare($query, $bindings);
53
54
            return $statement->execute();
55
        });
56
    }
57
58
    /**
59
     * Execute an SQL statement and return the boolean result.
60
     *
61
     * @param  string  $query
62
     * @param  array<mixed>  $bindings
63
     * @return bool
64
     */
65 87
    public function statement($query, $bindings = [])
66
    {
67 87
        [$query, $bindings] = $this->handleQueryBuilder(
68 87
            $query,
69 87
            $bindings,
70 87
        );
71
72 87
        return $this->run($query, $bindings, function ($query, $bindings) {
73 87
            if ($this->pretending()) {
74
                return true;
75
            }
76
77 87
            if ($this->arangoClient === null) {
78
                throw new NoArangoClientException();
79
            }
80 87
            $statement = $this->arangoClient->prepare($query, $bindings);
81
82 87
            $statement->execute();
83
84 86
            $affectedDocumentCount = $statement->getWritesExecuted();
85
86 86
            $this->recordsHaveBeenModified($changed = $affectedDocumentCount > 0);
0 ignored issues
show
Bug introduced by
It seems like recordsHaveBeenModified() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

86
            $this->/** @scrutinizer ignore-call */ 
87
                   recordsHaveBeenModified($changed = $affectedDocumentCount > 0);
Loading history...
87
88 86
            return $changed;
89 87
        });
90
    }
91
92
    /**
93
     * Run an SQL statement and get the number of rows affected.
94
     *
95
     * @param  string  $query
96
     * @param  array<mixed>  $bindings
97
     * @return int
98
     */
99 69
    public function affectingStatement($query, $bindings = [])
100
    {
101 69
        [$query, $bindings] = $this->handleQueryBuilder(
102 69
            $query,
103 69
            $bindings,
104 69
        );
105
106 69
        return $this->run($query, $bindings, function () use ($query, $bindings) {
107 69
            if ($this->pretending()) {
108
                return 0;
109
            }
110
111 69
            if ($this->arangoClient === null) {
112
                throw new NoArangoClientException();
113
            }
114
115
            // For update or delete statements, we want to get the number of rows affected
116
            // by the statement and return that back to the developer. We'll first need
117
            // to execute the statement and get the executed writes from the extra.
118 69
            $statement = $this->arangoClient->prepare($query, $bindings);
119
120 69
            $statement->execute();
121
122 69
            $affectedDocumentCount = $statement->getWritesExecuted();
123
124 69
            $this->recordsHaveBeenModified($affectedDocumentCount > 0);
125
126 69
            return $affectedDocumentCount;
127 69
        });
128
    }
129
130
    /**
131
     * Run a raw, unprepared query against the connection.
132
     *
133
     * @param  string  $query
134
     */
135
    public function unprepared($query): bool
136
    {
137
        return $this->run($query, [], function ($query) {
138
            if ($this->pretending()) {
139
                return true;
140
            }
141
142
            $statement = $$this->arangoClient->prepare($query);
143
            $statement->execute();
144
            $affectedDocumentCount = $statement->getWritesExecuted();
145
            $change = $affectedDocumentCount > 0;
146
147
            $this->recordsHaveBeenModified($change);
148
149
            return $change;
150
        });
151
    }
152
153
    /**
154
     * Returns the query execution plan. The query will not be executed.
155
     *
156
     * @param  string  $query
157
     * @param  array<mixed>  $bindings
158
     */
159 1
    public function explain(string|FluentAqlBuilder $query, $bindings = []): stdClass
160
    {
161 1
        [$query, $bindings] = $this->handleQueryBuilder(
162 1
            $query,
163 1
            $bindings,
164 1
        );
165
166 1
        if ($this->arangoClient === null) {
167
            throw new NoArangoClientException();
168
        }
169 1
        $statement = $this->arangoClient->prepare($query, $bindings);
170
171 1
        return $statement->explain();
172
    }
173
174
    /**
175
     * @param FluentAqlBuilder|string|QueryBuilder $query
176
     * @param array<mixed> $bindings
177
     * @return array<mixed>
178
     */
179 362
    protected function handleQueryBuilder($query, array $bindings): array
180
    {
181
182 362
        if ($query instanceof FluentAqlBuilder) {
183 7
            $bindings = $query->binds;
184 7
            $query = $query->query;
185
        }
186
187 362
        if ($query instanceof QueryBuilder) {
188
            $bindings = $query->getBindings();
189
            $query = $query->toSql();
190
        }
191
192 362
        return [$query, $bindings];
193
    }
194
195
    /**
196
     * Run a select statement against the database.
197
     *
198
     * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
199
     *
200
     * @param  string|FluentAqlBuilder  $query
201
     * @param  array<mixed>  $bindings
202
     * @param  bool  $useReadPdo
203
     * @return mixed
204
     */
205 356
    public function select($query, $bindings = [], $useReadPdo = true)
206
    {
207 356
        return $this->execute($query, $bindings, $useReadPdo);
208
    }
209
210
    /**
211
     * Run an AQL query against the database and return the results.
212
     *
213
     * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
214
     *
215
     * @param  string|FluentAqlBuilder  $query
216
     * @param  array<mixed>  $bindings
217
     * @param  bool  $useReadPdo
218
     * @return mixed
219
     */
220 360
    public function execute($query, array $bindings = [], $useReadPdo = true)
0 ignored issues
show
Unused Code introduced by
The parameter $useReadPdo is not used and could be removed. ( Ignorable by Annotation )

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

220
    public function execute($query, array $bindings = [], /** @scrutinizer ignore-unused */ $useReadPdo = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
221
    {
222
        // Usage of a separate DB to read date isn't supported at this time
223 360
        $useReadPdo = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $useReadPdo is dead and can be removed.
Loading history...
224
225 360
        [$query, $bindings] = $this->handleQueryBuilder(
226 360
            $query,
227 360
            $bindings,
228 360
        );
229
230 360
        return $this->run($query, $bindings, function () use ($query, $bindings) {
231 360
            if ($this->pretending()) {
232
                return [];
233
            }
234
235 360
            if ($this->arangoClient === null) {
236
                throw new NoArangoClientException();
237
            }
238
239 360
            $statement = $this->arangoClient->prepare($query, $bindings);
240
241 360
            $statement->execute();
242
243 355
            return $statement->fetchAll();
244 360
        });
245
    }
246
247
    /**
248
     * Get a new query builder instance.
249
     */
250 361
    public function query(): QueryBuilder
251
    {
252 361
        return new QueryBuilder(
253 361
            $this,
0 ignored issues
show
Bug introduced by
$this of type LaravelFreelancerNL\Aranguent\Concerns\RunsQueries is incompatible with the type Illuminate\Database\ConnectionInterface expected by parameter $connection of LaravelFreelancerNL\Aran...\Builder::__construct(). ( Ignorable by Annotation )

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

253
            /** @scrutinizer ignore-type */ $this,
Loading history...
254 361
            $this->getQueryGrammar(),
0 ignored issues
show
Bug introduced by
It seems like getQueryGrammar() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

254
            $this->/** @scrutinizer ignore-call */ 
255
                   getQueryGrammar(),
Loading history...
255 361
            $this->getPostProcessor(),
0 ignored issues
show
Bug introduced by
It seems like getPostProcessor() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

255
            $this->/** @scrutinizer ignore-call */ 
256
                   getPostProcessor(),
Loading history...
256 361
        );
257
    }
258
259
    /**
260
     * Run a SQL statement and log its execution context.
261
     *
262
     * @param  string  $query
263
     * @param  array<mixed>  $bindings
264
     * @return mixed
265
     *
266
     * @throws QueryException
267
     */
268 361
    protected function run($query, $bindings, Closure $callback)
269
    {
270 361
        foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
271
            $beforeExecutingCallback($query, $bindings, $this);
272
        }
273
274 361
        $this->reconnectIfMissingConnection();
0 ignored issues
show
Bug introduced by
It seems like reconnectIfMissingConnection() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

274
        $this->/** @scrutinizer ignore-call */ 
275
               reconnectIfMissingConnection();
Loading history...
275
276 361
        $start = microtime(true);
277
278
        // Here we will run this query. If an exception occurs we'll determine if it was
279
        // caused by a connection that has been lost. If that is the cause, we'll try
280
        // to re-establish connection and re-run the query with a fresh connection.
281
        try {
282 361
            $result = $this->runQueryCallback($query, $bindings, $callback);
283 8
        } catch (QueryException $e) {
284 8
            $result = $this->handleQueryException(
0 ignored issues
show
Bug introduced by
It seems like handleQueryException() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

284
            /** @scrutinizer ignore-call */ 
285
            $result = $this->handleQueryException(
Loading history...
285 8
                $e,
286 8
                $query,
287 8
                $bindings,
288 8
                $callback,
289 8
            );
290
        }
291
292
        // Once we have run the query we will calculate the time that it took to run and
293
        // then log the query, bindings, and execution time so we will report them on
294
        // the event that the developer needs them. We'll log time in milliseconds.
295 355
        $this->logQuery(
0 ignored issues
show
Bug introduced by
It seems like logQuery() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

295
        $this->/** @scrutinizer ignore-call */ 
296
               logQuery(
Loading history...
296 355
            $query,
297 355
            $bindings,
298 355
            $this->getElapsedTime($start),
0 ignored issues
show
Bug introduced by
It seems like getElapsedTime() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

298
            $this->/** @scrutinizer ignore-call */ 
299
                   getElapsedTime($start),
Loading history...
299 355
        );
300
301 355
        return $result;
302
    }
303
304
    /**
305
     * Run a SQL statement.
306
     *
307
     * @param  string  $query
308
     * @param  array<mixed>  $bindings
309
     * @return mixed
310
     *
311
     * @throws QueryException
312
     */
313 361
    protected function runQueryCallback($query, $bindings, Closure $callback)
314
    {
315
        // To execute the statement, we'll simply call the callback, which will actually
316
        // run the SQL against the PDO connection. Then we can calculate the time it
317
        // took to execute and log the query SQL, bindings and time in our memory.
318
        try {
319 361
            return $callback($query, $bindings);
320 8
        } catch (Exception $e) {
321
            // If an exception occurs when attempting to run a query, we'll format the error
322
            // message to include the bindings with SQL, which will make this exception a
323
            // lot more helpful to the developer instead of just the database's errors.
324
325 8
            if ($this->isUniqueConstraintError($e)) {
326 2
                throw new UniqueConstraintViolationException(
327 2
                    (string) $this->getName(),
0 ignored issues
show
Bug introduced by
It seems like getName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

327
                    (string) $this->/** @scrutinizer ignore-call */ getName(),
Loading history...
328 2
                    $query,
329 2
                    $this->prepareBindings($bindings),
0 ignored issues
show
Bug introduced by
It seems like prepareBindings() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

329
                    $this->/** @scrutinizer ignore-call */ 
330
                           prepareBindings($bindings),
Loading history...
330 2
                    $e,
331 2
                );
332
            }
333
334 6
            throw new QueryException(
335 6
                (string) $this->getName(),
336 6
                $query,
337 6
                $this->prepareBindings($bindings),
338 6
                $e,
339 6
            );
340
        }
341
    }
342
}
343