|
23 | 23 | import org.apache.lucene.search.Query; |
24 | 24 | import org.apache.lucene.search.Weight; |
25 | 25 | import org.apache.lucene.util.Bits; |
| 26 | +import org.elasticsearch.common.ParseField; |
| 27 | +import org.elasticsearch.common.io.stream.StreamInput; |
| 28 | +import org.elasticsearch.common.io.stream.StreamOutput; |
| 29 | +import org.elasticsearch.common.io.stream.Writeable; |
26 | 30 | import org.elasticsearch.common.lucene.Lucene; |
| 31 | +import org.elasticsearch.common.xcontent.ToXContent; |
| 32 | +import org.elasticsearch.common.xcontent.XContentBuilder; |
| 33 | +import org.elasticsearch.index.query.QueryBuilder; |
27 | 34 | import org.elasticsearch.search.aggregations.Aggregator; |
28 | 35 | import org.elasticsearch.search.aggregations.AggregatorFactories; |
29 | 36 | import org.elasticsearch.search.aggregations.AggregatorFactory; |
|
39 | 46 | import java.util.ArrayList; |
40 | 47 | import java.util.List; |
41 | 48 | import java.util.Map; |
| 49 | +import java.util.Objects; |
42 | 50 |
|
43 | 51 | /** |
44 | 52 | * |
45 | 53 | */ |
46 | 54 | public class FiltersAggregator extends BucketsAggregator { |
47 | 55 |
|
48 | | - static class KeyedFilter { |
| 56 | + public static final ParseField FILTERS_FIELD = new ParseField("filters"); |
| 57 | + public static final ParseField OTHER_BUCKET_FIELD = new ParseField("other_bucket"); |
| 58 | + public static final ParseField OTHER_BUCKET_KEY_FIELD = new ParseField("other_bucket_key"); |
49 | 59 |
|
50 | | - final String key; |
51 | | - final Query filter; |
| 60 | + public static class KeyedFilter implements Writeable<KeyedFilter>, ToXContent { |
52 | 61 |
|
53 | | - KeyedFilter(String key, Query filter) { |
| 62 | + static final KeyedFilter PROTOTYPE = new KeyedFilter(null, null); |
| 63 | + private final String key; |
| 64 | + private final QueryBuilder<?> filter; |
| 65 | + |
| 66 | + public KeyedFilter(String key, QueryBuilder<?> filter) { |
54 | 67 | this.key = key; |
55 | 68 | this.filter = filter; |
56 | 69 | } |
| 70 | + |
| 71 | + public String key() { |
| 72 | + return key; |
| 73 | + } |
| 74 | + |
| 75 | + public QueryBuilder<?> filter() { |
| 76 | + return filter; |
| 77 | + } |
| 78 | + |
| 79 | + @Override |
| 80 | + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { |
| 81 | + builder.field(key, filter); |
| 82 | + return builder; |
| 83 | + } |
| 84 | + |
| 85 | + @Override |
| 86 | + public KeyedFilter readFrom(StreamInput in) throws IOException { |
| 87 | + String key = in.readString(); |
| 88 | + QueryBuilder<?> filter = in.readQuery(); |
| 89 | + return new KeyedFilter(key, filter); |
| 90 | + } |
| 91 | + |
| 92 | + @Override |
| 93 | + public void writeTo(StreamOutput out) throws IOException { |
| 94 | + out.writeString(key); |
| 95 | + out.writeQuery(filter); |
| 96 | + } |
| 97 | + |
| 98 | + @Override |
| 99 | + public int hashCode() { |
| 100 | + return Objects.hash(key, filter); |
| 101 | + } |
| 102 | + |
| 103 | + @Override |
| 104 | + public boolean equals(Object obj) { |
| 105 | + if (obj == null) { |
| 106 | + return false; |
| 107 | + } |
| 108 | + if (getClass() != obj.getClass()) { |
| 109 | + return false; |
| 110 | + } |
| 111 | + KeyedFilter other = (KeyedFilter) obj; |
| 112 | + return Objects.equals(key, other.key) |
| 113 | + && Objects.equals(filter, other.filter); |
| 114 | + } |
57 | 115 | } |
58 | 116 |
|
59 | 117 | private final String[] keys; |
@@ -81,7 +139,8 @@ public FiltersAggregator(String name, AggregatorFactories factories, List<KeyedF |
81 | 139 | for (int i = 0; i < filters.size(); ++i) { |
82 | 140 | KeyedFilter keyedFilter = filters.get(i); |
83 | 141 | this.keys[i] = keyedFilter.key; |
84 | | - this.filters[i] = aggregationContext.searchContext().searcher().createNormalizedWeight(keyedFilter.filter, false); |
| 142 | + Query filter = keyedFilter.filter.toFilter(context.searchContext().indexShard().getQueryShardContext()); |
| 143 | + this.filters[i] = aggregationContext.searchContext().searcher().createNormalizedWeight(filter, false); |
85 | 144 | } |
86 | 145 | } |
87 | 146 |
|
@@ -146,20 +205,138 @@ final long bucketOrd(long owningBucketOrdinal, int filterOrd) { |
146 | 205 | public static class Factory extends AggregatorFactory { |
147 | 206 |
|
148 | 207 | private final List<KeyedFilter> filters; |
149 | | - private boolean keyed; |
150 | | - private String otherBucketKey; |
| 208 | + private final boolean keyed; |
| 209 | + private boolean otherBucket = false; |
| 210 | + private String otherBucketKey = "_other_"; |
151 | 211 |
|
152 | | - public Factory(String name, List<KeyedFilter> filters, boolean keyed, String otherBucketKey) { |
| 212 | + public Factory(String name, List<KeyedFilter> filters) { |
153 | 213 | super(name, InternalFilters.TYPE); |
154 | 214 | this.filters = filters; |
155 | | - this.keyed = keyed; |
| 215 | + this.keyed = true; |
| 216 | + } |
| 217 | + |
| 218 | + public Factory(String name, QueryBuilder<?>... filters) { |
| 219 | + super(name, InternalFilters.TYPE); |
| 220 | + List<KeyedFilter> keyedFilters = new ArrayList<>(filters.length); |
| 221 | + for (int i = 0; i < filters.length; i++) { |
| 222 | + keyedFilters.add(new KeyedFilter(String.valueOf(i), filters[i])); |
| 223 | + } |
| 224 | + this.filters = keyedFilters; |
| 225 | + this.keyed = false; |
| 226 | + } |
| 227 | + |
| 228 | + /** |
| 229 | + * Set whether to include a bucket for documents not matching any filter |
| 230 | + */ |
| 231 | + public void otherBucket(boolean otherBucket) { |
| 232 | + this.otherBucket = otherBucket; |
| 233 | + } |
| 234 | + |
| 235 | + /** |
| 236 | + * Get whether to include a bucket for documents not matching any filter |
| 237 | + */ |
| 238 | + public boolean otherBucket() { |
| 239 | + return otherBucket; |
| 240 | + } |
| 241 | + |
| 242 | + /** |
| 243 | + * Set the key to use for the bucket for documents not matching any |
| 244 | + * filter. |
| 245 | + */ |
| 246 | + public void otherBucketKey(String otherBucketKey) { |
156 | 247 | this.otherBucketKey = otherBucketKey; |
157 | 248 | } |
158 | 249 |
|
| 250 | + /** |
| 251 | + * Get the key to use for the bucket for documents not matching any |
| 252 | + * filter. |
| 253 | + */ |
| 254 | + public String otherBucketKey() { |
| 255 | + return otherBucketKey; |
| 256 | + } |
| 257 | + |
159 | 258 | @Override |
160 | 259 | public Aggregator createInternal(AggregationContext context, Aggregator parent, boolean collectsFromSingleBucket, |
161 | 260 | List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException { |
162 | | - return new FiltersAggregator(name, factories, filters, keyed, otherBucketKey, context, parent, pipelineAggregators, metaData); |
| 261 | + return new FiltersAggregator(name, factories, filters, keyed, otherBucket ? otherBucketKey : null, context, parent, |
| 262 | + pipelineAggregators, metaData); |
| 263 | + } |
| 264 | + |
| 265 | + @Override |
| 266 | + protected XContentBuilder internalXContent(XContentBuilder builder, Params params) throws IOException { |
| 267 | + builder.startObject(); |
| 268 | + if (keyed) { |
| 269 | + builder.startObject(FILTERS_FIELD.getPreferredName()); |
| 270 | + for (KeyedFilter keyedFilter : filters) { |
| 271 | + builder.field(keyedFilter.key(), keyedFilter.filter()); |
| 272 | + } |
| 273 | + builder.endObject(); |
| 274 | + } else { |
| 275 | + builder.startArray(FILTERS_FIELD.getPreferredName()); |
| 276 | + for (KeyedFilter keyedFilter : filters) { |
| 277 | + builder.value(keyedFilter.filter()); |
| 278 | + } |
| 279 | + builder.endArray(); |
| 280 | + } |
| 281 | + builder.field(OTHER_BUCKET_FIELD.getPreferredName(), otherBucket); |
| 282 | + builder.field(OTHER_BUCKET_KEY_FIELD.getPreferredName(), otherBucketKey); |
| 283 | + builder.endObject(); |
| 284 | + return builder; |
| 285 | + } |
| 286 | + |
| 287 | + @Override |
| 288 | + protected AggregatorFactory doReadFrom(String name, StreamInput in) throws IOException { |
| 289 | + Factory factory; |
| 290 | + if (in.readBoolean()) { |
| 291 | + int size = in.readVInt(); |
| 292 | + List<KeyedFilter> filters = new ArrayList<>(size); |
| 293 | + for (int i = 0; i < size; i++) { |
| 294 | + filters.add(KeyedFilter.PROTOTYPE.readFrom(in)); |
| 295 | + } |
| 296 | + factory = new Factory(name, filters); |
| 297 | + } else { |
| 298 | + int size = in.readVInt(); |
| 299 | + QueryBuilder<?>[] filters = new QueryBuilder<?>[size]; |
| 300 | + for (int i = 0; i < size; i++) { |
| 301 | + filters[i] = in.readQuery(); |
| 302 | + } |
| 303 | + factory = new Factory(name, filters); |
| 304 | + } |
| 305 | + factory.otherBucket = in.readBoolean(); |
| 306 | + factory.otherBucketKey = in.readString(); |
| 307 | + return factory; |
| 308 | + } |
| 309 | + |
| 310 | + @Override |
| 311 | + protected void doWriteTo(StreamOutput out) throws IOException { |
| 312 | + out.writeBoolean(keyed); |
| 313 | + if (keyed) { |
| 314 | + out.writeVInt(filters.size()); |
| 315 | + for (KeyedFilter keyedFilter : filters) { |
| 316 | + keyedFilter.writeTo(out); |
| 317 | + } |
| 318 | + } else { |
| 319 | + out.writeVInt(filters.size()); |
| 320 | + for (KeyedFilter keyedFilter : filters) { |
| 321 | + out.writeQuery(keyedFilter.filter()); |
| 322 | + } |
| 323 | + } |
| 324 | + out.writeBoolean(otherBucket); |
| 325 | + out.writeString(otherBucketKey); |
| 326 | + } |
| 327 | + |
| 328 | + @Override |
| 329 | + protected int doHashCode() { |
| 330 | + return Objects.hash(filters, keyed, otherBucket, otherBucketKey); |
| 331 | + } |
| 332 | + |
| 333 | + @Override |
| 334 | + protected boolean doEquals(Object obj) { |
| 335 | + Factory other = (Factory) obj; |
| 336 | + return Objects.equals(filters, other.filters) |
| 337 | + && Objects.equals(keyed, other.keyed) |
| 338 | + && Objects.equals(otherBucket, other.otherBucket) |
| 339 | + && Objects.equals(otherBucketKey, other.otherBucketKey); |
163 | 340 | } |
164 | 341 | } |
165 | 342 |
|
|
0 commit comments