diff --git a/ChangeLog.md b/ChangeLog.md index f453fe068..3bf87b6fa 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added +- added support for Analyzers - added support for Stream Transactions - added support for named indices - added support for TTL indices diff --git a/src/main/java/com/arangodb/ArangoDatabase.java b/src/main/java/com/arangodb/ArangoDatabase.java index 355da7774..0df415007 100644 --- a/src/main/java/com/arangodb/ArangoDatabase.java +++ b/src/main/java/com/arangodb/ArangoDatabase.java @@ -25,6 +25,8 @@ import com.arangodb.entity.*; import com.arangodb.model.*; +import com.arangodb.entity.arangosearch.AnalyzerEntity; +import com.arangodb.model.arangosearch.AnalyzerDeleteOptions; import com.arangodb.model.arangosearch.ArangoSearchCreateOptions; /** @@ -707,4 +709,57 @@ TraversalEntity executeTraversal(Class vertexClass, Class edg */ ViewEntity createArangoSearch(String name, ArangoSearchCreateOptions options) throws ArangoDBException; + /** + * Creates an Analyzer + * + * @param options AnalyzerEntity + * @return the created Analyzer + * @throws ArangoDBException + * @see API Documentation + * @since ArangoDB 3.5.0 + */ + AnalyzerEntity createAnalyzer(AnalyzerEntity options) throws ArangoDBException; + + /** + * Gets information about an Analyzer + * + * @param name of the Analyzer without database prefix + * @return information about an Analyzer + * @throws ArangoDBException + * @see API Documentation + * @since ArangoDB 3.5.0 + */ + AnalyzerEntity getAnalyzer(String name) throws ArangoDBException; + + /** + * Retrieves all analyzers definitions. + * + * @return collection of all analyzers definitions + * @throws ArangoDBException + * @see API Documentation + * @since ArangoDB 3.5.0 + */ + Collection getAnalyzers() throws ArangoDBException; + + /** + * Deletes an Analyzer + * + * @param name of the Analyzer without database prefix + * @throws ArangoDBException + * @see API Documentation + * @since ArangoDB 3.5.0 + */ + void deleteAnalyzer(String name) throws ArangoDBException; + + /** + * Deletes an Analyzer + * + * @param name of the Analyzer without database prefix + * @param options AnalyzerDeleteOptions + * @throws ArangoDBException + * @see API Documentation + * @since ArangoDB 3.5.0 + */ + void deleteAnalyzer(String name, AnalyzerDeleteOptions options) throws ArangoDBException; + } diff --git a/src/main/java/com/arangodb/entity/arangosearch/AnalyzerEntity.java b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerEntity.java new file mode 100644 index 000000000..71c7e358b --- /dev/null +++ b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerEntity.java @@ -0,0 +1,71 @@ +/* + * DISCLAIMER + * + * Copyright 2018 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.entity.arangosearch; + +import java.util.Map; +import java.util.Set; + +/** + * @author Michele Rastelli + */ +public class AnalyzerEntity { + + private Set features; + private AnalyzerType type; + private String name; + private Map properties; + + public AnalyzerEntity() { + } + + public Set getFeatures() { + return features; + } + + public void setFeatures(Set features) { + this.features = features; + } + + public AnalyzerType getType() { + return type; + } + + public void setType(AnalyzerType type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + +} diff --git a/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java new file mode 100644 index 000000000..1b40357bb --- /dev/null +++ b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerFeature.java @@ -0,0 +1,28 @@ +/* + * 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.entity.arangosearch; + +/** + * @author Michele Rastelli + */ +public enum AnalyzerFeature { + frequency, norm, position +} diff --git a/src/main/java/com/arangodb/entity/arangosearch/AnalyzerType.java b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerType.java new file mode 100644 index 000000000..5b6d1d1db --- /dev/null +++ b/src/main/java/com/arangodb/entity/arangosearch/AnalyzerType.java @@ -0,0 +1,28 @@ +/* + * 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.entity.arangosearch; + +/** + * @author Michele Rastelli + */ +public enum AnalyzerType { + identity, delimiter, stem, norm, ngram, text +} diff --git a/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java b/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java index 5db0e6795..047127ca0 100644 --- a/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java +++ b/src/main/java/com/arangodb/internal/ArangoDatabaseImpl.java @@ -33,10 +33,12 @@ import com.arangodb.ArangoSearch; import com.arangodb.ArangoView; import com.arangodb.entity.*; +import com.arangodb.entity.arangosearch.AnalyzerEntity; import com.arangodb.internal.cursor.ArangoCursorImpl; import com.arangodb.internal.net.HostHandle; import com.arangodb.internal.util.DocumentUtil; import com.arangodb.model.*; +import com.arangodb.model.arangosearch.AnalyzerDeleteOptions; import com.arangodb.model.arangosearch.ArangoSearchCreateOptions; import com.arangodb.util.ArangoCursorInitializer; import com.arangodb.velocypack.Type; @@ -423,4 +425,29 @@ public ViewEntity createArangoSearch(final String name, final ArangoSearchCreate return executor.execute(createArangoSearchRequest(name, options), ViewEntity.class); } + @Override + public AnalyzerEntity createAnalyzer(AnalyzerEntity options) throws ArangoDBException { + return executor.execute(createAnalyzerRequest(options), AnalyzerEntity.class); + } + + @Override + public AnalyzerEntity getAnalyzer(String name) throws ArangoDBException { + return executor.execute(getAnalyzerRequest(name), AnalyzerEntity.class); + } + + @Override + public Collection getAnalyzers() throws ArangoDBException { + return executor.execute(getAnalyzersRequest(), getAnalyzersResponseDeserializer()); + } + + @Override + public void deleteAnalyzer(String name) throws ArangoDBException { + executor.execute(deleteAnalyzerRequest(name, null), Void.class); + } + + @Override + public void deleteAnalyzer(String name, AnalyzerDeleteOptions options) throws ArangoDBException { + executor.execute(deleteAnalyzerRequest(name, options), Void.class); + } + } diff --git a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java index d48002d5f..e8e7c7ab3 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDatabase.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDatabase.java @@ -26,10 +26,12 @@ import java.util.Map; import com.arangodb.entity.*; +import com.arangodb.entity.arangosearch.AnalyzerEntity; import com.arangodb.internal.ArangoExecutor.ResponseDeserializer; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; import com.arangodb.internal.util.RequestUtils; import com.arangodb.model.*; +import com.arangodb.model.arangosearch.AnalyzerDeleteOptions; import com.arangodb.model.arangosearch.ArangoSearchCreateOptions; import com.arangodb.model.arangosearch.ArangoSearchOptionsBuilder; import com.arangodb.util.ArangoSerializer; @@ -493,4 +495,35 @@ protected Request createArangoSearchRequest(final String name, final ArangoSearc return request(name(), RequestType.POST, InternalArangoView.PATH_API_VIEW).setBody(util().serialize( ArangoSearchOptionsBuilder.build(options != null ? options : new ArangoSearchCreateOptions(), name))); } + + protected Request getAnalyzerRequest(final String name) { + return request(name(), RequestType.GET, InternalArangoView.PATH_API_ANALYZER, name); + } + + protected Request getAnalyzersRequest() { + return request(name(), RequestType.GET, InternalArangoView.PATH_API_ANALYZER); + } + + protected ResponseDeserializer> getAnalyzersResponseDeserializer() { + return new ResponseDeserializer>() { + @Override + public Collection deserialize(final Response response) throws VPackException { + final VPackSlice result = response.getBody().get(ArangoResponseField.RESULT); + return util().deserialize(result, new Type>() { + }.getType()); + } + }; + } + + protected Request createAnalyzerRequest(final AnalyzerEntity options) { + return request(name(), RequestType.POST, InternalArangoView.PATH_API_ANALYZER) + .setBody(util().serialize(options)); + } + + protected Request deleteAnalyzerRequest(final String name, final AnalyzerDeleteOptions options) { + Request request = request(name(), RequestType.DELETE, InternalArangoView.PATH_API_ANALYZER, name); + request.putQueryParam("force", options != null ? options.getForce() : null); + return request; + } + } diff --git a/src/main/java/com/arangodb/internal/InternalArangoView.java b/src/main/java/com/arangodb/internal/InternalArangoView.java index 742d2dc10..a30671e62 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoView.java +++ b/src/main/java/com/arangodb/internal/InternalArangoView.java @@ -27,12 +27,14 @@ /** * @author Mark Vollmary + * @author Michele Rastelli * */ public abstract class InternalArangoView, D extends InternalArangoDatabase, E extends ArangoExecutor> extends ArangoExecuteable { protected static final String PATH_API_VIEW = "/_api/view"; + protected static final String PATH_API_ANALYZER = "/_api/analyzer"; protected final D db; protected volatile String name; diff --git a/src/main/java/com/arangodb/model/arangosearch/AnalyzerDeleteOptions.java b/src/main/java/com/arangodb/model/arangosearch/AnalyzerDeleteOptions.java new file mode 100644 index 000000000..09b62e801 --- /dev/null +++ b/src/main/java/com/arangodb/model/arangosearch/AnalyzerDeleteOptions.java @@ -0,0 +1,40 @@ +/* + * DISCLAIMER + * + * Copyright 2018 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.arangosearch; + +/** + * @author Michele Rastelli + */ +public class AnalyzerDeleteOptions { + + private Boolean force; + + public AnalyzerDeleteOptions() { + } + + public Boolean getForce() { + return force; + } + + public void setForce(Boolean force) { + this.force = force; + } +} diff --git a/src/test/java/com/arangodb/ArangoSearchTest.java b/src/test/java/com/arangodb/ArangoSearchTest.java index bc2389bc8..dab180661 100644 --- a/src/test/java/com/arangodb/ArangoSearchTest.java +++ b/src/test/java/com/arangodb/ArangoSearchTest.java @@ -20,31 +20,26 @@ package com.arangodb; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - -import java.util.Collection; - -import com.arangodb.entity.arangosearch.*; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - import com.arangodb.ArangoDB.Builder; import com.arangodb.entity.ServerRole; import com.arangodb.entity.ViewEntity; import com.arangodb.entity.ViewType; +import com.arangodb.entity.arangosearch.*; +import com.arangodb.model.arangosearch.AnalyzerDeleteOptions; import com.arangodb.model.arangosearch.ArangoSearchCreateOptions; import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.*; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; /** * @author Mark Vollmary - * */ @RunWith(Parameterized.class) public class ArangoSearchTest extends BaseTest { @@ -62,19 +57,19 @@ public void teardown() { c.drop(); } catch (final ArangoDBException e) { } - + try { ArangoCollection c = db.collection("view_replace_prop_test_collection"); c.drop(); } catch (final ArangoDBException e) { } - + try { db.view(VIEW_NAME).drop(); } catch (final ArangoDBException e) { } } - + @Test public void exists() { if (!requireVersion(3, 4)) { @@ -223,9 +218,9 @@ public void updateProperties() { options.cleanupIntervalStep(15L); options.consolidationIntervalMsec(65000L); options.consolidationPolicy(ConsolidationPolicy.of(ConsolidationType.BYTES_ACCUM).threshold(1.)); - options.link( - CollectionLink.on("view_update_prop_test_collection").fields(FieldLink.on("value").analyzers("identity") - .trackListPositions(true).includeAllFields(true).storeValues(StoreValuesType.ID))); + options.link(CollectionLink.on("view_update_prop_test_collection") + .fields(FieldLink.on("value").analyzers("identity").trackListPositions(true).includeAllFields(true) + .storeValues(StoreValuesType.ID))); final ArangoSearchPropertiesEntity properties = view.updateProperties(options); assertThat(properties, is(not(nullValue()))); assertThat(properties.getCleanupIntervalStep(), is(15L)); @@ -250,13 +245,13 @@ public void replaceProperties() { if (!requireVersion(3, 4)) { return; } - + db.createCollection("view_replace_prop_test_collection"); final ArangoSearch view = db.arangoSearch(VIEW_NAME); view.create(new ArangoSearchCreateOptions()); final ArangoSearchPropertiesOptions options = new ArangoSearchPropertiesOptions(); - options.link( - CollectionLink.on("view_replace_prop_test_collection").fields(FieldLink.on("value").analyzers("identity"))); + options.link(CollectionLink.on("view_replace_prop_test_collection") + .fields(FieldLink.on("value").analyzers("identity"))); final ArangoSearchPropertiesEntity properties = view.replaceProperties(options); assertThat(properties, is(not(nullValue()))); assertThat(properties.getLinks().size(), is(1)); @@ -266,4 +261,199 @@ public void replaceProperties() { assertThat(link.getFields().iterator().next().getName(), is("value")); } + private void createGetAndDeleteAnalyzer(AnalyzerEntity options){ + + String fullyQualifiedName = db.name() + "::" + options.getName(); + + // createAnalyzer + AnalyzerEntity createdAnalyzer = db.createAnalyzer(options); + + assertThat(createdAnalyzer.getName(), is(fullyQualifiedName)); + assertThat(createdAnalyzer.getType(), is(options.getType())); + assertThat(createdAnalyzer.getFeatures(), is(options.getFeatures())); + assertThat(createdAnalyzer.getProperties(), is(options.getProperties())); + + // getAnalyzer + AnalyzerEntity gotAnalyzer = db.getAnalyzer(options.getName()); + assertThat(gotAnalyzer.getName(), is(fullyQualifiedName)); + assertThat(gotAnalyzer.getType(), is(options.getType())); + assertThat(gotAnalyzer.getFeatures(), is(options.getFeatures())); + assertThat(gotAnalyzer.getProperties(), is(options.getProperties())); + + // getAnalyzers + @SuppressWarnings("OptionalGetWithoutIsPresent") + AnalyzerEntity foundAnalyzer = db.getAnalyzers().stream().filter(it -> it.getName().equals(fullyQualifiedName)) + .findFirst().get(); + + assertThat(foundAnalyzer.getName(), is(fullyQualifiedName)); + assertThat(foundAnalyzer.getType(), is(options.getType())); + assertThat(foundAnalyzer.getFeatures(), is(options.getFeatures())); + assertThat(foundAnalyzer.getProperties(), is(options.getProperties())); + + AnalyzerDeleteOptions deleteOptions = new AnalyzerDeleteOptions(); + deleteOptions.setForce(true); + + // deleteAnalyzer + db.deleteAnalyzer(options.getName(), deleteOptions); + + try { + db.getAnalyzer(options.getName()); + throw new RuntimeException("deleted analyzer should not be found!"); + } catch (ArangoDBException e) { + // ok + } + + } + + @Test + public void identityAnalyzer() { + if (!requireVersion(3, 5)) { + return; + } + + String name = "test-" + UUID.randomUUID().toString(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.identity); + options.setProperties(Collections.emptyMap()); + + createGetAndDeleteAnalyzer(options); + } + + @Test + public void delimiterAnalyzer() { + if (!requireVersion(3, 5)) { + return; + } + + String name = "test-" + UUID.randomUUID().toString(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.delimiter); + options.setProperties(Collections.singletonMap("delimiter", "-")); + + createGetAndDeleteAnalyzer(options); + } + + @Test + public void stemAnalyzer() { + if (!requireVersion(3, 5)) { + return; + } + + String name = "test-" + UUID.randomUUID().toString(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.stem); + options.setProperties(Collections.singletonMap("locale", "ru.utf-8")); + + createGetAndDeleteAnalyzer(options); + } + + @Test + public void normAnalyzer() { + if (!requireVersion(3, 5)) { + return; + } + + String name = "test-" + UUID.randomUUID().toString(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + Map properties = new HashMap<>(); + properties.put("locale", "ru.utf-8"); + properties.put("case", "lower"); + properties.put("accent", true); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.norm); + options.setProperties(properties); + + createGetAndDeleteAnalyzer(options); + } + + @Test + public void ngramAnalyzer() { + if (!requireVersion(3, 5)) { + return; + } + + String name = "test-" + UUID.randomUUID().toString(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + Map properties = new HashMap<>(); + properties.put("max", 6L); + properties.put("min", 3L); + properties.put("preserveOriginal", true); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.ngram); + options.setProperties(properties); + + createGetAndDeleteAnalyzer(options); + } + + @Test + public void textAnalyzer() { + if (!requireVersion(3, 5)) { + return; + } + + String name = "test-" + UUID.randomUUID().toString(); + + Set features = new HashSet<>(); + features.add(AnalyzerFeature.frequency); + features.add(AnalyzerFeature.norm); + features.add(AnalyzerFeature.position); + + Map properties = new HashMap<>(); + properties.put("locale", "ru.utf-8"); + properties.put("case", "lower"); + properties.put("stopwords", Collections.emptyList()); + properties.put("accent", true); + properties.put("stemming", true); + + AnalyzerEntity options = new AnalyzerEntity(); + options.setFeatures(features); + options.setName(name); + options.setType(AnalyzerType.text); + options.setProperties(properties); + + createGetAndDeleteAnalyzer(options); + } + + + }