Skip to content

Commit

Permalink
Discrepancy in result from _validate/query API and actual query valid…
Browse files Browse the repository at this point in the history
…ity (#2416)

* Discrepancy in result from _validate/query API and actual query validity

Signed-off-by: Andriy Redko <andriy.redko@aiven.io>

* Moved the validate() check later into the flow to allow range validation to trigger first

Signed-off-by: Andriy Redko <andriy.redko@aiven.io>
  • Loading branch information
reta authored Mar 14, 2022
1 parent d190813 commit 5c0f9bc
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@

import static org.opensearch.cluster.metadata.IndexMetadata.SETTING_NUMBER_OF_SHARDS;
import static org.opensearch.index.query.QueryBuilders.queryStringQuery;
import static org.opensearch.index.query.QueryBuilders.rangeQuery;
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.allOf;
Expand Down Expand Up @@ -500,4 +501,100 @@ public void testExplainTermsQueryWithLookup() throws Exception {
.actionGet();
assertThat(response.isValid(), is(true));
}

// Issue: https://github.com/opensearch-project/OpenSearch/issues/2036
public void testValidateDateRangeInQueryString() throws IOException {
assertAcked(prepareCreate("test").setSettings(Settings.builder().put(indexSettings()).put("index.number_of_shards", 1)));

assertAcked(
client().admin()
.indices()
.preparePutMapping("test")
.setSource(
XContentFactory.jsonBuilder()
.startObject()
.startObject(MapperService.SINGLE_MAPPING_NAME)
.startObject("properties")
.startObject("name")
.field("type", "keyword")
.endObject()
.startObject("timestamp")
.field("type", "date")
.endObject()
.endObject()
.endObject()
.endObject()
)
);

client().prepareIndex("test").setId("1").setSource("name", "username", "timestamp", 200).get();
refresh();

ValidateQueryResponse response = client().admin()
.indices()
.prepareValidateQuery()
.setQuery(
QueryBuilders.boolQuery()
.must(rangeQuery("timestamp").gte(0).lte(100))
.must(queryStringQuery("username").allowLeadingWildcard(false))
)
.setRewrite(true)
.get();

assertNoFailures(response);
assertThat(response.isValid(), is(true));

// Use wildcard and date outside the range
response = client().admin()
.indices()
.prepareValidateQuery()
.setQuery(
QueryBuilders.boolQuery()
.must(rangeQuery("timestamp").gte(0).lte(100))
.must(queryStringQuery("*erna*").allowLeadingWildcard(false))
)
.setRewrite(true)
.get();

assertNoFailures(response);
assertThat(response.isValid(), is(false));

// Use wildcard and date inside the range
response = client().admin()
.indices()
.prepareValidateQuery()
.setQuery(
QueryBuilders.boolQuery()
.must(rangeQuery("timestamp").gte(0).lte(1000))
.must(queryStringQuery("*erna*").allowLeadingWildcard(false))
)
.setRewrite(true)
.get();

assertNoFailures(response);
assertThat(response.isValid(), is(false));

// Use wildcard and date inside the range (allow leading wildcard)
response = client().admin()
.indices()
.prepareValidateQuery()
.setQuery(QueryBuilders.boolQuery().must(rangeQuery("timestamp").gte(0).lte(1000)).must(queryStringQuery("*erna*")))
.setRewrite(true)
.get();

assertNoFailures(response);
assertThat(response.isValid(), is(true));

// Use invalid date range
response = client().admin()
.indices()
.prepareValidateQuery()
.setQuery(QueryBuilders.boolQuery().must(rangeQuery("timestamp").gte("aaa").lte(100)))
.setRewrite(true)
.get();

assertNoFailures(response);
assertThat(response.isValid(), is(false));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ protected void doExecute(Task task, ValidateQueryRequest request, ActionListener
if (request.query() == null) {
rewriteListener.onResponse(request.query());
} else {
Rewriteable.rewriteAndFetch(request.query(), searchService.getRewriteContext(timeProvider), rewriteListener);
Rewriteable.rewriteAndFetch(request.query(), searchService.getValidationRewriteContext(timeProvider), rewriteListener);
}
}

Expand Down Expand Up @@ -225,7 +225,7 @@ protected ShardValidateQueryResponse shardOperation(ShardValidateQueryRequest re
request.nowInMillis(),
request.filteringAliases()
);
SearchContext searchContext = searchService.createSearchContext(shardSearchLocalRequest, SearchService.NO_TIMEOUT);
SearchContext searchContext = searchService.createValidationContext(shardSearchLocalRequest, SearchService.NO_TIMEOUT);
try {
ParsedQuery parsedQuery = searchContext.getQueryShardContext().toQuery(request.query());
searchContext.parsedQuery(parsedQuery);
Expand Down
19 changes: 18 additions & 1 deletion server/src/main/java/org/opensearch/index/IndexService.java
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,22 @@ public IndexSettings getIndexSettings() {
* {@link IndexReader}-specific optimizations, such as rewriting containing range queries.
*/
public QueryShardContext newQueryShardContext(int shardId, IndexSearcher searcher, LongSupplier nowInMillis, String clusterAlias) {
return newQueryShardContext(shardId, searcher, nowInMillis, clusterAlias, false);
}

/**
* Creates a new QueryShardContext.
*
* Passing a {@code null} {@link IndexSearcher} will return a valid context, however it won't be able to make
* {@link IndexReader}-specific optimizations, such as rewriting containing range queries.
*/
public QueryShardContext newQueryShardContext(
int shardId,
IndexSearcher searcher,
LongSupplier nowInMillis,
String clusterAlias,
boolean validate
) {
final SearchIndexNameMatcher indexNameMatcher = new SearchIndexNameMatcher(
index().getName(),
clusterAlias,
Expand All @@ -653,7 +669,8 @@ public QueryShardContext newQueryShardContext(int shardId, IndexSearcher searche
clusterAlias,
indexNameMatcher,
allowExpensiveQueries,
valuesSourceRegistry
valuesSourceRegistry,
validate
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,30 @@ public class QueryRewriteContext {
protected final Client client;
protected final LongSupplier nowInMillis;
private final List<BiConsumer<Client, ActionListener<?>>> asyncActions = new ArrayList<>();
private final boolean validate;

public QueryRewriteContext(
NamedXContentRegistry xContentRegistry,
NamedWriteableRegistry writeableRegistry,
Client client,
LongSupplier nowInMillis
) {
this(xContentRegistry, writeableRegistry, client, nowInMillis, false);
}

public QueryRewriteContext(
NamedXContentRegistry xContentRegistry,
NamedWriteableRegistry writeableRegistry,
Client client,
LongSupplier nowInMillis,
boolean validate
) {

this.xContentRegistry = xContentRegistry;
this.writeableRegistry = writeableRegistry;
this.client = client;
this.nowInMillis = nowInMillis;
this.validate = validate;
}

/**
Expand Down Expand Up @@ -140,4 +152,7 @@ public void onFailure(Exception e) {
}
}

public boolean validate() {
return validate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,48 @@ public QueryShardContext(
Predicate<String> indexNameMatcher,
BooleanSupplier allowExpensiveQueries,
ValuesSourceRegistry valuesSourceRegistry
) {
this(
shardId,
indexSettings,
bigArrays,
bitsetFilterCache,
indexFieldDataLookup,
mapperService,
similarityService,
scriptService,
xContentRegistry,
namedWriteableRegistry,
client,
searcher,
nowInMillis,
clusterAlias,
indexNameMatcher,
allowExpensiveQueries,
valuesSourceRegistry,
false
);
}

public QueryShardContext(
int shardId,
IndexSettings indexSettings,
BigArrays bigArrays,
BitsetFilterCache bitsetFilterCache,
TriFunction<MappedFieldType, String, Supplier<SearchLookup>, IndexFieldData<?>> indexFieldDataLookup,
MapperService mapperService,
SimilarityService similarityService,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
NamedWriteableRegistry namedWriteableRegistry,
Client client,
IndexSearcher searcher,
LongSupplier nowInMillis,
String clusterAlias,
Predicate<String> indexNameMatcher,
BooleanSupplier allowExpensiveQueries,
ValuesSourceRegistry valuesSourceRegistry,
boolean validate
) {
this(
shardId,
Expand All @@ -153,7 +195,8 @@ public QueryShardContext(
indexSettings.getIndex().getUUID()
),
allowExpensiveQueries,
valuesSourceRegistry
valuesSourceRegistry,
validate
);
}

Expand All @@ -175,7 +218,8 @@ public QueryShardContext(QueryShardContext source) {
source.indexNameMatcher,
source.fullyQualifiedIndex,
source.allowExpensiveQueries,
source.valuesSourceRegistry
source.valuesSourceRegistry,
source.validate()
);
}

Expand All @@ -196,9 +240,10 @@ private QueryShardContext(
Predicate<String> indexNameMatcher,
Index fullyQualifiedIndex,
BooleanSupplier allowExpensiveQueries,
ValuesSourceRegistry valuesSourceRegistry
ValuesSourceRegistry valuesSourceRegistry,
boolean validate
) {
super(xContentRegistry, namedWriteableRegistry, client, nowInMillis);
super(xContentRegistry, namedWriteableRegistry, client, nowInMillis, validate);
this.shardId = shardId;
this.similarityService = similarityService;
this.mapperService = mapperService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteC
}

DateMathParser dateMathParser = getForceDateParser();
return fieldType.isFieldWithinQuery(
final MappedFieldType.Relation relation = fieldType.isFieldWithinQuery(
shardContext.getIndexReader(),
from,
to,
Expand All @@ -462,6 +462,13 @@ protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteC
dateMathParser,
queryRewriteContext
);

// For validation, always assume that there is an intersection
if (relation == MappedFieldType.Relation.DISJOINT && shardContext.validate()) {
return MappedFieldType.Relation.INTERSECTS;
}

return relation;
}

// Not on the shard, we have no way to know what the relation is.
Expand Down
16 changes: 15 additions & 1 deletion server/src/main/java/org/opensearch/indices/IndicesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -1632,7 +1632,21 @@ public AliasFilter buildAliasFilter(ClusterState state, String index, Set<String
* Returns a new {@link QueryRewriteContext} with the given {@code now} provider
*/
public QueryRewriteContext getRewriteContext(LongSupplier nowInMillis) {
return new QueryRewriteContext(xContentRegistry, namedWriteableRegistry, client, nowInMillis);
return getRewriteContext(nowInMillis, false);
}

/**
* Returns a new {@link QueryRewriteContext} for query validation with the given {@code now} provider
*/
public QueryRewriteContext getValidationRewriteContext(LongSupplier nowInMillis) {
return getRewriteContext(nowInMillis, true);
}

/**
* Returns a new {@link QueryRewriteContext} with the given {@code now} provider
*/
private QueryRewriteContext getRewriteContext(LongSupplier nowInMillis, boolean validate) {
return new QueryRewriteContext(xContentRegistry, namedWriteableRegistry, client, nowInMillis, validate);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ final class DefaultSearchContext extends SearchContext {
TimeValue timeout,
FetchPhase fetchPhase,
boolean lowLevelCancellation,
Version minNodeVersion
Version minNodeVersion,
boolean validate
) throws IOException {
this.readerContext = readerContext;
this.request = request;
Expand Down Expand Up @@ -206,7 +207,8 @@ final class DefaultSearchContext extends SearchContext {
request.shardId().id(),
this.searcher,
request::nowInMillis,
shardTarget.getClusterAlias()
shardTarget.getClusterAlias(),
validate
);
queryBoost = request.indexBoost();
this.lowLevelCancellation = lowLevelCancellation;
Expand Down
Loading

0 comments on commit 5c0f9bc

Please sign in to comment.