QueryBuilders.java
// 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.doris.datasource.es;
import org.apache.doris.analysis.BinaryPredicate;
import org.apache.doris.analysis.BoolLiteral;
import org.apache.doris.analysis.CastExpr;
import org.apache.doris.analysis.CompoundPredicate;
import org.apache.doris.analysis.DecimalLiteral;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.FloatLiteral;
import org.apache.doris.analysis.FunctionCallExpr;
import org.apache.doris.analysis.InPredicate;
import org.apache.doris.analysis.IntLiteral;
import org.apache.doris.analysis.IsNullPredicate;
import org.apache.doris.analysis.LargeIntLiteral;
import org.apache.doris.analysis.LikePredicate;
import org.apache.doris.analysis.LikePredicate.Operator;
import org.apache.doris.analysis.LiteralExpr;
import org.apache.doris.analysis.SlotRef;
import org.apache.doris.catalog.EsResource;
import org.apache.doris.thrift.TExprOpcode;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Utility class to generate elastic search queries.
* Some query builders and static helper method have been copied from Elasticsearch
*/
public final class QueryBuilders {
/**
* Generate dsl from compound expr.
**/
private static QueryBuilder toCompoundEsDsl(Expr expr, List<Expr> notPushDownList,
Map<String, String> fieldsContext, BuilderOptions builderOptions, Map<String, String> column2typeMap) {
CompoundPredicate compoundPredicate = (CompoundPredicate) expr;
switch (compoundPredicate.getOp()) {
case AND: {
QueryBuilder left = toEsDsl(compoundPredicate.getChild(0), notPushDownList, fieldsContext,
builderOptions, column2typeMap);
QueryBuilder right = toEsDsl(compoundPredicate.getChild(1), notPushDownList, fieldsContext,
builderOptions, column2typeMap);
if (left != null && right != null) {
return QueryBuilders.boolQuery().must(left).must(right);
}
return null;
}
case OR: {
int beforeSize = notPushDownList.size();
QueryBuilder left = toEsDsl(compoundPredicate.getChild(0), notPushDownList, fieldsContext,
builderOptions, column2typeMap);
QueryBuilder right = toEsDsl(compoundPredicate.getChild(1), notPushDownList, fieldsContext,
builderOptions, column2typeMap);
int afterSize = notPushDownList.size();
if (left != null && right != null) {
return QueryBuilders.boolQuery().should(left).should(right);
}
// One 'or' association cannot be pushed down and the other cannot be pushed down
if (afterSize > beforeSize) {
notPushDownList.add(compoundPredicate);
}
return null;
}
case NOT: {
QueryBuilder child = toEsDsl(compoundPredicate.getChild(0), notPushDownList, fieldsContext,
builderOptions, column2typeMap);
if (child != null) {
return QueryBuilders.boolQuery().mustNot(child);
}
return null;
}
default:
return null;
}
}
/**
* Get the expr inside the cast.
**/
private static Expr exprWithoutCast(Expr expr) {
if (expr instanceof CastExpr) {
return exprWithoutCast(expr.getChild(0));
}
return expr;
}
public static QueryBuilder toEsDsl(Expr expr, Map<String, String> column2typeMap) {
return toEsDsl(expr, new ArrayList<>(), new HashMap<>(),
BuilderOptions.builder().likePushDown(Boolean.parseBoolean(EsResource.LIKE_PUSH_DOWN_DEFAULT_VALUE))
.build(), column2typeMap);
}
private static TExprOpcode flipOpCode(TExprOpcode opCode) {
switch (opCode) {
case GE:
return TExprOpcode.LE;
case GT:
return TExprOpcode.LT;
case LE:
return TExprOpcode.GE;
case LT:
return TExprOpcode.GT;
default:
return opCode;
}
}
private static QueryBuilder parseBinaryPredicate(LiteralExpr expr, TExprOpcode opCode, String column,
boolean needDateCompat) {
Object value = toDorisLiteral(expr);
if (needDateCompat) {
value = compatDefaultDate(value);
}
switch (opCode) {
case EQ:
case EQ_FOR_NULL:
return QueryBuilders.termQuery(column, value);
case NE:
// col != '' means col.length() > 0 in SQL syntax.
// The `NULL` value should not present in results.
// It equals
// '{"bool":{"must":{"bool":{"must_not":{"term":{"col":""}},"must":{"exists":{"field":"col"}}}}}}'
// in Elasticsearch
if (value instanceof String && StringUtils.isEmpty((String) value)) {
return QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery(column, value))
.must(QueryBuilders.existsQuery(column));
}
return QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery(column, value));
case GE:
return QueryBuilders.rangeQuery(column).gte(value);
case GT:
return QueryBuilders.rangeQuery(column).gt(value);
case LE:
return QueryBuilders.rangeQuery(column).lte(value);
case LT:
return QueryBuilders.rangeQuery(column).lt(value);
default:
return null;
}
}
private static QueryBuilder parseIsNullPredicate(Expr expr, String column) {
IsNullPredicate isNullPredicate = (IsNullPredicate) expr;
if (isNullPredicate.isNotNull()) {
return QueryBuilders.existsQuery(column);
}
return QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(column));
}
private static QueryBuilder parseLikeExpression(Expr expr, String column) {
String pattern;
if (expr instanceof LikePredicate) {
LikePredicate likePredicate = (LikePredicate) expr;
if (!likePredicate.getOp().equals(Operator.LIKE)) {
return QueryBuilders.wildcardQuery(column, likePredicate.getChild(1).getStringValue());
}
pattern = likePredicate.getChild(1).getStringValue();
} else if (expr instanceof FunctionCallExpr) {
FunctionCallExpr functionCallExpr = (FunctionCallExpr) expr;
String fnName = functionCallExpr.getFnName().getFunction();
if (!fnName.equalsIgnoreCase("like")) {
return QueryBuilders.wildcardQuery(column, functionCallExpr.getChild(1).getStringValue());
}
pattern = functionCallExpr.getChild(1).getStringValue();
} else {
throw new IllegalArgumentException("Unsupported expression type");
}
char[] chars = pattern.toCharArray();
// example of translation :
// abc_123 ===> abc?123
// abc%ykz ===> abc*123
// %abc123 ===> *abc123
// _abc123 ===> ?abc123
// \\_abc1 ===> \\_abc1
// abc\\_123 ===> abc\\_123
// abc\\%123 ===> abc\\%123
// NOTE. user must input sql like 'abc\\_123' or 'abc\\%ykz'
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '_' || chars[i] == '%') {
if (i == 0) {
chars[i] = (chars[i] == '_') ? '?' : '*';
} else if (chars[i - 1] != '\\') {
chars[i] = (chars[i] == '_') ? '?' : '*';
}
}
}
return QueryBuilders.wildcardQuery(column, new String(chars));
}
private static QueryBuilder parseInPredicate(Expr expr, String column, boolean needDateCompat) {
InPredicate inPredicate = (InPredicate) expr;
List<Object> values = inPredicate.getListChildren().stream().map(v -> {
if (needDateCompat) {
return compatDefaultDate(v);
}
return toDorisLiteral(v);
}).collect(Collectors.toList());
if (inPredicate.isNotIn()) {
return QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery(column, values));
}
return QueryBuilders.termsQuery(column, values);
}
private static QueryBuilder parseFunctionCallExpr(Expr expr) {
// esquery(k1, '{
// "match_phrase": {
// "k1": "doris on es"
// }
// }');
// The first child k1 compatible with expr syntax
FunctionCallExpr functionCallExpr = (FunctionCallExpr) expr;
String stringValue = functionCallExpr.getChild(1).getStringValue();
return new QueryBuilders.EsQueryBuilder(stringValue);
}
private static String getColumnFromExpr(Expr expr) {
// Type transformed cast can not pushdown
if (expr instanceof CastExpr) {
Expr withoutCastExpr = exprWithoutCast(expr);
if (withoutCastExpr.getType().equals(expr.getType())
|| (withoutCastExpr.getType().isFloatingPointType() && expr.getType().isFloatingPointType())) {
return ((SlotRef) withoutCastExpr).getColumnName();
}
} else if (expr instanceof SlotRef) {
return ((SlotRef) expr).getColumnName();
}
return null;
}
/**
* Doris expr to es dsl.
**/
public static QueryBuilder toEsDsl(Expr expr, List<Expr> notPushDownList, Map<String, String> fieldsContext,
BuilderOptions builderOptions, Map<String, String> column2typeMap) {
if (expr == null) {
return null;
}
// esquery functionCallExpr will be rewritten to castExpr in where clause rewriter,
// so we get the functionCallExpr here.
if (expr instanceof CastExpr) {
return toEsDsl(expr.getChild(0), notPushDownList, fieldsContext, builderOptions, column2typeMap);
}
// CompoundPredicate, `between` also converted to CompoundPredicate.
if (expr instanceof CompoundPredicate) {
return toCompoundEsDsl(expr, notPushDownList, fieldsContext, builderOptions, column2typeMap);
}
TExprOpcode opCode = expr.getOpcode();
boolean isFlip = false;
Expr leftExpr = expr.getChild(0);
String column = getColumnFromExpr(leftExpr);
if (StringUtils.isEmpty(column)) {
Expr rightExpr = expr.getChild(1);
column = getColumnFromExpr(rightExpr);
opCode = flipOpCode(opCode);
isFlip = true;
}
if (StringUtils.isEmpty(column)) {
notPushDownList.add(expr);
return null;
}
String type = column2typeMap.get(column);
// Check whether the date type need compat, it must before keyword replace.
List<String> needCompatDateFields = builderOptions.getNeedCompatDateFields();
boolean needDateCompat = needCompatDateFields != null && needCompatDateFields.contains(column);
// Replace col with col.keyword if mapping exist.
column = fieldsContext.getOrDefault(column, column);
if (expr instanceof BinaryPredicate) {
BinaryPredicate binaryPredicate = (BinaryPredicate) expr;
Expr value;
if (isFlip) {
value = binaryPredicate.getChild(0);
} else {
value = binaryPredicate.getChild(1);
}
// only push down literal expr to ES
if (value instanceof LiteralExpr) {
LiteralExpr literalExpr = (LiteralExpr) value;
return parseBinaryPredicate(literalExpr, opCode, column, needDateCompat);
} else {
notPushDownList.add(expr);
return null;
}
}
if (expr instanceof IsNullPredicate) {
return parseIsNullPredicate(expr, column);
}
if (expr instanceof LikePredicate) {
if (builderOptions.isLikePushDown() && "keyword".equals(type)) {
// only keyword can apply wildcard query
return parseLikeExpression(expr, column);
} else {
notPushDownList.add(expr);
return null;
}
}
if (expr instanceof InPredicate) {
return parseInPredicate(expr, column, needDateCompat);
}
if (expr instanceof FunctionCallExpr) {
// current only esquery and like applied in keyword functionCallExpr can be push down to ES
String fnName = ((FunctionCallExpr) expr).getFnName().getFunction();
if ("esquery".equals(fnName)) {
return parseFunctionCallExpr(expr);
} else if (builderOptions.isLikePushDown() && "like".equalsIgnoreCase(fnName) && "keyword".equals(type)) {
return parseLikeExpression(expr, column);
} else if (builderOptions.isLikePushDown() && "regexp".equalsIgnoreCase(fnName)) {
return parseLikeExpression(expr, column);
} else {
notPushDownList.add(expr);
return null;
}
}
return null;
}
private static final DateTimeFormatter dorisFmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter esFmt = ISODateTimeFormat.dateTime();
private static Object compatDefaultDate(Object value) {
if (value == null) {
return null;
}
return dorisFmt.parseDateTime(value.toString()).toString(esFmt);
}
/**
* Expr trans to doris literal.
**/
private static Object toDorisLiteral(Expr expr) {
if (!expr.isLiteral()) {
return null;
}
if (expr instanceof BoolLiteral) {
BoolLiteral boolLiteral = (BoolLiteral) expr;
return boolLiteral.getValue();
} else if (expr instanceof DecimalLiteral) {
DecimalLiteral decimalLiteral = (DecimalLiteral) expr;
return decimalLiteral.getValue();
} else if (expr instanceof FloatLiteral) {
FloatLiteral floatLiteral = (FloatLiteral) expr;
return floatLiteral.getValue();
} else if (expr instanceof IntLiteral) {
IntLiteral intLiteral = (IntLiteral) expr;
return intLiteral.getValue();
} else if (expr instanceof LargeIntLiteral) {
LargeIntLiteral largeIntLiteral = (LargeIntLiteral) expr;
return largeIntLiteral.getLongValue();
}
return expr.getStringValue();
}
/**
* A query that matches on all documents.
*/
public static MatchAllQueryBuilder matchAllQuery() {
return new MatchAllQueryBuilder();
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, String value) {
return new TermQueryBuilder(name, value);
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, int value) {
return new TermQueryBuilder(name, value);
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, long value) {
return new TermQueryBuilder(name, value);
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, float value) {
return new TermQueryBuilder(name, value);
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, double value) {
return new TermQueryBuilder(name, value);
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, boolean value) {
return new TermQueryBuilder(name, value);
}
/**
* A Query that matches documents containing a term.
*
* @param name The name of the field
* @param value The value of the term
*/
public static TermQueryBuilder termQuery(String name, Object value) {
return new TermQueryBuilder(name, value);
}
/**
* Implements the wildcard search query. Supported wildcards are {@code *}, which
* matches any character sequence (including the empty one), and {@code ?},
* which matches any single character. Note this query can be slow, as it
* needs to iterate over many terms. In order to prevent extremely slow WildcardQueries,
* a Wildcard term should not start with one of the wildcards {@code *} or
* {@code ?}.
*
* @param name The field name
* @param query The wildcard query string
*/
public static WildcardQueryBuilder wildcardQuery(String name, String query) {
return new WildcardQueryBuilder(name, query);
}
/**
* A Query that matches documents matching boolean combinations of other queries.
*/
public static BoolQueryBuilder boolQuery() {
return new BoolQueryBuilder();
}
/**
* A filter for a field based on several terms matching on any of them.
*
* @param name The field name
* @param values The terms
*/
public static TermsQueryBuilder termsQuery(String name, Iterable<?> values) {
return new TermsQueryBuilder(name, values);
}
/**
* A filter to filter only documents where a field exists in them.
*
* @param name The name of the field
*/
public static ExistsQueryBuilder existsQuery(String name) {
return new ExistsQueryBuilder(name);
}
/**
* A Query that matches documents within an range of terms.
*
* @param name The field name
*/
public static RangeQueryBuilder rangeQuery(String name) {
return new RangeQueryBuilder(name);
}
/**
* Used to pass some parameters to generate the dsl
**/
@Builder
@Data
public static class BuilderOptions {
private boolean likePushDown;
private List<String> needCompatDateFields;
}
/**
* Base class to build various ES queries
*/
public abstract static class QueryBuilder {
private static final Logger LOG = LogManager.getLogger(QueryBuilder.class);
final ObjectMapper mapper = new ObjectMapper();
/**
* Convert query to JSON format
*
* @param out used to generate JSON elements
* @throws IOException if IO error occurred
*/
public abstract void toJson(JsonGenerator out) throws IOException;
/**
* Convert query to JSON format and catch error.
**/
public String toJson() {
StringWriter writer = new StringWriter();
try {
JsonGenerator gen = mapper.getFactory().createGenerator(writer);
this.toJson(gen);
gen.flush();
gen.close();
} catch (IOException e) {
LOG.warn("QueryBuilder toJson error", e);
return null;
}
return writer.toString();
}
}
/**
* Use for esquery, directly save value.
**/
public static class EsQueryBuilder extends QueryBuilder {
private final String value;
public EsQueryBuilder(String value) {
this.value = value;
}
@Override
public void toJson(JsonGenerator out) throws IOException {
JsonNode jsonNode = mapper.readTree(value);
out.writeStartObject();
Iterator<Entry<String, JsonNode>> values = jsonNode.fields();
while (values.hasNext()) {
Entry<String, JsonNode> value = values.next();
out.writeFieldName(value.getKey());
out.writeObject(value.getValue());
}
out.writeEndObject();
}
}
/**
* A Query that matches documents matching boolean combinations of other queries.
*/
public static class BoolQueryBuilder extends QueryBuilder {
private final List<QueryBuilder> mustClauses = new ArrayList<>();
private final List<QueryBuilder> mustNotClauses = new ArrayList<>();
private final List<QueryBuilder> filterClauses = new ArrayList<>();
private final List<QueryBuilder> shouldClauses = new ArrayList<>();
/**
* Use for EsScanNode generate dsl.
**/
public BoolQueryBuilder must(QueryBuilder queryBuilder) {
Objects.requireNonNull(queryBuilder);
mustClauses.add(queryBuilder);
return this;
}
public BoolQueryBuilder filter(QueryBuilder queryBuilder) {
Objects.requireNonNull(queryBuilder);
filterClauses.add(queryBuilder);
return this;
}
public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) {
Objects.requireNonNull(queryBuilder);
mustNotClauses.add(queryBuilder);
return this;
}
public BoolQueryBuilder should(QueryBuilder queryBuilder) {
Objects.requireNonNull(queryBuilder);
shouldClauses.add(queryBuilder);
return this;
}
@Override
public void toJson(JsonGenerator out) throws IOException {
out.writeStartObject();
out.writeFieldName("bool");
out.writeStartObject();
writeJsonArray("must", mustClauses, out);
writeJsonArray("filter", filterClauses, out);
writeJsonArray("must_not", mustNotClauses, out);
writeJsonArray("should", shouldClauses, out);
out.writeEndObject();
out.writeEndObject();
}
private void writeJsonArray(String field, List<QueryBuilder> clauses, JsonGenerator out) throws IOException {
if (clauses.isEmpty()) {
return;
}
if (clauses.size() == 1) {
out.writeFieldName(field);
clauses.get(0).toJson(out);
} else {
out.writeArrayFieldStart(field);
for (QueryBuilder clause : clauses) {
clause.toJson(out);
}
out.writeEndArray();
}
}
}
/**
* A Query that matches documents containing a term
*/
static class TermQueryBuilder extends QueryBuilder {
private final String fieldName;
private final Object value;
private TermQueryBuilder(final String fieldName, final Object value) {
this.fieldName = Objects.requireNonNull(fieldName, "fieldName");
this.value = Objects.requireNonNull(value, "value");
}
@Override
public void toJson(final JsonGenerator out) throws IOException {
out.writeStartObject();
out.writeFieldName("term");
out.writeStartObject();
out.writeFieldName(fieldName);
writeObject(out, value);
out.writeEndObject();
out.writeEndObject();
}
}
/**
* A filter for a field based on several terms matching on any of them.
*/
static class TermsQueryBuilder extends QueryBuilder {
private final String fieldName;
private final Iterable<?> values;
private TermsQueryBuilder(final String fieldName, final Iterable<?> values) {
this.fieldName = Objects.requireNonNull(fieldName, "fieldName");
this.values = Objects.requireNonNull(values, "values");
}
@Override
public void toJson(final JsonGenerator out) throws IOException {
out.writeStartObject();
out.writeFieldName("terms");
out.writeStartObject();
out.writeFieldName(fieldName);
out.writeStartArray();
for (Object value : values) {
writeObject(out, value);
}
out.writeEndArray();
out.writeEndObject();
out.writeEndObject();
}
}
/**
* A Query that matches documents within an range of terms
*/
public static class RangeQueryBuilder extends QueryBuilder {
private final String field;
private Object lt;
private boolean lte;
private Object gt;
private boolean gte;
private String format;
private RangeQueryBuilder(final String field) {
this.field = Objects.requireNonNull(field, "fieldName");
}
private RangeQueryBuilder to(Object value, boolean lte) {
this.lt = Objects.requireNonNull(value, "value");
this.lte = lte;
return this;
}
private RangeQueryBuilder from(Object value, boolean gte) {
this.gt = Objects.requireNonNull(value, "value");
this.gte = gte;
return this;
}
public RangeQueryBuilder lt(Object value) {
return to(value, false);
}
public RangeQueryBuilder lte(Object value) {
return to(value, true);
}
public RangeQueryBuilder gt(Object value) {
return from(value, false);
}
public RangeQueryBuilder gte(Object value) {
return from(value, true);
}
public RangeQueryBuilder format(String format) {
this.format = format;
return this;
}
@Override
public void toJson(final JsonGenerator out) throws IOException {
if (lt == null && gt == null) {
throw new IllegalStateException("Either lower or upper bound should be provided");
}
out.writeStartObject();
out.writeFieldName("range");
out.writeStartObject();
out.writeFieldName(field);
out.writeStartObject();
if (gt != null) {
final String op = gte ? "gte" : "gt";
out.writeFieldName(op);
writeObject(out, gt);
}
if (lt != null) {
final String op = lte ? "lte" : "lt";
out.writeFieldName(op);
writeObject(out, lt);
}
if (format != null) {
out.writeStringField("format", format);
}
out.writeEndObject();
out.writeEndObject();
out.writeEndObject();
}
}
/**
* Supported wildcards are {@code *}, which
* matches any character sequence (including the empty one), and {@code ?},
* which matches any single character
*/
static class WildcardQueryBuilder extends QueryBuilder {
private final String fieldName;
private final String value;
public WildcardQueryBuilder(String fieldName, String value) {
this.fieldName = Objects.requireNonNull(fieldName, "fieldName");
this.value = Objects.requireNonNull(value, "value");
}
@Override
public void toJson(JsonGenerator out) throws IOException {
out.writeStartObject();
out.writeFieldName("wildcard");
out.writeStartObject();
out.writeFieldName(fieldName);
out.writeString(value);
out.writeEndObject();
out.writeEndObject();
}
}
/**
* Query that only match on documents that the fieldName has a value in them
*/
static class ExistsQueryBuilder extends QueryBuilder {
private final String fieldName;
ExistsQueryBuilder(final String fieldName) {
this.fieldName = Objects.requireNonNull(fieldName, "fieldName");
}
@Override
public void toJson(JsonGenerator out) throws IOException {
out.writeStartObject();
out.writeFieldName("exists");
out.writeStartObject();
out.writeStringField("field", fieldName);
out.writeEndObject();
out.writeEndObject();
}
}
/**
* A query that matches on all documents
*/
static class MatchAllQueryBuilder extends QueryBuilder {
private MatchAllQueryBuilder() {
}
@Override
public void toJson(final JsonGenerator out) throws IOException {
out.writeStartObject();
out.writeFieldName("match_all");
out.writeStartObject();
out.writeEndObject();
out.writeEndObject();
}
}
/**
* Write (scalar) value (string, number, boolean or null) to json format
*
* @param out source target
* @param value value to write
* @throws IOException if error
*/
private static void writeObject(JsonGenerator out, Object value) throws IOException {
out.writeObject(value);
}
}