1 <?php
2
3 /**
4 * ArangoDB PHP client: result set cursor for exports
5 *
6 * @package triagens\ArangoDb
7 * @author Jan Steemann
8 * @copyright Copyright 2012, triagens GmbH, Cologne, Germany
9 */
10
11 namespace triagens\ArangoDb;
12
13 /**
14 * Provides access to the results of a collection export
15 *
16 * The cursor might not contain all results in the beginning.<br>
17 *
18 * If the result set is too big to be transferred in one go, the
19 * cursor might issue additional HTTP requests to fetch the
20 * remaining results from the server.
21 *
22 * @package triagens\ArangoDb
23 * @since 2.6
24 */
25 class ExportCursor
26 {
27 /**
28 * The connection object
29 *
30 * @var Connection
31 */
32 private $_connection;
33
34 /**
35 * Cursor options
36 *
37 * @var array
38 */
39 private $_options;
40
41 /**
42 * The current result set
43 *
44 * @var array
45 */
46 private $_result;
47
48 /**
49 * "has more" indicator - if true, the server has more results
50 *
51 * @var bool
52 */
53 private $_hasMore;
54
55 /**
56 * cursor id - might be NULL if cursor does not have an id
57 *
58 * @var mixed
59 */
60 private $_id;
61
62 /**
63 * number of HTTP calls that were made to build the cursor result
64 */
65 private $_fetches = 1;
66
67 /**
68 * result entry for cursor id
69 */
70 const ENTRY_ID = 'id';
71
72 /**
73 * result entry for "hasMore" flag
74 */
75 const ENTRY_HASMORE = 'hasMore';
76
77 /**
78 * result entry for result documents
79 */
80 const ENTRY_RESULT = 'result';
81
82 /**
83 * "flat" option entry (will treat the results as a simple array, not documents)
84 */
85 const ENTRY_FLAT = '_flat';
86
87 /**
88 * result entry for document count
89 */
90 const ENTRY_COUNT = 'count';
91
92 /**
93 * "type" option entry (is used when converting the result into documents or edges objects)
94 */
95 const ENTRY_TYPE = 'type';
96
97 /**
98 * "baseurl" option entry.
99 */
100 const ENTRY_BASEURL = 'baseurl';
101
102 /**
103 * Initialize the cursor with the first results and some metadata
104 *
105 * @param Connection $connection - connection to be used
106 * @param array $data - initial result data as returned by the server
107 * @param array $options - cursor options
108 *
109 * @throws \triagens\ArangoDb\ClientException
110 */
111 public function __construct(Connection $connection, array $data, array $options)
112 {
113 $this->_connection = $connection;
114 $this->data = $data;
115 $this->_id = null;
116
117 if (isset($data[self::ENTRY_ID])) {
118 $this->_id = $data[self::ENTRY_ID];
119 }
120
121 // attribute must be there
122 assert(isset($data[self::ENTRY_HASMORE]));
123 $this->_hasMore = (bool) $data[self::ENTRY_HASMORE];
124
125 $this->_options = $options;
126 $this->_result = [];
127 $this->setData((array) $data[self::ENTRY_RESULT]);
128 }
129
130
131 /**
132 * Explicitly delete the cursor
133 *
134 * This might issue an HTTP DELETE request to inform the server about
135 * the deletion.
136 *
137 * @throws Exception
138 * @return bool - true if the server acknowledged the deletion request, false otherwise
139 */
140 public function delete()
141 {
142 if ($this->_id) {
143 try {
144 $this->_connection->delete($this->url() . '/' . $this->_id);
145
146 return true;
147 } catch (Exception $e) {
148 }
149 }
150
151 return false;
152 }
153
154
155 /**
156 * Get the total number of results in the export
157 *
158 * @return int - total number of results
159 */
160 public function getCount()
161 {
162 return $this->data[self::ENTRY_COUNT];
163 }
164
165 /**
166 * Get next results as an array
167 *
168 * This might issue additional HTTP requests to fetch any outstanding
169 * results from the server
170 *
171 * @throws Exception
172 * @return mixed - an array with the next results or false if the cursor is exhausted
173 */
174 public function getNextBatch()
175 {
176 if ($this->_result === [] && $this->_hasMore) {
177 // read more from server
178 $this->fetchOutstanding();
179 }
180
181 if ($this->_result !== []) {
182 $result = $this->_result;
183 $this->_result = [];
184
185 return $result;
186 }
187
188 // cursor is exhausted
189 return false;
190 }
191
192 /**
193 * Create an array of results from the input array
194 *
195 * @param array $data - incoming result
196 *
197 * @return void
198 * @throws \triagens\ArangoDb\ClientException
199 */
200 private function setData(array $data)
201 {
202 if (isset($this->_options[self::ENTRY_FLAT]) && $this->_options[self::ENTRY_FLAT]) {
203 $this->_result = $data;
204 } else {
205 $this->_result = [];
206
207 if ($this->_options[self::ENTRY_TYPE] === Collection::TYPE_EDGE) {
208 foreach ($data as $row) {
209 $this->_result[] = Edge::createFromArray($row, $this->_options);
210 }
211 } else {
212 foreach ($data as $row) {
213 $this->_result[] = Document::createFromArray($row, $this->_options);
214 }
215 }
216 }
217 }
218
219
220 /**
221 * Fetch outstanding results from the server
222 *
223 * @throws Exception
224 * @return void
225 */
226 private function fetchOutstanding()
227 {
228 // continuation
229 $response = $this->_connection->put($this->url() . '/' . $this->_id, '');
230 ++$this->_fetches;
231
232 $data = $response->getJson();
233
234 $this->_hasMore = (bool) $data[self::ENTRY_HASMORE];
235 $this->setData($data[self::ENTRY_RESULT]);
236
237 if (!$this->_hasMore) {
238 // we have fetched the complete result set and can unset the id now
239 $this->_id = null;
240 }
241 }
242
243 /**
244 * Return the base URL for the cursor
245 *
246 * @return string
247 */
248 private function url()
249 {
250 if (isset($this->_options[self::ENTRY_BASEURL])) {
251 return $this->_options[self::ENTRY_BASEURL];
252 }
253
254 // this is the default
255 return Urls::URL_EXPORT;
256 }
257
258 /**
259 * Return the number of HTTP calls that were made to build the cursor result
260 *
261 * @return int
262 */
263 public function getFetches()
264 {
265 return $this->_fetches;
266 }
267
268 /**
269 * Return the cursor id, if any
270 *
271 * @return string
272 */
273 public function getId()
274 {
275 return $this->_id;
276 }
277
278 }
279