Author: fortino
Date: Thu Jun 25 14:58:08 2020
New Revision: 1879190
URL: http://svn.apache.org/viewvc?rev=1879190&view=rev
Log:
OAK-9120: oak-search-elastic: integrate spellcheck with async iterator, remove
sync iterator
Added:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
(with props)
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResponseHandler.java
(with props)
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckIterator.java
(with props)
Removed:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticProcess.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticQueryProcess.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResultRowIterator.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSearcher.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSearcherModel.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckProcess.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticRequestHandler.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResponseHandler.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/facets/ElasticAggregationData.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/facets/ElasticFacetHelper.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/facets/ElasticFacets.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/facets/InsecureElasticFacets.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/facets/SecureElasticFacets.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/facets/StatisticalElasticFacets.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticAggregationBuilderUtil.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticConstants.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/ElasticQueryUtil.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/util/SearchSourceBuilderUtil.java
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSpellcheckTest.java
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
Thu Jun 25 14:58:08 2020
@@ -187,7 +187,7 @@ public class ElasticIndexDefinition exte
}
@Override
- public ElasticIndexDefinition.Builder reindex() {
+ public Builder reindex() {
super.reindex();
return this;
}
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelper.java
Thu Jun 25 14:58:08 2020
@@ -74,6 +74,13 @@ class ElasticIndexHelper {
settingsBuilder.field("preserve_original",
indexDefinition.indexOriginalTerms());
}
settingsBuilder.endObject();
+ if (indexDefinition.isSpellcheckEnabled()) {
+ settingsBuilder.startObject("shingle")
+ .field("type", "shingle")
+ .field("min_shingle_size", 2)
+ .field("max_shingle_size", 3)
+ .endObject();
+ }
}
settingsBuilder.endObject();
@@ -93,14 +100,18 @@ class ElasticIndexHelper {
settingsBuilder.field("tokenizer", "path_hierarchy");
}
settingsBuilder.endObject();
+ if (indexDefinition.isSpellcheckEnabled()) {
+ settingsBuilder.startObject("trigram")
+ .field("type", "custom")
+ .field("tokenizer", "standard")
+ .array("filter", "lowercase", "shingle")
+ .endObject();
+ }
}
settingsBuilder.endObject();
}
settingsBuilder.endObject();
}
- if (indexDefinition.isSpellcheckEnabled()) {
- createSpellcheckMapping(indexDefinition, settingsBuilder);
- }
settingsBuilder.endObject();
return settingsBuilder;
}
@@ -132,25 +143,23 @@ class ElasticIndexHelper {
}
private static void mapIndexRules(ElasticIndexDefinition indexDefinition,
XContentBuilder mappingBuilder) throws IOException {
- // we need to check if in the defined rules there are properties with
the same name and different types
- final List<Map.Entry<String, List<PropertyDefinition>>>
multiTypesFields = indexDefinition.getPropertiesByName()
- .entrySet()
- .stream()
- .filter(e -> e.getValue().size() > 1)
- .filter(e ->
e.getValue().stream().map(PropertyDefinition::getType).distinct().count() > 1)
- .collect(Collectors.toList());
-
- if (!multiTypesFields.isEmpty()) {
- String fields =
multiTypesFields.stream().map(Map.Entry::getKey).collect(Collectors.joining(",
", "[", "]"));
- throw new IllegalStateException(indexDefinition.getIndexPath() + "
has properties with the same name and " +
- "different types " + fields);
- }
+ checkIndexRules(indexDefinition);
for (Map.Entry<String, List<PropertyDefinition>> entry :
indexDefinition.getPropertiesByName().entrySet()) {
final String name = entry.getKey();
final List<PropertyDefinition> propertyDefinitions =
entry.getValue();
- Type<?> type = Type.fromTag(propertyDefinitions.get(0).getType(),
false);
+ Type<?> type = null;
+ boolean useInSpellCheck = false;
+ for (PropertyDefinition pd: propertyDefinitions) {
+ type = Type.fromTag(pd.getType(), false);
+ if (pd.useInSpellcheck) {
+ useInSpellCheck = true;
+ break;
+ }
+
+ }
+
mappingBuilder.startObject(name);
{
//
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
@@ -174,7 +183,7 @@ class ElasticIndexHelper {
mappingBuilder.startObject("keyword")
.field("type", "keyword")
.endObject();
- if (indexDefinition.isSpellcheckEnabled()) {
+ if (useInSpellCheck) {
mappingBuilder.startObject("trigram")
.field("type",
"text").field("analyzer", "trigram")
.endObject();
@@ -191,33 +200,19 @@ class ElasticIndexHelper {
}
}
- private static void createSpellcheckMapping(ElasticIndexDefinition
indexDefinition, XContentBuilder settingsBuilder) throws IOException {
- settingsBuilder.startObject("index");
- {
- settingsBuilder.startObject("analysis");
- {
- settingsBuilder.startObject("analyzer");
- {
- settingsBuilder.startObject("trigram")
- .field("type", "custom")
- .field("tokenizer", "standard")
- .array("filter", "lowercase", "shingle")
- .endObject();
- }
- settingsBuilder.endObject();
+ // we need to check if in the defined rules there are properties with the
same name and different types
+ private static void checkIndexRules(ElasticIndexDefinition
indexDefinition) {
+ final List<Map.Entry<String, List<PropertyDefinition>>>
multiTypesFields = indexDefinition.getPropertiesByName()
+ .entrySet()
+ .stream()
+ .filter(e -> e.getValue().size() > 1)
+ .filter(e ->
e.getValue().stream().map(PropertyDefinition::getType).distinct().count() > 1)
+ .collect(Collectors.toList());
- settingsBuilder.startObject("filter");
- {
- settingsBuilder.startObject("shingle")
- .field("type", "shingle")
- .field("min_shingle_size", 2)
- .field("max_shingle_size", 3)
- .endObject();
- }
- settingsBuilder.endObject();
- }
- settingsBuilder.endObject();
+ if (!multiTypesFields.isEmpty()) {
+ String fields =
multiTypesFields.stream().map(Map.Entry::getKey).collect(Collectors.joining(",
", "[", "]"));
+ throw new IllegalStateException(indexDefinition.getIndexPath() + "
has properties with the same name and " +
+ "different types " + fields);
}
- settingsBuilder.endObject();
}
}
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java
Thu Jun 25 14:58:08 2020
@@ -18,7 +18,7 @@ package org.apache.jackrabbit.oak.plugin
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticRequestHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResultRowAsyncIterator;
import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
import org.apache.jackrabbit.oak.plugins.index.search.SizeEstimator;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
@@ -28,12 +28,12 @@ import org.apache.jackrabbit.oak.spi.que
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.elasticsearch.common.Strings;
-import org.elasticsearch.index.query.QueryBuilder;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
+import java.util.function.BiFunction;
import java.util.function.Predicate;
import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
@@ -44,6 +44,9 @@ class ElasticIndex extends FulltextIndex
state ->
TYPE_ELASTICSEARCH.equals(state.getString(TYPE_PROPERTY_NAME));
private static final Map<String, LMSEstimator> ESTIMATORS = new
WeakHashMap<>();
+ // no concept of rewound in ES (even if it might be doing it internally,
we can't do much about it
+ private static final IteratorRewoundStateProvider
REWOUND_STATE_PROVIDER_NOOP = () -> 0;
+
// higher than some threshold below which the query should rather be
answered by something else if possible
private static final double MIN_COST = 100.1;
@@ -92,44 +95,38 @@ class ElasticIndex extends FulltextIndex
@Override
protected String getFulltextRequestString(IndexPlan plan, IndexNode
indexNode) {
- return Strings.toString(new ElasticRequestHandler(plan,
getPlanResult(plan)).build());
+ return Strings.toString(new ElasticRequestHandler(plan,
getPlanResult(plan)).baseQuery());
}
@Override
public Cursor query(IndexPlan plan, NodeState rootState) {
final Filter filter = plan.getFilter();
+ final FulltextIndexPlanner.PlanResult planResult = getPlanResult(plan);
- // TODO: sorting
+ final ElasticRequestHandler requestHandler = new
ElasticRequestHandler(plan, planResult);
+ final ElasticResponseHandler responseHandler = new
ElasticResponseHandler(planResult, filter);
- final FulltextIndexPlanner.PlanResult pr = getPlanResult(plan);
+ final Iterator<FulltextResultRow> itr;
+ if (requestHandler.requiresSpellCheck()) {
+ itr = new ElasticSpellcheckIterator(acquireIndexNode(plan),
requestHandler, responseHandler);
+ } else {
+ // this function is called for each extracted row. Passing
FulltextIndex::shouldInclude means that for each
+ // row we evaluate getPathRestriction(plan) &
plan.getFilter().getPathRestriction(). Providing a partial
+ // function (https://en.wikipedia.org/wiki/Partial_function) we
can evaluate them once and still use a predicate as before
+ BiFunction<String, Filter.PathRestriction, Predicate<String>>
partialShouldInclude = (path, pathRestriction) -> docPath ->
+ shouldInclude(path, pathRestriction, docPath);
+
+ itr = new ElasticResultRowAsyncIterator(
+ acquireIndexNode(plan),
+ requestHandler,
+ responseHandler,
+ plan,
+ partialShouldInclude.apply(getPathRestriction(plan),
filter.getPathRestriction()),
+ getEstimator(plan.getPlanName())
+ );
- // this function is called for each extracted row. Passing
FulltextIndex::shouldInclude means that for each
- // row we evaluate getPathRestriction(plan) &
plan.getFilter().getPathRestriction(). Providing a partial
- // function (https://en.wikipedia.org/wiki/Partial_function) we can
evaluate them once and still use a predicate as before
-// BiFunction<String, Filter.PathRestriction, Predicate<String>>
partialShouldInclude = (path, pathRestriction) -> docPath ->
-// shouldInclude(path, pathRestriction, docPath);
-//
-// Iterator<FulltextResultRow> itr = new ElasticResultRowAsyncIterator(
-// acquireIndexNode(plan),
-// plan,
-// pr,
-// partialShouldInclude.apply(getPathRestriction(plan),
plan.getFilter().getPathRestriction()),
-// getEstimator(plan.getPlanName())
-// );
-
- Iterator<FulltextResultRow> itr = new ElasticResultRowIterator(filter,
pr, plan,
- acquireIndexNode(plan), FulltextIndex::shouldInclude,
getEstimator(plan.getPlanName()));
-
- /*
- TODO: sync (nrt too??)
- if (pr.hasPropertyIndexResult() ||
pr.evaluateSyncNodeTypeRestriction()) {
- itr = mergePropertyIndexResult(plan, rootState, itr);
}
- */
-
- // no concept of rewound in ES (even if it might be doing it
internally, we can't do much about it
- IteratorRewoundStateProvider rewoundStateProvider = () -> 0;
- return new FulltextPathCursor(itr, rewoundStateProvider, plan,
filter.getQueryLimits(), getSizeEstimator(plan));
+ return new FulltextPathCursor(itr, REWOUND_STATE_PROVIDER_NOOP, plan,
filter.getQueryLimits(), getSizeEstimator(plan));
}
private LMSEstimator getEstimator(String path) {
Added:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java?rev=1879190&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
(added)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
Thu Jun 25 14:58:08 2020
@@ -0,0 +1,549 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elastic.query;
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets.ElasticFacetProvider;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
+import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
+import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner.PlanResult;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextAnd;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextContains;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextExpression;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextOr;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextTerm;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextVisitor;
+import org.apache.lucene.search.WildcardQuery;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
+import org.elasticsearch.index.query.MultiMatchQueryBuilder;
+import org.elasticsearch.index.query.Operator;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.search.MatchQuery;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import
org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
+import org.elasticsearch.search.suggest.SuggestBuilders;
+import org.elasticsearch.search.suggest.phrase.DirectCandidateGeneratorBuilder;
+import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.jcr.PropertyType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newAncestorQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newDepthQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newMixinTypeQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newNodeTypeQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newNotNullPropQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newNullPropQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newPathQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newPrefixPathQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newPrefixQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newPropertyRestrictionQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newWildcardPathQuery;
+import static
org.apache.jackrabbit.oak.plugins.index.elastic.util.TermQueryBuilderFactory.newWildcardQuery;
+import static org.apache.jackrabbit.oak.spi.query.QueryConstants.JCR_PATH;
+import static org.apache.jackrabbit.util.ISO8601.parse;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
+import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+
+/**
+ * Class to map query plans into Elastic request objects.
+ */
+public class ElasticRequestHandler {
+
+ private final static String SPELLCHECK_PREFIX = "spellcheck?term=";
+ private static final String ES_TRIGRAM_SUFFIX = ".trigram";
+
+ private final IndexPlan indexPlan;
+ private final Filter filter;
+ private final PlanResult planResult;
+ private final ElasticIndexDefinition elasticIndexDefinition;
+ private final String propertyRestrictionQuery;
+
+ ElasticRequestHandler(@NotNull IndexPlan indexPlan, @NotNull
FulltextIndexPlanner.PlanResult planResult) {
+ this.indexPlan = indexPlan;
+ this.filter = indexPlan.getFilter();
+ this.planResult = planResult;
+ this.elasticIndexDefinition = (ElasticIndexDefinition)
planResult.indexDefinition;
+
+ //Check if native function is supported
+ Filter.PropertyRestriction pr = null;
+ if (elasticIndexDefinition.hasFunctionDefined()) {
+ pr =
filter.getPropertyRestriction(elasticIndexDefinition.getFunctionName());
+ }
+
+ this.propertyRestrictionQuery = pr != null ?
String.valueOf(pr.first.getValue(pr.first.getType())) : null;
+ }
+
+ public BoolQueryBuilder baseQuery() {
+ final BoolQueryBuilder boolQuery = boolQuery();
+
+ FullTextExpression ft = filter.getFullTextConstraint();
+
+ if (ft != null) {
+ boolQuery.must(fullTextQuery(ft, planResult));
+ }
+
+ if (propertyRestrictionQuery != null) {
+ boolQuery.must(queryStringQuery(propertyRestrictionQuery));
+ } else if (planResult.evaluateNonFullTextConstraints()) {
+ for (QueryBuilder constraint: nonFullTextConstraints(indexPlan,
planResult)) {
+ boolQuery.filter(constraint);
+ }
+ }
+
+ // TODO: sort with no other restriction
+
+ if (!boolQuery.hasClauses()) {
+ // TODO: what happens here in planning mode (specially, apparently
for things like rep:similar)
+ //For purely nodeType based queries all the documents would have to
+ //be returned (if the index definition has a single rule)
+ if (planResult.evaluateNodeTypeRestriction()) {
+ boolQuery.must(matchAllQuery());
+ }
+ }
+
+ return boolQuery;
+ }
+
+ public String getPropertyRestrictionQuery() {
+ return propertyRestrictionQuery;
+ }
+
+ public boolean requiresSpellCheck() {
+ return propertyRestrictionQuery != null &&
propertyRestrictionQuery.startsWith(SPELLCHECK_PREFIX);
+ }
+
+ public ElasticFacetProvider getAsyncFacetProvider(ElasticResponseHandler
responseHandler) {
+ return requiresFacets() ?
+ ElasticFacetProvider.getProvider(
+
planResult.indexDefinition.getSecureFacetConfiguration(),
+ this, responseHandler,
+ filter::isAccessible
+ ) : null;
+ }
+
+ private boolean requiresFacets() {
+ return filter.getPropertyRestrictions()
+ .stream()
+ .anyMatch(pr ->
QueryConstants.REP_FACET.equals(pr.propertyName));
+ }
+
+ public Stream<TermsAggregationBuilder> aggregations() {
+ return facetFields()
+ .map(facetProp ->
+ AggregationBuilders.terms(facetProp)
+
.field(elasticIndexDefinition.getElasticKeyword(facetProp))
+
.size(elasticIndexDefinition.getNumberOfTopFacets())
+ );
+ }
+
+ public Stream<String> facetFields() {
+ return filter.getPropertyRestrictions()
+ .stream()
+ .filter(pr -> QueryConstants.REP_FACET.equals(pr.propertyName))
+ .map(pr ->
FulltextIndex.parseFacetField(pr.first.getValue(Type.STRING)));
+ }
+
+ public Stream<String> spellCheckFields() {
+ return StreamSupport
+ .stream(planResult.indexingRule.getProperties().spliterator(),
false)
+ .filter(pd -> pd.useInSpellcheck)
+ .map(pd -> pd.name);
+ }
+
+ public PhraseSuggestionBuilder suggestQuery(String field, String
spellCheckQuery) {
+ BoolQueryBuilder query = boolQuery()
+ .must(new MatchPhraseQueryBuilder(field, "{{suggestion}}"));
+
+ nonFullTextConstraints(indexPlan, planResult).forEach(query::must);
+
+ PhraseSuggestionBuilder.CandidateGenerator candidateGeneratorBuilder =
+ new
DirectCandidateGeneratorBuilder(getTrigramField(field)).suggestMode("missing");
+ return SuggestBuilders
+ .phraseSuggestion(getTrigramField(field))
+ .size(10)
+ .addCandidateGenerator(candidateGeneratorBuilder)
+ .text(spellCheckQuery)
+ .collateQuery(query.toString());
+ }
+
+ public BoolQueryBuilder suggestMatchQuery(String suggestion, String[]
fields) {
+ BoolQueryBuilder query = boolQuery()
+ .must(new MultiMatchQueryBuilder(suggestion, fields)
+ .operator(Operator.AND).fuzzyTranspositions(false)
+ .autoGenerateSynonymsPhraseQuery(false)
+ .type(MatchQuery.Type.PHRASE));
+
+ nonFullTextConstraints(indexPlan, planResult).forEach(query::must);
+
+ return query;
+ }
+
+ private String getTrigramField(String field) {
+ return field + ES_TRIGRAM_SUFFIX;
+ }
+
+ private QueryBuilder fullTextQuery(FullTextExpression ft, final PlanResult
pr) {
+ // a reference to the query, so it can be set in the visitor
+ // (a "non-local return")
+ final AtomicReference<QueryBuilder> result = new AtomicReference<>();
+ ft.accept(new FullTextVisitor() {
+
+ @Override
+ public boolean visit(FullTextContains contains) {
+ visitTerm(contains.getPropertyName(), contains.getRawText(),
null, contains.isNot());
+ return true;
+ }
+
+ @Override
+ public boolean visit(FullTextOr or) {
+ BoolQueryBuilder q = boolQuery();
+ for (FullTextExpression e : or.list) {
+ q.should(fullTextQuery(e, pr));
+ }
+ result.set(q);
+ return true;
+ }
+
+ @Override
+ public boolean visit(FullTextAnd and) {
+ BoolQueryBuilder q = boolQuery();
+ for (FullTextExpression e : and.list) {
+ QueryBuilder x = fullTextQuery(e, pr);
+ // TODO: see OAK-2434 and see if ES also can't work
without unwrapping
+ /* Only unwrap the clause if MUST_NOT(x) */
+ boolean hasMustNot = false;
+ if (x instanceof BoolQueryBuilder) {
+ BoolQueryBuilder bq = (BoolQueryBuilder) x;
+ if (bq.mustNot().size() == 1
+ // no other clauses
+ && bq.should().isEmpty() &&
bq.must().isEmpty() && bq.filter().isEmpty()) {
+ hasMustNot = true;
+ q.mustNot(bq.mustNot().get(0));
+ }
+ }
+
+ if (!hasMustNot) {
+ q.must(x);
+ }
+ }
+ result.set(q);
+ return true;
+ }
+
+ @Override
+ public boolean visit(FullTextTerm term) {
+ return visitTerm(term.getPropertyName(), term.getText(),
term.getBoost(), term.isNot());
+ }
+
+ private boolean visitTerm(String propertyName, String text, String
boost, boolean not) {
+ String p = getLuceneFieldName(propertyName, pr);
+ QueryBuilder q = tokenToQuery(text, p, pr);
+ if (boost != null) {
+ q.boost(Float.parseFloat(boost));
+ }
+ if (not) {
+ BoolQueryBuilder bq = boolQuery().mustNot(q);
+ result.set(bq);
+ } else {
+ result.set(q);
+ }
+ return true;
+ }
+ });
+ return result.get();
+ }
+
+ private List<QueryBuilder> nonFullTextConstraints(IndexPlan plan,
PlanResult planResult) {
+ final BiPredicate<Iterable<String>, String> any = (iterable, value) ->
+ StreamSupport.stream(iterable.spliterator(),
false).anyMatch(value::equals);
+
+ final List<QueryBuilder> queries = new ArrayList<>();
+
+ Filter filter = plan.getFilter();
+ if (!filter.matchesAllTypes()) {
+ queries.add(nodeTypeConstraints(planResult.indexingRule, filter));
+ }
+
+ String path = FulltextIndex.getPathRestriction(plan);
+ switch (filter.getPathRestriction()) {
+ case ALL_CHILDREN:
+ if (!"/".equals(path)) {
+ queries.add(newAncestorQuery(path));
+ }
+ break;
+ case DIRECT_CHILDREN:
+ BoolQueryBuilder bq = boolQuery()
+ .must(newAncestorQuery(path))
+ .must(newDepthQuery(path, planResult));
+ queries.add(bq);
+ break;
+ case EXACT:
+ // For transformed paths, we can only add path restriction if
absolute path to property can be
+ // deduced
+ if (planResult.isPathTransformed()) {
+ String parentPathSegment =
planResult.getParentPathSegment();
+ if (!any.test(PathUtils.elements(parentPathSegment), "*"))
{
+ queries.add(newPathQuery(path + parentPathSegment));
+ }
+ } else {
+ queries.add(newPathQuery(path));
+ }
+ break;
+ case PARENT:
+ if (denotesRoot(path)) {
+ // there's no parent of the root node
+ // we add a path that can not possibly occur because there
+ // is no way to say "match no documents" in Lucene
+ queries.add(newPathQuery("///"));
+ } else {
+ // For transformed paths, we can only add path restriction
if absolute path to property can be
+ // deduced
+ if (planResult.isPathTransformed()) {
+ String parentPathSegment =
planResult.getParentPathSegment();
+ if (!any.test(PathUtils.elements(parentPathSegment),
"*")) {
+ queries.add(newPathQuery(getParentPath(path) +
parentPathSegment));
+ }
+ } else {
+ queries.add(newPathQuery(getParentPath(path)));
+ }
+ }
+ break;
+ case NO_RESTRICTION:
+ break;
+ }
+
+ for (Filter.PropertyRestriction pr : filter.getPropertyRestrictions())
{
+ String name = pr.propertyName;
+
+ if (QueryConstants.REP_EXCERPT.equals(name) ||
QueryConstants.OAK_SCORE_EXPLANATION.equals(name)
+ || QueryConstants.REP_FACET.equals(name)) {
+ continue;
+ }
+
+ if (QueryConstants.RESTRICTION_LOCAL_NAME.equals(name)) {
+ if (planResult.evaluateNodeNameRestriction()) {
+ QueryBuilder q = nodeName(pr);
+ if (q != null) {
+ queries.add(q);
+ }
+ }
+ continue;
+ }
+
+ if (pr.first != null && pr.first.equals(pr.last) &&
pr.firstIncluding && pr.lastIncluding) {
+ String first = pr.first.getValue(Type.STRING);
+ first = first.replace("\\", "");
+ if (JCR_PATH.equals(name)) {
+ queries.add(newPathQuery(first));
+ continue;
+ } else if ("*".equals(name)) {
+ //TODO Revisit reference constraint. For performant impl
+ //references need to be indexed in a different manner
+ queries.add(referenceConstraint(first));
+ continue;
+ }
+ }
+
+ PropertyDefinition pd = planResult.getPropDefn(pr);
+ if (pd == null) {
+ continue;
+ }
+
+ QueryBuilder q = createQuery(planResult.getPropertyName(pr), pr,
pd);
+ if (q != null) {
+ queries.add(q);
+ }
+ }
+ return queries;
+ }
+
+ private static QueryBuilder
nodeTypeConstraints(IndexDefinition.IndexingRule defn, Filter filter) {
+ final BoolQueryBuilder bq = boolQuery();
+ PropertyDefinition primaryType = defn.getConfig(JCR_PRIMARYTYPE);
+ //TODO OAK-2198 Add proper nodeType query support
+
+ if (primaryType != null && primaryType.propertyIndex) {
+ for (String type : filter.getPrimaryTypes()) {
+ bq.should(newNodeTypeQuery(type));
+ }
+ }
+
+ PropertyDefinition mixinType = defn.getConfig(JCR_MIXINTYPES);
+ if (mixinType != null && mixinType.propertyIndex) {
+ for (String type : filter.getMixinTypes()) {
+ bq.should(newMixinTypeQuery(type));
+ }
+ }
+
+ return bq;
+ }
+
+ private static QueryBuilder nodeName(Filter.PropertyRestriction pr) {
+ String first = pr.first != null ? pr.first.getValue(Type.STRING) :
null;
+ if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding
+ && pr.lastIncluding) {
+ // [property]=[value]
+ return termQuery(FieldNames.NODE_NAME, first);
+ }
+
+ if (pr.isLike) {
+ return like(FieldNames.NODE_NAME, first);
+ }
+
+ throw new IllegalStateException("For nodeName queries only EQUALS and
LIKE are supported " + pr);
+ }
+
+ private static QueryBuilder like(String name, String first) {
+ first = first.replace('%', WildcardQuery.WILDCARD_STRING);
+ first = first.replace('_', WildcardQuery.WILDCARD_CHAR);
+
+ int indexOfWS = first.indexOf(WildcardQuery.WILDCARD_STRING);
+ int indexOfWC = first.indexOf(WildcardQuery.WILDCARD_CHAR);
+ int len = first.length();
+
+ if (indexOfWS == len || indexOfWC == len) {
+ // remove trailing "*" for prefix query
+ first = first.substring(0, first.length() - 1);
+ if (JCR_PATH.equals(name)) {
+ return newPrefixPathQuery(first);
+ } else {
+ return newPrefixQuery(name, first);
+ }
+ } else {
+ if (JCR_PATH.equals(name)) {
+ return newWildcardPathQuery(first);
+ } else {
+ return newWildcardQuery(name, first);
+ }
+ }
+ }
+
+ private static QueryBuilder referenceConstraint(String uuid) {
+ // TODO: this seems very bad as a query - do we really want to support
it. In fact, is it even used?
+ // reference query
+ return QueryBuilders.multiMatchQuery(uuid);
+ }
+
+ private static QueryBuilder tokenToQuery(String text, String fieldName,
PlanResult pr) {
+ QueryBuilder ret;
+ IndexDefinition.IndexingRule indexingRule = pr.indexingRule;
+ //Expand the query on fulltext field
+ if (FieldNames.FULLTEXT.equals(fieldName) &&
+ !indexingRule.getNodeScopeAnalyzedProps().isEmpty()) {
+ BoolQueryBuilder in = boolQuery();
+ for (PropertyDefinition pd :
indexingRule.getNodeScopeAnalyzedProps()) {
+ QueryBuilder q = matchQuery(pd.name, text).boost(pd.boost);
+ in.should(q);
+ }
+
+ //Add the query for actual fulltext field also. That query would
not be boosted
+ // TODO: do we need this if all the analyzed fields are queried?
+ ret = in.should(matchQuery(fieldName, text));
+ } else {
+ ret = matchQuery(fieldName, text);
+ }
+
+ return ret;
+ }
+
+ private QueryBuilder createQuery(String propertyName,
Filter.PropertyRestriction pr,
+ PropertyDefinition defn) {
+ int propType = FulltextIndex.determinePropertyType(defn, pr);
+
+ if (pr.isNullRestriction()) {
+ return newNullPropQuery(defn.name);
+ }
+
+ //If notNullCheckEnabled explicitly enabled use the simple TermQuery
+ //otherwise later fallback to range query
+ if (pr.isNotNullRestriction() && defn.notNullCheckEnabled) {
+ return newNotNullPropQuery(defn.name);
+ }
+
+ final String field =
elasticIndexDefinition.getElasticKeyword(propertyName);
+
+ QueryBuilder in;
+ switch (propType) {
+ case PropertyType.DATE: {
+ in = newPropertyRestrictionQuery(field, pr, value ->
parse(value.getValue(Type.DATE)).getTime());
+ break;
+ }
+ case PropertyType.DOUBLE: {
+ in = newPropertyRestrictionQuery(field, pr, value ->
value.getValue(Type.DOUBLE));
+ break;
+ }
+ case PropertyType.LONG: {
+ in = newPropertyRestrictionQuery(field, pr, value ->
value.getValue(Type.LONG));
+ break;
+ }
+ default: {
+ if (pr.isLike) {
+ return like(propertyName, pr.first.getValue(Type.STRING));
+ }
+
+ //TODO Confirm that all other types can be treated as string
+ in = newPropertyRestrictionQuery(field, pr, value ->
value.getValue(Type.STRING));
+ }
+ }
+
+ if (in != null) {
+ return in;
+ }
+
+ throw new IllegalStateException("PropertyRestriction not handled " +
pr + " for index " + defn);
+ }
+
+ private static String getLuceneFieldName(@Nullable String p, PlanResult
pr) {
+ if (p == null) {
+ return FieldNames.FULLTEXT;
+ }
+
+ if (pr.isPathTransformed()) {
+ p = PathUtils.getName(p);
+ }
+
+ if ("*".equals(p)) {
+ p = FieldNames.FULLTEXT;
+ }
+ return p;
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResponseHandler.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResponseHandler.java?rev=1879190&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResponseHandler.java
(added)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResponseHandler.java
Thu Jun 25 14:58:08 2020
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elastic.query;
+
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
+import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner.PlanResult;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.elasticsearch.search.SearchHit;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+/**
+ * Class to process Elastic response objects.
+ */
+public class ElasticResponseHandler {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ElasticResponseHandler.class);
+
+ private final PlanResult planResult;
+ private final Filter filter;
+
+ ElasticResponseHandler(@NotNull FulltextIndexPlanner.PlanResult
planResult, @NotNull Filter filter) {
+ this.planResult = planResult;
+ this.filter = filter;
+ }
+
+ public String getPath(SearchHit hit) {
+ final Map<String, Object> sourceMap = hit.getSourceAsMap();
+ String path = (String) sourceMap.get(FieldNames.PATH);
+
+ if ("".equals(path)) {
+ path = "/";
+ }
+ if (planResult.isPathTransformed()) {
+ String originalPath = path;
+ path = planResult.transformPath(path);
+
+ if (path == null) {
+ LOG.trace("Ignoring path {} : Transformation returned null",
originalPath);
+ return null;
+ }
+ }
+
+ return path;
+ }
+
+ public boolean isAccessible(String path) {
+ return filter.isAccessible(path);
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticResponseHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckIterator.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckIterator.java?rev=1879190&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckIterator.java
(added)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckIterator.java
Thu Jun 25 14:58:08 2020
@@ -0,0 +1,135 @@
+package org.apache.jackrabbit.oak.plugins.index.elastic.query;
+
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex.FulltextResultRow;
+import org.elasticsearch.action.search.MultiSearchRequest;
+import org.elasticsearch.action.search.MultiSearchResponse;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.elasticsearch.search.suggest.SuggestBuilder;
+import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * This class is in charge to extract spell checked suggestions for a given
query.
+ *
+ * It requires 2 calls to Elastic:
+ * <ul>
+ * <li>get all the possible spellchecked suggestions</li>
+ * <li>multi search query to get a sample of 100 results for each
suggestion for ACL check</li>
+ * </ul>
+ */
+class ElasticSpellcheckIterator implements Iterator<FulltextResultRow> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ElasticSpellcheckIterator.class);
+ protected final static String SPELLCHECK_PREFIX = "spellcheck?term=";
+
+ private final ElasticIndexNode indexNode;
+ private final ElasticRequestHandler requestHandler;
+ private final ElasticResponseHandler responseHandler;
+ private final String[] spellCheckFields;
+ private final String spellCheckQuery;
+
+ private Iterator<FulltextResultRow> internalIterator;
+ private boolean loaded = false;
+
+ ElasticSpellcheckIterator(@NotNull ElasticIndexNode indexNode,
+ @NotNull ElasticRequestHandler requestHandler,
+ @NotNull ElasticResponseHandler responseHandler)
{
+ this.indexNode = indexNode;
+ this.requestHandler = requestHandler;
+ this.responseHandler = responseHandler;
+ this.spellCheckFields =
requestHandler.spellCheckFields().toArray(String[]::new);
+ this.spellCheckQuery =
requestHandler.getPropertyRestrictionQuery().replace(SPELLCHECK_PREFIX, "");
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!loaded) {
+ loadSuggestions();
+ loaded = true;
+ }
+ return internalIterator != null && internalIterator.hasNext();
+ }
+
+ @Override
+ public FulltextResultRow next() {
+ return internalIterator.next();
+ }
+
+ private void loadSuggestions() {
+ try {
+ final ArrayDeque<String> suggestionTexts = new ArrayDeque<>();
+ final MultiSearchRequest multiSearch = suggestions()
+ .map(s -> {
+ String text = s.getText().string();
+ suggestionTexts.offer(text);
+ return requestHandler.suggestMatchQuery(text,
spellCheckFields);
+ })
+ .map(query -> SearchSourceBuilder.searchSource()
+ .query(query)
+ .size(100)
+ .fetchSource(FieldNames.PATH, null))
+ .map(searchSource -> new
SearchRequest(indexNode.getDefinition().getRemoteIndexAlias())
+ .source(searchSource))
+ .reduce(new MultiSearchRequest(), MultiSearchRequest::add,
(ms, ms2) -> ms);
+
+ ArrayList<FulltextResultRow> results = new ArrayList<>();
+ MultiSearchResponse res =
indexNode.getConnection().getClient().msearch(multiSearch,
RequestOptions.DEFAULT);
+ for (MultiSearchResponse.Item response : res.getResponses()) {
+ boolean hasResults = false;
+ for (SearchHit doc : response.getResponse().getHits()) {
+ if
(responseHandler.isAccessible(responseHandler.getPath(doc))) {
+ hasResults = true;
+ break;
+ }
+ }
+ String suggestion = suggestionTexts.poll();
+ if (hasResults) {
+ results.add(new FulltextResultRow(suggestion));
+ }
+ }
+
+ this.internalIterator = results.iterator();
+
+ } catch (IOException e) {
+ LOG.error("Error processing suggestions for " + spellCheckQuery,
e);
+ }
+
+ }
+
+ private Stream<PhraseSuggestion.Entry.Option> suggestions() throws
IOException {
+ final SuggestBuilder suggestBuilder = new SuggestBuilder();
+ for (int i = 0; i < spellCheckFields.length; i++) {
+ suggestBuilder.addSuggestion("oak:suggestion-" + i,
+ requestHandler.suggestQuery(spellCheckFields[i],
spellCheckQuery));
+ }
+
+ final SearchSourceBuilder searchSourceBuilder =
SearchSourceBuilder.searchSource()
+ .suggest(suggestBuilder);
+
+ final SearchRequest searchRequest = new
SearchRequest(indexNode.getDefinition().getRemoteIndexAlias())
+ .source(searchSourceBuilder);
+
+ SearchResponse searchResponse =
indexNode.getConnection().getClient().search(searchRequest,
RequestOptions.DEFAULT);
+
+ return StreamSupport
+ .stream(searchResponse.getSuggest().spliterator(), false)
+ .map(s -> (PhraseSuggestion) s)
+ .flatMap(ps -> ps.getEntries().stream())
+ .flatMap(ps -> ps.getOptions().stream())
+ .sorted((o1, o2) -> Float.compare(o2.getScore(),
o1.getScore()));
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticSpellcheckIterator.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
Thu Jun 25 14:58:08 2020
@@ -17,11 +17,11 @@
package org.apache.jackrabbit.oak.plugins.index.elastic.query.async;
import org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticIndexNode;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets.ElasticFacetProvider;
import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex.FulltextResultRow;
-import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
-import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner.PlanResult;
import org.apache.jackrabbit.oak.plugins.index.search.util.LMSEstimator;
import org.apache.jackrabbit.oak.spi.query.QueryIndex;
import org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
@@ -68,7 +68,6 @@ public class ElasticResultRowAsyncIterat
private final ElasticIndexNode indexNode;
private final IndexPlan indexPlan;
- private final PlanResult planResult;
private final Predicate<String> rowInclusionPredicate;
private final LMSEstimator estimator;
@@ -80,19 +79,19 @@ public class ElasticResultRowAsyncIterat
private FulltextResultRow nextRow;
public ElasticResultRowAsyncIterator(@NotNull ElasticIndexNode indexNode,
+ @NotNull ElasticRequestHandler
elasticRequestHandler,
+ @NotNull ElasticResponseHandler
elasticResponseHandler,
@NotNull QueryIndex.IndexPlan
indexPlan,
- @NotNull
FulltextIndexPlanner.PlanResult planResult,
Predicate<String>
rowInclusionPredicate,
LMSEstimator estimator) {
this.indexNode = indexNode;
+ this.elasticRequestHandler = elasticRequestHandler;
+ this.elasticResponseHandler = elasticResponseHandler;
this.indexPlan = indexPlan;
- this.planResult = planResult;
this.rowInclusionPredicate = rowInclusionPredicate;
this.estimator = estimator;
- this.elasticRequestHandler = new ElasticRequestHandler(indexPlan,
planResult);
- this.elasticResponseHandler = new ElasticResponseHandler(planResult);
- this.elasticFacetProvider = initFacetProvider();
+ this.elasticFacetProvider =
elasticRequestHandler.getAsyncFacetProvider(elasticResponseHandler);
this.elasticQueryScanner = initScanner();
}
@@ -107,15 +106,17 @@ public class ElasticResultRowAsyncIterat
} catch (InterruptedException e) {
throw new IllegalStateException("Error reading next result from
Elastic", e);
}
- if (POISON_PILL.path.equals(nextRow.path)) {
- nextRow = null;
- }
- return nextRow != null;
+ return !POISON_PILL.path.equals(nextRow.path);
}
@Override
public FulltextResultRow next() {
- return nextRow;
+ if (nextRow == null) { // next is called without hasNext
+ if (!hasNext()) {
+ return null;
+ }
+ }
+ return nextRow != null && POISON_PILL.path.equals(nextRow.path) ? null
: nextRow;
}
@Override
@@ -143,15 +144,6 @@ public class ElasticResultRowAsyncIterat
}
}
- private ElasticFacetProvider initFacetProvider() {
- return elasticRequestHandler.requiresFacets() ?
- ElasticFacetProvider.getProvider(
-
planResult.indexDefinition.getSecureFacetConfiguration(),
- elasticRequestHandler, elasticResponseHandler,
- indexPlan.getFilter()::isAccessible
- ) : null;
- }
-
private ElasticQueryScanner initScanner() {
List<ElasticResponseListener> listeners = new ArrayList<>();
// TODO: we could avoid to register this listener when the client is
interested in facets only. It would save space and time
@@ -194,7 +186,7 @@ public class ElasticResultRowAsyncIterat
ElasticQueryScanner(ElasticRequestHandler requestHandler,
List<ElasticResponseListener> listeners) {
- this.query = requestHandler.build();
+ this.query = requestHandler.baseQuery();
final Set<String> sourceFieldsSet = new HashSet<>();
final AtomicBoolean needsAggregations = new AtomicBoolean(false);
@@ -242,6 +234,9 @@ public class ElasticResultRowAsyncIterat
/**
* Handle the response action notifying the registered listeners.
Depending on the listeners' configuration
* it could keep loading chunks or wait for a {@code #scan} call to
resume scanning.
+ *
+ * Some code in this method relies on structure that are not thread
safe. We need to make sure
+ * these data structures are modified before releasing the semaphore.
*/
@Override
public void onResponse(SearchResponse searchResponse) {
@@ -303,7 +298,7 @@ public class ElasticResultRowAsyncIterat
/**
* Triggers a scan of a new chunk of the result set, if needed.
*/
- public void scan() {
+ private void scan() {
if (semaphore.tryAcquire() && anyDataLeft.get()) {
final SearchSourceBuilder searchSourceBuilder =
SearchSourceBuilder.searchSource()
.query(query)
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
Thu Jun 25 14:58:08 2020
@@ -16,8 +16,8 @@
*/
package org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticRequestHandler;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
import
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.SecureFacetConfiguration;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
Thu Jun 25 14:58:08 2020
@@ -16,8 +16,8 @@
*/
package org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticRequestHandler;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
import org.elasticsearch.search.SearchHit;
@@ -123,7 +123,7 @@ class ElasticSecureFacetAsyncProvider im
} catch (InterruptedException e) {
throw new IllegalStateException("Error while waiting for facets",
e);
}
- LOG.trace("Reading facets for {} from {}", columnName, facetsMap);
- return facets.get(FulltextIndex.parseFacetField(columnName));
+ LOG.trace("Reading facets for {} from {}", columnName, facets);
+ return facets != null ?
facets.get(FulltextIndex.parseFacetField(columnName)) : null;
}
}
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
Thu Jun 25 14:58:08 2020
@@ -16,8 +16,8 @@
*/
package org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticRequestHandler;
-import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
+import
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
import
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
import org.elasticsearch.search.SearchHit;
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSpellcheckTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSpellcheckTest.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSpellcheckTest.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticSpellcheckTest.java
Thu Jun 25 14:58:08 2020
@@ -16,9 +16,6 @@
*/
package org.apache.jackrabbit.oak.plugins.index.elastic;
-import com.github.dockerjava.api.DockerClient;
-import com.google.common.collect.Lists;
-import com.google.common.io.Closer;
import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.JcrConstants;
import
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
@@ -32,17 +29,10 @@ import org.apache.jackrabbit.oak.plugins
import
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
-import org.elasticsearch.Version;
import org.junit.After;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Rule;
import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testcontainers.DockerClientFactory;
-import org.testcontainers.elasticsearch.ElasticsearchContainer;
import javax.jcr.GuestCredentials;
import javax.jcr.Node;
@@ -57,6 +47,7 @@ import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import javax.jcr.security.Privilege;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -67,16 +58,13 @@ import static org.apache.jackrabbit.oak.
import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_ANALYZED;
import static
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_USE_IN_SPELLCHECK;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeNotNull;
public class ElasticSpellcheckTest {
- private static final Logger LOG =
LoggerFactory.getLogger(ElasticSpellcheckTest.class);
private Session adminSession;
private Session anonymousSession;
private QueryManager qe;
private Node indexNode;
- private static final String TEST_INDEX = "testIndex";
// Set this connection string as
// <scheme>://<hostname>:<port>?key_id=<>,key_secret=<>
@@ -164,7 +152,6 @@ public class ElasticSpellcheckTest {
@Test
public void testSpellcheckSingleWord() throws Exception {
- //Session session = superuser;
QueryManager qm = adminSession.getWorkspace().getQueryManager();
Node par = allow(getOrCreateByPath("/parent", "oak:Unstructured",
adminSession));
Node n1 = par.addNode("node1");
@@ -177,7 +164,7 @@ public class ElasticSpellcheckTest {
Query q = qm.createQuery(sql, Query.SQL);
assertEventually(() -> {
try {
- assertEquals("[decent, descent]", getResult(q.execute(),
"rep:spellcheck()").toString());
+ assertEquals("[decent, descent]",
getResult(q.execute()).toString());
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
@@ -186,7 +173,6 @@ public class ElasticSpellcheckTest {
@Test
public void testSpellcheckSingleWordWithDescendantNode() throws Exception {
- //Session session = superuser;
QueryManager qm = adminSession.getWorkspace().getQueryManager();
Node par = allow(getOrCreateByPath("/parent", "oak:Unstructured",
adminSession));
Node n1 = par.addNode("node1");
@@ -199,7 +185,7 @@ public class ElasticSpellcheckTest {
Query q = qm.createQuery(sql, Query.SQL);
assertEventually(() -> {
try {
- assertEquals("[decent]", getResult(q.execute(),
"rep:spellcheck()").toString());
+ assertEquals("[decent]", getResult(q.execute()).toString());
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
@@ -226,31 +212,24 @@ public class ElasticSpellcheckTest {
assertEventually(() -> {
try {
- assertEquals("[voting in ontario]", getResult(q.execute(),
"rep:spellcheck()").toString());
+ assertEquals("[voting in ontario]",
getResult(q.execute()).toString());
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
});
}
- private Node deny(Node node) throws RepositoryException {
- AccessControlUtils.deny(node, "anonymous", Privilege.JCR_ALL);
- return node;
- }
-
private Node allow(Node node) throws RepositoryException {
AccessControlUtils.allow(node, "anonymous", Privilege.JCR_READ);
return node;
}
- static List<String> getResult(QueryResult result, String propertyName)
throws RepositoryException {
- List<String> results = Lists.newArrayList();
- RowIterator it = null;
-
- it = result.getRows();
+ private static List<String> getResult(QueryResult result) throws
RepositoryException {
+ List<String> results = new ArrayList<>();
+ RowIterator it = result.getRows();
while (it.hasNext()) {
Row row = it.nextRow();
- results.add(row.getValue(propertyName).getString());
+ results.add(row.getValue("rep:spellcheck()").getString());
}
return results;
}
Modified:
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java?rev=1879190&r1=1879189&r2=1879190&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
(original)
+++
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexHelperTest.java
Thu Jun 25 14:58:08 2020
@@ -29,6 +29,7 @@ import java.io.IOException;
import java.util.Map;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -88,6 +89,9 @@ public class ElasticIndexHelperTest {
assertThat(request.settings().get("analysis.filter.oak_word_delimiter_graph_filter.preserve_original"),
is("false"));
+ assertThat(request.settings().get("analysis.filter.shingle.type"),
nullValue());
+
assertThat(request.settings().get("analysis.filter.analyzer.trigram.type"),
nullValue());
+
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jsonMappings =
mapper.readValue(request.mappings().streamInput(), Map.class);
Map fooMapping = (Map) ((Map)
jsonMappings.get("properties")).get("foo");
@@ -114,4 +118,31 @@ public class ElasticIndexHelperTest {
assertThat(request.settings().get("analysis.filter.oak_word_delimiter_graph_filter.preserve_original"),
is("true"));
}
+ @Test
+ public void spellCheck() throws IOException {
+ IndexDefinitionBuilder builder = new ElasticIndexDefinitionBuilder();
+ IndexDefinitionBuilder.IndexRule indexRule = builder.indexRule("type");
+ indexRule.property("foo").type("String").analyzed().useInSpellcheck();
+ indexRule.property("bar").type("String").analyzed();
+
+ NodeState nodeState = builder.build();
+
+ ElasticIndexDefinition definition =
+ new ElasticIndexDefinition(nodeState, nodeState, "path",
"prefix");
+
+ CreateIndexRequest request =
ElasticIndexHelper.createIndexRequest(definition);
+
+ assertThat(request.settings().get("analysis.filter.shingle.type"),
is("shingle"));
+ assertThat(request.settings().get("analysis.analyzer.trigram.type"),
is("custom"));
+
+ ObjectMapper mapper = new ObjectMapper();
+ Map<String, Object> jsonMappings =
mapper.readValue(request.mappings().streamInput(), Map.class);
+ Map fooMapping = (Map) ((Map)
jsonMappings.get("properties")).get("foo");
+ Map fooFields = (Map) fooMapping.get("fields");
+ assertThat(fooFields.get("trigram"), notNullValue());
+ Map barMapping = (Map) ((Map)
jsonMappings.get("properties")).get("bar");
+ Map barFields = (Map) barMapping.get("fields");
+ assertThat(barFields.get("trigram"), nullValue());
+ }
+
}