diff --git a/core/src/main/java/org/elasticsearch/search/builder/NewSearchSourceBuilder.java b/core/src/main/java/org/elasticsearch/search/builder/NewSearchSourceBuilder.java new file mode 100644 index 00000000000..08219d2026d --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/builder/NewSearchSourceBuilder.java @@ -0,0 +1,1128 @@ +/* + * 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.elasticsearch.search.builder; + +import com.carrotsearch.hppc.ObjectFloatHashMap; +import com.carrotsearch.hppc.cursors.ObjectCursor; + +import org.elasticsearch.action.support.ToXContentToBytes; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.script.Script; +import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder; +import org.elasticsearch.search.fetch.source.FetchSourceContext; +import org.elasticsearch.search.highlight.HighlightBuilder; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.rescore.RescoreBuilder; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.search.suggest.SuggestBuilder; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A search source builder allowing to easily build search source. Simple + * construction using + * {@link org.elasticsearch.search.builder.NewSearchSourceBuilder#searchSource()}. + * + * @see org.elasticsearch.action.search.SearchRequest#source(NewSearchSourceBuilder) + */ +public class NewSearchSourceBuilder extends ToXContentToBytes implements Writeable { + + /** + * A static factory method to construct a new search source. + */ + public static NewSearchSourceBuilder searchSource() { + return new NewSearchSourceBuilder(); + } + + /** + * A static factory method to construct new search highlights. + */ + public static HighlightBuilder highlight() { + return new HighlightBuilder(); + } + + private QueryBuilder queryBuilder; + + private QueryBuilder postQueryBuilder; + + private BytesReference filterBinary; + + private int from = -1; + + private int size = -1; + + private Boolean explain; + + private Boolean version; + + private List sorts; + + private boolean trackScores = false; + + private Float minScore; + + private long timeoutInMillis = -1; + private int terminateAfter = SearchContext.DEFAULT_TERMINATE_AFTER; + + private List fieldNames; + private List fieldDataFields; + private List scriptFields; + private FetchSourceContext fetchSourceContext; + + private List aggregations; + private BytesReference aggregationsBinary; + + private BytesReference highlightBuilder; + + private BytesReference suggestBuilder; + + private BytesReference innerHitsBuilder; + + private List rescoreBuilders; + private Integer defaultRescoreWindowSize; + + private ObjectFloatHashMap indexBoost = null; + + private String[] stats; + + /** + * Constructs a new search source builder. + */ + public NewSearchSourceBuilder() { + } + + /** + * Constructs a new search source builder with a search query. + * + * @see org.elasticsearch.index.query.QueryBuilders + */ + public NewSearchSourceBuilder query(QueryBuilder query) { + this.queryBuilder = query; + return this; + } + + // NOCOMMIT Do we need these methods? + // /** + // * Constructs a new search source builder with a raw search query. + // */ + // public NewSearchSourceBuilder query(byte[] queryBinary) { + // return query(queryBinary, 0, queryBinary.length); + // } + // + // /** + // * Constructs a new search source builder with a raw search query. + // */ + // public NewSearchSourceBuilder query(byte[] queryBinary, int + // queryBinaryOffset, int queryBinaryLength) { + // return query(new BytesArray(queryBinary, queryBinaryOffset, + // queryBinaryLength)); + // } + + // /** + // * Constructs a new search source builder with a raw search query. + // */ + // public NewSearchSourceBuilder query(BytesReference queryBinary) { + // if (this.queryBuilder == null) { + // this.queryBuilder = new QuerySourceBuilder(); + // } + // this.queryBuilder.setQuery(queryBinary); + // return this; + // } + // + // /** + // * Constructs a new search source builder with a raw search query. + // */ + // public NewSearchSourceBuilder query(String queryString) { + // return query(queryString.getBytes(Charsets.UTF_8)); + // } + // + // /** + // * Constructs a new search source builder with a query from a builder. + // */ + // public NewSearchSourceBuilder query(XContentBuilder query) { + // return query(query.bytes()); + // } + // + // /** + // * Constructs a new search source builder with a query from a map. + // */ + // @SuppressWarnings("unchecked") + // public NewSearchSourceBuilder query(Map query) { + // try { + // XContentBuilder builder = + // XContentFactory.contentBuilder(Requests.CONTENT_TYPE); + // builder.map(query); + // return query(builder); + // } catch (IOException e) { + // throw new ElasticsearchGenerationException("Failed to generate [" + query + // + "]", e); + // } + // } + + /** + * Sets a filter that will be executed after the query has been executed and + * only has affect on the search hits (not aggregations). This filter is + * always executed as last filtering mechanism. + */ + public NewSearchSourceBuilder postFilter(QueryBuilder postFilter) { + this.postQueryBuilder = postFilter; + return this; + } + + // NOCOMMIT do we need these methods? + // /** + // * Sets a filter on the query executed that only applies to the search + // query + // * (and not aggs for example). + // */ + // public NewSearchSourceBuilder postFilter(String postFilterString) { + // return postFilter(postFilterString.getBytes(Charsets.UTF_8)); + // } + // + // /** + // * Sets a filter on the query executed that only applies to the search + // query + // * (and not aggs for example). + // */ + // public NewSearchSourceBuilder postFilter(byte[] postFilter) { + // return postFilter(postFilter, 0, postFilter.length); + // } + // + // /** + // * Sets a filter on the query executed that only applies to the search + // query + // * (and not aggs for example). + // */ + // public NewSearchSourceBuilder postFilter(byte[] postFilterBinary, int + // postFilterBinaryOffset, int postFilterBinaryLength) { + // return postFilter(new BytesArray(postFilterBinary, + // postFilterBinaryOffset, postFilterBinaryLength)); + // } + // + // /** + // * Sets a filter on the query executed that only applies to the search + // query + // * (and not aggs for example). + // */ + // public NewSearchSourceBuilder postFilter(BytesReference postFilterBinary) + // { + // this.filterBinary = postFilterBinary; + // return this; + // } + // + // /** + // * Constructs a new search source builder with a query from a builder. + // */ + // public NewSearchSourceBuilder postFilter(XContentBuilder postFilter) { + // return postFilter(postFilter.bytes()); + // } + // + // /** + // * Constructs a new search source builder with a query from a map. + // */ + // @SuppressWarnings("unchecked") + // public NewSearchSourceBuilder postFilter(Map postFilter) { + // try { + // XContentBuilder builder = + // XContentFactory.contentBuilder(Requests.CONTENT_TYPE); + // builder.map(postFilter); + // return postFilter(builder); + // } catch (IOException e) { + // throw new ElasticsearchGenerationException("Failed to generate [" + + // postFilter + "]", e); + // } + // } + + /** + * From index to start the search from. Defaults to 0. + */ + public NewSearchSourceBuilder from(int from) { + this.from = from; + return this; + } + + /** + * The number of search hits to return. Defaults to 10. + */ + public NewSearchSourceBuilder size(int size) { + this.size = size; + return this; + } + + /** + * Sets the minimum score below which docs will be filtered out. + */ + public NewSearchSourceBuilder minScore(float minScore) { + this.minScore = minScore; + return this; + } + + /** + * Should each {@link org.elasticsearch.search.SearchHit} be returned with + * an explanation of the hit (ranking). + */ + public NewSearchSourceBuilder explain(Boolean explain) { + this.explain = explain; + return this; + } + + /** + * Should each {@link org.elasticsearch.search.SearchHit} be returned with a + * version associated with it. + */ + public NewSearchSourceBuilder version(Boolean version) { + this.version = version; + return this; + } + + /** + * An optional timeout to control how long search is allowed to take. + */ + public NewSearchSourceBuilder timeout(TimeValue timeout) { + this.timeoutInMillis = timeout.millis(); + return this; + } + + /** + * An optional timeout to control how long search is allowed to take. + */ + public NewSearchSourceBuilder timeout(String timeout) { + this.timeoutInMillis = TimeValue.parseTimeValue(timeout, null, getClass().getSimpleName() + ".timeout").millis(); + return this; + } + + /** + * An optional terminate_after to terminate the search after collecting + * terminateAfter documents + */ + public NewSearchSourceBuilder terminateAfter(int terminateAfter) { + if (terminateAfter <= 0) { + throw new IllegalArgumentException("terminateAfter must be > 0"); + } + this.terminateAfter = terminateAfter; + return this; + } + + /** + * Adds a sort against the given field name and the sort ordering. + * + * @param name + * The name of the field + * @param order + * The sort ordering + * @throws IOException + */ + public NewSearchSourceBuilder sort(String name, SortOrder order) throws IOException { + return sort(SortBuilders.fieldSort(name).order(order)); + } + + /** + * Add a sort against the given field name. + * + * @param name + * The name of the field to sort by + * @throws IOException + */ + public NewSearchSourceBuilder sort(String name) throws IOException { + return sort(SortBuilders.fieldSort(name)); + } + + /** + * Adds a sort builder. + */ + public NewSearchSourceBuilder sort(SortBuilder sort) throws IOException { + if (sorts == null) { + sorts = new ArrayList<>(); + } + XContentBuilder builder = XContentFactory.jsonBuilder(); + sort.toXContent(builder, EMPTY_PARAMS); + sorts.add(builder.bytes()); + return this; + } + + /** + * Applies when sorting, and controls if scores will be tracked as well. + * Defaults to false. + */ + public NewSearchSourceBuilder trackScores(boolean trackScores) { + this.trackScores = trackScores; + return this; + } + + /** + * Add an get to perform as part of the search. + */ + public NewSearchSourceBuilder aggregation(AbstractAggregationBuilder aggregation) throws IOException { + if (aggregations == null) { + aggregations = new ArrayList<>(); + } + XContentBuilder builder = XContentFactory.jsonBuilder(); + aggregation.toXContent(builder, EMPTY_PARAMS); + aggregations.add(builder.bytes()); + return this; + } + + // NOCOMMIT do we need these methods? + // /** + // * Sets a raw (xcontent / json) addAggregation. + // */ + // public NewSearchSourceBuilder aggregations(byte[] aggregationsBinary) { + // return aggregations(aggregationsBinary, 0, aggregationsBinary.length); + // } + // + // /** + // * Sets a raw (xcontent / json) addAggregation. + // */ + // public NewSearchSourceBuilder aggregations(byte[] aggregationsBinary, int + // aggregationsBinaryOffset, int aggregationsBinaryLength) { + // return aggregations(new BytesArray(aggregationsBinary, + // aggregationsBinaryOffset, aggregationsBinaryLength)); + // } + // + // /** + // * Sets a raw (xcontent / json) addAggregation. + // */ + // public NewSearchSourceBuilder aggregations(BytesReference + // aggregationsBinary) { + // this.aggregationsBinary = aggregationsBinary; + // return this; + // } + // + // /** + // * Sets a raw (xcontent / json) addAggregation. + // */ + // public NewSearchSourceBuilder aggregations(XContentBuilder aggs) { + // return aggregations(aggs.bytes()); + // } + // + // /** + // * Sets a raw (xcontent / json) addAggregation. + // */ + // @SuppressWarnings("unchecked") + // public NewSearchSourceBuilder aggregations(Map aggregations) { + // try { + // XContentBuilder builder = + // XContentFactory.contentBuilder(Requests.CONTENT_TYPE); + // builder.map(aggregations); + // return aggregations(builder); + // } catch (IOException e) { + // throw new ElasticsearchGenerationException("Failed to generate [" + + // aggregations + "]", e); + // } + // } + + /** + * Set the rescore window size for rescores that don't specify their window. + */ + public NewSearchSourceBuilder defaultRescoreWindowSize(int defaultRescoreWindowSize) { + this.defaultRescoreWindowSize = defaultRescoreWindowSize; + return this; + } + + // NOCOMMIT do we need this? + // public HighlightBuilder highlighter() { + // if (highlightBuilder == null) { + // highlightBuilder = new HighlightBuilder(); + // } + // return highlightBuilder; + // } + + /** + * Adds highlight to perform as part of the search. + */ + public NewSearchSourceBuilder highlight(HighlightBuilder highlightBuilder) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + highlightBuilder.toXContent(builder, EMPTY_PARAMS); + this.highlightBuilder = builder.bytes(); + return this; + } + + // NOCOMMIT do we need this? + // public InnerHitsBuilder innerHitsBuilder() { + // if (innerHitsBuilder == null) { + // innerHitsBuilder = new InnerHitsBuilder(); + // } + // return innerHitsBuilder; + // } + + public NewSearchSourceBuilder innerHits(InnerHitsBuilder innerHitsBuilder) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + innerHitsBuilder.toXContent(builder, EMPTY_PARAMS); + this.innerHitsBuilder = builder.bytes(); + return this; + } + + // NOCOMMIT do we need this? + // public SuggestBuilder suggest() { + // if (suggestBuilder == null) { + // suggestBuilder = new SuggestBuilder("suggest"); + // } + // return suggestBuilder; + // } + + public NewSearchSourceBuilder suggest(SuggestBuilder suggestBuilder) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + suggestBuilder.toXContent(builder, EMPTY_PARAMS); + this.suggestBuilder = builder.bytes(); + return this; + } + + public NewSearchSourceBuilder addRescorer(RescoreBuilder rescoreBuilder) throws IOException { + if (rescoreBuilders == null) { + rescoreBuilders = new ArrayList<>(); + } + XContentBuilder builder = XContentFactory.jsonBuilder(); + rescoreBuilder.toXContent(builder, EMPTY_PARAMS); + rescoreBuilders.add(builder.bytes()); + return this; + } + + public NewSearchSourceBuilder clearRescorers() { + rescoreBuilders = null; + return this; + } + + /** + * Indicates whether the response should contain the stored _source for + * every hit + */ + public NewSearchSourceBuilder fetchSource(boolean fetch) { + if (this.fetchSourceContext == null) { + this.fetchSourceContext = new FetchSourceContext(fetch); + } else { + this.fetchSourceContext.fetchSource(fetch); + } + return this; + } + + /** + * Indicate that _source should be returned with every hit, with an + * "include" and/or "exclude" set which can include simple wildcard + * elements. + * + * @param include + * An optional include (optionally wildcarded) pattern to filter + * the returned _source + * @param exclude + * An optional exclude (optionally wildcarded) pattern to filter + * the returned _source + */ + public NewSearchSourceBuilder fetchSource(@Nullable String include, @Nullable String exclude) { + return fetchSource(include == null ? Strings.EMPTY_ARRAY : new String[] { include }, exclude == null ? Strings.EMPTY_ARRAY + : new String[] { exclude }); + } + + /** + * Indicate that _source should be returned with every hit, with an + * "include" and/or "exclude" set which can include simple wildcard + * elements. + * + * @param includes + * An optional list of include (optionally wildcarded) pattern to + * filter the returned _source + * @param excludes + * An optional list of exclude (optionally wildcarded) pattern to + * filter the returned _source + */ + public NewSearchSourceBuilder fetchSource(@Nullable String[] includes, @Nullable String[] excludes) { + fetchSourceContext = new FetchSourceContext(includes, excludes); + return this; + } + + /** + * Indicate how the _source should be fetched. + */ + public NewSearchSourceBuilder fetchSource(@Nullable FetchSourceContext fetchSourceContext) { + this.fetchSourceContext = fetchSourceContext; + return this; + } + + /** + * Sets no fields to be loaded, resulting in only id and type to be returned + * per field. + */ + public NewSearchSourceBuilder noFields() { + this.fieldNames = Collections.emptyList(); + return this; + } + + /** + * Sets the fields to load and return as part of the search request. If none + * are specified, the source of the document will be returned. + */ + public NewSearchSourceBuilder fields(List fields) { + this.fieldNames = fields; + return this; + } + + /** + * Adds the fields to load and return as part of the search request. If none + * are specified, the source of the document will be returned. + */ + public NewSearchSourceBuilder fields(String... fields) { + if (fieldNames == null) { + fieldNames = new ArrayList<>(); + } + Collections.addAll(fieldNames, fields); + return this; + } + + /** + * Adds a field to load and return (note, it must be stored) as part of the + * search request. If none are specified, the source of the document will be + * return. + */ + public NewSearchSourceBuilder field(String name) { + if (fieldNames == null) { + fieldNames = new ArrayList<>(); + } + fieldNames.add(name); + return this; + } + + /** + * Adds a field to load from the field data cache and return as part of the + * search request. + */ + public NewSearchSourceBuilder fieldDataField(String name) { + if (fieldDataFields == null) { + fieldDataFields = new ArrayList<>(); + } + fieldDataFields.add(name); + return this; + } + + /** + * Adds a script field under the given name with the provided script. + * + * @param name + * The name of the field + * @param script + * The script + */ + public NewSearchSourceBuilder scriptField(String name, Script script) { + if (scriptFields == null) { + scriptFields = new ArrayList<>(); + } + scriptFields.add(new ScriptField(name, script)); + return this; + } + + /** + * Sets the boost a specific index will receive when the query is executeed + * against it. + * + * @param index + * The index to apply the boost against + * @param indexBoost + * The boost to apply to the index + */ + public NewSearchSourceBuilder indexBoost(String index, float indexBoost) { + if (this.indexBoost == null) { + this.indexBoost = new ObjectFloatHashMap<>(); + } + this.indexBoost.put(index, indexBoost); + return this; + } + + /** + * The stats groups this request will be aggregated under. + */ + public NewSearchSourceBuilder stats(String... statsGroups) { + this.stats = statsGroups; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + innerToXContent(builder, params); + builder.endObject(); + return builder; + } + + public void innerToXContent(XContentBuilder builder, Params params) throws IOException { + if (from != -1) { + builder.field("from", from); + } + if (size != -1) { + builder.field("size", size); + } + + if (timeoutInMillis != -1) { + builder.field("timeout", timeoutInMillis); + } + + if (terminateAfter != SearchContext.DEFAULT_TERMINATE_AFTER) { + builder.field("terminate_after", terminateAfter); + } + + if (queryBuilder != null) { + queryBuilder.toXContent(builder, params); + } + + if (postQueryBuilder != null) { + builder.field("post_filter"); + postQueryBuilder.toXContent(builder, params); + } + + if (filterBinary != null) { + if (XContentFactory.xContentType(filterBinary) == builder.contentType()) { + builder.rawField("filter", filterBinary); + } else { + builder.field("filter_binary", filterBinary); + } + } + + if (minScore != null) { + builder.field("min_score", minScore); + } + + if (version != null) { + builder.field("version", version); + } + + if (explain != null) { + builder.field("explain", explain); + } + + if (fetchSourceContext != null) { + if (!fetchSourceContext.fetchSource()) { + builder.field("_source", false); + } else { + builder.startObject("_source"); + builder.array("includes", fetchSourceContext.includes()); + builder.array("excludes", fetchSourceContext.excludes()); + builder.endObject(); + } + } + + if (fieldNames != null) { + if (fieldNames.size() == 1) { + builder.field("fields", fieldNames.get(0)); + } else { + builder.startArray("fields"); + for (String fieldName : fieldNames) { + builder.value(fieldName); + } + builder.endArray(); + } + } + + if (fieldDataFields != null) { + builder.startArray("fielddata_fields"); + for (String fieldName : fieldDataFields) { + builder.value(fieldName); + } + builder.endArray(); + } + + if (scriptFields != null) { + builder.startObject("script_fields"); + for (ScriptField scriptField : scriptFields) { + builder.startObject(scriptField.fieldName()); + builder.field("script", scriptField.script()); + builder.endObject(); + } + builder.endObject(); + } + + if (sorts != null) { + builder.startArray("sort"); + for (BytesReference sort : sorts) { + XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(sort); + parser.nextToken(); + builder.copyCurrentStructure(parser); + } + builder.endArray(); + } + + if (trackScores) { + builder.field("track_scores", true); + } + + if (indexBoost != null) { + builder.startObject("indices_boost"); + assert !indexBoost.containsKey(null); + final Object[] keys = indexBoost.keys; + final float[] values = indexBoost.values; + for (int i = 0; i < keys.length; i++) { + if (keys[i] != null) { + builder.field((String) keys[i], values[i]); + } + } + builder.endObject(); + } + + if (aggregations != null) { + builder.field("aggregations"); + builder.startObject(); + for (BytesReference aggregation : aggregations) { + XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(aggregation); + parser.nextToken(); + builder.copyCurrentStructure(parser); + } + builder.endObject(); + } + + if (aggregationsBinary != null) { + if (XContentFactory.xContentType(aggregationsBinary) == builder.contentType()) { + builder.rawField("aggregations", aggregationsBinary); + } else { + builder.field("aggregations_binary", aggregationsBinary); + } + } + + if (highlightBuilder != null) { + XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(highlightBuilder); + parser.nextToken(); + builder.copyCurrentStructure(parser); + } + + if (innerHitsBuilder != null) { + XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(innerHitsBuilder); + parser.nextToken(); + builder.copyCurrentStructure(parser); + } + + if (suggestBuilder != null) { + XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(suggestBuilder); + parser.nextToken(); + builder.copyCurrentStructure(parser); + } + + if (rescoreBuilders != null) { + builder.startArray("sort"); + for (BytesReference sort : sorts) { + XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(sort); + parser.nextToken(); + builder.copyCurrentStructure(parser); + } + builder.endArray(); + // // Strip empty rescoreBuilders from the request + // Iterator itr = rescoreBuilders.iterator(); + // while (itr.hasNext()) { + // if (itr.next().isEmpty()) { + // itr.remove(); + // } + // } + // + // // Now build the request taking care to skip empty lists and only + // send the object form + // // if there is just one builder. + // if (rescoreBuilders.size() == 1) { + // builder.startObject("rescore"); + // rescoreBuilders.get(0).toXContent(builder, params); + // if (rescoreBuilders.get(0).windowSize() == null && + // defaultRescoreWindowSize != null) { + // builder.field("window_size", defaultRescoreWindowSize); + // } + // builder.endObject(); + // } else if (!rescoreBuilders.isEmpty()) { + // builder.startArray("rescore"); + // for (RescoreBuilder rescoreBuilder : rescoreBuilders) { + // builder.startObject(); + // rescoreBuilder.toXContent(builder, params); + // if (rescoreBuilder.windowSize() == null && + // defaultRescoreWindowSize != null) { + // builder.field("window_size", defaultRescoreWindowSize); + // } + // builder.endObject(); + // } + // builder.endArray(); + // } + } + + if (stats != null) { + builder.startArray("stats"); + for (String stat : stats) { + builder.value(stat); + } + builder.endArray(); + } + } + + private static class ScriptField implements Writeable, ToXContent { + + public static final ScriptField PROTOTYPE = new ScriptField(null, null); + + private final String fieldName; + private final Script script; + + private ScriptField(String fieldName, Script script) { + this.fieldName = fieldName; + this.script = script; + } + + public String fieldName() { + return fieldName; + } + + public Script script() { + return script; + } + + @Override + public ScriptField readFrom(StreamInput in) throws IOException { + return new ScriptField(in.readString(), Script.readScript(in)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(fieldName); + script.writeTo(out); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("field", fieldName); + builder.field("script", script); + builder.endObject(); + return builder; + } + } + + @Override + public NewSearchSourceBuilder readFrom(StreamInput in) throws IOException { + NewSearchSourceBuilder builder = new NewSearchSourceBuilder(); + if (in.readBoolean()) { + int size = in.readVInt(); + List aggregations = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + aggregations.add(in.readBytesReference()); + } + builder.aggregations = aggregations; + } + builder.defaultRescoreWindowSize = in.readVInt(); + builder.explain = in.readBoolean(); + builder.fetchSourceContext = FetchSourceContext.optionalReadFromStream(in); + boolean hasFieldDataFields = in.readBoolean(); + if (hasFieldDataFields) { + int size = in.readVInt(); + List fieldDataFields = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + fieldDataFields.add(in.readString()); + } + builder.fieldDataFields = fieldDataFields; + } + boolean hasFieldNames = in.readBoolean(); + if (hasFieldNames) { + int size = in.readVInt(); + List fieldNames = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + fieldNames.add(in.readString()); + } + builder.fieldNames = fieldNames; + } + if (in.readBoolean()) { + builder.filterBinary = in.readBytesReference(); + } + builder.from = in.readVInt(); + if (in.readBoolean()) { + builder.highlightBuilder = in.readBytesReference(); + } + boolean hasIndexBoost = in.readBoolean(); + if (hasIndexBoost) { + int size = in.readVInt(); + ObjectFloatHashMap indexBoost = new ObjectFloatHashMap(size); + for (int i = 0; i < size; i++) { + indexBoost.put(in.readString(), in.readFloat()); + } + builder.indexBoost = indexBoost; + } + if (in.readBoolean()) { + builder.innerHitsBuilder = in.readBytesReference(); + } + if (in.readBoolean()) { + builder.minScore = in.readFloat(); + } + if (in.readBoolean()) { + builder.postQueryBuilder = in.readQuery(); + } + if (in.readBoolean()) { + builder.queryBuilder = in.readQuery(); + } + if (in.readBoolean()) { + int size = in.readVInt(); + List rescoreBuilders = new ArrayList<>(); + for (int i = 0; i < size; i++) { + rescoreBuilders.add(in.readBytesReference()); + } + builder.rescoreBuilders = rescoreBuilders; + } + if (in.readBoolean()) { + int size = in.readVInt(); + List scriptFields = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + scriptFields.add(ScriptField.PROTOTYPE.readFrom(in)); + } + builder.scriptFields = scriptFields; + } + builder.size = in.readVInt(); + if (in.readBoolean()) { + int size = in.readVInt(); + List sorts = new ArrayList<>(); + for (int i = 0; i < size; i++) { + sorts.add(in.readBytesReference()); + } + builder.sorts = sorts; + } + if (in.readBoolean()) { + builder.stats = in.readStringArray(); + } + if (in.readBoolean()) { + builder.suggestBuilder = in.readBytesReference(); + } + builder.terminateAfter = in.readVInt(); + builder.timeoutInMillis = in.readVLong(); + builder.trackScores = in.readBoolean(); + builder.version = in.readBoolean(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + boolean hasAggregations = aggregations != null; + out.writeBoolean(hasAggregations); + if (hasAggregations) { + out.writeVInt(aggregations.size()); + for (BytesReference aggregation : aggregations) { + out.writeBytesReference(aggregation); + } + } + out.writeVInt(defaultRescoreWindowSize); + out.writeBoolean(explain); + FetchSourceContext.optionalWriteToStream(fetchSourceContext, out); + boolean hasFieldDataFields = fieldDataFields != null; + out.writeBoolean(hasFieldDataFields); + if (hasFieldDataFields) { + out.writeVInt(fieldDataFields.size()); + for (String field : fieldDataFields) { + out.writeString(field); + } + } + boolean hasFieldNames = fieldNames != null; + out.writeBoolean(hasFieldNames); + if (hasFieldNames) { + out.writeVInt(fieldNames.size()); + for (String field : fieldNames) { + out.writeString(field); + } + } + boolean hasFilterBinary = filterBinary != null; + out.writeBoolean(hasFilterBinary); + if (hasFilterBinary) { + out.writeBytesReference(filterBinary); + } + out.writeVInt(from); + boolean hasHighlightBuilder = highlightBuilder != null; + out.writeBoolean(hasHighlightBuilder); + if (hasHighlightBuilder) { + out.writeBytesReference(highlightBuilder); + } + boolean hasIndexBoost = indexBoost != null; + out.writeBoolean(hasIndexBoost); + if (hasIndexBoost) { + out.writeVInt(indexBoost.size()); + for (ObjectCursor key : indexBoost.keys()) { + out.writeString(key.value); + out.writeFloat(indexBoost.get(key.value)); + } + } + boolean hasInnerHitsBuilder = innerHitsBuilder != null; + out.writeBoolean(hasInnerHitsBuilder); + if (hasInnerHitsBuilder) { + out.writeBytesReference(innerHitsBuilder); + } + boolean hasMinScore = minScore != null; + out.writeBoolean(hasMinScore); + if (hasMinScore) { + out.writeFloat(minScore); + } + boolean haspostQuery = postQueryBuilder != null; + out.writeBoolean(haspostQuery); + if (haspostQuery) { + postQueryBuilder.writeTo(out); + } + boolean hasQuery = queryBuilder != null; + out.writeBoolean(hasQuery); + if (hasQuery) { + queryBuilder.writeTo(out); + } + boolean hasRescoreBuilders = rescoreBuilders != null; + out.writeBoolean(hasRescoreBuilders); + if (hasRescoreBuilders) { + out.writeVInt(rescoreBuilders.size()); + for (BytesReference rescoreBuilder : rescoreBuilders) { + out.writeBytesReference(rescoreBuilder); + } + } + boolean hasScriptFields = scriptFields != null; + out.writeBoolean(hasScriptFields); + if (hasScriptFields) { + out.writeVInt(scriptFields.size()); + for (ScriptField scriptField : scriptFields) { + scriptField.writeTo(out); + } + } + out.writeVInt(size); + boolean hasSorts = sorts != null; + out.writeBoolean(hasSorts); + if (hasSorts) { + out.writeVInt(sorts.size()); + for (BytesReference sort : sorts) { + out.writeBytesReference(sort); + } + } + boolean hasStats = stats != null; + out.writeBoolean(hasStats); + if (hasStats) { + out.writeStringArray(stats); + } + boolean hasSuggestBuilder = suggestBuilder != null; + out.writeBoolean(hasSuggestBuilder); + if (hasSuggestBuilder) { + out.writeBytesReference(suggestBuilder); + } + out.writeVInt(terminateAfter); + out.writeVLong(timeoutInMillis); + out.writeBoolean(trackScores); + out.writeBoolean(version); + } +}