2020import org .elasticsearch .compute .operator .Warnings ;
2121import org .elasticsearch .compute .querydsl .query .SingleValueMatchQuery ;
2222import org .elasticsearch .index .mapper .MappedFieldType ;
23+ import org .elasticsearch .index .mapper .TextFieldMapper ;
2324import org .elasticsearch .index .query .AbstractQueryBuilder ;
2425import org .elasticsearch .index .query .MatchNoneQueryBuilder ;
2526import org .elasticsearch .index .query .QueryBuilder ;
@@ -57,16 +58,28 @@ public class SingleValueQuery extends Query {
5758
5859 private final Query next ;
5960 private final String field ;
61+ private final boolean useSyntheticSourceDelegate ;
6062
61- public SingleValueQuery (Query next , String field ) {
63+ /**
64+ * Build.
65+ * @param next the query whose documents we should use for single-valued fields
66+ * @param field the name of the field whose values to check
67+ * @param useSyntheticSourceDelegate Should we check the field's synthetic source delegate (true)
68+ * or it's values itself? If the field is a {@code text} field
69+ * we often want to use its delegate.
70+ */
71+ public SingleValueQuery (Query next , String field , boolean useSyntheticSourceDelegate ) {
6272 super (next .source ());
6373 this .next = next ;
6474 this .field = field ;
75+ this .useSyntheticSourceDelegate = useSyntheticSourceDelegate ;
6576 }
6677
6778 @ Override
68- protected Builder asBuilder () {
69- return new Builder (next .toQueryBuilder (), field , next .source ());
79+ protected AbstractBuilder asBuilder () {
80+ return useSyntheticSourceDelegate
81+ ? new SyntheticSourceDelegateBuilder (next .toQueryBuilder (), field , next .source ())
82+ : new Builder (next .toQueryBuilder (), field , next .source ());
7083 }
7184
7285 @ Override
@@ -76,7 +89,7 @@ protected String innerToString() {
7689
7790 @ Override
7891 public SingleValueQuery negate (Source source ) {
79- return new SingleValueQuery (next .negate (source ), field );
92+ return new SingleValueQuery (next .negate (source ), field , useSyntheticSourceDelegate );
8093 }
8194
8295 @ Override
@@ -85,26 +98,28 @@ public boolean equals(Object o) {
8598 return false ;
8699 }
87100 SingleValueQuery other = (SingleValueQuery ) o ;
88- return Objects .equals (next , other .next ) && Objects .equals (field , other .field );
101+ return Objects .equals (next , other .next )
102+ && Objects .equals (field , other .field )
103+ && useSyntheticSourceDelegate == other .useSyntheticSourceDelegate ;
89104 }
90105
91106 @ Override
92107 public int hashCode () {
93- return Objects .hash (super .hashCode (), next , field );
108+ return Objects .hash (super .hashCode (), next , field , useSyntheticSourceDelegate );
94109 }
95110
96- public static class Builder extends AbstractQueryBuilder <Builder > {
111+ public abstract static class AbstractBuilder extends AbstractQueryBuilder <AbstractBuilder > {
97112 private final QueryBuilder next ;
98113 private final String field ;
99114 private final Source source ;
100115
101- Builder (QueryBuilder next , String field , Source source ) {
116+ AbstractBuilder (QueryBuilder next , String field , Source source ) {
102117 this .next = next ;
103118 this .field = field ;
104119 this .source = source ;
105120 }
106121
107- Builder (StreamInput in ) throws IOException {
122+ AbstractBuilder (StreamInput in ) throws IOException {
108123 super (in );
109124 this .next = in .readNamedWriteable (QueryBuilder .class );
110125 this .field = in .readString ();
@@ -126,7 +141,7 @@ public static class Builder extends AbstractQueryBuilder<Builder> {
126141 }
127142
128143 @ Override
129- protected void doWriteTo (StreamOutput out ) throws IOException {
144+ protected final void doWriteTo (StreamOutput out ) throws IOException {
130145 out .writeNamedWriteable (next );
131146 out .writeString (field );
132147 if (out .getTransportVersion ().onOrAfter (TransportVersions .V_8_16_0 )) {
@@ -148,28 +163,11 @@ public Source source() {
148163 return source ;
149164 }
150165
151- @ Override
152- public String getWriteableName () {
153- return ENTRY .name ;
154- }
155-
156- @ Override
157- protected void doXContent (XContentBuilder builder , Params params ) throws IOException {
158- builder .startObject (ENTRY .name );
159- builder .field ("field" , field );
160- builder .field ("next" , next , params );
161- builder .field ("source" , source .toString ());
162- builder .endObject ();
163- }
164-
165- @ Override
166- public TransportVersion getMinimalSupportedVersion () {
167- return TransportVersions .V_8_11_X ; // the first version of ESQL
168- }
166+ protected abstract MappedFieldType mappedFieldType (SearchExecutionContext context );
169167
170168 @ Override
171- protected org .apache .lucene .search .Query doToQuery (SearchExecutionContext context ) throws IOException {
172- MappedFieldType ft = context . getFieldType ( field );
169+ protected final org .apache .lucene .search .Query doToQuery (SearchExecutionContext context ) throws IOException {
170+ MappedFieldType ft = mappedFieldType ( context );
173171 if (ft == null ) {
174172 return new MatchNoDocsQuery ("missing field [" + field + "]" );
175173 }
@@ -194,29 +192,109 @@ protected org.apache.lucene.search.Query doToQuery(SearchExecutionContext contex
194192 return builder .build ();
195193 }
196194
195+ protected abstract AbstractBuilder rewrite (QueryBuilder next );
196+
197197 @ Override
198- protected QueryBuilder doRewrite (QueryRewriteContext queryRewriteContext ) throws IOException {
198+ protected final QueryBuilder doRewrite (QueryRewriteContext queryRewriteContext ) throws IOException {
199199 QueryBuilder rewritten = next .rewrite (queryRewriteContext );
200200 if (rewritten instanceof MatchNoneQueryBuilder ) {
201201 return rewritten ;
202202 }
203203 if (rewritten == next ) {
204204 return this ;
205205 }
206- return new Builder (rewritten , field , source );
206+ return rewrite (rewritten );
207207 }
208208
209209 @ Override
210- protected boolean doEquals (Builder other ) {
210+ protected final boolean doEquals (AbstractBuilder other ) {
211211 return next .equals (other .next ) && field .equals (other .field );
212212 }
213213
214214 @ Override
215- protected int doHashCode () {
215+ protected final int doHashCode () {
216216 return Objects .hash (next , field );
217217 }
218218 }
219219
220+ public static class Builder extends AbstractBuilder {
221+ Builder (QueryBuilder next , String field , Source source ) {
222+ super (next , field , source );
223+ }
224+
225+ Builder (StreamInput in ) throws IOException {
226+ super (in );
227+ }
228+
229+ @ Override
230+ public String getWriteableName () {
231+ return ENTRY .name ;
232+ }
233+
234+ @ Override
235+ protected void doXContent (XContentBuilder builder , Params params ) throws IOException {
236+ builder .startObject (ENTRY .name );
237+ builder .field ("field" , field ());
238+ builder .field ("next" , next (), params );
239+ builder .field ("source" , source ().toString ());
240+ builder .endObject ();
241+ }
242+
243+ @ Override
244+ public TransportVersion getMinimalSupportedVersion () {
245+ return TransportVersions .V_8_11_X ; // the first version of ESQL
246+ }
247+
248+ @ Override
249+ protected MappedFieldType mappedFieldType (SearchExecutionContext context ) {
250+ return context .getFieldType (field ());
251+ }
252+
253+ @ Override
254+ protected AbstractBuilder rewrite (QueryBuilder next ) {
255+ return new Builder (next , field (), source ());
256+ }
257+ }
258+
259+ public static class SyntheticSourceDelegateBuilder extends AbstractBuilder {
260+ SyntheticSourceDelegateBuilder (QueryBuilder next , String field , Source source ) {
261+ super (next , field , source );
262+ }
263+
264+ SyntheticSourceDelegateBuilder (StreamInput in ) throws IOException {
265+ super (in );
266+ }
267+
268+ @ Override
269+ public String getWriteableName () {
270+ throw new UnsupportedOperationException ();
271+ }
272+
273+ @ Override
274+ protected void doXContent (XContentBuilder builder , Params params ) throws IOException {
275+ builder .startObject (ENTRY .name );
276+ builder .field ("field" , field () + ":synthetic_source_delegate" );
277+ builder .field ("next" , next (), params );
278+ builder .field ("source" , source ().toString ());
279+ builder .endObject ();
280+ }
281+
282+ @ Override
283+ public TransportVersion getMinimalSupportedVersion () {
284+ throw new UnsupportedOperationException ();
285+ }
286+
287+ @ Override
288+ protected MappedFieldType mappedFieldType (SearchExecutionContext context ) {
289+ return ((TextFieldMapper .TextFieldType ) context .getFieldType (field ())).syntheticSourceDelegate ();
290+ }
291+
292+ @ Override
293+ protected AbstractBuilder rewrite (QueryBuilder next ) {
294+ return new Builder (next , field (), source ());
295+ }
296+ }
297+
220298 /**
221299 * Write a {@link Source} including the text in it.
222300 */
0 commit comments