@@ -99,6 +99,8 @@ void shouldUseRuntimeFieldFromQueryInSearch() {
99
99
@ DisplayName ("should use runtime-field without script" )
100
100
void shouldUseRuntimeFieldWithoutScript () {
101
101
102
+ // a runtime field without a script can be used to redefine the type of a field for the search,
103
+ // here we change the type from text to double
102
104
insert ("1" , "11" , 10 );
103
105
Query query = new CriteriaQuery (new Criteria ("description" ).matches (11.0 ));
104
106
RuntimeField runtimeField = new RuntimeField ("description" , "double" );
@@ -133,6 +135,25 @@ void shouldReturnValueFromRuntimeFieldDefinedInMapping() {
133
135
assertThat (foundPerson .getBirthDate ()).isEqualTo (birthDate );
134
136
}
135
137
138
+ @ Test // #3076
139
+ @ DisplayName ("should return scripted fields that are lists" )
140
+ void shouldReturnScriptedFieldsThatAreLists () {
141
+ var person = new Person ();
142
+ person .setFirstName ("John" );
143
+ person .setLastName ("Doe" );
144
+ operations .save (person );
145
+ var query = Query .findAll ();
146
+ query .addFields ("allNames" );
147
+ query .addSourceFilter (new FetchSourceFilterBuilder ().withIncludes ("*" ).build ());
148
+
149
+ var searchHits = operations .search (query , Person .class );
150
+
151
+ assertThat (searchHits .getTotalHits ()).isEqualTo (1 );
152
+ var foundPerson = searchHits .getSearchHit (0 ).getContent ();
153
+ // the painless script seems to return the data sorted no matter in which order the values are emitted
154
+ assertThat (foundPerson .getAllNames ()).containsExactlyInAnyOrderElementsOf (List .of ("John" , "Doe" ));
155
+ }
156
+
136
157
@ Test // #2035
137
158
@ DisplayName ("should use repository method with ScriptedField parameters" )
138
159
void shouldUseRepositoryMethodWithScriptedFieldParameters () {
@@ -143,9 +164,11 @@ void shouldUseRepositoryMethodWithScriptedFieldParameters() {
143
164
144
165
repository .save (entity );
145
166
146
- org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField1 = getScriptedField ("scriptedValue1" ,
167
+ org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField1 = buildScriptedField (
168
+ "scriptedValue1" ,
147
169
2 );
148
- org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField2 = getScriptedField ("scriptedValue2" ,
170
+ org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField2 = buildScriptedField (
171
+ "scriptedValue2" ,
149
172
3 );
150
173
151
174
var searchHits = repository .findByValue (3 , scriptedField1 , scriptedField2 );
@@ -157,17 +180,6 @@ void shouldUseRepositoryMethodWithScriptedFieldParameters() {
157
180
assertThat (foundEntity .getScriptedValue2 ()).isEqualTo (9 );
158
181
}
159
182
160
- @ NotNull
161
- private static org .springframework .data .elasticsearch .core .query .ScriptedField getScriptedField (String fieldName ,
162
- int factor ) {
163
- return org .springframework .data .elasticsearch .core .query .ScriptedField .of (
164
- fieldName ,
165
- ScriptData .of (b -> b
166
- .withType (ScriptType .INLINE )
167
- .withScript ("doc['value'].size() > 0 ? doc['value'].value * params['factor'] : 0" )
168
- .withParams (Map .of ("factor" , factor ))));
169
- }
170
-
171
183
@ Test // #2035
172
184
@ DisplayName ("should use repository string query method with ScriptedField parameters" )
173
185
void shouldUseRepositoryStringQueryMethodWithScriptedFieldParameters () {
@@ -178,9 +190,11 @@ void shouldUseRepositoryStringQueryMethodWithScriptedFieldParameters() {
178
190
179
191
repository .save (entity );
180
192
181
- org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField1 = getScriptedField ("scriptedValue1" ,
193
+ org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField1 = buildScriptedField (
194
+ "scriptedValue1" ,
182
195
2 );
183
- org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField2 = getScriptedField ("scriptedValue2" ,
196
+ org .springframework .data .elasticsearch .core .query .ScriptedField scriptedField2 = buildScriptedField (
197
+ "scriptedValue2" ,
184
198
3 );
185
199
186
200
var searchHits = repository .findWithScriptedFields (3 , scriptedField1 , scriptedField2 );
@@ -202,8 +216,8 @@ void shouldUseRepositoryMethodWithRuntimeFieldParameters() {
202
216
203
217
repository .save (entity );
204
218
205
- var runtimeField1 = getRuntimeField ("scriptedValue1" , 3 );
206
- var runtimeField2 = getRuntimeField ("scriptedValue2" , 4 );
219
+ var runtimeField1 = buildRuntimeField ("scriptedValue1" , 3 );
220
+ var runtimeField2 = buildRuntimeField ("scriptedValue2" , 4 );
207
221
208
222
var searchHits = repository .findByValue (3 , runtimeField1 , runtimeField2 );
209
223
@@ -214,14 +228,6 @@ void shouldUseRepositoryMethodWithRuntimeFieldParameters() {
214
228
assertThat (foundEntity .getScriptedValue2 ()).isEqualTo (12 );
215
229
}
216
230
217
- @ NotNull
218
- private static RuntimeField getRuntimeField (String fieldName , int factor ) {
219
- return new RuntimeField (
220
- fieldName ,
221
- "long" ,
222
- String .format ("emit(doc['value'].size() > 0 ? doc['value'].value * %d : 0)" , factor ));
223
- }
224
-
225
231
@ Test // #2035
226
232
@ DisplayName ("should use repository string query method with RuntimeField parameters" )
227
233
void shouldUseRepositoryStringQueryMethodWithRuntimeFieldParameters () {
@@ -232,8 +238,8 @@ void shouldUseRepositoryStringQueryMethodWithRuntimeFieldParameters() {
232
238
233
239
repository .save (entity );
234
240
235
- var runtimeField1 = getRuntimeField ("scriptedValue1" , 3 );
236
- var runtimeField2 = getRuntimeField ("scriptedValue2" , 4 );
241
+ var runtimeField1 = buildRuntimeField ("scriptedValue1" , 3 );
242
+ var runtimeField2 = buildRuntimeField ("scriptedValue2" , 4 );
237
243
238
244
var searchHits = repository .findWithRuntimeFields (3 , runtimeField1 , runtimeField2 );
239
245
@@ -263,8 +269,7 @@ void shouldUseParametersForRuntimeFieldsInSearchQueries() {
263
269
"priceWithTax" ,
264
270
"double" ,
265
271
"emit(doc['price'].value * params.tax)" ,
266
- Map .of ("tax" , 1.19 )
267
- );
272
+ Map .of ("tax" , 1.19 ));
268
273
var query = CriteriaQuery .builder (
269
274
Criteria .where ("priceWithTax" ).greaterThan (100.0 ))
270
275
.withRuntimeFields (List .of (runtimeField ))
@@ -275,6 +280,56 @@ void shouldUseParametersForRuntimeFieldsInSearchQueries() {
275
280
assertThat (searchHits ).hasSize (1 );
276
281
}
277
282
283
+ @ Test // #3076
284
+ @ DisplayName ("should use runtime fields in queries returning lists" )
285
+ void shouldUseRuntimeFieldsInQueriesReturningLists () {
286
+
287
+ insert ("1" , "item 1" , 80.0 );
288
+
289
+ var runtimeField = new RuntimeField (
290
+ "someStrings" ,
291
+ "keyword" ,
292
+ "emit('foo'); emit('bar');" ,
293
+ null );
294
+
295
+ var query = Query .findAll ();
296
+ query .addRuntimeField (runtimeField );
297
+ query .addFields ("someStrings" );
298
+ query .addSourceFilter (new FetchSourceFilterBuilder ().withIncludes ("*" ).build ());
299
+
300
+ var searchHits = operations .search (query , SomethingToBuy .class );
301
+
302
+ assertThat (searchHits ).hasSize (1 );
303
+ var somethingToBuy = searchHits .getSearchHit (0 ).getContent ();
304
+ assertThat (somethingToBuy .someStrings ).containsExactlyInAnyOrder ("foo" , "bar" );
305
+ }
306
+
307
+ /**
308
+ * build a {@link org.springframework.data.elasticsearch.core.query.ScriptedField} to return the product of the
309
+ * document's value property and the given factor
310
+ */
311
+ @ NotNull
312
+ private static org .springframework .data .elasticsearch .core .query .ScriptedField buildScriptedField (String fieldName ,
313
+ int factor ) {
314
+ return org .springframework .data .elasticsearch .core .query .ScriptedField .of (
315
+ fieldName ,
316
+ ScriptData .of (b -> b
317
+ .withType (ScriptType .INLINE )
318
+ .withScript ("doc['value'].size() > 0 ? doc['value'].value * params['factor'] : 0" )
319
+ .withParams (Map .of ("factor" , factor ))));
320
+ }
321
+
322
+ /**
323
+ * build a {@link RuntimeField} to return the product of the document's value property and the given factor
324
+ */
325
+ @ NotNull
326
+ private static RuntimeField buildRuntimeField (String fieldName , int factor ) {
327
+ return new RuntimeField (
328
+ fieldName ,
329
+ "long" ,
330
+ String .format ("emit(doc['value'].size() > 0 ? doc['value'].value * %d : 0)" , factor ));
331
+ }
332
+
278
333
@ SuppressWarnings ("unused" )
279
334
@ Document (indexName = "#{@indexNameProvider.indexName()}-something-to-by" )
280
335
private static class SomethingToBuy {
@@ -286,6 +341,9 @@ private static class SomethingToBuy {
286
341
@ Nullable
287
342
@ Field (type = FieldType .Double ) private Double price ;
288
343
344
+ @ Nullable
345
+ @ ScriptedField private List <String > someStrings ;
346
+
289
347
@ Nullable
290
348
public String getId () {
291
349
return id ;
@@ -312,6 +370,15 @@ public Double getPrice() {
312
370
public void setPrice (@ Nullable Double price ) {
313
371
this .price = price ;
314
372
}
373
+
374
+ @ Nullable
375
+ public List <String > getSomeStrings () {
376
+ return someStrings ;
377
+ }
378
+
379
+ public void setSomeStrings (@ Nullable List <String > someStrings ) {
380
+ this .someStrings = someStrings ;
381
+ }
315
382
}
316
383
317
384
@ SuppressWarnings ("unused" )
@@ -320,6 +387,13 @@ public void setPrice(@Nullable Double price) {
320
387
public static class Person {
321
388
@ Nullable private String id ;
322
389
390
+ // need keywords as we are using them in the script
391
+ @ Nullable
392
+ @ Field (type = FieldType .Keyword ) private String firstName ;
393
+ @ Nullable
394
+ @ Field (type = FieldType .Keyword ) private String lastName ;
395
+ @ ScriptedField private List <String > allNames = List .of ();
396
+
323
397
@ Field (type = FieldType .Date , format = DateFormat .basic_date )
324
398
@ Nullable private LocalDate birthDate ;
325
399
@@ -335,6 +409,24 @@ public void setId(@Nullable String id) {
335
409
this .id = id ;
336
410
}
337
411
412
+ @ Nullable
413
+ public String getFirstName () {
414
+ return firstName ;
415
+ }
416
+
417
+ public void setFirstName (@ Nullable String firstName ) {
418
+ this .firstName = firstName ;
419
+ }
420
+
421
+ @ Nullable
422
+ public String getLastName () {
423
+ return lastName ;
424
+ }
425
+
426
+ public void setLastName (@ Nullable String lastName ) {
427
+ this .lastName = lastName ;
428
+ }
429
+
338
430
@ Nullable
339
431
public LocalDate getBirthDate () {
340
432
return birthDate ;
@@ -352,6 +444,14 @@ public Integer getAge() {
352
444
public void setAge (@ Nullable Integer age ) {
353
445
this .age = age ;
354
446
}
447
+
448
+ public List <String > getAllNames () {
449
+ return allNames ;
450
+ }
451
+
452
+ public void setAllNames (List <String > allNames ) {
453
+ this .allNames = allNames ;
454
+ }
355
455
}
356
456
357
457
@ SuppressWarnings ("unused" )
0 commit comments