2929import org .elasticsearch .search .aggregations .metrics .MaxAggregationBuilder ;
3030import org .elasticsearch .search .aggregations .support .ValuesSourceAggregationBuilder ;
3131import org .elasticsearch .search .builder .SearchSourceBuilder ;
32+ import org .elasticsearch .xpack .core .common .time .TimeUtils ;
3233import org .elasticsearch .xpack .core .ml .datafeed .extractor .ExtractorUtils ;
3334import org .elasticsearch .xpack .core .ml .job .config .Job ;
3435import org .elasticsearch .xpack .core .ml .job .messages .Messages ;
3738import org .elasticsearch .xpack .core .ml .utils .QueryProvider ;
3839import org .elasticsearch .xpack .core .ml .utils .ToXContentParams ;
3940import org .elasticsearch .xpack .core .ml .utils .XContentObjectTransformer ;
40- import org .elasticsearch .xpack .core .common .time .TimeUtils ;
4141
4242import java .io .IOException ;
4343import java .util .ArrayList ;
@@ -166,6 +166,7 @@ private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFie
166166 parser .declareObject (Builder ::setIndicesOptions ,
167167 (p , c ) -> IndicesOptions .fromMap (p .map (), SearchRequest .DEFAULT_INDICES_OPTIONS ),
168168 INDICES_OPTIONS );
169+ parser .declareObject (Builder ::setRuntimeMappings , (p , c ) -> p .map (), SearchSourceBuilder .RUNTIME_MAPPINGS_FIELD );
169170 return parser ;
170171 }
171172
@@ -192,11 +193,13 @@ private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFie
192193 private final DelayedDataCheckConfig delayedDataCheckConfig ;
193194 private final Integer maxEmptySearches ;
194195 private final IndicesOptions indicesOptions ;
196+ private final Map <String , Object > runtimeMappings ;
195197
196198 private DatafeedConfig (String id , String jobId , TimeValue queryDelay , TimeValue frequency , List <String > indices ,
197199 QueryProvider queryProvider , AggProvider aggProvider , List <SearchSourceBuilder .ScriptField > scriptFields ,
198200 Integer scrollSize , ChunkingConfig chunkingConfig , Map <String , String > headers ,
199- DelayedDataCheckConfig delayedDataCheckConfig , Integer maxEmptySearches , IndicesOptions indicesOptions ) {
201+ DelayedDataCheckConfig delayedDataCheckConfig , Integer maxEmptySearches , IndicesOptions indicesOptions ,
202+ Map <String , Object > runtimeMappings ) {
200203 this .id = id ;
201204 this .jobId = jobId ;
202205 this .queryDelay = queryDelay ;
@@ -211,6 +214,7 @@ private DatafeedConfig(String id, String jobId, TimeValue queryDelay, TimeValue
211214 this .delayedDataCheckConfig = delayedDataCheckConfig ;
212215 this .maxEmptySearches = maxEmptySearches ;
213216 this .indicesOptions = ExceptionsHelper .requireNonNull (indicesOptions , INDICES_OPTIONS );
217+ this .runtimeMappings = Collections .unmodifiableMap (runtimeMappings );
214218 }
215219
216220 public DatafeedConfig (StreamInput in ) throws IOException {
@@ -261,6 +265,11 @@ public DatafeedConfig(StreamInput in) throws IOException {
261265 } else {
262266 indicesOptions = SearchRequest .DEFAULT_INDICES_OPTIONS ;
263267 }
268+ if (in .getVersion ().onOrAfter (Version .V_7_11_0 )) {
269+ runtimeMappings = in .readMap ();
270+ } else {
271+ runtimeMappings = Collections .emptyMap ();
272+ }
264273 }
265274
266275 /**
@@ -437,6 +446,10 @@ public IndicesOptions getIndicesOptions() {
437446 return indicesOptions ;
438447 }
439448
449+ public Map <String , Object > getRuntimeMappings () {
450+ return runtimeMappings ;
451+ }
452+
440453 @ Override
441454 public void writeTo (StreamOutput out ) throws IOException {
442455 out .writeString (id );
@@ -481,6 +494,9 @@ public void writeTo(StreamOutput out) throws IOException {
481494 if (out .getVersion ().onOrAfter (Version .V_7_7_0 )) {
482495 indicesOptions .writeIndicesOptions (out );
483496 }
497+ if (out .getVersion ().onOrAfter (Version .V_7_11_0 )) {
498+ out .writeMap (runtimeMappings );
499+ }
484500 }
485501
486502 @ Override
@@ -539,6 +555,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
539555 if (maxEmptySearches != null ) {
540556 builder .field (MAX_EMPTY_SEARCHES .getPreferredName (), maxEmptySearches );
541557 }
558+ if (runtimeMappings .isEmpty () == false ) {
559+ builder .field (SearchSourceBuilder .RUNTIME_MAPPINGS_FIELD .getPreferredName (), runtimeMappings );
560+ }
542561 builder .endObject ();
543562 return builder ;
544563 }
@@ -591,13 +610,14 @@ public boolean equals(Object other) {
591610 && Objects .equals (this .headers , that .headers )
592611 && Objects .equals (this .delayedDataCheckConfig , that .delayedDataCheckConfig )
593612 && Objects .equals (this .maxEmptySearches , that .maxEmptySearches )
594- && Objects .equals (this .indicesOptions , that .indicesOptions );
613+ && Objects .equals (this .indicesOptions , that .indicesOptions )
614+ && Objects .equals (this .runtimeMappings , that .runtimeMappings );
595615 }
596616
597617 @ Override
598618 public int hashCode () {
599619 return Objects .hash (id , jobId , frequency , queryDelay , indices , queryProvider , scrollSize , aggProvider , scriptFields , chunkingConfig ,
600- headers , delayedDataCheckConfig , maxEmptySearches , indicesOptions );
620+ headers , delayedDataCheckConfig , maxEmptySearches , indicesOptions , runtimeMappings );
601621 }
602622
603623 @ Override
@@ -668,6 +688,7 @@ public static class Builder {
668688 private DelayedDataCheckConfig delayedDataCheckConfig = DelayedDataCheckConfig .defaultDelayedDataCheckConfig ();
669689 private Integer maxEmptySearches ;
670690 private IndicesOptions indicesOptions ;
691+ private Map <String , Object > runtimeMappings = Collections .emptyMap ();
671692
672693 public Builder () { }
673694
@@ -692,6 +713,7 @@ public Builder(DatafeedConfig config) {
692713 this .delayedDataCheckConfig = config .getDelayedDataCheckConfig ();
693714 this .maxEmptySearches = config .getMaxEmptySearches ();
694715 this .indicesOptions = config .indicesOptions ;
716+ this .runtimeMappings = new HashMap <>(config .runtimeMappings );
695717 }
696718
697719 public Builder setId (String datafeedId ) {
@@ -819,6 +841,11 @@ public IndicesOptions getIndicesOptions() {
819841 return this .indicesOptions ;
820842 }
821843
844+ public void setRuntimeMappings (Map <String , Object > runtimeMappings ) {
845+ this .runtimeMappings = ExceptionsHelper .requireNonNull (runtimeMappings ,
846+ SearchSourceBuilder .RUNTIME_MAPPINGS_FIELD .getPreferredName ());
847+ }
848+
822849 public DatafeedConfig build () {
823850 ExceptionsHelper .requireNonNull (id , ID .getPreferredName ());
824851 ExceptionsHelper .requireNonNull (jobId , Job .ID .getPreferredName ());
@@ -830,14 +857,15 @@ public DatafeedConfig build() {
830857 }
831858
832859 validateScriptFields ();
860+ validateRuntimeMappings ();
833861 setDefaultChunkingConfig ();
834862
835863 setDefaultQueryDelay ();
836864 if (indicesOptions == null ) {
837865 indicesOptions = SearchRequest .DEFAULT_INDICES_OPTIONS ;
838866 }
839867 return new DatafeedConfig (id , jobId , queryDelay , frequency , indices , queryProvider , aggProvider , scriptFields , scrollSize ,
840- chunkingConfig , headers , delayedDataCheckConfig , maxEmptySearches , indicesOptions );
868+ chunkingConfig , headers , delayedDataCheckConfig , maxEmptySearches , indicesOptions , runtimeMappings );
841869 }
842870
843871 void validateScriptFields () {
@@ -850,6 +878,28 @@ void validateScriptFields() {
850878 }
851879 }
852880
881+ /**
882+ * Perform a light check that the structure resembles runtime_mappings.
883+ * The full check cannot happen until search
884+ */
885+ void validateRuntimeMappings () {
886+ for (Map .Entry <String , Object > entry : runtimeMappings .entrySet ()) {
887+ // top level objects are fields
888+ String fieldName = entry .getKey ();
889+ if (entry .getValue () instanceof Map ) {
890+ @ SuppressWarnings ("unchecked" )
891+ Map <String , Object > propNode = new HashMap <>(((Map <String , Object >) entry .getValue ()));
892+ Object typeNode = propNode .get ("type" );
893+ if (typeNode == null ) {
894+ throw ExceptionsHelper .badRequestException ("No type specified for runtime field [" + fieldName + "]" );
895+ }
896+ } else {
897+ throw ExceptionsHelper .badRequestException ("Expected map for runtime field [" + fieldName + "] " +
898+ "definition but got a " + fieldName .getClass ().getSimpleName ());
899+ }
900+ }
901+ }
902+
853903 private static void checkNoMoreHistogramAggregations (Collection <AggregationBuilder > aggregations ) {
854904 for (AggregationBuilder agg : aggregations ) {
855905 if (ExtractorUtils .isHistogram (agg )) {
0 commit comments