org.opensearch.index.query.IntervalsSourceProvider Maven / Gradle / Ivy
Show all versions of opensearch Show documentation
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.index.query;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.intervals.FilteredIntervalsSource;
import org.apache.lucene.queries.intervals.IntervalIterator;
import org.apache.lucene.queries.intervals.Intervals;
import org.apache.lucene.queries.intervals.IntervalsSource;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.CompiledAutomaton;
import org.apache.lucene.util.automaton.RegExp;
import org.opensearch.LegacyESVersion;
import org.opensearch.Version;
import org.opensearch.common.unit.Fuzziness;
import org.opensearch.core.ParseField;
import org.opensearch.core.common.ParsingException;
import org.opensearch.core.common.io.stream.NamedWriteable;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.core.common.io.stream.Writeable;
import org.opensearch.core.xcontent.ConstructingObjectParser;
import org.opensearch.core.xcontent.ObjectParser;
import org.opensearch.core.xcontent.ToXContentFragment;
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.index.analysis.NamedAnalyzer;
import org.opensearch.index.mapper.MappedFieldType;
import org.opensearch.script.Script;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import static org.opensearch.core.xcontent.ConstructingObjectParser.constructorArg;
import static org.opensearch.core.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* Factory class for {@link IntervalsSource}
*
* Built-in sources include {@link Match}, which analyzes a text string and converts it
* to a proximity source (phrase, ordered, unordered, unordered without overlaps depending on how
* strict the matching should be); {@link Combine}, which allows proximity queries
* between different sub-sources; and {@link Disjunction}.
*
* @opensearch.internal
*/
public abstract class IntervalsSourceProvider implements NamedWriteable, ToXContentFragment {
public abstract IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) throws IOException;
public abstract void extractFields(Set fields);
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object other);
public static IntervalsSourceProvider fromXContent(XContentParser parser) throws IOException {
assert parser.currentToken() == XContentParser.Token.FIELD_NAME;
switch (parser.currentName()) {
case "match":
return Match.fromXContent(parser);
case "any_of":
return Disjunction.fromXContent(parser);
case "all_of":
return Combine.fromXContent(parser);
case "prefix":
return Prefix.fromXContent(parser);
case "wildcard":
return Wildcard.fromXContent(parser);
case "regexp":
return Regexp.fromXContent(parser);
case "fuzzy":
return Fuzzy.fromXContent(parser);
}
throw new ParsingException(
parser.getTokenLocation(),
"Unknown interval type [" + parser.currentName() + "], expecting one of [match, any_of, all_of, prefix, wildcard, regexp]"
);
}
private static IntervalsSourceProvider parseInnerIntervals(XContentParser parser) throws IOException {
if (parser.nextToken() != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parser.getTokenLocation(), "Expected [FIELD_NAME] but got [" + parser.currentToken() + "]");
}
IntervalsSourceProvider isp = IntervalsSourceProvider.fromXContent(parser);
if (parser.nextToken() != XContentParser.Token.END_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "Expected [END_OBJECT] but got [" + parser.currentToken() + "]");
}
return isp;
}
/**
* Match interval
*
* @opensearch.internal
*/
public static class Match extends IntervalsSourceProvider {
public static final String NAME = "match";
private final String query;
private final int maxGaps;
private final IntervalMode mode;
private final String analyzer;
private final IntervalFilter filter;
private final String useField;
public Match(String query, int maxGaps, IntervalMode mode, String analyzer, IntervalFilter filter, String useField) {
this.query = query;
this.maxGaps = maxGaps;
this.mode = mode;
this.analyzer = analyzer;
this.filter = filter;
this.useField = useField;
}
public Match(StreamInput in) throws IOException {
this.query = in.readString();
this.maxGaps = in.readVInt();
if (in.getVersion().onOrAfter(Version.V_1_3_0)) {
this.mode = IntervalMode.readFromStream(in);
} else {
if (in.readBoolean()) {
this.mode = IntervalMode.ORDERED;
} else {
this.mode = IntervalMode.UNORDERED;
}
}
this.analyzer = in.readOptionalString();
this.filter = in.readOptionalWriteable(IntervalFilter::new);
if (in.getVersion().onOrAfter(LegacyESVersion.V_7_2_0)) {
this.useField = in.readOptionalString();
} else {
this.useField = null;
}
}
@Override
public IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) throws IOException {
NamedAnalyzer analyzer = null;
if (this.analyzer != null) {
analyzer = context.getMapperService().getIndexAnalyzers().get(this.analyzer);
}
IntervalsSource source;
if (useField != null) {
fieldType = context.fieldMapper(useField);
assert fieldType != null;
source = Intervals.fixField(useField, fieldType.intervals(query, maxGaps, mode, analyzer, false));
} else {
source = fieldType.intervals(query, maxGaps, mode, analyzer, false);
}
if (filter != null) {
return filter.filter(source, context, fieldType);
}
return source;
}
@Override
public void extractFields(Set fields) {
if (useField != null) {
fields.add(useField);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Match match = (Match) o;
return maxGaps == match.maxGaps
&& mode == match.mode
&& Objects.equals(query, match.query)
&& Objects.equals(filter, match.filter)
&& Objects.equals(useField, match.useField)
&& Objects.equals(analyzer, match.analyzer);
}
@Override
public int hashCode() {
return Objects.hash(query, maxGaps, mode, analyzer, filter, useField);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(query);
out.writeVInt(maxGaps);
if (out.getVersion().onOrAfter(Version.V_1_3_0)) {
mode.writeTo(out);
} else {
out.writeBoolean(mode == IntervalMode.ORDERED);
}
out.writeOptionalString(analyzer);
out.writeOptionalWriteable(filter);
if (out.getVersion().onOrAfter(LegacyESVersion.V_7_2_0)) {
out.writeOptionalString(useField);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(NAME);
builder.startObject();
builder.field("query", query);
builder.field("max_gaps", maxGaps);
builder.field("mode", mode);
if (analyzer != null) {
builder.field("analyzer", analyzer);
}
if (filter != null) {
builder.field("filter", filter);
}
if (useField != null) {
builder.field("use_field", useField);
}
return builder.endObject();
}
private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
String query = (String) args[0];
int max_gaps = (args[1] == null ? -1 : (Integer) args[1]);
Boolean ordered = (Boolean) args[2];
String mode = (String) args[3];
String analyzer = (String) args[4];
IntervalFilter filter = (IntervalFilter) args[5];
String useField = (String) args[6];
IntervalMode intervalMode;
if (ordered != null) {
intervalMode = ordered ? IntervalMode.ORDERED : IntervalMode.UNORDERED;
} else if (mode != null) {
intervalMode = IntervalMode.fromString(mode);
} else {
intervalMode = IntervalMode.UNORDERED;
}
return new Match(query, max_gaps, intervalMode, analyzer, filter, useField);
});
static {
PARSER.declareString(constructorArg(), new ParseField("query"));
PARSER.declareInt(optionalConstructorArg(), new ParseField("max_gaps"));
PARSER.declareBoolean(optionalConstructorArg(), new ParseField("ordered").withAllDeprecated());
PARSER.declareString(optionalConstructorArg(), new ParseField("mode"));
PARSER.declareString(optionalConstructorArg(), new ParseField("analyzer"));
PARSER.declareObject(optionalConstructorArg(), (p, c) -> IntervalFilter.fromXContent(p), new ParseField("filter"));
PARSER.declareString(optionalConstructorArg(), new ParseField("use_field"));
}
public static Match fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
String getQuery() {
return query;
}
int getMaxGaps() {
return maxGaps;
}
IntervalMode getMode() {
return mode;
}
String getAnalyzer() {
return analyzer;
}
IntervalFilter getFilter() {
return filter;
}
String getUseField() {
return useField;
}
}
/**
* Disjunction interval
*
* @opensearch.internal
*/
public static class Disjunction extends IntervalsSourceProvider {
public static final String NAME = "any_of";
private final List subSources;
private final IntervalFilter filter;
public Disjunction(List subSources, IntervalFilter filter) {
this.subSources = subSources;
this.filter = filter;
}
public Disjunction(StreamInput in) throws IOException {
this.subSources = in.readNamedWriteableList(IntervalsSourceProvider.class);
this.filter = in.readOptionalWriteable(IntervalFilter::new);
}
@Override
public IntervalsSource getSource(QueryShardContext ctx, MappedFieldType fieldType) throws IOException {
List sources = new ArrayList<>();
for (IntervalsSourceProvider provider : subSources) {
sources.add(provider.getSource(ctx, fieldType));
}
IntervalsSource source = Intervals.or(sources.toArray(new IntervalsSource[0]));
if (filter == null) {
return source;
}
return filter.filter(source, ctx, fieldType);
}
@Override
public void extractFields(Set fields) {
for (IntervalsSourceProvider provider : subSources) {
provider.extractFields(fields);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Disjunction that = (Disjunction) o;
return Objects.equals(subSources, that.subSources) && Objects.equals(filter, that.filter);
}
@Override
public int hashCode() {
return Objects.hash(subSources, filter);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteableList(subSources);
out.writeOptionalWriteable(filter);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.startArray("intervals");
for (IntervalsSourceProvider provider : subSources) {
builder.startObject();
provider.toXContent(builder, params);
builder.endObject();
}
builder.endArray();
if (filter != null) {
builder.field("filter", filter);
}
return builder.endObject();
}
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
List subSources = (List) args[0];
IntervalFilter filter = (IntervalFilter) args[1];
return new Disjunction(subSources, filter);
});
static {
PARSER.declareObjectArray(
constructorArg(),
(p, c) -> IntervalsSourceProvider.parseInnerIntervals(p),
new ParseField("intervals")
);
PARSER.declareObject(optionalConstructorArg(), (p, c) -> IntervalFilter.fromXContent(p), new ParseField("filter"));
}
public static Disjunction fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
List getSubSources() {
return subSources;
}
IntervalFilter getFilter() {
return filter;
}
}
/**
* Combine interval
*
* @opensearch.internal
*/
public static class Combine extends IntervalsSourceProvider {
public static final String NAME = "all_of";
private final List subSources;
private final IntervalMode mode;
private final int maxGaps;
private final IntervalFilter filter;
public Combine(List subSources, IntervalMode mode, int maxGaps, IntervalFilter filter) {
this.subSources = subSources;
this.mode = mode;
this.maxGaps = maxGaps;
this.filter = filter;
}
public Combine(StreamInput in) throws IOException {
if (in.getVersion().onOrAfter(Version.V_1_3_0)) {
this.mode = IntervalMode.readFromStream(in);
} else {
this.mode = in.readBoolean() ? IntervalMode.ORDERED : IntervalMode.UNORDERED;
}
this.subSources = in.readNamedWriteableList(IntervalsSourceProvider.class);
this.maxGaps = in.readInt();
this.filter = in.readOptionalWriteable(IntervalFilter::new);
}
@Override
public IntervalsSource getSource(QueryShardContext ctx, MappedFieldType fieldType) throws IOException {
List ss = new ArrayList<>();
for (IntervalsSourceProvider provider : subSources) {
ss.add(provider.getSource(ctx, fieldType));
}
IntervalsSource source = IntervalBuilder.combineSources(ss, maxGaps, mode);
if (filter != null) {
return filter.filter(source, ctx, fieldType);
}
return source;
}
@Override
public void extractFields(Set fields) {
for (IntervalsSourceProvider provider : subSources) {
provider.extractFields(fields);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Combine combine = (Combine) o;
return Objects.equals(subSources, combine.subSources)
&& mode == combine.mode
&& maxGaps == combine.maxGaps
&& Objects.equals(filter, combine.filter);
}
@Override
public int hashCode() {
return Objects.hash(subSources, mode, maxGaps, filter);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
if (out.getVersion().onOrAfter(Version.V_1_3_0)) {
mode.writeTo(out);
} else {
out.writeBoolean(mode == IntervalMode.ORDERED);
}
out.writeNamedWriteableList(subSources);
out.writeInt(maxGaps);
out.writeOptionalWriteable(filter);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field("mode", mode);
builder.field("max_gaps", maxGaps);
builder.startArray("intervals");
for (IntervalsSourceProvider provider : subSources) {
builder.startObject();
provider.toXContent(builder, params);
builder.endObject();
}
builder.endArray();
if (filter != null) {
builder.field("filter", filter);
}
return builder.endObject();
}
@SuppressWarnings("unchecked")
static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
Boolean ordered = (Boolean) args[0];
String mode = (String) args[1];
List subSources = (List) args[2];
Integer maxGaps = (args[3] == null ? -1 : (Integer) args[3]);
IntervalFilter filter = (IntervalFilter) args[4];
IntervalMode intervalMode;
if (ordered != null) {
intervalMode = ordered ? IntervalMode.ORDERED : IntervalMode.UNORDERED;
} else if (mode != null) {
intervalMode = IntervalMode.fromString(mode);
} else {
intervalMode = IntervalMode.UNORDERED;
}
return new Combine(subSources, intervalMode, maxGaps, filter);
});
static {
PARSER.declareBoolean(optionalConstructorArg(), new ParseField("ordered").withAllDeprecated());
PARSER.declareString(optionalConstructorArg(), new ParseField("mode"));
PARSER.declareObjectArray(
constructorArg(),
(p, c) -> IntervalsSourceProvider.parseInnerIntervals(p),
new ParseField("intervals")
);
PARSER.declareInt(optionalConstructorArg(), new ParseField("max_gaps"));
PARSER.declareObject(optionalConstructorArg(), (p, c) -> IntervalFilter.fromXContent(p), new ParseField("filter"));
}
public static Combine fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
List getSubSources() {
return subSources;
}
IntervalMode getMode() {
return mode;
}
int getMaxGaps() {
return maxGaps;
}
IntervalFilter getFilter() {
return filter;
}
}
/**
* Prefix interval
*
* @opensearch.internal
*/
public static class Prefix extends IntervalsSourceProvider {
public static final String NAME = "prefix";
private final String prefix;
private final String analyzer;
private final String useField;
public Prefix(String prefix, String analyzer, String useField) {
this.prefix = prefix;
this.analyzer = analyzer;
this.useField = useField;
}
public Prefix(StreamInput in) throws IOException {
this.prefix = in.readString();
this.analyzer = in.readOptionalString();
this.useField = in.readOptionalString();
}
@Override
public IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) throws IOException {
NamedAnalyzer analyzer = null;
if (this.analyzer != null) {
analyzer = context.getMapperService().getIndexAnalyzers().get(this.analyzer);
}
IntervalsSource source;
if (useField != null) {
fieldType = context.fieldMapper(useField);
assert fieldType != null;
source = Intervals.fixField(useField, fieldType.intervals(prefix, 0, IntervalMode.UNORDERED, analyzer, true));
} else {
source = fieldType.intervals(prefix, 0, IntervalMode.UNORDERED, analyzer, true);
}
return source;
}
@Override
public void extractFields(Set fields) {
if (useField != null) {
fields.add(useField);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Prefix prefix = (Prefix) o;
return Objects.equals(this.prefix, prefix.prefix)
&& Objects.equals(analyzer, prefix.analyzer)
&& Objects.equals(useField, prefix.useField);
}
@Override
public int hashCode() {
return Objects.hash(prefix, analyzer, useField);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(prefix);
out.writeOptionalString(analyzer);
out.writeOptionalString(useField);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field("prefix", prefix);
if (analyzer != null) {
builder.field("analyzer", analyzer);
}
if (useField != null) {
builder.field("use_field", useField);
}
builder.endObject();
return builder;
}
private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
String term = (String) args[0];
String analyzer = (String) args[1];
String useField = (String) args[2];
return new Prefix(term, analyzer, useField);
});
static {
PARSER.declareString(constructorArg(), new ParseField("prefix"));
PARSER.declareString(optionalConstructorArg(), new ParseField("analyzer"));
PARSER.declareString(optionalConstructorArg(), new ParseField("use_field"));
}
public static Prefix fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
String getPrefix() {
return prefix;
}
String getAnalyzer() {
return analyzer;
}
String getUseField() {
return useField;
}
}
/**
* Regular expression interval
*
* @opensearch.internal
*/
public static class Regexp extends IntervalsSourceProvider {
public static final String NAME = "regexp";
public static final int DEFAULT_FLAGS_VALUE = RegexpFlag.ALL.value();
private final String pattern;
private final int flags;
private final String useField;
private final Integer maxExpansions;
private final boolean caseInsensitive;
/**
* Constructor
*
* {@code flags} is Lucene's syntax flags
* and {@code caseInsensitive} enables Lucene's only matching flag.
*/
public Regexp(String pattern, int flags, String useField, Integer maxExpansions, boolean caseInsensitive) {
this.pattern = pattern;
this.flags = flags;
this.useField = useField;
this.maxExpansions = (maxExpansions != null && maxExpansions > 0) ? maxExpansions : null;
this.caseInsensitive = caseInsensitive;
}
public Regexp(StreamInput in) throws IOException {
this.pattern = in.readString();
this.flags = in.readVInt();
this.useField = in.readOptionalString();
this.maxExpansions = in.readOptionalVInt();
if (in.getVersion().onOrAfter(Version.V_1_3_0)) {
this.caseInsensitive = in.readBoolean();
} else {
this.caseInsensitive = false;
}
}
@Override
public IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) {
final org.apache.lucene.util.automaton.RegExp regexp = new org.apache.lucene.util.automaton.RegExp(
pattern,
flags,
caseInsensitive ? RegExp.ASCII_CASE_INSENSITIVE : 0
);
final CompiledAutomaton automaton = new CompiledAutomaton(regexp.toAutomaton());
if (useField != null) {
fieldType = context.fieldMapper(useField);
assert fieldType != null;
checkPositions(fieldType);
IntervalsSource regexpSource = maxExpansions == null
? Intervals.multiterm(automaton, regexp.toString())
: Intervals.multiterm(automaton, maxExpansions, regexp.toString());
return Intervals.fixField(useField, regexpSource);
} else {
checkPositions(fieldType);
return maxExpansions == null
? Intervals.multiterm(automaton, regexp.toString())
: Intervals.multiterm(automaton, maxExpansions, regexp.toString());
}
}
private void checkPositions(MappedFieldType type) {
if (type.getTextSearchInfo().hasPositions() == false) {
throw new IllegalArgumentException("Cannot create intervals over field [" + type.name() + "] with no positions indexed");
}
}
@Override
public void extractFields(Set fields) {
if (useField != null) {
fields.add(useField);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Regexp regexp = (Regexp) o;
return Objects.equals(pattern, regexp.pattern)
&& Objects.equals(flags, regexp.flags)
&& Objects.equals(useField, regexp.useField)
&& Objects.equals(maxExpansions, regexp.maxExpansions)
&& Objects.equals(caseInsensitive, regexp.caseInsensitive);
}
@Override
public int hashCode() {
return Objects.hash(pattern, flags, useField, maxExpansions, caseInsensitive);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(pattern);
out.writeVInt(flags);
out.writeOptionalString(useField);
out.writeOptionalVInt(maxExpansions);
if (out.getVersion().onOrAfter(Version.V_1_3_0)) {
out.writeBoolean(caseInsensitive);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field("pattern", pattern);
if (flags != DEFAULT_FLAGS_VALUE) {
builder.field("flags_value", flags);
}
if (useField != null) {
builder.field("use_field", useField);
}
if (maxExpansions != null) {
builder.field("max_expansions", maxExpansions);
}
if (caseInsensitive) {
builder.field("case_insensitive", caseInsensitive);
}
builder.endObject();
return builder;
}
private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
String pattern = (String) args[0];
String flags = (String) args[1];
Integer flagsValue = (Integer) args[2];
String useField = (String) args[3];
Integer maxExpansions = (Integer) args[4];
boolean caseInsensitive = args[5] != null && (boolean) args[5];
if (flagsValue != null) {
return new Regexp(pattern, flagsValue, useField, maxExpansions, caseInsensitive);
} else if (flags != null) {
return new Regexp(pattern, RegexpFlag.resolveValue(flags), useField, maxExpansions, caseInsensitive);
} else {
return new Regexp(pattern, DEFAULT_FLAGS_VALUE, useField, maxExpansions, caseInsensitive);
}
});
static {
PARSER.declareString(constructorArg(), new ParseField("pattern"));
PARSER.declareString(optionalConstructorArg(), new ParseField("flags"));
PARSER.declareInt(optionalConstructorArg(), new ParseField("flags_value"));
PARSER.declareString(optionalConstructorArg(), new ParseField("use_field"));
PARSER.declareInt(optionalConstructorArg(), new ParseField("max_expansions"));
PARSER.declareBoolean(optionalConstructorArg(), new ParseField("case_insensitive"));
}
public static Regexp fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
String getPattern() {
return pattern;
}
int getFlags() {
return flags;
}
String getUseField() {
return useField;
}
Integer getMaxExpansions() {
return maxExpansions;
}
boolean isCaseInsensitive() {
return caseInsensitive;
}
}
/**
* Wildcard interval
*
* @opensearch.internal
*/
public static class Wildcard extends IntervalsSourceProvider {
public static final String NAME = "wildcard";
private final String pattern;
private final String analyzer;
private final String useField;
private final Integer maxExpansions;
public Wildcard(String pattern, String analyzer, String useField, Integer maxExpansions) {
this.pattern = pattern;
this.analyzer = analyzer;
this.useField = useField;
this.maxExpansions = (maxExpansions != null && maxExpansions > 0) ? maxExpansions : null;
}
public Wildcard(StreamInput in) throws IOException {
this.pattern = in.readString();
this.analyzer = in.readOptionalString();
this.useField = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_1_3_0)) {
this.maxExpansions = in.readOptionalVInt();
} else {
this.maxExpansions = null;
}
}
@Override
public IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) {
NamedAnalyzer analyzer = fieldType.getTextSearchInfo().getSearchAnalyzer();
if (this.analyzer != null) {
analyzer = context.getMapperService().getIndexAnalyzers().get(this.analyzer);
}
IntervalsSource source;
if (useField != null) {
fieldType = context.fieldMapper(useField);
assert fieldType != null;
checkPositions(fieldType);
if (this.analyzer == null) {
analyzer = fieldType.getTextSearchInfo().getSearchAnalyzer();
}
BytesRef normalizedTerm = analyzer.normalize(useField, pattern);
IntervalsSource wildcardSource = maxExpansions == null
? Intervals.wildcard(normalizedTerm)
: Intervals.wildcard(normalizedTerm, maxExpansions);
source = Intervals.fixField(useField, wildcardSource);
} else {
checkPositions(fieldType);
BytesRef normalizedTerm = analyzer.normalize(fieldType.name(), pattern);
source = maxExpansions == null ? Intervals.wildcard(normalizedTerm) : Intervals.wildcard(normalizedTerm, maxExpansions);
}
return source;
}
private void checkPositions(MappedFieldType type) {
if (type.getTextSearchInfo().hasPositions() == false) {
throw new IllegalArgumentException("Cannot create intervals over field [" + type.name() + "] with no positions indexed");
}
}
@Override
public void extractFields(Set fields) {
if (useField != null) {
fields.add(useField);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Wildcard wildcard = (Wildcard) o;
return Objects.equals(pattern, wildcard.pattern)
&& Objects.equals(analyzer, wildcard.analyzer)
&& Objects.equals(useField, wildcard.useField)
&& Objects.equals(maxExpansions, wildcard.maxExpansions);
}
@Override
public int hashCode() {
return Objects.hash(pattern, analyzer, useField, maxExpansions);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(pattern);
out.writeOptionalString(analyzer);
out.writeOptionalString(useField);
if (out.getVersion().onOrAfter(Version.V_1_3_0)) {
out.writeOptionalVInt(maxExpansions);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field("pattern", pattern);
if (analyzer != null) {
builder.field("analyzer", analyzer);
}
if (useField != null) {
builder.field("use_field", useField);
}
if (maxExpansions != null) {
builder.field("max_expansions", maxExpansions);
}
builder.endObject();
return builder;
}
private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
String term = (String) args[0];
String analyzer = (String) args[1];
String useField = (String) args[2];
Integer maxExpansions = (Integer) args[3];
return new Wildcard(term, analyzer, useField, maxExpansions);
});
static {
PARSER.declareString(constructorArg(), new ParseField("pattern"));
PARSER.declareString(optionalConstructorArg(), new ParseField("analyzer"));
PARSER.declareString(optionalConstructorArg(), new ParseField("use_field"));
PARSER.declareInt(optionalConstructorArg(), new ParseField("max_expansions"));
}
public static Wildcard fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
String getPattern() {
return pattern;
}
String getAnalyzer() {
return analyzer;
}
String getUseField() {
return useField;
}
Integer getMaxExpansions() {
return maxExpansions;
}
}
/**
* Fuzzy interval
*
* @opensearch.internal
*/
public static class Fuzzy extends IntervalsSourceProvider {
public static final String NAME = "fuzzy";
private final String term;
private final int prefixLength;
private final boolean transpositions;
private final Fuzziness fuzziness;
private final String analyzer;
private final String useField;
public Fuzzy(String term, int prefixLength, boolean transpositions, Fuzziness fuzziness, String analyzer, String useField) {
this.term = term;
this.prefixLength = prefixLength;
this.transpositions = transpositions;
this.fuzziness = fuzziness;
this.analyzer = analyzer;
this.useField = useField;
}
public Fuzzy(StreamInput in) throws IOException {
this.term = in.readString();
this.prefixLength = in.readVInt();
this.transpositions = in.readBoolean();
this.fuzziness = new Fuzziness(in);
this.analyzer = in.readOptionalString();
this.useField = in.readOptionalString();
}
@Override
public IntervalsSource getSource(QueryShardContext context, MappedFieldType fieldType) {
NamedAnalyzer analyzer = fieldType.getTextSearchInfo().getSearchAnalyzer();
if (this.analyzer != null) {
analyzer = context.getMapperService().getIndexAnalyzers().get(this.analyzer);
}
IntervalsSource source;
if (useField != null) {
fieldType = context.fieldMapper(useField);
assert fieldType != null;
checkPositions(fieldType);
if (this.analyzer == null) {
analyzer = fieldType.getTextSearchInfo().getSearchAnalyzer();
}
}
checkPositions(fieldType);
BytesRef normalizedTerm = analyzer.normalize(fieldType.name(), term);
FuzzyQuery fq = new FuzzyQuery(
new Term(fieldType.name(), normalizedTerm),
fuzziness.asDistance(term),
prefixLength,
128,
transpositions
);
source = Intervals.multiterm(fq.getAutomata(), term);
if (useField != null) {
source = Intervals.fixField(useField, source);
}
return source;
}
private void checkPositions(MappedFieldType type) {
if (type.getTextSearchInfo().hasPositions() == false) {
throw new IllegalArgumentException("Cannot create intervals over field [" + type.name() + "] with no positions indexed");
}
}
@Override
public void extractFields(Set fields) {
if (useField != null) {
fields.add(useField);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Fuzzy fuzzy = (Fuzzy) o;
return prefixLength == fuzzy.prefixLength
&& transpositions == fuzzy.transpositions
&& Objects.equals(term, fuzzy.term)
&& Objects.equals(fuzziness, fuzzy.fuzziness)
&& Objects.equals(analyzer, fuzzy.analyzer)
&& Objects.equals(useField, fuzzy.useField);
}
@Override
public int hashCode() {
return Objects.hash(term, prefixLength, transpositions, fuzziness, analyzer, useField);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(term);
out.writeVInt(prefixLength);
out.writeBoolean(transpositions);
fuzziness.writeTo(out);
out.writeOptionalString(analyzer);
out.writeOptionalString(useField);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field("term", term);
builder.field("prefix_length", prefixLength);
builder.field("transpositions", transpositions);
fuzziness.toXContent(builder, params);
if (analyzer != null) {
builder.field("analyzer", analyzer);
}
if (useField != null) {
builder.field("use_field", useField);
}
builder.endObject();
return builder;
}
private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, args -> {
String term = (String) args[0];
int prefixLength = (args[1] == null) ? FuzzyQueryBuilder.DEFAULT_PREFIX_LENGTH : (int) args[1];
boolean transpositions = (args[2] == null) ? FuzzyQueryBuilder.DEFAULT_TRANSPOSITIONS : (boolean) args[2];
Fuzziness fuzziness = (args[3] == null) ? FuzzyQueryBuilder.DEFAULT_FUZZINESS : (Fuzziness) args[3];
String analyzer = (String) args[4];
String useField = (String) args[5];
return new Fuzzy(term, prefixLength, transpositions, fuzziness, analyzer, useField);
});
static {
PARSER.declareString(constructorArg(), new ParseField("term"));
PARSER.declareInt(optionalConstructorArg(), new ParseField("prefix_length"));
PARSER.declareBoolean(optionalConstructorArg(), new ParseField("transpositions"));
PARSER.declareField(optionalConstructorArg(), (p, c) -> Fuzziness.parse(p), Fuzziness.FIELD, ObjectParser.ValueType.VALUE);
PARSER.declareString(optionalConstructorArg(), new ParseField("analyzer"));
PARSER.declareString(optionalConstructorArg(), new ParseField("use_field"));
}
public static Fuzzy fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
String getTerm() {
return term;
}
int getPrefixLength() {
return prefixLength;
}
boolean isTranspositions() {
return transpositions;
}
Fuzziness getFuzziness() {
return fuzziness;
}
String getAnalyzer() {
return analyzer;
}
String getUseField() {
return useField;
}
}
/**
* Script filter source
*
* @opensearch.internal
*/
static class ScriptFilterSource extends FilteredIntervalsSource {
final IntervalFilterScript script;
IntervalFilterScript.Interval interval = new IntervalFilterScript.Interval();
ScriptFilterSource(IntervalsSource in, String name, IntervalFilterScript script) {
super("FILTER(" + name + ")", in);
this.script = script;
}
@Override
protected boolean accept(IntervalIterator it) {
interval.setIterator(it);
return script.execute(interval);
}
}
/**
* An interval filter
*
* @opensearch.internal
*/
public static class IntervalFilter implements ToXContentObject, Writeable {
public static final String NAME = "filter";
private final String type;
private final IntervalsSourceProvider filter;
private final Script script;
public IntervalFilter(IntervalsSourceProvider filter, String type) {
this.filter = filter;
this.type = type.toLowerCase(Locale.ROOT);
this.script = null;
}
IntervalFilter(Script script) {
this.script = script;
this.type = "script";
this.filter = null;
}
public IntervalFilter(StreamInput in) throws IOException {
this.type = in.readString();
this.filter = in.readOptionalNamedWriteable(IntervalsSourceProvider.class);
if (in.readBoolean()) {
this.script = new Script(in);
} else {
this.script = null;
}
}
public IntervalsSource filter(IntervalsSource input, QueryShardContext context, MappedFieldType fieldType) throws IOException {
if (script != null) {
IntervalFilterScript ifs = context.compile(script, IntervalFilterScript.CONTEXT).newInstance();
return new ScriptFilterSource(input, script.getIdOrCode(), ifs);
}
IntervalsSource filterSource = filter.getSource(context, fieldType);
switch (type) {
case "containing":
return Intervals.containing(input, filterSource);
case "contained_by":
return Intervals.containedBy(input, filterSource);
case "not_containing":
return Intervals.notContaining(input, filterSource);
case "not_contained_by":
return Intervals.notContainedBy(input, filterSource);
case "overlapping":
return Intervals.overlapping(input, filterSource);
case "not_overlapping":
return Intervals.nonOverlapping(input, filterSource);
case "before":
return Intervals.before(input, filterSource);
case "after":
return Intervals.after(input, filterSource);
default:
throw new IllegalArgumentException("Unknown filter type [" + type + "]");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IntervalFilter that = (IntervalFilter) o;
return Objects.equals(type, that.type) && Objects.equals(script, that.script) && Objects.equals(filter, that.filter);
}
@Override
public int hashCode() {
return Objects.hash(type, filter, script);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(type);
out.writeOptionalNamedWriteable(filter);
if (script == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
script.writeTo(out);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (filter != null) {
builder.startObject(type);
filter.toXContent(builder, params);
builder.endObject();
} else {
builder.field(Script.SCRIPT_PARSE_FIELD.getPreferredName(), script);
}
builder.endObject();
return builder;
}
public static IntervalFilter fromXContent(XContentParser parser) throws IOException {
if (parser.nextToken() != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parser.getTokenLocation(), "Expected [FIELD_NAME] but got [" + parser.currentToken() + "]");
}
String type = parser.currentName();
if (Script.SCRIPT_PARSE_FIELD.match(type, parser.getDeprecationHandler())) {
Script script = Script.parse(parser);
if (parser.nextToken() != XContentParser.Token.END_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "Expected [END_OBJECT] but got [" + parser.currentToken() + "]");
}
return new IntervalFilter(script);
}
if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "Expected [START_OBJECT] but got [" + parser.currentToken() + "]");
}
if (parser.nextToken() != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parser.getTokenLocation(), "Expected [FIELD_NAME] but got [" + parser.currentToken() + "]");
}
IntervalsSourceProvider intervals = IntervalsSourceProvider.fromXContent(parser);
if (parser.nextToken() != XContentParser.Token.END_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "Expected [END_OBJECT] but got [" + parser.currentToken() + "]");
}
if (parser.nextToken() != XContentParser.Token.END_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "Expected [END_OBJECT] but got [" + parser.currentToken() + "]");
}
return new IntervalFilter(intervals, type);
}
String getType() {
return type;
}
IntervalsSourceProvider getFilter() {
return filter;
}
Script getScript() {
return script;
}
}
}