diff --git a/src/main/java/com/arangodb/ArangoCollection.java b/src/main/java/com/arangodb/ArangoCollection.java index 15acb6e13..298182b85 100644 --- a/src/main/java/com/arangodb/ArangoCollection.java +++ b/src/main/java/com/arangodb/ArangoCollection.java @@ -528,6 +528,19 @@ MultiDocumentEntity> deleteDocuments( */ IndexEntity ensureTtlIndex(Iterable fields, TtlIndexOptions options) throws ArangoDBException; + /** + * Creates a ZKD multi-dimensional index for the collection, if it does not already exist. + * Note that zkd indexes are an experimental feature in ArangoDB 3.9. + * + * @param fields A list of attribute paths + * @param options Additional options, can be null + * @return information about the index + * @throws ArangoDBException + * @see API Documentation + * @since ArangoDB 3.9 + */ + IndexEntity ensureZKDIndex(Iterable fields, ZKDIndexOptions options) throws ArangoDBException; + /** * Fetches a list of all indexes on this collection. * diff --git a/src/main/java/com/arangodb/async/ArangoCollectionAsync.java b/src/main/java/com/arangodb/async/ArangoCollectionAsync.java index dab91f734..e0d81b44c 100644 --- a/src/main/java/com/arangodb/async/ArangoCollectionAsync.java +++ b/src/main/java/com/arangodb/async/ArangoCollectionAsync.java @@ -270,10 +270,10 @@ CompletableFuture> updateDocument( * to patch (the patch document). All attributes from the patch document will be added to the existing document if * they do not yet exist, and overwritten in the existing document if they do exist there. * - * @param key The key of the document - * @param value A representation of a single document (POJO, VPackSlice or String for Json) - * @param options Additional options, can be null - * @param returnType Type of the returned newDocument and/or oldDocument + * @param key The key of the document + * @param value A representation of a single document (POJO, VPackSlice or String for Json) + * @param options Additional options, can be null + * @param returnType Type of the returned newDocument and/or oldDocument * @return information about the document * @see API * Documentation @@ -497,6 +497,18 @@ CompletableFuture ensureFulltextIndex( */ CompletableFuture ensureTtlIndex(Iterable fields, TtlIndexOptions options); + /** + * Creates a ZKD multi-dimensional index for the collection, if it does not already exist. + * Note that zkd indexes are an experimental feature in ArangoDB 3.9. + * + * @param fields A list of attribute paths + * @param options Additional options, can be null + * @return information about the index + * @see API Documentation + * @since ArangoDB 3.9 + */ + CompletableFuture ensureZKDIndex(final Iterable fields, final ZKDIndexOptions options); + /** * Returns all indexes of the collection * diff --git a/src/main/java/com/arangodb/async/internal/ArangoCollectionAsyncImpl.java b/src/main/java/com/arangodb/async/internal/ArangoCollectionAsyncImpl.java index 33cf69687..62bbc2dca 100644 --- a/src/main/java/com/arangodb/async/internal/ArangoCollectionAsyncImpl.java +++ b/src/main/java/com/arangodb/async/internal/ArangoCollectionAsyncImpl.java @@ -301,6 +301,13 @@ public CompletableFuture ensureTtlIndex(Iterable fields, Tt return executor.execute(createTtlIndexRequest(fields, options), IndexEntity.class); } + @Override + public CompletableFuture ensureZKDIndex( + final Iterable fields, + final ZKDIndexOptions options) { + return executor.execute(createZKDIndexRequest(fields, options), IndexEntity.class); + } + @Override public CompletableFuture> getIndexes() { return executor.execute(getIndexesRequest(), getIndexesResponseDeserializer()); diff --git a/src/main/java/com/arangodb/entity/IndexType.java b/src/main/java/com/arangodb/entity/IndexType.java index 9440063aa..062f35ff1 100644 --- a/src/main/java/com/arangodb/entity/IndexType.java +++ b/src/main/java/com/arangodb/entity/IndexType.java @@ -25,5 +25,5 @@ * @author Heiko Kernbach */ public enum IndexType { - primary, hash, skiplist, persistent, geo, geo1, geo2, fulltext, edge, ttl + primary, hash, skiplist, persistent, geo, geo1, geo2, fulltext, edge, ttl, zkd } diff --git a/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java b/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java index 907639bbb..1822a1dfe 100644 --- a/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java +++ b/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java @@ -295,6 +295,12 @@ public IndexEntity ensureTtlIndex(final Iterable fields, final TtlIndexO return executor.execute(createTtlIndexRequest(fields, options), IndexEntity.class); } + @Override + public IndexEntity ensureZKDIndex(final Iterable fields, final ZKDIndexOptions options) + throws ArangoDBException { + return executor.execute(createZKDIndexRequest(fields, options), IndexEntity.class); + } + @Override public Collection getIndexes() throws ArangoDBException { return executor.execute(getIndexesRequest(), getIndexesResponseDeserializer()); diff --git a/src/main/java/com/arangodb/internal/InternalArangoCollection.java b/src/main/java/com/arangodb/internal/InternalArangoCollection.java index 7b40925dd..ae590fe12 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoCollection.java +++ b/src/main/java/com/arangodb/internal/InternalArangoCollection.java @@ -596,6 +596,15 @@ protected Request createTtlIndexRequest(final Iterable fields, final Ttl return request; } + protected Request createZKDIndexRequest( + final Iterable fields, final ZKDIndexOptions options) { + final Request request = request(db.dbName(), RequestType.POST, PATH_API_INDEX); + request.putQueryParam(COLLECTION, name); + request.setBody(util().serialize(OptionsBuilder.build(options != null ? options : + new ZKDIndexOptions().fieldValueTypes(ZKDIndexOptions.FieldValueTypes.DOUBLE), fields))); + return request; + } + protected Request getIndexesRequest() { final Request request = request(db.dbName(), RequestType.GET, PATH_API_INDEX); request.putQueryParam(COLLECTION, name); diff --git a/src/main/java/com/arangodb/internal/velocypack/VPackDeserializers.java b/src/main/java/com/arangodb/internal/velocypack/VPackDeserializers.java index 8308c4b7a..da6ff99f4 100644 --- a/src/main/java/com/arangodb/internal/velocypack/VPackDeserializers.java +++ b/src/main/java/com/arangodb/internal/velocypack/VPackDeserializers.java @@ -56,6 +56,7 @@ import com.arangodb.entity.arangosearch.analyzer.StopwordsAnalyzer; import com.arangodb.entity.arangosearch.analyzer.TextAnalyzer; import com.arangodb.model.CollectionSchema; +import com.arangodb.model.ZKDIndexOptions; import com.arangodb.velocypack.VPackDeserializer; import com.arangodb.velocypack.VPackParser; import com.arangodb.velocypack.VPackSlice; @@ -332,4 +333,8 @@ protected static FieldLink deserializeField(final Entry fiel return collectionValidation; }; + public static final VPackDeserializer ZKD_FIELD_VALUE_TYPES = + (parent, vpack, context) -> ZKDIndexOptions.FieldValueTypes.valueOf(vpack.getAsString().toUpperCase(Locale.ENGLISH)); + + } diff --git a/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java b/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java index 723a696b2..a3ad3f4c4 100644 --- a/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java +++ b/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java @@ -30,6 +30,7 @@ import com.arangodb.internal.velocystream.internal.JwtAuthenticationRequest; import com.arangodb.model.CollectionSchema; import com.arangodb.model.TraversalOptions; +import com.arangodb.model.ZKDIndexOptions; import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions; import com.arangodb.velocypack.VPackModule; import com.arangodb.velocypack.VPackParserModule; @@ -70,6 +71,7 @@ public > void setup(final C context) { context.registerSerializer(ArangoSearchProperties.class, VPackSerializers.ARANGO_SEARCH_PROPERTIES); context.registerSerializer(ConsolidationType.class, VPackSerializers.CONSOLIDATE_TYPE); context.registerSerializer(CollectionSchema.class, VPackSerializers.COLLECTION_VALIDATION); + context.registerSerializer(ZKDIndexOptions.FieldValueTypes.class, VPackSerializers.ZKD_FIELD_VALUE_TYPES); context.registerDeserializer(Response.class, VPackDeserializers.RESPONSE); context.registerDeserializer(CollectionType.class, VPackDeserializers.COLLECTION_TYPE); @@ -89,6 +91,7 @@ public > void setup(final C context) { context.registerDeserializer(ArangoSearchPropertiesEntity.class, VPackDeserializers.ARANGO_SEARCH_PROPERTIES_ENTITY); context.registerDeserializer(ConsolidationPolicy.class, VPackDeserializers.CONSOLIDATE); context.registerDeserializer(CollectionSchema.class, VPackDeserializers.COLLECTION_VALIDATION); + context.registerDeserializer(ZKDIndexOptions.FieldValueTypes.class, VPackDeserializers.ZKD_FIELD_VALUE_TYPES); } @Override diff --git a/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java b/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java index 1d95b211f..1769eba19 100644 --- a/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java +++ b/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java @@ -42,6 +42,7 @@ import com.arangodb.model.CollectionSchema; import com.arangodb.model.TraversalOptions; import com.arangodb.model.TraversalOptions.Order; +import com.arangodb.model.ZKDIndexOptions; import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions; import com.arangodb.velocypack.VPackBuilder; import com.arangodb.velocypack.VPackParser; @@ -285,4 +286,7 @@ private static void serializeFieldLinks(final VPackBuilder builder, final Collec context.serialize(builder, attribute, doc); }; + public static final VPackSerializer ZKD_FIELD_VALUE_TYPES = + (builder, attribute, value, context) -> builder.add(attribute, value.name().toLowerCase(Locale.ENGLISH)); + } diff --git a/src/main/java/com/arangodb/model/OptionsBuilder.java b/src/main/java/com/arangodb/model/OptionsBuilder.java index 6533bfa0d..c9da750cf 100644 --- a/src/main/java/com/arangodb/model/OptionsBuilder.java +++ b/src/main/java/com/arangodb/model/OptionsBuilder.java @@ -65,6 +65,10 @@ public static TtlIndexOptions build(final TtlIndexOptions options, final Iterabl return options.fields(fields); } + public static ZKDIndexOptions build(final ZKDIndexOptions options, final Iterable fields) { + return options.fields(fields); + } + public static CollectionCreateOptions build(final CollectionCreateOptions options, final String name) { return options.name(name); } diff --git a/src/main/java/com/arangodb/model/ZKDIndexOptions.java b/src/main/java/com/arangodb/model/ZKDIndexOptions.java new file mode 100644 index 000000000..4e570a9ad --- /dev/null +++ b/src/main/java/com/arangodb/model/ZKDIndexOptions.java @@ -0,0 +1,93 @@ +/* + * DISCLAIMER + * + * Copyright 2016 ArangoDB GmbH, Cologne, Germany + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb.model; + +import com.arangodb.entity.IndexType; + +/** + * @author Michele Rastelli + * @see API Documentation + * @since ArangoDB 3.9 + */ +public class ZKDIndexOptions extends IndexOptions { + + private Iterable fields; + protected final IndexType type = IndexType.zkd; + private Boolean unique; + private FieldValueTypes fieldValueTypes; + + public ZKDIndexOptions() { + super(); + } + + @Override + protected ZKDIndexOptions getThis() { + return this; + } + + protected Iterable getFields() { + return fields; + } + + /** + * @param fields A list of attribute paths + * @return options + */ + protected ZKDIndexOptions fields(final Iterable fields) { + this.fields = fields; + return this; + } + + protected IndexType getType() { + return type; + } + + public Boolean getUnique() { + return unique; + } + + /** + * @param unique if true, then create a unique index + * @return options + */ + public ZKDIndexOptions unique(final Boolean unique) { + this.unique = unique; + return this; + } + + public FieldValueTypes getFieldValueTypes() { + return fieldValueTypes; + } + + /** + * @param fieldValueTypes must be {@link FieldValueTypes#DOUBLE}, currently only doubles are supported as values. + * @return options + */ + public ZKDIndexOptions fieldValueTypes(final FieldValueTypes fieldValueTypes) { + this.fieldValueTypes = fieldValueTypes; + return this; + } + + public enum FieldValueTypes { + DOUBLE + } + +} diff --git a/src/test/java/com/arangodb/ArangoCollectionTest.java b/src/test/java/com/arangodb/ArangoCollectionTest.java index 06e5fb61b..e704109a0 100644 --- a/src/test/java/com/arangodb/ArangoCollectionTest.java +++ b/src/test/java/com/arangodb/ArangoCollectionTest.java @@ -35,24 +35,8 @@ import com.arangodb.entity.MultiDocumentEntity; import com.arangodb.entity.Permissions; import com.arangodb.entity.ShardEntity; -import com.arangodb.model.CollectionCreateOptions; -import com.arangodb.model.CollectionPropertiesOptions; -import com.arangodb.model.CollectionSchema; -import com.arangodb.model.DocumentCreateOptions; -import com.arangodb.model.DocumentDeleteOptions; -import com.arangodb.model.DocumentExistsOptions; -import com.arangodb.model.DocumentImportOptions; +import com.arangodb.model.*; import com.arangodb.model.DocumentImportOptions.OnDuplicate; -import com.arangodb.model.DocumentReadOptions; -import com.arangodb.model.DocumentReplaceOptions; -import com.arangodb.model.DocumentUpdateOptions; -import com.arangodb.model.FulltextIndexOptions; -import com.arangodb.model.GeoIndexOptions; -import com.arangodb.model.HashIndexOptions; -import com.arangodb.model.OverwriteMode; -import com.arangodb.model.PersistentIndexOptions; -import com.arangodb.model.SkiplistIndexOptions; -import com.arangodb.model.TtlIndexOptions; import com.arangodb.util.MapBuilder; import com.arangodb.velocypack.VPackSlice; import com.fasterxml.jackson.core.JsonProcessingException; @@ -1429,6 +1413,55 @@ public void createPersistentIndexWithOptions() { assertThat(indexResult.getName(), is(name)); } + @Test + public void createZKDIndex() { + assumeTrue(isAtLeastVersion(3, 9)); + collection.truncate(); + String f1 = "field-" + rnd(); + String f2 = "field-" + rnd(); + final Collection fields = Arrays.asList(f1, f2); + + final IndexEntity indexResult = collection.ensureZKDIndex(fields, null); + assertThat(indexResult, is(notNullValue())); + assertThat(indexResult.getConstraint(), is(nullValue())); + assertThat(indexResult.getFields(), hasItem(f1)); + assertThat(indexResult.getFields(), hasItem(f2)); + assertThat(indexResult.getId(), startsWith(COLLECTION_NAME)); + assertThat(indexResult.getIsNewlyCreated(), is(true)); + assertThat(indexResult.getMinLength(), is(nullValue())); + assertThat(indexResult.getType(), is(IndexType.zkd)); + assertThat(indexResult.getUnique(), is(false)); + collection.deleteIndex(indexResult.getId()); + } + + @Test + public void createZKDIndexWithOptions() { + assumeTrue(isAtLeastVersion(3, 9)); + collection.truncate(); + + String name = "ZKDIndex-" + rnd(); + final ZKDIndexOptions options = new ZKDIndexOptions() + .name(name) + .fieldValueTypes(ZKDIndexOptions.FieldValueTypes.DOUBLE); + + String f1 = "field-" + rnd(); + String f2 = "field-" + rnd(); + + final Collection fields = Arrays.asList(f1, f2); + final IndexEntity indexResult = collection.ensureZKDIndex(fields, options); + assertThat(indexResult, is(notNullValue())); + assertThat(indexResult.getConstraint(), is(nullValue())); + assertThat(indexResult.getFields(), hasItem(f1)); + assertThat(indexResult.getFields(), hasItem(f2)); + assertThat(indexResult.getId(), startsWith(COLLECTION_NAME)); + assertThat(indexResult.getIsNewlyCreated(), is(true)); + assertThat(indexResult.getMinLength(), is(nullValue())); + assertThat(indexResult.getType(), is(IndexType.zkd)); + assertThat(indexResult.getUnique(), is(false)); + assertThat(indexResult.getName(), is(name)); + collection.deleteIndex(indexResult.getId()); + } + @Test public void indexEstimates() { assumeTrue(isAtLeastVersion(3, 8)); diff --git a/src/test/java/com/arangodb/async/ArangoCollectionTest.java b/src/test/java/com/arangodb/async/ArangoCollectionTest.java index 110f744d8..2eee24c53 100644 --- a/src/test/java/com/arangodb/async/ArangoCollectionTest.java +++ b/src/test/java/com/arangodb/async/ArangoCollectionTest.java @@ -1112,6 +1112,54 @@ public void createTtlIndexWithOptions() throws ExecutionException, InterruptedEx assertThat(indexResult.getName(), is("myTtlIndex")); } + @Test + public void createZKDIndex() throws InterruptedException, ExecutionException { + assumeTrue(isAtLeastVersion(3, 9)); + db.collection(COLLECTION_NAME).truncate().get(); + + final Collection fields = new ArrayList<>(); + fields.add("a"); + fields.add("b"); + IndexEntity indexResult = db.collection(COLLECTION_NAME).ensureZKDIndex(fields, null).get(); + assertThat(indexResult, is(notNullValue())); + assertThat(indexResult.getConstraint(), is(nullValue())); + assertThat(indexResult.getFields(), hasItem("a")); + assertThat(indexResult.getFields(), hasItem("b")); + assertThat(indexResult.getGeoJson(), is(nullValue())); + assertThat(indexResult.getId(), startsWith(COLLECTION_NAME)); + assertThat(indexResult.getIsNewlyCreated(), is(true)); + assertThat(indexResult.getMinLength(), is(nullValue())); + assertThat(indexResult.getType(), is(IndexType.zkd)); + assertThat(indexResult.getUnique(), is(false)); + db.collection(COLLECTION_NAME).deleteIndex(indexResult.getId()).get(); + } + + @Test + public void createZKDIndexWithOptions() throws ExecutionException, InterruptedException { + assumeTrue(isAtLeastVersion(3, 9)); + db.collection(COLLECTION_NAME).truncate().get(); + + final ZKDIndexOptions options = new ZKDIndexOptions() + .name("myZKDIndex") + .fieldValueTypes(ZKDIndexOptions.FieldValueTypes.DOUBLE); + + final Collection fields = new ArrayList<>(); + fields.add("a"); + fields.add("b"); + final IndexEntity indexResult = db.collection(COLLECTION_NAME).ensureZKDIndex(fields, options).get(); + assertThat(indexResult, is(notNullValue())); + assertThat(indexResult.getConstraint(), is(nullValue())); + assertThat(indexResult.getFields(), hasItem("a")); + assertThat(indexResult.getFields(), hasItem("b")); + assertThat(indexResult.getId(), startsWith(COLLECTION_NAME)); + assertThat(indexResult.getIsNewlyCreated(), is(true)); + assertThat(indexResult.getMinLength(), is(nullValue())); + assertThat(indexResult.getType(), is(IndexType.zkd)); + assertThat(indexResult.getUnique(), is(false)); + assertThat(indexResult.getName(), is("myZKDIndex")); + db.collection(COLLECTION_NAME).deleteIndex(indexResult.getId()).get(); + } + @Test public void getIndexes() throws InterruptedException, ExecutionException { final int initialIndexCount = db.collection(COLLECTION_NAME).getIndexes().get().size();