1 <?php
2
3 /**
4 * ArangoDB PHP client: collection handler
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 management of collections
15 *
16 * The collection handler fetches collection data from the server and
17 * creates collections on the server.
18 *
19 * @package triagens\ArangoDb
20 * @since 0.2
21 */
22 class CollectionHandler extends
23 Handler
24 {
25
26 /**
27 * documents array index
28 */
29 const ENTRY_DOCUMENTS = 'documents';
30
31 /**
32 * collection parameter
33 */
34 const OPTION_COLLECTION = 'collection';
35
36 /**
37 * example parameter
38 */
39 const OPTION_EXAMPLE = 'example';
40
41 /**
42 * example parameter
43 */
44 const OPTION_NEW_VALUE = 'newValue';
45
46 /**
47 * example parameter
48 */
49 const OPTION_CREATE_COLLECTION = 'createCollection';
50
51 /**
52 * attribute parameter
53 */
54 const OPTION_ATTRIBUTE = 'attribute';
55
56 /**
57 * keys parameter
58 */
59 const OPTION_KEYS = 'keys';
60
61 /**
62 * left parameter
63 */
64 const OPTION_LEFT = 'left';
65
66 /**
67 * right parameter
68 */
69 const OPTION_RIGHT = 'right';
70
71 /**
72 * closed parameter
73 */
74 const OPTION_CLOSED = 'closed';
75
76 /**
77 * latitude parameter
78 */
79 const OPTION_LATITUDE = 'latitude';
80
81 /**
82 * longitude parameter
83 */
84 const OPTION_LONGITUDE = 'longitude';
85
86 /**
87 * distance parameter
88 */
89 const OPTION_DISTANCE = 'distance';
90
91 /**
92 * radius parameter
93 */
94 const OPTION_RADIUS = 'radius';
95
96 /**
97 * skip parameter
98 */
99 const OPTION_SKIP = 'skip';
100
101 /**
102 * index parameter
103 */
104 const OPTION_INDEX = 'index';
105
106 /**
107 * limit parameter
108 */
109 const OPTION_LIMIT = 'limit';
110
111 /**
112 * fields
113 */
114 const OPTION_FIELDS = 'fields';
115
116 /**
117 * unique
118 */
119 const OPTION_UNIQUE = 'unique';
120
121 /**
122 * type
123 */
124 const OPTION_TYPE = 'type';
125
126 /**
127 * size option
128 */
129 const OPTION_SIZE = 'size';
130
131 /**
132 * geo index option
133 */
134 const OPTION_GEO_INDEX = 'geo';
135
136 /**
137 * ignoreNull option
138 */
139 const OPTION_IGNORE_NULL = 'ignoreNull';
140
141 /**
142 * constraint option
143 */
144 const OPTION_CONSTRAINT = 'constraint';
145
146 /**
147 * geoJson option
148 */
149 const OPTION_GEOJSON = 'geoJson';
150
151 /**
152 * hash index option
153 */
154 const OPTION_HASH_INDEX = 'hash';
155
156 /**
157 * fulltext index option
158 */
159 const OPTION_FULLTEXT_INDEX = 'fulltext';
160
161 /**
162 * minLength option
163 */
164 const OPTION_MIN_LENGTH = 'minLength';
165
166 /**
167 * skiplist index option
168 */
169 const OPTION_SKIPLIST_INDEX = 'skiplist';
170
171 /**
172 * persistent index option
173 */
174 const OPTION_PERSISTENT_INDEX = 'persistent';
175
176 /**
177 * sparse index option
178 */
179 const OPTION_SPARSE = 'sparse';
180
181 /**
182 * count option
183 */
184 const OPTION_COUNT = 'count';
185
186 /**
187 * query option
188 */
189 const OPTION_QUERY = 'query';
190
191 /**
192 * checksum option
193 */
194 const OPTION_CHECKSUM = 'checksum';
195
196 /**
197 * revision option
198 */
199 const OPTION_REVISION = 'revision';
200
201 /**
202 * properties option
203 */
204 const OPTION_PROPERTIES = 'properties';
205
206 /**
207 * figures option
208 */
209 const OPTION_FIGURES = 'figures';
210
211 /**
212 * load option
213 */
214 const OPTION_LOAD = 'load';
215
216 /**
217 * Creates a new collection on the server
218 *
219 * This will add the collection on the server and return its id
220 * The id is mainly returned for backwards compatibility, but you should use the collection name for any reference to the collection. *
221 * This will throw if the collection cannot be created
222 *
223 * @throws Exception
224 *
225 * @param mixed $collection - collection object to be created on the server or a string with the name
226 * @param array $options - an array of options.
227 * <p>Options are :<br>
228 * <li>'type' - 2 -> normal collection, 3 -> edge-collection</li>
229 * <li>'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk / If this is not specified, then the collection's default sync behavior will be applied.</li>
230 * <li>'journalSize' - journalSize value.</li>
231 * <li>'isSystem' - false->user collection(default), true->system collection .</li>
232 * <li>'isVolatile' - false->persistent collection(default), true->volatile (in-memory) collection .</li>
233 * <li>'numberOfShards' - number of shards for the collection.</li>
234 * <li>'shardKeys' - list of shard key attributes.</li>
235 * </p>
236 *
237 * @return mixed - id of collection created
238 */
239 public function create($collection, array $options = [])
240 {
241 if (is_string($collection)) {
242 $name = $collection;
243 $collection = new Collection();
244 $collection->setName($name);
245 foreach ($options as $key => $value) {
246 $collection->{'set' . ucfirst($key)}($value);
247 }
248 }
249 if ($collection->getWaitForSync() === null) {
250 $collection->setWaitForSync($this->getConnectionOption(ConnectionOptions::OPTION_WAIT_SYNC));
251 }
252
253 if ($collection->getJournalSize() === null) {
254 $collection->setJournalSize($this->getConnectionOption(ConnectionOptions::OPTION_JOURNAL_SIZE));
255 }
256
257 if ($collection->getIsSystem() === null) {
258 $collection->setIsSystem($this->getConnectionOption(ConnectionOptions::OPTION_IS_SYSTEM));
259 }
260
261 if ($collection->getIsVolatile() === null) {
262 $collection->setIsVolatile($this->getConnectionOption(ConnectionOptions::OPTION_IS_VOLATILE));
263 }
264
265 $type = $collection->getType() ?: Collection::getDefaultType();
266 $params = [
267 Collection::ENTRY_NAME => $collection->getName(),
268 Collection::ENTRY_TYPE => $type,
269 Collection::ENTRY_WAIT_SYNC => $collection->getWaitForSync(),
270 Collection::ENTRY_JOURNAL_SIZE => $collection->getJournalSize(),
271 Collection::ENTRY_IS_SYSTEM => $collection->getIsSystem(),
272 Collection::ENTRY_IS_VOLATILE => $collection->getIsVolatile(),
273 Collection::ENTRY_KEY_OPTIONS => $collection->getKeyOptions(),
274 ];
275
276 // set extra cluster attributes
277 if ($collection->getNumberOfShards() !== null) {
278 $params[Collection::ENTRY_NUMBER_OF_SHARDS] = $collection->getNumberOfShards();
279 }
280
281 if (is_array($collection->getShardKeys())) {
282 $params[Collection::ENTRY_SHARD_KEYS] = $collection->getShardKeys();
283 }
284
285 $response = $this->getConnection()->post(Urls::URL_COLLECTION, $this->json_encode_wrapper($params));
286
287 // $location = $response->getLocationHeader();
288 // if (!$location) {
289 // throw new ClientException('Did not find location header in server response');
290 // }
291 $jsonResponse = $response->getJson();
292 $id = $jsonResponse['id'];
293 $collection->setId($id);
294
295 return $id;
296 }
297
298 /**
299 * unload option
300 */
301 const OPTION_UNLOAD = 'unload';
302
303 /**
304 * truncate option
305 */
306 const OPTION_TRUNCATE = 'truncate';
307
308 /**
309 * rename option
310 */
311 const OPTION_RENAME = 'rename';
312
313
314 /**
315 * exclude system collections
316 */
317 const OPTION_EXCLUDE_SYSTEM = 'excludeSystem';
318
319
320 /**
321 * Check if a collection exists
322 *
323 * This will call self::get() internally and checks if there
324 * was an exception thrown which represents an 404 request.
325 *
326 * @throws Exception When any other error than a 404 occurs
327 *
328 * @param mixed $collection - collection id as a string or number
329 *
330 * @return boolean
331 */
332 public function has($collection)
333 {
334 $collection = $this->makeCollection($collection);
335
336 try {
337 // will throw ServerException if entry could not be retrieved
338 $this->get($collection);
339
340 return true;
341 } catch (ServerException $e) {
342 // we are expecting a 404 to return boolean false
343 if ($e->getCode() === 404) {
344 return false;
345 }
346
347 // just rethrow
348 throw $e;
349 }
350 }
351
352
353 /**
354 * Get the number of documents in a collection
355 *
356 * This will throw if the collection cannot be fetched from the server
357 *
358 * @throws Exception
359 *
360 * @param mixed $collection - collection id as a string or number
361 *
362 * @return int - the number of documents in the collection
363 */
364 public function count($collection)
365 {
366 $collection = $this->makeCollection($collection);
367 $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_COUNT]);
368 $response = $this->getConnection()->get($url);
369
370 $data = $response->getJson();
371 $count = $data[self::OPTION_COUNT];
372
373 return (int) $count;
374 }
375
376
377 /**
378 * Get information about a collection
379 *
380 * This will throw if the collection cannot be fetched from the server
381 *
382 * @throws Exception
383 *
384 * @param mixed $collection - collection id as a string or number
385 *
386 * @return Collection - the collection fetched from the server
387 */
388 public function get($collection)
389 {
390 $collection = $this->makeCollection($collection);
391
392 $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection]);
393 $response = $this->getConnection()->get($url);
394
395 $data = $response->getJson();
396
397 return Collection::createFromArray($data);
398 }
399
400
401 /**
402 * Get properties of a collection
403 *
404 * This will throw if the collection cannot be fetched from the server
405 *
406 * @throws Exception
407 *
408 * @param mixed $collection - collection id as a string or number
409 *
410 * @return Collection - the collection fetched from the server
411 */
412 public function getProperties($collection)
413 {
414 $collection = $this->makeCollection($collection);
415 $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_PROPERTIES]);
416 $response = $this->getConnection()->get($url);
417
418 $data = $response->getJson();
419
420 return Collection::createFromArray($data);
421 }
422
423
424 /**
425 * Get figures for a collection
426 *
427 * This will throw if the collection cannot be fetched from the server
428 *
429 * @throws Exception
430 *
431 * @param mixed $collection - collection id as a string or number
432 *
433 * @return array - the figures for the collection
434 */
435 public function figures($collection)
436 {
437 $collection = $this->makeCollection($collection);
438 $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collection, self::OPTION_FIGURES]);
439 $response = $this->getConnection()->get($url);
440
441 $data = $response->getJson();
442
443 return $data[self::OPTION_FIGURES];
444 }
445
446
447 /**
448 * Calculate a checksum of the collection.
449 *
450 * Will calculate a checksum of the meta-data (keys and optionally revision ids)
451 * and optionally the document data in the collection.
452 *
453 * @throws Exception
454 *
455 * @param mixed $collectionId - collection id as a string or number
456 * @param boolean $withRevisions - optional boolean whether or not to include document revision ids
457 * in the checksum calculation.
458 * @param boolean $withData - optional boolean whether or not to include document body data in the
459 * checksum calculation.
460 *
461 * @return array - array containing keys "checksum" and "revision"
462 */
463 public function getChecksum($collectionId, $withRevisions = false, $withData = false)
464 {
465
466 $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_CHECKSUM]);
467 $url = UrlHelper::appendParamsUrl($url, ['withRevisions' => $withRevisions, 'withData' => $withData]);
468 $response = $this->getConnection()->get($url);
469
470 return $response->getJson();
471 }
472
473 /**
474 * Returns the Collections revision ID
475 *
476 * The revision id is a server-generated string that clients can use to check whether data in a collection has
477 * changed since the last revision check.
478 *
479 * @throws Exception
480 *
481 * @param mixed $collectionId - collection id as a string or number
482 *
483 * @return array - containing a key revision
484 */
485 public function getRevision($collectionId)
486 {
487
488 $url = UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_REVISION]);
489 $response = $this->getConnection()->get($url);
490
491 return $response->getJson();
492 }
493
494 /**
495 * Rename a collection
496 *
497 * @throws Exception
498 *
499 * @param mixed $collection - collection id as string or number or collection object
500 * @param string $name - new name for collection
501 *
502 * @return bool - always true, will throw if there is an error
503 */
504 public function rename($collection, $name)
505 {
506 $collectionId = $this->getCollectionId($collection);
507
508 if ($this->isValidCollectionId($collectionId)) {
509 throw new ClientException('Cannot alter a collection without a collection id');
510 }
511
512 $params = [Collection::ENTRY_NAME => $name];
513 $this->getConnection()->put(
514 UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_RENAME]),
515 $this->json_encode_wrapper($params)
516 );
517
518 return true;
519 }
520
521 /**
522 * Load a collection into the server's memory
523 *
524 * This will load the given collection into the server's memory.
525 *
526 * @throws Exception
527 *
528 * @param mixed $collection - collection id as string or number or collection object
529 *
530 * @return HttpResponse - HTTP response object
531 */
532 public function load($collection)
533 {
534 $collectionId = $this->getCollectionId($collection);
535
536 if ($this->isValidCollectionId($collectionId)) {
537 throw new ClientException('Cannot alter a collection without a collection id');
538 }
539
540 $result = $this->getConnection()->put(
541 UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_LOAD]),
542 ''
543 );
544
545 return $result;
546 }
547
548 /**
549 * Unload a collection from the server's memory
550 *
551 * This will unload the given collection from the server's memory.
552 *
553 * @throws Exception
554 *
555 * @param mixed $collection - collection id as string or number or collection object
556 *
557 * @return HttpResponse - HTTP response object
558 */
559 public function unload($collection)
560 {
561 $collectionId = $this->getCollectionId($collection);
562
563 if ($this->isValidCollectionId($collectionId)) {
564 throw new ClientException('Cannot alter a collection without a collection id');
565 }
566
567 $result = $this->getConnection()->put(
568 UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_UNLOAD]),
569 ''
570 );
571
572 return $result;
573 }
574
575
576 /**
577 * Truncate a collection
578 *
579 * This will remove all documents from the collection but will leave the metadata and indexes intact.
580 *
581 * @throws Exception
582 *
583 * @param mixed $collection - collection id as string or number or collection object
584 *
585 * @return bool - always true, will throw if there is an error
586 */
587 public function truncate($collection)
588 {
589 $collectionId = $this->getCollectionId($collection);
590
591 if ($this->isValidCollectionId($collectionId)) {
592 throw new ClientException('Cannot alter a collection without a collection id');
593 }
594
595 $this->getConnection()->put(
596 UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionId, self::OPTION_TRUNCATE]),
597 ''
598 );
599
600 return true;
601 }
602
603
604 /**
605 * Drop a collection
606 *
607 * @throws Exception
608 *
609 * @param mixed $collection - collection id as string or number or collection object
610 * @param array $options - an array of options for the drop operation
611 *
612 * @return bool - always true, will throw if there is an error
613 */
614 public function drop($collection, array $options = [])
615 {
616 $collectionName = $this->getCollectionName($collection);
617
618 if ($this->isValidCollectionId($collectionName)) {
619 throw new ClientException('Cannot alter a collection without a collection id');
620 }
621
622 $appendix = '';
623 if (is_array($options) && isset($options['isSystem'])) {
624 $appendix = '?isSystem=' . UrlHelper::getBoolString($options['isSystem']);
625 }
626
627 $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_COLLECTION, [$collectionName]) . $appendix);
628
629 return true;
630 }
631
632
633 /**
634 * Checks if the collectionId given, is valid. Returns true if it is, or false if it is not.
635 *
636 * @param $collectionId
637 *
638 * @return bool
639 */
640 public function isValidCollectionId($collectionId)
641 {
642 return !$collectionId || !(is_string($collectionId) || is_float($collectionId) || is_int($collectionId));
643 }
644
645 /**
646 * Get list of all available collections per default with the collection names as index.
647 * Returns empty array if none are available.
648 *
649 * @param array $options - optional - an array of options.
650 * <p>Options are :<br>
651 * <li>'excludeSystem' - With a value of true, all system collections will be excluded from the response.</li>
652 * <li>'keys' - With a value of "collections", the index of the resulting array is numerical,
653 * With a value of "names", the index of the resulting array are the collection names.</li>
654 * </p>
655 *
656 * @return array
657 * @throws \triagens\ArangoDb\Exception
658 * @throws \triagens\ArangoDb\ClientException
659 */
660 public function getAllCollections(array $options = [])
661 {
662 $options = array_merge(['excludeSystem' => false, 'keys' => 'result'], $options);
663 $params = [];
664 if ($options['excludeSystem'] === true) {
665 $params[self::OPTION_EXCLUDE_SYSTEM] = true;
666 }
667 $url = UrlHelper::appendParamsUrl(Urls::URL_COLLECTION, $params);
668 $response = $this->getConnection()->get(UrlHelper::buildUrl($url, []));
669 $response = $response->getJson();
670 if (isset($response[$options['keys']])) {
671 $result = [];
672 foreach ($response[$options['keys']] as $collection) {
673 $result[$collection['name']] = $collection;
674 }
675
676 return $result;
677 }
678
679 return $response;
680 }
681
682
683 /**
684 * Gets the collectionId from the given collectionObject or string/integer
685 *
686 * @param mixed $collection
687 *
688 * @return mixed
689 */
690 public function getCollectionId($collection)
691 {
692 if ($collection instanceof Collection) {
693 $collectionId = $collection->getId();
694
695 return $collectionId;
696 } else {
697 $collectionId = $collection;
698
699 return $collectionId;
700 }
701 }
702
703
704 /**
705 * Gets the collectionId from the given collectionObject or string/integer
706 *
707 * @param mixed $collection
708 *
709 * @return mixed
710 */
711 public function getCollectionName($collection)
712 {
713 if ($collection instanceof Collection) {
714 $collectionId = $collection->getName();
715
716 return $collectionId;
717 } else {
718 $collectionId = $collection;
719
720 return $collectionId;
721 }
722 }
723
724
725 /**
726 * Import documents from a file
727 *
728 * This will throw on all errors except insertion errors
729 *
730 * @throws Exception
731 *
732 * @param mixed $collectionId - collection id as string or number
733 * @param mixed $importFileName - The filename that holds the import data.
734 * @param array $options - optional - an array of options.
735 * <p>Options are :<br>
736 * 'type' - if type is not set or it's set to '' or null, the Header-Value format must be provided in the import file.<br>
737 * <p>
738 * <li> if set to 'documents', then the file's content must have its documents line by line. Each line will be interpreted as a document.</li>
739 * <li> if set to 'array' then the file's content must provide the documents as a list of documents instead of the above line by line.</li>
740 * <br>
741 * More info on how the import functionality works: <a href ="https://github.com/triAGENS/ArangoDB/wiki/HttpImport">https://github.com/triAGENS/ArangoDB/wiki/HttpImport</a>
742 * </p>
743 * <br>
744 * </li>
745 * <li>'createCollection' - If true, create the collection if it does not exist. Defaults to false </li>
746 * </p>
747 *
748 * @return array - returns an array with the server's response data from the import command
749 */
750 public function importFromFile(
751 $collectionId,
752 $importFileName,
753 array $options = []
754 )
755 {
756
757 $contents = file_get_contents($importFileName);
758 if ($contents === false) {
759 throw new ClientException('Input file "' . $importFileName . '" could not be found.');
760 }
761
762 return $this->import($collectionId, $contents, $options);
763 }
764
765
766 /**
767 * Import documents into a collection
768 *
769 * This will throw on all errors except insertion errors
770 *
771 *
772 * @param $collection mixed $collection - collection id as string or number
773 * @param string|array $importData - The data to import. This can be a string holding the data according to the type of import, or an array of documents
774 * @param array $options - optional - an array of options.
775 * <p>Options are :<br>
776 * <li>
777 * 'type' - if type is not set or it's set to '' or null, the Header-Value format must be provided in the import file.<br>
778 * <p>
779 * <li> if set to 'documents', then the file's content must have its documents line by line. Each line will be interpreted as a document.</li>
780 * <li> if set to 'array' then the file's content must provide the documents as a list of documents instead of the above line by line.</li>
781 * <br>
782 * More info on how the import functionality works: <a href ="https://github.com/triAGENS/ArangoDB/wiki/HttpImport">https://github.com/triAGENS/ArangoDB/wiki/HttpImport</a>
783 * </p>
784 * <br>
785 *
786 * </li>
787 * <li>'createCollection' - If true, create the collection if it does not exist. Defaults to false </li>
788 * </p>
789 *
790 * @return array
791 * @throws \triagens\ArangoDb\Exception
792 * @throws \triagens\ArangoDb\ClientException
793 */
794 public function import(
795 $collection,
796 $importData,
797 array $options = []
798 )
799 {
800 $collection = $this->makeCollection($collection);
801
802 $tmpContent = '';
803 if (is_array($importData)) {
804 foreach ($importData as $document) {
805 /** @var $document Document */
806 $tmpContent .= $document->toJson() . "\r\n";
807 }
808 $importData = $tmpContent;
809 unset($tmpContent);
810 $options['type'] = 'documents';
811 }
812
813 $this->createCollectionIfOptions($collection, $options);
814
815 $params = [
816 self::OPTION_COLLECTION => $collection
817 ];
818
819 if (array_key_exists('type', $options)) {
820 switch ($options['type']) {
821 case 'documents':
822 $params[self::OPTION_TYPE] = 'documents';
823 break;
824 case 'array':
825 $params[self::OPTION_TYPE] = 'array';
826 break;
827 }
828 }
829
830 $url = UrlHelper::appendParamsUrl(Urls::URL_IMPORT, $params);
831
832 $response = $this->getConnection()->post($url, $importData);
833
834 return $response->getJson();
835 }
836
837
838 /**
839 * Create a hash index
840 *
841 * @param string $collectionId - the collection id
842 * @param array $fields - an array of fields
843 * @param boolean $unique - whether the values in the index should be unique or not
844 * @param boolean $sparse - whether the index should be sparse
845 *
846 * @link https://docs.arangodb.com/HTTP/Indexes/Hash.html
847 *
848 * @return array - server response of the created index
849 * @throws \triagens\ArangoDb\Exception
850 */
851 public function createHashIndex($collectionId, array $fields, $unique = null, $sparse = null)
852 {
853 $indexOptions = [];
854
855 if ($unique) {
856 $indexOptions[self::OPTION_UNIQUE] = (bool) $unique;
857 }
858 if ($sparse) {
859 $indexOptions[self::OPTION_SPARSE] = (bool) $sparse;
860 }
861
862 return $this->index($collectionId, self::OPTION_HASH_INDEX, $fields, null, $indexOptions);
863 }
864
865 /**
866 * Create a fulltext index
867 *
868 * @param string $collectionId - the collection id
869 * @param array $fields - an array of fields
870 * @param int $minLength - the minimum length of words to index
871 *
872 * @link https://docs.arangodb.com/HTTP/Indexes/Fulltext.html
873 *
874 * @return array - server response of the created index
875 * @throws \triagens\ArangoDb\Exception
876 */
877 public function createFulltextIndex($collectionId, array $fields, $minLength = null)
878 {
879 $indexOptions = [];
880
881 if ($minLength) {
882 $indexOptions[self::OPTION_MIN_LENGTH] = $minLength;
883 }
884
885 return $this->index($collectionId, self::OPTION_FULLTEXT_INDEX, $fields, null, $indexOptions);
886 }
887
888 /**
889 * Create a skip-list index
890 *
891 * @param string $collectionId - the collection id
892 * @param array $fields - an array of fields
893 * @param bool $unique - whether the index is unique or not
894 * @param bool $sparse - whether the index should be sparse
895 *
896 * @link https://docs.arangodb.com/HTTP/Indexes/Skiplist.html
897 *
898 * @return array - server response of the created index
899 * @throws \triagens\ArangoDb\Exception
900 */
901 public function createSkipListIndex($collectionId, array $fields, $unique = null, $sparse = null)
902 {
903 $indexOptions = [];
904
905 if ($unique) {
906 $indexOptions[self::OPTION_UNIQUE] = (bool) $unique;
907 }
908 if ($sparse) {
909 $indexOptions[self::OPTION_SPARSE] = (bool) $sparse;
910 }
911
912 return $this->index($collectionId, self::OPTION_SKIPLIST_INDEX, $fields, null, $indexOptions);
913 }
914
915 /**
916 * Create a persistent index
917 *
918 * @param string $collectionId - the collection id
919 * @param array $fields - an array of fields
920 * @param bool $unique - whether the index is unique or not
921 * @param bool $sparse - whether the index should be sparse
922 *
923 * @link https://docs.arangodb.com/HTTP/Indexes/Persistent.html
924 *
925 * @return array - server response of the created index
926 * @throws \triagens\ArangoDb\Exception
927 */
928 public function createPersistentIndex($collectionId, array $fields, $unique = null, $sparse = null)
929 {
930 $indexOptions = [];
931
932 if ($unique) {
933 $indexOptions[self::OPTION_UNIQUE] = (bool) $unique;
934 }
935 if ($sparse) {
936 $indexOptions[self::OPTION_SPARSE] = (bool) $sparse;
937 }
938
939 return $this->index($collectionId, self::OPTION_PERSISTENT_INDEX, $fields, null, $indexOptions);
940 }
941
942 /**
943 * Create a geo index
944 *
945 * @param string $collectionId - the collection id
946 * @param array $fields - an array of fields
947 * @param boolean $geoJson - whether to use geoJson or not
948 * @param boolean $constraint - whether this is a constraint or not
949 * @param boolean $ignoreNull - whether to ignore null
950 *
951 * @link https://docs.arangodb.com/HTTP/Indexes/Geo.html
952 *
953 * @return array - server response of the created index
954 * @throws \triagens\ArangoDb\Exception
955 */
956 public function createGeoIndex($collectionId, array $fields, $geoJson = null, $constraint = null, $ignoreNull = null)
957 {
958 $indexOptions = [];
959
960 if ($geoJson) {
961 $indexOptions[self::OPTION_GEOJSON] = (bool) $geoJson;
962 }
963
964 if ($constraint) {
965 $indexOptions[self::OPTION_CONSTRAINT] = (bool) $constraint;
966 }
967
968 if ($ignoreNull) {
969 $indexOptions[self::OPTION_IGNORE_NULL] = $ignoreNull;
970 }
971
972 return $this->index($collectionId, self::OPTION_GEO_INDEX, $fields, null, $indexOptions);
973 }
974
975 /**
976 * Creates an index on a collection on the server
977 *
978 * This will create an index on the collection on the server and return its id
979 *
980 * This will throw if the index cannot be created
981 *
982 * @throws Exception
983 *
984 * @param mixed $collectionId - The id of the collection where the index is to be created
985 * @param string $type - index type: hash, skiplist, geo, fulltext, or persistent
986 * @param array $attributes - an array of attributes that can be defined like array('a') or array('a', 'b.c')
987 * @param bool $unique - true/false to create a unique index
988 * @param array $indexOptions - an associative array of options for the index like array('geoJson' => true, 'sparse' => false)
989 *
990 * @return array - server response of the created index
991 */
992 public function index($collectionId, $type = '', array $attributes = [], $unique = false, array $indexOptions = [])
993 {
994
995 $urlParams = [self::OPTION_COLLECTION => $collectionId];
996 $bodyParams = [
997 self::OPTION_TYPE => $type,
998 self::OPTION_FIELDS => $attributes,
999 ];
1000
1001 if ($unique !== null) {
1002 $bodyParams[self::OPTION_UNIQUE] = (bool) $unique;
1003 }
1004
1005 $bodyParams = array_merge($bodyParams, $indexOptions);
1006
1007 $url = UrlHelper::appendParamsUrl(Urls::URL_INDEX, $urlParams);
1008 $response = $this->getConnection()->post($url, $this->json_encode_wrapper($bodyParams));
1009
1010 $httpCode = $response->getHttpCode();
1011 switch ($httpCode) {
1012 case 404:
1013 throw new ClientException('Collection-identifier is unknown');
1014
1015 break;
1016 case 400:
1017 throw new ClientException('cannot create unique index due to documents violating uniqueness');
1018 break;
1019 }
1020
1021 return $response->getJson();
1022 }
1023
1024
1025 /**
1026 * Get the information about an index in a collection
1027 *
1028 * @param string $collection - the id of the collection
1029 * @param string $indexId - the id of the index
1030 *
1031 * @return array
1032 * @throws \triagens\ArangoDb\Exception
1033 * @throws \triagens\ArangoDb\ClientException
1034 */
1035 public function getIndex($collection, $indexId)
1036 {
1037 $url = UrlHelper::buildUrl(Urls::URL_INDEX, [$collection, $indexId]);
1038 $response = $this->getConnection()->get($url);
1039
1040 return $response->getJson();
1041 }
1042
1043
1044 /**
1045 * Get indexes of a collection
1046 *
1047 * This will throw if the collection cannot be fetched from the server
1048 *
1049 * @throws Exception
1050 *
1051 * @param mixed $collectionId - collection id as a string or number
1052 *
1053 * @return array $data - the indexes result-set from the server
1054 */
1055 public function getIndexes($collectionId)
1056 {
1057 $urlParams = [self::OPTION_COLLECTION => $collectionId];
1058 $url = UrlHelper::appendParamsUrl(Urls::URL_INDEX, $urlParams);
1059 $response = $this->getConnection()->get($url);
1060
1061 return $response->getJson();
1062 }
1063
1064 /**
1065 * Drop an index
1066 *
1067 * @throws Exception
1068 *
1069 * @param mixed $indexHandle - index handle (collection name / index id)
1070 *
1071 * @return bool - always true, will throw if there is an error
1072 */
1073 public function dropIndex($indexHandle)
1074 {
1075 $handle = explode('/', $indexHandle);
1076 $this->getConnection()->delete(UrlHelper::buildUrl(Urls::URL_INDEX, [$handle[0], $handle[1]]));
1077
1078 return true;
1079 }
1080
1081 /**
1082 * Get a random document from the collection.
1083 *
1084 * This will throw if the document cannot be fetched from the server
1085 *
1086 *
1087 * @throws Exception
1088 *
1089 * @param mixed $collectionId - collection id as string or number
1090 *
1091 * @return Document - the document fetched from the server
1092 * @since 1.2
1093 */
1094 public function any($collectionId)
1095 {
1096
1097 $data = [
1098 self::OPTION_COLLECTION => $collectionId,
1099 ];
1100
1101 $response = $this->getConnection()->put(Urls::URL_ANY, $this->json_encode_wrapper($data));
1102 $data = $response->getJson();
1103
1104 if ($data['document']) {
1105 return Document::createFromArray($data['document']);
1106 } else {
1107 return null;
1108 }
1109 }
1110
1111
1112 /**
1113 * Returns all documents of a collection
1114 *
1115 * @param mixed $collectionId - collection id as string or number
1116 * @param array $options - optional array of options.
1117 * <p>Options are :<br>
1118 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1119 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1120 * <p>
1121 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document.<br>
1122 * The difference is, that if you're returning a result set of documents, the getAll() is already called<br>
1123 * and the hidden attributes would not be applied to the attributes.<br>
1124 * </p>
1125 *
1126 * <li>'batchSize' - can optionally be used to tell the server to limit the number of results to be transferred in one batch</li>
1127 * <li>'skip' - Optional, The number of documents to skip in the query.</li>
1128 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1129 * </li>
1130 * </p>
1131 *
1132 * @return Cursor - documents
1133 * @throws \triagens\ArangoDb\Exception
1134 * @throws \triagens\ArangoDb\ClientException
1135 */
1136 public function all($collectionId, array $options = [])
1137 {
1138 $body = [
1139 self::OPTION_COLLECTION => $collectionId,
1140 ];
1141
1142 $body = $this->includeOptionsInBody(
1143 $options,
1144 $body,
1145 [
1146 self::OPTION_LIMIT => null,
1147 self::OPTION_SKIP => null,
1148 ]
1149 );
1150
1151 $response = $this->getConnection()->put(Urls::URL_ALL, $this->json_encode_wrapper($body));
1152
1153 return new Cursor($this->getConnection(), $response->getJson(), $options);
1154 }
1155
1156
1157 /**
1158 * Get the list of all documents' ids from a collection
1159 *
1160 * This will throw if the list cannot be fetched from the server
1161 *
1162 * @throws Exception
1163 *
1164 * @param mixed $collection - collection id as string or number
1165 *
1166 * @return array - ids of documents in the collection
1167 */
1168 public function getAllIds($collection)
1169 {
1170 $params = [
1171 self::OPTION_COLLECTION => $this->makeCollection($collection)
1172 ];
1173 $response = $this->getConnection()->put(Urls::URL_ALL_KEYS, $this->json_encode_wrapper($params));
1174
1175 $data = $response->getJson();
1176 if (!isset($data[Cursor::ENTRY_RESULT])) {
1177 throw new ClientException('Got an invalid document list from the server');
1178 }
1179
1180 $cursor = new Cursor($this->getConnection(), $response->getJson(), []);
1181 $ids = [];
1182 foreach ($cursor->getAll() as $location) {
1183 $ids[] = UrlHelper::getDocumentIdFromLocation($location);
1184 }
1185
1186 return $ids;
1187 }
1188
1189 /**
1190 * Get document(s) by specifying an example
1191 *
1192 * This will throw if the list cannot be fetched from the server
1193 *
1194 *
1195 * @throws Exception
1196 *
1197 * @param mixed $collectionId - collection id as string or number
1198 * @param mixed $document - the example document as a Document object or an array
1199 * @param array $options - optional, prior to v1.0.0 this was a boolean value for sanitize, since v1.0.0 it's an array of options.
1200 * <p>Options are :<br>
1201 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1202 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1203 * <p>
1204 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document. <br>
1205 * The difference is, that if you're returning a result set of documents, the getAll() is already called <br>
1206 * and the hidden attributes would not be applied to the attributes.<br>
1207 * </p>
1208 * </li>
1209 * <li>'batchSize' - can optionally be used to tell the server to limit the number of results to be transferred in one batch</li>
1210 * <li>'skip' - Optional, The number of documents to skip in the query.</li>
1211 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1212 * </p>
1213 *
1214 * @return cursor - Returns a cursor containing the result
1215 */
1216 public function byExample($collectionId, $document, array $options = [])
1217 {
1218 if (is_array($document)) {
1219 $document = Document::createFromArray($document, $options);
1220 }
1221
1222 if (!($document instanceof Document)) {
1223 throw new ClientException('Invalid example document specification');
1224 }
1225
1226 $body = [
1227 self::OPTION_COLLECTION => $collectionId,
1228 self::OPTION_EXAMPLE => $document->getAllAsObject(['_ignoreHiddenAttributes' => true])
1229 ];
1230
1231 $body = $this->includeOptionsInBody(
1232 $options,
1233 $body,
1234 [
1235 ConnectionOptions::OPTION_BATCHSIZE => $this->getConnectionOption(
1236 ConnectionOptions::OPTION_BATCHSIZE
1237 ),
1238 self::OPTION_LIMIT => null,
1239 self::OPTION_SKIP => null,
1240 ]
1241 );
1242
1243 $response = $this->getConnection()->put(Urls::URL_EXAMPLE, $this->json_encode_wrapper($body));
1244
1245 $options['isNew'] = false;
1246
1247 return new Cursor($this->getConnection(), $response->getJson(), $options);
1248 }
1249
1250 /**
1251 * Get the first document matching a given example.
1252 *
1253 * This will throw if the document cannot be fetched from the server
1254 *
1255 *
1256 * @throws Exception
1257 *
1258 * @param mixed $collectionId - collection id as string or number
1259 * @param mixed $document - the example document as a Document object or an array
1260 * @param array $options - optional, an array of options.
1261 * <p>Options are :<br>
1262 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1263 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1264 * <p>
1265 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document. <br>
1266 * The difference is, that if you're returning a result set of documents, the getAll() is already called <br>
1267 * and the hidden attributes would not be applied to the attributes.<br>
1268 * </p>
1269 * </li>
1270 * </p>
1271 *
1272 * @return Document - the document fetched from the server
1273 * @since 1.2
1274 */
1275 public function firstExample($collectionId, $document, array $options = [])
1276 {
1277 if (is_array($document)) {
1278 $document = Document::createFromArray($document, $options);
1279 }
1280
1281 if (!($document instanceof Document)) {
1282 throw new ClientException('Invalid example document specification');
1283 }
1284
1285 $data = [
1286 self::OPTION_COLLECTION => $collectionId,
1287 self::OPTION_EXAMPLE => $document->getAll(['_ignoreHiddenAttributes' => true])
1288 ];
1289
1290 $response = $this->getConnection()->put(Urls::URL_FIRST_EXAMPLE, $this->json_encode_wrapper($data));
1291 $data = $response->getJson();
1292
1293 $options['_isNew'] = false;
1294
1295 return Document::createFromArray($data['document'], $options);
1296 }
1297
1298
1299 /**
1300 * Get document(s) by a fulltext query
1301 *
1302 * This will find all documents from the collection that match the fulltext query specified in query.
1303 * In order to use the fulltext operator, a fulltext index must be defined for the collection and the specified attribute.
1304 *
1305 *
1306 * @throws Exception
1307 *
1308 * @param mixed $collection - collection id as string or number
1309 * @param mixed $attribute - The attribute that contains the texts.
1310 * @param mixed $query - The fulltext query.
1311 * @param array $options - optional, prior to v1.0.0 this was a boolean value for sanitize, since v1.0.0 it's an array of options.
1312 * <p>Options are :<br>
1313 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1314 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1315 * <p>
1316 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document. <br>
1317 * The difference is, that if you're returning a result set of documents, the getAll() is already called <br>
1318 * and the hidden attributes would not be applied to the attributes.<br>
1319 * </p>
1320 * </li>
1321 * <li>'batchSize' - can optionally be used to tell the server to limit the number of results to be transferred in one batch</li>
1322 * <li>'skip' - Optional, The number of documents to skip in the query.</li>
1323 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1324 * <li>'index' - If given, the identifier of the fulltext-index to use.</li>
1325 * </p>
1326 *
1327 * @return cursor - Returns a cursor containing the result
1328 */
1329 public function fulltext($collection, $attribute, $query, array $options = [])
1330 {
1331 $body = [
1332 self::OPTION_COLLECTION => $collection,
1333 self::OPTION_ATTRIBUTE => $attribute,
1334 self::OPTION_QUERY => $query,
1335 ];
1336
1337 $body = $this->includeOptionsInBody(
1338 $options,
1339 $body,
1340 [
1341 ConnectionOptions::OPTION_BATCHSIZE => $this->getConnectionOption(
1342 ConnectionOptions::OPTION_BATCHSIZE
1343 ),
1344 self::OPTION_LIMIT => null,
1345 self::OPTION_SKIP => null,
1346 self::OPTION_INDEX => null,
1347 ]
1348 );
1349
1350 $response = $this->getConnection()->put(Urls::URL_FULLTEXT, $this->json_encode_wrapper($body));
1351
1352 $options['isNew'] = false;
1353
1354 return new Cursor($this->getConnection(), $response->getJson(), $options);
1355 }
1356
1357
1358 /**
1359 * Update document(s) matching a given example
1360 *
1361 * This will update the document(s) on the server
1362 *
1363 * This will throw if the document cannot be updated
1364 *
1365 * @throws Exception
1366 *
1367 * @param mixed $collectionId - collection id as string or number
1368 * @param mixed $example - the example document as a Document object or an array
1369 * @param mixed $newValue - patch document or array which contains the attributes and values to be updated
1370 * @param mixed $options - optional, array of options (see below) or the boolean value for $policy (for compatibility prior to version 1.1 of this method)
1371 * <p>Options are :
1372 * <li>'keepNull' - can be used to instruct ArangoDB to delete existing attributes instead setting their values to null. Defaults to true (keep attributes when set to null)</li>
1373 * <li>'waitForSync' - can be used to force synchronisation of the document update operation to disk even in case that the waitForSync flag had been disabled for the entire collection</li>
1374 * <li>'limit' - can be used set a limit on how many documents to update at most. If limit is specified but is less than the number of documents in the collection, it is undefined which of the documents will be updated.</li>
1375 * </p>
1376 *
1377 * @return bool - always true, will throw if there is an error
1378 * @since 1.2
1379 */
1380 public function updateByExample($collectionId, $example, $newValue, array $options = [])
1381 {
1382 if (is_array($example)) {
1383 $example = Document::createFromArray($example);
1384 }
1385
1386 if (is_array($newValue)) {
1387 $newValue = Document::createFromArray($newValue);
1388 }
1389
1390 $body = [
1391 self::OPTION_COLLECTION => $collectionId,
1392 self::OPTION_EXAMPLE => $example->getAllAsObject(['_ignoreHiddenAttributes' => true]),
1393 self::OPTION_NEW_VALUE => $newValue->getAllAsObject(['_ignoreHiddenAttributes' => true])
1394 ];
1395
1396 $body = $this->includeOptionsInBody(
1397 $options,
1398 $body,
1399 [
1400 ConnectionOptions::OPTION_WAIT_SYNC => $this->getConnectionOption(
1401 ConnectionOptions::OPTION_WAIT_SYNC
1402 ),
1403 'keepNull' => true,
1404 self::OPTION_LIMIT => null,
1405 ]
1406 );
1407
1408 $response = $this->getConnection()->put(Urls::URL_UPDATE_BY_EXAMPLE, $this->json_encode_wrapper($body));
1409
1410 $responseArray = $response->getJson();
1411
1412 if ($responseArray['error'] === true) {
1413 throw new ClientException('Invalid example document specification');
1414 }
1415
1416 return $responseArray['updated'];
1417 }
1418
1419
1420 /**
1421 * Replace document(s) matching a given example
1422 *
1423 * This will replace the document(s) on the server
1424 *
1425 * This will throw if the document cannot be replaced
1426 *
1427 * @throws Exception
1428 *
1429 * @param mixed $collectionId - collection id as string or number
1430 * @param mixed $example - the example document as a Document object or an array
1431 * @param mixed $newValue - patch document or array which contains the attributes and values to be replaced
1432 * @param mixed $options - optional, array of options (see below) or the boolean value for $policy (for compatibility prior to version 1.1 of this method)
1433 * <p>Options are :
1434 * <li>'keepNull' - can be used to instruct ArangoDB to delete existing attributes instead setting their values to null. Defaults to true (keep attributes when set to null)</li>
1435 * <li>'waitForSync' - can be used to force synchronisation of the document replace operation to disk even in case that the waitForSync flag had been disabled for the entire collection</li>
1436 * <li>'limit' - can be used set a limit on how many documents to replace at most. If limit is specified but is less than the number of documents in the collection, it is undefined which of the documents will be replaced.</li>
1437 * </p>
1438 *
1439 * @return bool - always true, will throw if there is an error
1440 * @since 1.2
1441 */
1442 public function replaceByExample($collectionId, $example, $newValue, array $options = [])
1443 {
1444 if (is_array($example)) {
1445 $example = Document::createFromArray($example);
1446 }
1447
1448 if (is_array($newValue)) {
1449 $newValue = Document::createFromArray($newValue);
1450 }
1451
1452 $body = [
1453 self::OPTION_COLLECTION => $collectionId,
1454 self::OPTION_EXAMPLE => $example->getAllAsObject(['_ignoreHiddenAttributes' => true]),
1455 self::OPTION_NEW_VALUE => $newValue->getAllAsObject(['_ignoreHiddenAttributes' => true])
1456 ];
1457
1458 $body = $this->includeOptionsInBody(
1459 $options,
1460 $body,
1461 [
1462 ConnectionOptions::OPTION_WAIT_SYNC => $this->getConnectionOption(
1463 ConnectionOptions::OPTION_WAIT_SYNC
1464 ),
1465 'keepNull' => true,
1466 self::OPTION_LIMIT => null,
1467 ]
1468 );
1469
1470 $response = $this->getConnection()->put(Urls::URL_REPLACE_BY_EXAMPLE, $this->json_encode_wrapper($body));
1471
1472 $responseArray = $response->getJson();
1473
1474 if ($responseArray['error'] === true) {
1475 throw new ClientException('Invalid example document specification');
1476 }
1477
1478 return $responseArray['replaced'];
1479 }
1480
1481
1482 /**
1483 * Remove document(s) by specifying an example
1484 *
1485 * This will throw on any error
1486 *
1487 * @throws Exception
1488 *
1489 * @param mixed $collectionId - collection id as string or number
1490 * @param mixed $document - the example document as a Document object or an array
1491 * @param array $options - optional - an array of options.
1492 * <p>Options are :<br>
1493 * <li>
1494 * 'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk.<br>
1495 * If this is not specified, then the collection's default sync behavior will be applied.
1496 * </li>
1497 * </p>
1498 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1499 *
1500 * @return int - number of documents that were deleted
1501 *
1502 * @since 1.2
1503 */
1504 public function removeByExample($collectionId, $document, array $options = [])
1505 {
1506 if (is_array($document)) {
1507 $document = Document::createFromArray($document, $options);
1508 }
1509
1510 if (!($document instanceof Document)) {
1511 throw new ClientException('Invalid example document specification');
1512 }
1513
1514 $body = [
1515 self::OPTION_COLLECTION => $collectionId,
1516 self::OPTION_EXAMPLE => $document->getAllAsObject(['_ignoreHiddenAttributes' => true])
1517 ];
1518
1519 $body = $this->includeOptionsInBody(
1520 $options,
1521 $body,
1522 [
1523 ConnectionOptions::OPTION_WAIT_SYNC => $this->getConnectionOption(
1524 ConnectionOptions::OPTION_WAIT_SYNC
1525 ),
1526 self::OPTION_LIMIT => null,
1527 ]
1528 );
1529
1530 $response = $this->getConnection()->put(Urls::URL_REMOVE_BY_EXAMPLE, $this->json_encode_wrapper($body));
1531
1532 $responseArray = $response->getJson();
1533
1534 if ($responseArray['error'] === true) {
1535 throw new ClientException('Invalid example document specification');
1536 }
1537
1538 return $responseArray['deleted'];
1539 }
1540
1541 /**
1542 * Remove document(s) by specifying an array of keys
1543 *
1544 * This will throw on any error
1545 *
1546 * @throws Exception
1547 *
1548 * @param mixed $collectionId - collection id as string or number
1549 * @param array $keys - array of document keys
1550 * @param array $options - optional - an array of options.
1551 * <p>Options are :<br>
1552 * <li>
1553 * 'waitForSync' - if set to true, then all removal operations will instantly be synchronised to disk.<br>
1554 * If this is not specified, then the collection's default sync behavior will be applied.
1555 * </li>
1556 * </p>
1557 *
1558 * @return array - an array containing an attribute 'removed' with the number of documents that were deleted, an an array 'ignored' with the number of not removed keys/documents
1559 *
1560 * @since 2.6
1561 */
1562 public function removeByKeys($collectionId, array $keys, array $options = [])
1563 {
1564 $body = [
1565 self::OPTION_COLLECTION => $collectionId,
1566 self::OPTION_KEYS => $keys
1567 ];
1568
1569 $body = $this->includeOptionsInBody(
1570 $options,
1571 $body,
1572 [
1573 ConnectionOptions::OPTION_WAIT_SYNC => $this->getConnectionOption(
1574 ConnectionOptions::OPTION_WAIT_SYNC
1575 )
1576 ]
1577 );
1578
1579 $response = $this->getConnection()->put(Urls::URL_REMOVE_BY_KEYS, $this->json_encode_wrapper($body));
1580
1581 $responseArray = $response->getJson();
1582
1583 return [
1584 'removed' => $responseArray['removed'],
1585 'ignored' => $responseArray['ignored']
1586 ];
1587 }
1588
1589
1590 /**
1591 * Bulk lookup documents by specifying an array of keys
1592 *
1593 * This will throw on any error
1594 *
1595 * @throws Exception
1596 *
1597 * @param mixed $collectionId - collection id as string or number
1598 * @param array $keys - array of document keys
1599 * @param array $options - optional array of options.
1600 * <p>Options are :<br>
1601 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1602 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1603 * </p>
1604 *
1605 * @return array - an array containing all documents found for the keys specified.
1606 * note that if for a given key not document is found, it will not be returned nor will the document's non-existence be reported.
1607 *
1608 * @since 2.6
1609 */
1610 public function lookupByKeys($collectionId, array $keys, array $options = [])
1611 {
1612 $body = [
1613 self::OPTION_COLLECTION => $collectionId,
1614 self::OPTION_KEYS => $keys
1615 ];
1616
1617 $response = $this->getConnection()->put(Urls::URL_LOOKUP_BY_KEYS, $this->json_encode_wrapper($body));
1618
1619 $responseArray = $response->getJson();
1620
1621 $result = [];
1622 foreach ($responseArray['documents'] as $document) {
1623 $result[] = Document::createFromArray($document, $options);
1624 }
1625
1626 return $result;
1627 }
1628
1629
1630 /**
1631 * Get document(s) by specifying range
1632 *
1633 * This will throw if the list cannot be fetched from the server
1634 *
1635 *
1636 * @throws Exception
1637 *
1638 * @param mixed $collectionId - collection id as string or number
1639 * @param string $attribute - the attribute path , like 'a', 'a.b', etc...
1640 * @param mixed $left - The lower bound.
1641 * @param mixed $right - The upper bound.
1642 * @param array $options - optional array of options.
1643 * <p>Options are :<br>
1644 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1645 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1646 * <p>
1647 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document.<br>
1648 * The difference is, that if you're returning a result set of documents, the getAll() is already called<br>
1649 * and the hidden attributes would not be applied to the attributes.<br>
1650 * </p>
1651 *
1652 * <li>'closed' - If true, use interval including left and right, otherwise exclude right, but include left.
1653 * <li>'batchSize' - can optionally be used to tell the server to limit the number of results to be transferred in one batch</li>
1654 * <li>'skip' - Optional, The number of documents to skip in the query.</li>
1655 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1656 * </li>
1657 * </p>
1658 *
1659 * @return Cursor - documents matching the example [0...n]
1660 */
1661 public function range($collectionId, $attribute, $left, $right, array $options = [])
1662 {
1663 if ($attribute === '') {
1664 throw new ClientException('Invalid attribute specification');
1665 }
1666
1667 if (strpos($attribute, '.') !== false) {
1668 // split attribute name
1669 $attribute = explode('.', $attribute);
1670 }
1671
1672 $body = [
1673 self::OPTION_COLLECTION => $collectionId,
1674 self::OPTION_ATTRIBUTE => $attribute,
1675 self::OPTION_LEFT => $left,
1676 self::OPTION_RIGHT => $right
1677 ];
1678
1679 $body = $this->includeOptionsInBody(
1680 $options,
1681 $body,
1682 [
1683 self::OPTION_CLOSED => null,
1684 self::OPTION_LIMIT => null,
1685 self::OPTION_SKIP => null,
1686 ]
1687 );
1688
1689 $response = $this->getConnection()->put(Urls::URL_RANGE, $this->json_encode_wrapper($body));
1690
1691 return new Cursor($this->getConnection(), $response->getJson(), $options);
1692 }
1693
1694
1695 /**
1696 * Get document(s) by specifying near
1697 *
1698 * This will throw if the list cannot be fetched from the server
1699 *
1700 *
1701 * @throws Exception
1702 *
1703 * @param mixed $collectionId - collection id as string or number
1704 * @param double $latitude - The latitude of the coordinate.
1705 * @param double $longitude - The longitude of the coordinate.
1706 * @param array $options - optional array of options.
1707 * <p>Options are :<br>
1708 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1709 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1710 * <p>
1711 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document. <br>
1712 * The difference is, that if you're returning a result set of documents, the getAll() is already called <br>
1713 * and the hidden attributes would not be applied to the attributes.<br>
1714 * </p>
1715 *
1716 * <li>'distance' - If given, the attribute key used to store the distance. (optional)
1717 * <li>'batchSize' - can optionally be used to tell the server to limit the number of results to be transferred in one batch</li>
1718 * <li>'skip' - Optional, The number of documents to skip in the query.</li>
1719 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1720 * </li>
1721 * </p>
1722 *
1723 * @return Cursor - documents matching the example [0...n]
1724 */
1725 public function near($collectionId, $latitude, $longitude, array $options = [])
1726 {
1727 $body = [
1728 self::OPTION_COLLECTION => $collectionId,
1729 self::OPTION_LATITUDE => $latitude,
1730 self::OPTION_LONGITUDE => $longitude
1731 ];
1732
1733 $body = $this->includeOptionsInBody(
1734 $options,
1735 $body,
1736 [
1737 self::OPTION_DISTANCE => null,
1738 self::OPTION_LIMIT => null,
1739 self::OPTION_SKIP => null,
1740 ]
1741 );
1742
1743 $response = $this->getConnection()->put(Urls::URL_NEAR, $this->json_encode_wrapper($body));
1744
1745 return new Cursor($this->getConnection(), $response->getJson(), $options);
1746 }
1747
1748
1749 /**
1750 * Get document(s) by specifying within
1751 *
1752 * This will throw if the list cannot be fetched from the server
1753 *
1754 *
1755 * @throws Exception
1756 *
1757 * @param mixed $collectionId - collection id as string or number
1758 * @param double $latitude - The latitude of the coordinate.
1759 * @param double $longitude - The longitude of the coordinate.
1760 * @param int $radius - The maximal radius (in meters).
1761 * @param array $options - optional array of options.
1762 * <p>Options are :<br>
1763 * <li>'_sanitize' - True to remove _id and _rev attributes from result documents. Defaults to false.</li>
1764 * <li>'_hiddenAttributes' - Set an array of hidden attributes for created documents.
1765 * <p>
1766 * This is actually the same as setting hidden attributes using setHiddenAttributes() on a document.<br>
1767 * The difference is, that if you're returning a result set of documents, the getAll() is already called <br>
1768 * and the hidden attributes would not be applied to the attributes.<br>
1769 * </p>
1770 *
1771 * <li>'distance' - If given, the attribute key used to store the distance. (optional)
1772 * <li>'batchSize' - can optionally be used to tell the server to limit the number of results to be transferred in one batch</li>
1773 * <li>'skip' - Optional, The number of documents to skip in the query.</li>
1774 * <li>'limit' - Optional, The maximal amount of documents to return. 'skip' is applied before the limit restriction.</li>
1775 * </li>
1776 * </p>
1777 *
1778 * @return Cursor - documents matching the example [0...n]
1779 */
1780 public function within($collectionId, $latitude, $longitude, $radius, array $options = [])
1781 {
1782 $body = [
1783 self::OPTION_COLLECTION => $collectionId,
1784 self::OPTION_LATITUDE => $latitude,
1785 self::OPTION_LONGITUDE => $longitude,
1786 self::OPTION_RADIUS => $radius
1787 ];
1788
1789 $body = $this->includeOptionsInBody(
1790 $options,
1791 $body,
1792 [
1793 self::OPTION_DISTANCE => null,
1794 self::OPTION_LIMIT => null,
1795 self::OPTION_SKIP => null,
1796 ]
1797 );
1798
1799 $response = $this->getConnection()->put(Urls::URL_WITHIN, $this->json_encode_wrapper($body));
1800
1801 return new Cursor($this->getConnection(), $response->getJson(), $options);
1802 }
1803
1804 /**
1805 * @param $collection
1806 * @param $options
1807 */
1808 private function createCollectionIfOptions($collection, $options)
1809 {
1810 if (!array_key_exists(CollectionHandler::OPTION_CREATE_COLLECTION, $options)) {
1811 return;
1812 }
1813
1814 $value = (bool) $options[CollectionHandler::OPTION_CREATE_COLLECTION];
1815
1816 if (!$value) {
1817 return;
1818 }
1819
1820 $collectionOptions = [];
1821 if (isset($options['createCollectionType'])) {
1822 if ($options['createCollectionType'] === 'edge' ||
1823 $options['createCollectionType'] === 3
1824 ) {
1825 // edge collection
1826 $collectionOptions['type'] = 3;
1827 } else {
1828 // document collection
1829 $collectionOptions['type'] = 2;
1830 }
1831 }
1832
1833 try {
1834 // attempt to create the collection
1835 $this->create($collection, $collectionOptions);
1836 } catch (Exception $e) {
1837 // collection may have existed already
1838 }
1839 }
1840 }
1841