diff --git a/src/main/java/com/arangodb/ArangoCollection.java b/src/main/java/com/arangodb/ArangoCollection.java index f74dd4681..e01c3eaa6 100644 --- a/src/main/java/com/arangodb/ArangoCollection.java +++ b/src/main/java/com/arangodb/ArangoCollection.java @@ -498,6 +498,16 @@ MultiDocumentEntity> deleteDocuments( */ IndexEntity ensurePersistentIndex(Iterable fields, PersistentIndexOptions options) throws ArangoDBException; + /** + * Creates an inverted index for the collection, if it does not already exist. + * + * @param options index creation options + * @return information about the index + * @throws ArangoDBException + * @see API Documentation + */ + Object ensureInvertedIndex(InvertedIndexOptions options) throws ArangoDBException; + /** * Creates a geo-spatial index for the collection, if it does not already exist. * @@ -519,7 +529,7 @@ MultiDocumentEntity> deleteDocuments( * @throws ArangoDBException * @see API * Documentation - * @deprecated since ArangoDB 3.10, use ArangoSearch view instead. + * @deprecated since ArangoDB 3.10, use ArangoSearch or Inverted indexes instead. */ @Deprecated IndexEntity ensureFulltextIndex(Iterable fields, FulltextIndexOptions options) throws ArangoDBException; diff --git a/src/main/java/com/arangodb/async/ArangoCollectionAsync.java b/src/main/java/com/arangodb/async/ArangoCollectionAsync.java index 4e39bb6b1..d8bf6fb26 100644 --- a/src/main/java/com/arangodb/async/ArangoCollectionAsync.java +++ b/src/main/java/com/arangodb/async/ArangoCollectionAsync.java @@ -487,7 +487,7 @@ CompletableFuture ensurePersistentIndex( * @return information about the index * @see API * Documentation - * @deprecated since ArangoDB 3.10, use ArangoSearch view instead. + * @deprecated since ArangoDB 3.10, use ArangoSearch or Inverted indexes instead. */ @Deprecated CompletableFuture ensureFulltextIndex( diff --git a/src/main/java/com/arangodb/entity/IndexType.java b/src/main/java/com/arangodb/entity/IndexType.java index 26a048cb0..3fbd025c3 100644 --- a/src/main/java/com/arangodb/entity/IndexType.java +++ b/src/main/java/com/arangodb/entity/IndexType.java @@ -41,7 +41,7 @@ public enum IndexType { geo2, /** - * @deprecated since ArangoDB 3.10, use ArangoSearch view instead. + * @deprecated since ArangoDB 3.10, use ArangoSearch or Inverted indexes instead. */ @Deprecated fulltext, @@ -50,5 +50,10 @@ public enum IndexType { ttl, - zkd + zkd, + + /** + * @since ArangoDB 3.10 + */ + inverted } diff --git a/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java index 8fdf06509..76549f0cb 100644 --- a/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java +++ b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java @@ -37,8 +37,14 @@ public enum AnalyzerFeature { norm, /** - * sequentially increasing term position, required for PHRASE(). If present then the frequency feature is also required + * sequentially increasing term position, required for PHRASE(). If present then the frequency feature is also required. */ - position + position, + + /** + * enable search highlighting capabilities (Enterprise Edition only). If present, then the `position` and `frequency` features are also required. + * @since ArangoDB 3.10 + */ + offset } diff --git a/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java b/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java index 21aa94475..b4df3e67d 100644 --- a/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java +++ b/src/main/java/com/arangodb/internal/ArangoCollectionImpl.java @@ -279,6 +279,11 @@ public IndexEntity ensurePersistentIndex(final Iterable fields, final Pe return executor.execute(createPersistentIndexRequest(fields, options), IndexEntity.class); } + @Override + public Object ensureInvertedIndex(final InvertedIndexOptions options) throws ArangoDBException { + return executor.execute(createInvertedIndexRequest(options), Object.class); + } + @Override public IndexEntity ensureGeoIndex(final Iterable fields, final GeoIndexOptions options) throws ArangoDBException { diff --git a/src/main/java/com/arangodb/internal/InternalArangoCollection.java b/src/main/java/com/arangodb/internal/InternalArangoCollection.java index f7d87537e..7920c962e 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoCollection.java +++ b/src/main/java/com/arangodb/internal/InternalArangoCollection.java @@ -576,6 +576,13 @@ protected Request createPersistentIndexRequest( return request; } + protected Request createInvertedIndexRequest(final InvertedIndexOptions options) { + final Request request = request(db.dbName(), RequestType.POST, PATH_API_INDEX); + request.putQueryParam(COLLECTION, name); + request.setBody(util().serialize(options)); + return request; + } + protected Request createGeoIndexRequest(final Iterable fields, final GeoIndexOptions options) { final Request request = request(db.dbName(), RequestType.POST, PATH_API_INDEX); request.putQueryParam(COLLECTION, name); diff --git a/src/main/java/com/arangodb/model/FulltextIndexOptions.java b/src/main/java/com/arangodb/model/FulltextIndexOptions.java index 447c237b8..4ed5068ea 100644 --- a/src/main/java/com/arangodb/model/FulltextIndexOptions.java +++ b/src/main/java/com/arangodb/model/FulltextIndexOptions.java @@ -26,7 +26,7 @@ * @author Mark Vollmary * @see API * Documentation - * @deprecated since ArangoDB 3.10, use ArangoSearch view instead. + * @deprecated since ArangoDB 3.10, use ArangoSearch or Inverted indexes instead. */ @Deprecated public class FulltextIndexOptions extends IndexOptions { diff --git a/src/main/java/com/arangodb/model/InvertedIndexOptions.java b/src/main/java/com/arangodb/model/InvertedIndexOptions.java new file mode 100644 index 000000000..b8bd796b6 --- /dev/null +++ b/src/main/java/com/arangodb/model/InvertedIndexOptions.java @@ -0,0 +1,376 @@ +/* + * 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; +import com.arangodb.entity.arangosearch.*; + +import java.util.*; + +/** + * TODO: add documentation + * @author Michele Rastelli + * @see API Documentation + * @since ArangoDB 3.10 + */ +public class InvertedIndexOptions extends IndexOptions { + + protected final IndexType type = IndexType.inverted; + private Integer parallelism; + private PrimarySort primarySort; + private final Collection storedValues = new ArrayList<>(); + private String analyzer; + private final Collection features = new ArrayList<>(); + private Boolean includeAllFields; + private Boolean trackListPositions; + private Boolean searchField; + private final Collection fields = new ArrayList<>(); + private Long consolidationIntervalMsec; + private Long commitIntervalMsec; + private Long cleanupIntervalStep; + private ConsolidationPolicy consolidationPolicy; + private Long writebufferIdle; + private Long writebufferActive; + private Long writebufferSizeMax; + + public InvertedIndexOptions() { + super(); + } + + @Override + protected InvertedIndexOptions getThis() { + return this; + } + + protected IndexType getType() { + return type; + } + + public Integer getParallelism() { + return parallelism; + } + + public InvertedIndexOptions parallelism(Integer parallelism) { + this.parallelism = parallelism; + return this; + } + + public PrimarySort getPrimarySort() { + return primarySort; + } + + public InvertedIndexOptions primarySort(PrimarySort primarySort) { + this.primarySort = primarySort; + return this; + } + + public Collection getStoredValues() { + return storedValues; + } + + public InvertedIndexOptions storedValues(StoredValue... storedValues) { + Collections.addAll(this.storedValues, storedValues); + return this; + } + + public String getAnalyzer() { + return analyzer; + } + + public InvertedIndexOptions analyzer(String analyzer) { + this.analyzer = analyzer; + return this; + } + + public Collection getFeatures() { + return features; + } + + public InvertedIndexOptions features(AnalyzerFeature... features) { + Collections.addAll(this.features, features); + return this; + } + + public Boolean getIncludeAllFields() { + return includeAllFields; + } + + public InvertedIndexOptions includeAllFields(Boolean includeAllFields) { + this.includeAllFields = includeAllFields; + return this; + } + + public Boolean getTrackListPositions() { + return trackListPositions; + } + + public InvertedIndexOptions trackListPositions(Boolean trackListPositions) { + this.trackListPositions = trackListPositions; + return this; + } + + public Boolean getSearchField() { + return searchField; + } + + public InvertedIndexOptions searchField(Boolean searchField) { + this.searchField = searchField; + return this; + } + + public Collection getFields() { + return fields; + } + + public InvertedIndexOptions fields(InvertedIndexField... fields) { + Collections.addAll(this.fields, fields); + return this; + } + + public Long getConsolidationIntervalMsec() { + return consolidationIntervalMsec; + } + + public InvertedIndexOptions consolidationIntervalMsec(Long consolidationIntervalMsec) { + this.consolidationIntervalMsec = consolidationIntervalMsec; + return this; + } + + public Long getCommitIntervalMsec() { + return commitIntervalMsec; + } + + public InvertedIndexOptions commitIntervalMsec(Long commitIntervalMsec) { + this.commitIntervalMsec = commitIntervalMsec; + return this; + } + + public Long getCleanupIntervalStep() { + return cleanupIntervalStep; + } + + public InvertedIndexOptions cleanupIntervalStep(Long cleanupIntervalStep) { + this.cleanupIntervalStep = cleanupIntervalStep; + return this; + } + + public ConsolidationPolicy getConsolidationPolicy() { + return consolidationPolicy; + } + + public InvertedIndexOptions consolidationPolicy(ConsolidationPolicy consolidationPolicy) { + this.consolidationPolicy = consolidationPolicy; + return this; + } + + public Long getWritebufferIdle() { + return writebufferIdle; + } + + public InvertedIndexOptions writebufferIdle(Long writebufferIdle) { + this.writebufferIdle = writebufferIdle; + return this; + } + + public Long getWritebufferActive() { + return writebufferActive; + } + + public InvertedIndexOptions writebufferActive(Long writebufferActive) { + this.writebufferActive = writebufferActive; + return this; + } + + public Long getWritebufferSizeMax() { + return writebufferSizeMax; + } + + public InvertedIndexOptions writebufferSizeMax(Long writebufferSizeMax) { + this.writebufferSizeMax = writebufferSizeMax; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InvertedIndexOptions that = (InvertedIndexOptions) o; + return type == that.type && Objects.equals(parallelism, that.parallelism) && Objects.equals(primarySort, that.primarySort) && Objects.equals(storedValues, that.storedValues) && Objects.equals(analyzer, that.analyzer) && Objects.equals(features, that.features) && Objects.equals(includeAllFields, that.includeAllFields) && Objects.equals(trackListPositions, that.trackListPositions) && Objects.equals(searchField, that.searchField) && Objects.equals(fields, that.fields) && Objects.equals(consolidationIntervalMsec, that.consolidationIntervalMsec) && Objects.equals(commitIntervalMsec, that.commitIntervalMsec) && Objects.equals(cleanupIntervalStep, that.cleanupIntervalStep) && Objects.equals(consolidationPolicy, that.consolidationPolicy) && Objects.equals(writebufferIdle, that.writebufferIdle) && Objects.equals(writebufferActive, that.writebufferActive) && Objects.equals(writebufferSizeMax, that.writebufferSizeMax); + } + + @Override + public int hashCode() { + return Objects.hash(type, parallelism, primarySort, storedValues, analyzer, features, includeAllFields, trackListPositions, searchField, fields, consolidationIntervalMsec, commitIntervalMsec, cleanupIntervalStep, consolidationPolicy, writebufferIdle, writebufferActive, writebufferSizeMax); + } + + public static class PrimarySort { + private final List fields = new ArrayList<>(); + private ArangoSearchCompression compression; + + public List getFields() { + return fields; + } + + public PrimarySort fields(Field... fields) { + Collections.addAll(this.fields, fields); + return this; + } + + public ArangoSearchCompression getCompression() { + return compression; + } + + public PrimarySort compression(ArangoSearchCompression compression) { + this.compression = compression; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PrimarySort that = (PrimarySort) o; + return Objects.equals(fields, that.fields) && compression == that.compression; + } + + @Override + public int hashCode() { + return Objects.hash(fields, compression); + } + + public static class Field { + private final String field; + private final Direction direction; + + public Field(String field, Direction direction) { + this.field = field; + this.direction = direction; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Field field1 = (Field) o; + return Objects.equals(field, field1.field) && direction == field1.direction; + } + + @Override + public int hashCode() { + return Objects.hash(field, direction); + } + + public enum Direction { + asc, + desc + } + + } + + } + + public static class InvertedIndexField { + private String name; + private String analyzer; + private Boolean includeAllFields; + private Boolean searchField; + private Boolean trackListPositions; + private final Collection features = new ArrayList<>(); + private final Collection nested = new ArrayList<>(); + + public String getName() { + return name; + } + + public InvertedIndexField name(String name) { + this.name = name; + return this; + } + + public String getAnalyzer() { + return analyzer; + } + + public InvertedIndexField analyzer(String analyzer) { + this.analyzer = analyzer; + return this; + } + + public Boolean getIncludeAllFields() { + return includeAllFields; + } + + public InvertedIndexField includeAllFields(Boolean includeAllFields) { + this.includeAllFields = includeAllFields; + return this; + } + + public Boolean getSearchField() { + return searchField; + } + + public InvertedIndexField searchField(Boolean searchField) { + this.searchField = searchField; + return this; + } + + public Boolean getTrackListPositions() { + return trackListPositions; + } + + public InvertedIndexField trackListPositions(Boolean trackListPositions) { + this.trackListPositions = trackListPositions; + return this; + } + + public Collection getFeatures() { + return features; + } + + public InvertedIndexField features(AnalyzerFeature... features) { + Collections.addAll(this.features, features); + return this; + } + + public Collection getNested() { + return nested; + } + + public InvertedIndexField nested(InvertedIndexField... nested) { + Collections.addAll(this.nested, nested); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InvertedIndexField that = (InvertedIndexField) o; + return Objects.equals(name, that.name) && Objects.equals(analyzer, that.analyzer) && Objects.equals(includeAllFields, that.includeAllFields) && Objects.equals(searchField, that.searchField) && Objects.equals(trackListPositions, that.trackListPositions) && Objects.equals(features, that.features) && Objects.equals(nested, that.nested); + } + + @Override + public int hashCode() { + return Objects.hash(name, analyzer, includeAllFields, searchField, trackListPositions, features, nested); + } + } + +} diff --git a/src/test/java/com/arangodb/ArangoCollectionTest.java b/src/test/java/com/arangodb/ArangoCollectionTest.java index 67437779d..e55732010 100644 --- a/src/test/java/com/arangodb/ArangoCollectionTest.java +++ b/src/test/java/com/arangodb/ArangoCollectionTest.java @@ -35,6 +35,9 @@ import com.arangodb.entity.MultiDocumentEntity; import com.arangodb.entity.Permissions; import com.arangodb.entity.ShardEntity; +import com.arangodb.entity.arangosearch.*; +import com.arangodb.entity.arangosearch.analyzer.DelimiterAnalyzer; +import com.arangodb.entity.arangosearch.analyzer.DelimiterAnalyzerProperties; import com.arangodb.model.*; import com.arangodb.model.DocumentImportOptions.OnDuplicate; import com.arangodb.util.MapBuilder; @@ -47,14 +50,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -1723,6 +1719,82 @@ void indexDeduplicateFalse(ArangoCollection collection) { assertThat(indexResult.getDeduplicate()).isFalse(); } + @ParameterizedTest(name = "{index}") + @MethodSource("cols") + void createInvertedIndex(ArangoCollection collection) { + assumeTrue(isAtLeastVersion(3, 10)); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + DelimiterAnalyzer da = new DelimiterAnalyzer(); + da.setName("delimiter-" + UUID.randomUUID()); + da.setFeatures(features); + DelimiterAnalyzerProperties props = new DelimiterAnalyzerProperties(); + props.setDelimiter("-"); + da.setProperties(props); + + collection.db().createSearchAnalyzer(da); + + final Object indexResult = collection.ensureInvertedIndex(new InvertedIndexOptions() + .name("invertedIndex-" + UUID.randomUUID()) + .inBackground(true) + .parallelism(5) + .primarySort(new InvertedIndexOptions.PrimarySort() + .fields( + new InvertedIndexOptions.PrimarySort.Field("f1", InvertedIndexOptions.PrimarySort.Field.Direction.asc), + new InvertedIndexOptions.PrimarySort.Field("f2", InvertedIndexOptions.PrimarySort.Field.Direction.desc) + ) + .compression(ArangoSearchCompression.lz4) + ) + .storedValues(new StoredValue(Arrays.asList("f3", "f4"), ArangoSearchCompression.none)) + .analyzer(da.getName()) + .features(AnalyzerFeature.position, AnalyzerFeature.frequency) + .includeAllFields(false) + .trackListPositions(true) + .searchField(true) + .fields( + new InvertedIndexOptions.InvertedIndexField() + .name("foo") + .analyzer(AnalyzerType.identity.toString()) + .includeAllFields(true) + .searchField(false) + .trackListPositions(false) + .features( + AnalyzerFeature.position, + AnalyzerFeature.frequency, + AnalyzerFeature.norm, + AnalyzerFeature.offset + ) + .nested( + new InvertedIndexOptions.InvertedIndexField() + .name("bar") + .analyzer(da.getName()) + .searchField(true) + .features(AnalyzerFeature.position) + .nested( + new InvertedIndexOptions.InvertedIndexField() + .name("baz") + .analyzer(AnalyzerType.identity.toString()) + .searchField(false) + .features(AnalyzerFeature.frequency) + ) + ) + + ) + .consolidationIntervalMsec(11L) + .commitIntervalMsec(22L) + .cleanupIntervalStep(33L) + .consolidationPolicy(ConsolidationPolicy.of(ConsolidationType.BYTES_ACCUM).threshold(.7)) + .writebufferIdle(44L) + .writebufferActive(55L) + .writebufferSizeMax(66L) + ); + assertThat(indexResult).isNotNull(); + } + @ParameterizedTest(name = "{index}") @MethodSource("cols") void createFulltextIndex(ArangoCollection collection) { diff --git a/src/test/java/com/arangodb/ArangoSearchTest.java b/src/test/java/com/arangodb/ArangoSearchTest.java index 173c0a47e..22494fb02 100644 --- a/src/test/java/com/arangodb/ArangoSearchTest.java +++ b/src/test/java/com/arangodb/ArangoSearchTest.java @@ -987,5 +987,27 @@ void collationAnalyzer(ArangoDatabase db) { createGetAndDeleteTypedAnalyzer(db, collationAnalyzer); } + @ParameterizedTest(name = "{index}") + @MethodSource("dbs") + void offsetFeature(ArangoDatabase db) { + assumeTrue(isEnterprise()); + assumeTrue(isAtLeastVersion(3, 10)); + + String name = "test-" + rnd(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + features.add(AnalyzerFeature.offset); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.identity); + options.setProperties(Collections.emptyMap()); + + createGetAndDeleteAnalyzer(db, options); + } } diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index f67855e9c..579f1b9db 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -8,7 +8,7 @@ - +