Statement::explain()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 8
rs 10
1
<?php
2
3
namespace ArangoClient\Statement;
4
5
use ArangoClient\ArangoClient;
6
use ArangoClient\Exceptions\ArangoException;
7
use ArangoClient\Manager;
8
use ArrayIterator;
9
use IteratorAggregate;
10
use stdClass;
11
12
/**
13
 * Executes queries on ArangoDB
14
 *
15
 * @see https://www.arangodb.com/docs/stable/http/aql-query-cursor.html
16
 *
17
 * @template-implements \IteratorAggregate<mixed>
18
 */
19
class Statement extends Manager implements IteratorAggregate
20
{
21
    /**
22
     * @var array<mixed>
23
     */
24
    protected array $results = [];
25
26
    protected ?stdClass $stats = null;
27
28
    /**
29
     * @var array<mixed>
30
     */
31
    protected array $warnings = [];
32
33
    protected ?int $cursorId = null;
34
35
    protected bool $cursorHasMoreResults = false;
36
37
    protected ?int $count = null;
38
39
    protected ?stdClass $extra = null;
40
41
    /**
42
     * Statement constructor.
43
     *
44
     * @param  array<mixed>|null  $bindVars
45
     * @param  array<mixed>  $options
46
     */
47
    public function __construct(protected ArangoClient $arangoClient, protected string $query, protected ?array $bindVars, protected array $options = []) {}
48
49
    /**
50
     * A statement can be used like an array to access the results.
51
     *
52
     * @return ArrayIterator<array-key, mixed>
53
     */
54
    public function getIterator(): ArrayIterator
55
    {
56
        return new ArrayIterator($this->results);
57
    }
58
59
    /**
60
     * @throws ArangoException
61
     */
62
    public function execute(): bool
63
    {
64
        $this->results = [];
65
66
        $bodyContent = $this->prepareQueryBodyContent();
67
68
        $options = [
69
            'body' => $bodyContent,
70
        ];
71
        $results = $this->arangoClient->transactionAwareRequest('post', '/_api/cursor', $options);
72
73
        $this->handleQueryResults($results);
74
75
        $this->requestOutstandingResults($bodyContent);
76
77
        return true;
78
    }
79
80
    /**
81
     * @return array<mixed>
82
     */
83
    protected function prepareQueryBodyContent(): array
84
    {
85
        $bodyContent = $this->options;
86
        $bodyContent['query'] = $this->query;
87
        if (!empty($this->bindVars)) {
88
            $bodyContent['bindVars'] = $this->bindVars;
89
        }
90
91
        return $bodyContent;
92
    }
93
94
    protected function handleQueryResults(stdClass $results): void
95
    {
96
        $this->results = array_merge($this->results, (array) $results->result);
97
98
        if (property_exists($results, 'extra') && $results->extra !== null) {
99
            $this->extra = (object) $results->extra;
100
        }
101
102
        if (property_exists($results, 'count') && $results->count !== null) {
103
            $this->count = (int) $results->count;
104
        }
105
106
        $this->cursorHasMoreResults = (bool) $results->hasMore;
107
        $this->cursorId = $results->hasMore ? (int) $results->id : null;
108
    }
109
110
    /**
111
     * @param  array<mixed>  $body
112
     *
113
     * @throws ArangoException
114
     */
115
    protected function requestOutstandingResults(array $body): void
116
    {
117
        while ($this->cursorHasMoreResults) {
118
            $uri = '/_api/cursor/' . (string) $this->cursorId;
119
120
            $options = [
121
                'body' => $body,
122
            ];
123
124
            $results = $this->arangoClient->request('put', $uri, $options);
125
126
            $this->handleQueryResults($results);
127
        }
128
    }
129
130
    /**
131
     * @throws ArangoException
132
     */
133
    public function explain(): stdClass
134
    {
135
        $body = $this->prepareQueryBodyContent();
136
        $options = [
137
            'body' => $body,
138
        ];
139
140
        return $this->arangoClient->request('post', '/_api/explain', $options);
141
    }
142
143
    /**
144
     * Parse and validate the query, will through an ArangoException if the query is invalid.
145
     *
146
     * @throws ArangoException
147
     */
148
    public function parse(): stdClass
149
    {
150
        $body = $this->prepareQueryBodyContent();
151
        $options = [
152
            'body' => $body,
153
        ];
154
155
        return $this->arangoClient->request('post', '/_api/query', $options);
156
    }
157
158
    /**
159
     * Execute the query and return performance information on the query.
160
     *
161
     * @see https://www.arangodb.com/docs/3.7/aql/execution-and-performance-query-profiler.html
162
     *
163
     * @throws ArangoException
164
     */
165
    public function profile(int|bool $mode = 1): ?stdClass
166
    {
167
        $bodyContent = $this->prepareQueryBodyContent();
168
169
        if (!isset($bodyContent['options']) || !is_array($bodyContent['options'])) {
170
            $bodyContent['options'] = [];
171
        }
172
        $bodyContent['options']['profile'] = $mode;
173
174
        $options = [
175
            'body' => $bodyContent,
176
        ];
177
178
        $results = $this->arangoClient->request('post', '/_api/cursor', $options);
179
180
        $this->handleQueryResults($results);
181
182
        $this->requestOutstandingResults($bodyContent);
183
184
        return $this->extra;
185
    }
186
187
    public function setQuery(string $query): self
188
    {
189
        $this->query = $query;
190
191
        return $this;
192
    }
193
194
    public function getQuery(): string
195
    {
196
        return $this->query;
197
    }
198
199
    /**
200
     * Fetch all results.
201
     *
202
     * @return array<mixed>
203
     */
204
    public function fetchAll(): array
205
    {
206
        return $this->results;
207
    }
208
209
    /**
210
     * Return the total number of results. (not just the retrieved results)
211
     * Useful if not all results have been retrieved from the database yet.
212
     */
213
    public function getCount(): ?int
214
    {
215
        return $this->count;
216
    }
217
218
    public function getWritesExecuted(): int
219
    {
220
        return (int) $this->extra?->stats?->writesExecuted;
221
    }
222
}
223