Skip to content

[8.19] Fix behavior for _index LIKE for ESQL (#130849) #130957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/130849.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 130849
summary: Fix behavior for `_index` LIKE for ESQL
area: ES|QL
type: bug
issues:
- 129511
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19 = def(8_841_0_61);
public static final TransportVersion ESQL_PROFILE_INCLUDE_PLAN_8_19 = def(8_841_0_62);
public static final TransportVersion ESQL_SPLIT_ON_BIG_VALUES_8_19 = def(8_841_0_63);
public static final TransportVersion ESQL_FIXED_INDEX_LIKE_8_19 = def(8_841_0_64);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.core.Nullable;
Expand All @@ -23,6 +25,7 @@

import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;

/**
* A {@link MappedFieldType} that has the same value for all documents.
Expand Down Expand Up @@ -135,9 +138,47 @@ public final Query wildcardQuery(String value, boolean caseInsensitive, QueryRew
}
}

/**
* Returns a query that matches all documents or no documents
* It usually calls {@link #wildcardQuery(String, boolean, QueryRewriteContext)}
* except for IndexFieldType which overrides this method to use its own matching logic.
*/
public Query wildcardLikeQuery(String value, boolean caseInsensitive, QueryRewriteContext context) {
return wildcardQuery(value, caseInsensitive, context);
}

@Override
public final boolean fieldHasValue(FieldInfos fieldInfos) {
// We consider constant field types to always have value.
return true;
}

/**
* Returns the constant value of this field as a string.
* Based on the field type, we need to get it in a different way.
*/
public abstract String getConstantFieldValue(SearchExecutionContext context);

/**
* Returns a query that matches all documents or no documents
* depending on whether the constant value of this field matches or not
*/
@Override
public Query automatonQuery(
Supplier<Automaton> automatonSupplier,
Supplier<CharacterRunAutomaton> characterRunAutomatonSupplier,
@Nullable MultiTermQuery.RewriteMethod method,
SearchExecutionContext context,
String description
) {
CharacterRunAutomaton compiled = characterRunAutomatonSupplier.get();
boolean matches = compiled.run(getConstantFieldValue(context));
if (matches) {
return new MatchAllDocsQuery();
} else {
return new MatchNoDocsQuery(
"The \"" + context.getFullyQualifiedIndex().getName() + "\" query was rewritten to a \"match_none\" query."
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
package org.elasticsearch.index.mapper;

import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.FieldDataContext;
import org.elasticsearch.index.fielddata.IndexFieldData;
Expand All @@ -27,6 +31,7 @@

import java.util.Collections;
import java.util.List;
import java.util.Locale;

public class IndexFieldMapper extends MetadataFieldMapper {

Expand Down Expand Up @@ -102,6 +107,38 @@ public StoredFieldsSpec storedFieldsSpec() {
};
}

@Override
public Query wildcardLikeQuery(
String value,
@Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitve,
SearchExecutionContext context
) {
String indexName = context.getFullyQualifiedIndex().getName();
return getWildcardLikeQuery(value, caseInsensitve, indexName);
}

@Override
public Query wildcardLikeQuery(String value, boolean caseInsensitive, QueryRewriteContext context) {
String indexName = context.getFullyQualifiedIndex().getName();
return getWildcardLikeQuery(value, caseInsensitive, indexName);
}

private static Query getWildcardLikeQuery(String value, boolean caseInsensitve, String indexName) {
if (caseInsensitve) {
value = value.toLowerCase(Locale.ROOT);
indexName = indexName.toLowerCase(Locale.ROOT);
}
if (Regex.simpleMatch(value, indexName)) {
return new MatchAllDocsQuery();
}
return new MatchNoDocsQuery("The \"" + indexName + "\" query was rewritten to a \"match_none\" query.");
}

@Override
public String getConstantFieldValue(SearchExecutionContext context) {
return context.getFullyQualifiedIndex().getName();
}
}

public IndexFieldMapper() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ protected boolean matches(String pattern, boolean caseInsensitive, QueryRewriteC
return Regex.simpleMatch(pattern, indexMode, caseInsensitive);
}

@Override
public String getConstantFieldValue(SearchExecutionContext context) {
return context.getIndexSettings().getMode().getName();
}

@Override
public Query existsQuery(SearchExecutionContext context) {
return new MatchAllDocsQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.MultiTerms;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Automata;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import org.apache.lucene.util.automaton.CompiledAutomaton;
import org.apache.lucene.util.automaton.CompiledAutomaton.AUTOMATON_TYPE;
import org.apache.lucene.util.automaton.MinimizationOperations;
Expand All @@ -50,6 +52,7 @@
import org.elasticsearch.index.fielddata.SourceValueFetcherSortedBinaryIndexFieldData;
import org.elasticsearch.index.fielddata.StoredFieldSortedBinaryIndexFieldData;
import org.elasticsearch.index.fielddata.plain.SortedSetOrdinalsIndexFieldData;
import org.elasticsearch.index.query.AutomatonQueryWithDescription;
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.index.similarity.SimilarityProvider;
import org.elasticsearch.script.Script;
Expand Down Expand Up @@ -81,6 +84,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import static org.apache.lucene.index.IndexWriter.MAX_TERM_LENGTH;
import static org.elasticsearch.core.Strings.format;
Expand Down Expand Up @@ -930,6 +934,17 @@ public boolean hasScriptValues() {
public boolean hasNormalizer() {
return normalizer != Lucene.KEYWORD_ANALYZER;
}
}

@Override
public Query automatonQuery(
Supplier<Automaton> automatonSupplier,
Supplier<CharacterRunAutomaton> characterRunAutomatonSupplier,
@Nullable MultiTermQuery.RewriteMethod method,
SearchExecutionContext context,
String description
) {
return new AutomatonQueryWithDescription(new Term(name()), automatonSupplier.get(), description);
}

private final boolean indexed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.apache.lucene.search.TermInSetQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.cluster.metadata.IndexMetadata;
Expand Down Expand Up @@ -58,6 +60,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES;

Expand Down Expand Up @@ -333,6 +336,19 @@ public final Query wildcardQuery(String value, @Nullable MultiTermQuery.RewriteM
return wildcardQuery(value, method, false, context);
}

/**
* Similar to wildcardQuery, except that we change the behavior for ESQL
* to behave like a string LIKE query, where the value is matched as a string
*/
public Query wildcardLikeQuery(
String value,
@Nullable MultiTermQuery.RewriteMethod method,
boolean caseInsensitve,
SearchExecutionContext context
) {
return wildcardQuery(value, method, caseInsensitve, context);
}

public Query wildcardQuery(
String value,
@Nullable MultiTermQuery.RewriteMethod method,
Expand Down Expand Up @@ -374,6 +390,23 @@ public Query regexpQuery(
);
}

/**
* Returns a Lucine pushable Query for the current field
* For now can only be AutomatonQuery or MatchAllDocsQuery() or MatchNoDocsQuery()
*/
public Query automatonQuery(
Supplier<Automaton> automatonSupplier,
Supplier<CharacterRunAutomaton> characterRunAutomatonSupplier,
@Nullable MultiTermQuery.RewriteMethod method,
SearchExecutionContext context,
String description
) {
throw new QueryShardException(
context,
"Can only use automaton queries on keyword fields - not on [" + name + "] which is of type [" + typeName() + "]"
);
}

public Query existsQuery(SearchExecutionContext context) {
if (hasDocValues() || getTextSearchInfo().hasNorms()) {
return new FieldExistsQuery(name());
Expand Down

This file was deleted.

Loading