org.codelibs.elasticsearch.index.query.RangeQueryBuilder Maven / Gradle / Ivy
/*
* 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.
*/
package org.codelibs.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.codelibs.elasticsearch.Version;
import org.codelibs.elasticsearch.common.ParseField;
import org.codelibs.elasticsearch.common.ParsingException;
import org.codelibs.elasticsearch.common.Strings;
import org.codelibs.elasticsearch.common.geo.ShapeRelation;
import org.codelibs.elasticsearch.common.io.stream.StreamInput;
import org.codelibs.elasticsearch.common.io.stream.StreamOutput;
import org.codelibs.elasticsearch.common.joda.DateMathParser;
import org.codelibs.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.codelibs.elasticsearch.common.joda.Joda;
import org.codelibs.elasticsearch.common.xcontent.XContentBuilder;
import org.codelibs.elasticsearch.common.xcontent.XContentParser;
import org.codelibs.elasticsearch.index.mapper.MappedFieldType;
import org.joda.time.DateTimeZone;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
/**
* A Query that matches documents within an range of terms.
*/
public class RangeQueryBuilder extends AbstractQueryBuilder implements MultiTermQueryBuilder {
public static final String NAME = "range";
public static final boolean DEFAULT_INCLUDE_UPPER = true;
public static final boolean DEFAULT_INCLUDE_LOWER = true;
private static final ParseField FIELDDATA_FIELD = new ParseField("fielddata").withAllDeprecated("[no replacement]");
private static final ParseField NAME_FIELD = new ParseField("_name")
.withAllDeprecated("query name is not supported in short version of range query");
public static final ParseField LTE_FIELD = new ParseField("lte", "le");
public static final ParseField GTE_FIELD = new ParseField("gte", "ge");
public static final ParseField FROM_FIELD = new ParseField("from");
public static final ParseField TO_FIELD = new ParseField("to");
private static final ParseField INCLUDE_LOWER_FIELD = new ParseField("include_lower");
private static final ParseField INCLUDE_UPPER_FIELD = new ParseField("include_upper");
public static final ParseField GT_FIELD = new ParseField("gt");
public static final ParseField LT_FIELD = new ParseField("lt");
private static final ParseField TIME_ZONE_FIELD = new ParseField("time_zone");
private static final ParseField FORMAT_FIELD = new ParseField("format");
private static final ParseField RELATION_FIELD = new ParseField("relation");
private final String fieldName;
private Object from;
private Object to;
private DateTimeZone timeZone;
private boolean includeLower = DEFAULT_INCLUDE_LOWER;
private boolean includeUpper = DEFAULT_INCLUDE_UPPER;
private FormatDateTimeFormatter format;
private ShapeRelation relation;
/**
* A Query that matches documents within an range of terms.
*
* @param fieldName The field name
*/
public RangeQueryBuilder(String fieldName) {
if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("field name is null or empty");
}
this.fieldName = fieldName;
}
/**
* Read from a stream.
*/
public RangeQueryBuilder(StreamInput in) throws IOException {
super(in);
fieldName = in.readString();
from = in.readGenericValue();
to = in.readGenericValue();
includeLower = in.readBoolean();
includeUpper = in.readBoolean();
timeZone = in.readOptionalTimeZone();
String formatString = in.readOptionalString();
if (formatString != null) {
format = Joda.forPattern(formatString);
}
if (in.getVersion().onOrAfter(Version.V_5_2_0_UNRELEASED)) {
String relationString = in.readOptionalString();
if (relationString != null) {
relation = ShapeRelation.getRelationByName(relationString);
}
}
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(this.fieldName);
out.writeGenericValue(this.from);
out.writeGenericValue(this.to);
out.writeBoolean(this.includeLower);
out.writeBoolean(this.includeUpper);
out.writeOptionalTimeZone(timeZone);
String formatString = null;
if (this.format != null) {
formatString = this.format.format();
}
out.writeOptionalString(formatString);
if (out.getVersion().onOrAfter(Version.V_5_2_0_UNRELEASED)) {
String relationString = null;
if (this.relation != null) {
relationString = this.relation.getRelationName();
}
out.writeOptionalString(relationString);
}
}
/**
* Get the field name for this query.
*/
public String fieldName() {
return this.fieldName;
}
/**
* The from part of the range query. Null indicates unbounded.
* In case lower bound is assigned to a string, we internally convert it to a {BytesRef} because
* in {RangeQueryBuilder} field are later parsed as {BytesRef} and we need internal representation
* of query to be equal regardless of whether it was created from XContent or via Java API.
*/
public RangeQueryBuilder from(Object from, boolean includeLower) {
this.from = convertToBytesRefIfString(from);
this.includeLower = includeLower;
return this;
}
/**
* The from part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder from(Object from) {
return from(from, this.includeLower);
}
/**
* Gets the lower range value for this query.
*/
public Object from() {
return convertToStringIfBytesRef(this.from);
}
/**
* The from part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder gt(Object from) {
return from(from, false);
}
/**
* The from part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder gte(Object from) {
return from(from, true);
}
/**
* The to part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder to(Object to, boolean includeUpper) {
this.to = convertToBytesRefIfString(to);
this.includeUpper = includeUpper;
return this;
}
/**
* The to part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder to(Object to) {
return to(to, this.includeUpper);
}
/**
* Gets the upper range value for this query.
* In case upper bound is assigned to a string, we internally convert it to a {BytesRef} because
* in {RangeQueryBuilder} field are later parsed as {BytesRef} and we need internal representation
* of query to be equal regardless of whether it was created from XContent or via Java API.
*/
public Object to() {
return convertToStringIfBytesRef(this.to);
}
/**
* The to part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder lt(Object to) {
return to(to, false);
}
/**
* The to part of the range query. Null indicates unbounded.
*/
public RangeQueryBuilder lte(Object to) {
return to(to, true);
}
/**
* Should the lower bound be included or not. Defaults to true.
*/
public RangeQueryBuilder includeLower(boolean includeLower) {
this.includeLower = includeLower;
return this;
}
/**
* Gets the includeLower flag for this query.
*/
public boolean includeLower() {
return this.includeLower;
}
/**
* Should the upper bound be included or not. Defaults to true.
*/
public RangeQueryBuilder includeUpper(boolean includeUpper) {
this.includeUpper = includeUpper;
return this;
}
/**
* Gets the includeUpper flag for this query.
*/
public boolean includeUpper() {
return this.includeUpper;
}
/**
* In case of date field, we can adjust the from/to fields using a timezone
*/
public RangeQueryBuilder timeZone(String timeZone) {
if (timeZone == null) {
throw new IllegalArgumentException("timezone cannot be null");
}
this.timeZone = DateTimeZone.forID(timeZone);
return this;
}
/**
* In case of date field, gets the from/to fields timezone adjustment
*/
public String timeZone() {
return this.timeZone == null ? null : this.timeZone.getID();
}
DateTimeZone getDateTimeZone() { // for testing
return timeZone;
}
/**
* In case of format field, we can parse the from/to fields using this time format
*/
public RangeQueryBuilder format(String format) {
if (format == null) {
throw new IllegalArgumentException("format cannot be null");
}
this.format = Joda.forPattern(format);
return this;
}
/**
* Gets the format field to parse the from/to fields
*/
public String format() {
return this.format == null ? null : this.format.format();
}
DateMathParser getForceDateParser() { // pkg private for testing
if (this.format != null) {
return new DateMathParser(this.format);
}
return null;
}
public ShapeRelation relation() {
return this.relation;
}
public RangeQueryBuilder relation(String relation) {
if (relation == null) {
throw new IllegalArgumentException("relation cannot be null");
}
this.relation = ShapeRelation.getRelationByName(relation);
if (this.relation == null) {
throw new IllegalArgumentException(relation + " is not a valid relation");
}
return this;
}
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.startObject(fieldName);
builder.field(FROM_FIELD.getPreferredName(), convertToStringIfBytesRef(this.from));
builder.field(TO_FIELD.getPreferredName(), convertToStringIfBytesRef(this.to));
builder.field(INCLUDE_LOWER_FIELD.getPreferredName(), includeLower);
builder.field(INCLUDE_UPPER_FIELD.getPreferredName(), includeUpper);
if (timeZone != null) {
builder.field(TIME_ZONE_FIELD.getPreferredName(), timeZone.getID());
}
if (format != null) {
builder.field(FORMAT_FIELD.getPreferredName(), format.format());
}
if (relation != null) {
builder.field(RELATION_FIELD.getPreferredName(), relation.getRelationName());
}
printBoostAndQueryName(builder);
builder.endObject();
builder.endObject();
}
public static Optional fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
String fieldName = null;
Object from = null;
Object to = null;
boolean includeLower = RangeQueryBuilder.DEFAULT_INCLUDE_LOWER;
boolean includeUpper = RangeQueryBuilder.DEFAULT_INCLUDE_UPPER;
String timeZone = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;
String format = null;
String relation = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (parseContext.isDeprecatedSetting(currentFieldName)) {
// skip
} else if (token == XContentParser.Token.START_OBJECT) {
throwParsingExceptionOnMultipleFields(NAME, parser.getTokenLocation(), fieldName, currentFieldName);
fieldName = currentFieldName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else {
if (FROM_FIELD.match(currentFieldName)) {
from = parser.objectBytes();
} else if (TO_FIELD.match(currentFieldName)) {
to = parser.objectBytes();
} else if (INCLUDE_LOWER_FIELD.match(currentFieldName)) {
includeLower = parser.booleanValue();
} else if (INCLUDE_UPPER_FIELD.match(currentFieldName)) {
includeUpper = parser.booleanValue();
} else if (AbstractQueryBuilder.BOOST_FIELD.match(currentFieldName)) {
boost = parser.floatValue();
} else if (GT_FIELD.match(currentFieldName)) {
from = parser.objectBytes();
includeLower = false;
} else if (GTE_FIELD.match(currentFieldName)) {
from = parser.objectBytes();
includeLower = true;
} else if (LT_FIELD.match(currentFieldName)) {
to = parser.objectBytes();
includeUpper = false;
} else if (LTE_FIELD.match(currentFieldName)) {
to = parser.objectBytes();
includeUpper = true;
} else if (TIME_ZONE_FIELD.match(currentFieldName)) {
timeZone = parser.text();
} else if (FORMAT_FIELD.match(currentFieldName)) {
format = parser.text();
} else if (RELATION_FIELD.match(currentFieldName)) {
relation = parser.text();
} else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName)) {
queryName = parser.text();
} else {
throw new ParsingException(parser.getTokenLocation(),
"[range] query does not support [" + currentFieldName + "]");
}
}
}
} else if (token.isValue()) {
if (NAME_FIELD.match(currentFieldName)) {
queryName = parser.text();
} else if (FIELDDATA_FIELD.match(currentFieldName)) {
// ignore
} else {
throw new ParsingException(parser.getTokenLocation(), "[range] query does not support [" + currentFieldName + "]");
}
}
}
RangeQueryBuilder rangeQuery = new RangeQueryBuilder(fieldName);
rangeQuery.from(from);
rangeQuery.to(to);
rangeQuery.includeLower(includeLower);
rangeQuery.includeUpper(includeUpper);
if (timeZone != null) {
rangeQuery.timeZone(timeZone);
}
rangeQuery.boost(boost);
rangeQuery.queryName(queryName);
if (format != null) {
rangeQuery.format(format);
}
if (relation != null) {
rangeQuery.relation(relation);
}
return Optional.of(rangeQuery);
}
@Override
public String getWriteableName() {
return NAME;
}
// Overridable for testing only
protected MappedFieldType.Relation getRelation(QueryRewriteContext queryRewriteContext) throws IOException {
throw new UnsupportedOperationException("querybuilders does not support this operation.");
}
@Override
protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
final MappedFieldType.Relation relation = getRelation(queryRewriteContext);
switch (relation) {
case DISJOINT:
return new MatchNoneQueryBuilder();
case WITHIN:
if (from != null || to != null) {
RangeQueryBuilder newRangeQuery = new RangeQueryBuilder(fieldName);
newRangeQuery.from(null);
newRangeQuery.to(null);
newRangeQuery.format = format;
newRangeQuery.timeZone = timeZone;
return newRangeQuery;
} else {
return this;
}
case INTERSECTS:
return this;
default:
throw new AssertionError();
}
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
throw new UnsupportedOperationException("querybuilders does not support this operation.");
}
@Override
protected int doHashCode() {
String timeZoneId = timeZone == null ? null : timeZone.getID();
String formatString = format == null ? null : format.format();
return Objects.hash(fieldName, from, to, timeZoneId, includeLower, includeUpper, formatString);
}
@Override
protected boolean doEquals(RangeQueryBuilder other) {
String timeZoneId = timeZone == null ? null : timeZone.getID();
String formatString = format == null ? null : format.format();
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(from, other.from) &&
Objects.equals(to, other.to) &&
Objects.equals(timeZoneId, other.timeZone()) &&
Objects.equals(includeLower, other.includeLower) &&
Objects.equals(includeUpper, other.includeUpper) &&
Objects.equals(formatString, other.format());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy