|
19 | 19 |
|
20 | 20 | package org.elasticsearch.index.query;
|
21 | 21 |
|
| 22 | +import org.apache.lucene.search.Query; |
| 23 | +import org.apache.lucene.search.spans.SpanNearQuery; |
| 24 | +import org.apache.lucene.search.spans.SpanQuery; |
| 25 | +import org.elasticsearch.common.io.stream.StreamInput; |
| 26 | +import org.elasticsearch.common.io.stream.StreamOutput; |
22 | 27 | import org.elasticsearch.common.xcontent.XContentBuilder;
|
23 | 28 |
|
24 | 29 | import java.io.IOException;
|
25 | 30 | import java.util.ArrayList;
|
| 31 | +import java.util.List; |
| 32 | +import java.util.Objects; |
26 | 33 |
|
| 34 | +/** |
| 35 | + * Matches spans which are near one another. One can specify slop, the maximum number |
| 36 | + * of intervening unmatched positions, as well as whether matches are required to be in-order. |
| 37 | + * The span near query maps to Lucene {@link SpanNearQuery}. |
| 38 | + */ |
27 | 39 | public class SpanNearQueryBuilder extends AbstractQueryBuilder<SpanNearQueryBuilder> implements SpanQueryBuilder<SpanNearQueryBuilder> {
|
28 | 40 |
|
29 | 41 | public static final String NAME = "span_near";
|
30 | 42 |
|
31 |
| - private ArrayList<SpanQueryBuilder> clauses = new ArrayList<>(); |
| 43 | + /** Default for flag controlling whether matches are required to be in-order */ |
| 44 | + public static boolean DEFAULT_IN_ORDER = true; |
| 45 | + |
| 46 | + /** Default for flag controlling whether payloads are collected */ |
| 47 | + public static boolean DEFAULT_COLLECT_PAYLOADS = true; |
| 48 | + |
| 49 | + private final ArrayList<SpanQueryBuilder> clauses = new ArrayList<>(); |
32 | 50 |
|
33 |
| - private Integer slop = null; |
| 51 | + private final int slop; |
34 | 52 |
|
35 |
| - private Boolean inOrder; |
| 53 | + private boolean inOrder = DEFAULT_IN_ORDER; |
36 | 54 |
|
37 |
| - private Boolean collectPayloads; |
| 55 | + private boolean collectPayloads = DEFAULT_COLLECT_PAYLOADS; |
38 | 56 |
|
39 | 57 | static final SpanNearQueryBuilder PROTOTYPE = new SpanNearQueryBuilder();
|
40 | 58 |
|
| 59 | + /** |
| 60 | + * @param slop controls the maximum number of intervening unmatched positions permitted |
| 61 | + */ |
| 62 | + public SpanNearQueryBuilder(int slop) { |
| 63 | + this.slop = slop; |
| 64 | + } |
| 65 | + |
| 66 | + /** |
| 67 | + * only used for prototype |
| 68 | + */ |
| 69 | + private SpanNearQueryBuilder() { |
| 70 | + this.slop = 0; |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * @return the maximum number of intervening unmatched positions permitted |
| 75 | + */ |
| 76 | + public int slop() { |
| 77 | + return this.slop; |
| 78 | + } |
| 79 | + |
41 | 80 | public SpanNearQueryBuilder clause(SpanQueryBuilder clause) {
|
42 |
| - clauses.add(clause); |
| 81 | + clauses.add(Objects.requireNonNull(clause)); |
43 | 82 | return this;
|
44 | 83 | }
|
45 | 84 |
|
46 |
| - public SpanNearQueryBuilder slop(int slop) { |
47 |
| - this.slop = slop; |
48 |
| - return this; |
| 85 | + /** |
| 86 | + * @return the {@link SpanQueryBuilder} clauses that were set for this query |
| 87 | + */ |
| 88 | + public List<SpanQueryBuilder> clauses() { |
| 89 | + return this.clauses; |
49 | 90 | }
|
50 | 91 |
|
| 92 | + /** |
| 93 | + * When <code>inOrder</code> is true, the spans from each clause |
| 94 | + * must be in the same order as in <code>clauses</code> and must be non-overlapping. |
| 95 | + * Defaults to <code>true</code> |
| 96 | + */ |
51 | 97 | public SpanNearQueryBuilder inOrder(boolean inOrder) {
|
52 | 98 | this.inOrder = inOrder;
|
53 | 99 | return this;
|
54 | 100 | }
|
55 | 101 |
|
| 102 | + /** |
| 103 | + * @see SpanNearQueryBuilder#inOrder(boolean)) |
| 104 | + */ |
| 105 | + public boolean inOrder() { |
| 106 | + return this.inOrder; |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * @param collectPayloads flag controlling whether payloads are collected |
| 111 | + */ |
56 | 112 | public SpanNearQueryBuilder collectPayloads(boolean collectPayloads) {
|
57 | 113 | this.collectPayloads = collectPayloads;
|
58 | 114 | return this;
|
59 | 115 | }
|
60 | 116 |
|
| 117 | + /** |
| 118 | + * @see SpanNearQueryBuilder#collectPayloads(boolean)) |
| 119 | + */ |
| 120 | + public boolean collectPayloads() { |
| 121 | + return this.collectPayloads; |
| 122 | + } |
| 123 | + |
61 | 124 | @Override
|
62 | 125 | protected void doXContent(XContentBuilder builder, Params params) throws IOException {
|
63 |
| - if (clauses.isEmpty()) { |
64 |
| - throw new IllegalArgumentException("Must have at least one clause when building a spanNear query"); |
65 |
| - } |
66 |
| - if (slop == null) { |
67 |
| - throw new IllegalArgumentException("Must set the slop when building a spanNear query"); |
68 |
| - } |
69 | 126 | builder.startObject(NAME);
|
70 | 127 | builder.startArray("clauses");
|
71 | 128 | for (SpanQueryBuilder clause : clauses) {
|
72 | 129 | clause.toXContent(builder, params);
|
73 | 130 | }
|
74 | 131 | builder.endArray();
|
75 |
| - builder.field("slop", slop.intValue()); |
76 |
| - if (inOrder != null) { |
77 |
| - builder.field("in_order", inOrder); |
78 |
| - } |
79 |
| - if (collectPayloads != null) { |
80 |
| - builder.field("collect_payloads", collectPayloads); |
81 |
| - } |
| 132 | + builder.field("slop", slop); |
| 133 | + builder.field("in_order", inOrder); |
| 134 | + builder.field("collect_payloads", collectPayloads); |
82 | 135 | printBoostAndQueryName(builder);
|
83 | 136 | builder.endObject();
|
84 | 137 | }
|
85 | 138 |
|
| 139 | + @Override |
| 140 | + protected Query doToQuery(QueryParseContext parseContext) throws IOException { |
| 141 | + SpanQuery[] spanQueries = new SpanQuery[clauses.size()]; |
| 142 | + for (int i = 0; i < clauses.size(); i++) { |
| 143 | + Query query = clauses.get(i).toQuery(parseContext); |
| 144 | + assert query instanceof SpanQuery; |
| 145 | + spanQueries[i] = (SpanQuery) query; |
| 146 | + } |
| 147 | + return new SpanNearQuery(spanQueries, slop, inOrder, collectPayloads); |
| 148 | + } |
| 149 | + |
| 150 | + @Override |
| 151 | + public QueryValidationException validate() { |
| 152 | + QueryValidationException validationExceptions = null; |
| 153 | + if (clauses.isEmpty()) { |
| 154 | + validationExceptions = addValidationError("query must include [clauses]", validationExceptions); |
| 155 | + } |
| 156 | + for (SpanQueryBuilder innerClause : clauses) { |
| 157 | + validationExceptions = validateInnerQuery(innerClause, validationExceptions); |
| 158 | + } |
| 159 | + return validationExceptions; |
| 160 | + } |
| 161 | + |
| 162 | + @Override |
| 163 | + protected SpanNearQueryBuilder doReadFrom(StreamInput in) throws IOException { |
| 164 | + SpanNearQueryBuilder queryBuilder = new SpanNearQueryBuilder(in.readVInt()); |
| 165 | + List<SpanQueryBuilder> clauses = in.readNamedWriteableList(); |
| 166 | + for (SpanQueryBuilder subClause : clauses) { |
| 167 | + queryBuilder.clause(subClause); |
| 168 | + } |
| 169 | + queryBuilder.collectPayloads = in.readBoolean(); |
| 170 | + queryBuilder.inOrder = in.readBoolean(); |
| 171 | + return queryBuilder; |
| 172 | + |
| 173 | + } |
| 174 | + |
| 175 | + @Override |
| 176 | + protected void doWriteTo(StreamOutput out) throws IOException { |
| 177 | + out.writeVInt(slop); |
| 178 | + out.writeNamedWriteableList(clauses); |
| 179 | + out.writeBoolean(collectPayloads); |
| 180 | + out.writeBoolean(inOrder); |
| 181 | + } |
| 182 | + |
| 183 | + @Override |
| 184 | + protected int doHashCode() { |
| 185 | + return Objects.hash(clauses, slop, collectPayloads, inOrder); |
| 186 | + } |
| 187 | + |
| 188 | + @Override |
| 189 | + protected boolean doEquals(SpanNearQueryBuilder other) { |
| 190 | + return Objects.equals(clauses, other.clauses) && |
| 191 | + Objects.equals(slop, other.slop) && |
| 192 | + Objects.equals(collectPayloads, other.collectPayloads) && |
| 193 | + Objects.equals(inOrder, other.inOrder); |
| 194 | + } |
| 195 | + |
86 | 196 | @Override
|
87 | 197 | public String getName() {
|
88 | 198 | return NAME;
|
|
0 commit comments