1212import org .elasticsearch .common .io .stream .NamedWriteableRegistry ;
1313import org .elasticsearch .common .io .stream .StreamInput ;
1414import org .elasticsearch .common .io .stream .StreamOutput ;
15+ import org .elasticsearch .common .lucene .BytesRefs ;
1516import org .elasticsearch .compute .ann .Evaluator ;
1617import org .elasticsearch .compute .ann .Fixed ;
1718import org .elasticsearch .compute .operator .EvalOperator ;
4546import java .util .Collection ;
4647import java .util .HashMap ;
4748import java .util .List ;
49+ import java .util .Locale ;
4850import java .util .Map ;
4951import java .util .Set ;
5052import java .util .function .Predicate ;
5153import java .util .stream .Collectors ;
5254
55+ import static org .elasticsearch .common .logging .LoggerMessageFormat .format ;
5356import static org .elasticsearch .xpack .esql .common .Failure .fail ;
5457import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FIRST ;
5558import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FOURTH ;
6164import static org .elasticsearch .xpack .esql .core .type .DataType .INTEGER ;
6265import static org .elasticsearch .xpack .esql .core .type .DataType .KEYWORD ;
6366import static org .elasticsearch .xpack .esql .core .type .DataType .LONG ;
67+ import static org .elasticsearch .xpack .esql .core .type .DataType .NULL ;
6468import static org .elasticsearch .xpack .esql .core .type .DataType .TEXT ;
6569import static org .elasticsearch .xpack .esql .core .type .DataType .TIME_DURATION ;
6670import static org .elasticsearch .xpack .esql .core .type .DataType .isDateNanos ;
@@ -212,8 +216,9 @@ protected TypeResolution resolveType() {
212216 return new TypeResolution ("Unresolved children" );
213217 }
214218
215- return validateValue ().and (() -> validateOriginAndScale (value .dataType ()))
216- .and (() -> Options .resolveWithMultipleDataTypesAllowed (options , source (), FOURTH , ALLOWED_OPTIONS ));
219+ return validateValue ().and (() -> Options .resolveWithMultipleDataTypesAllowed (options , source (), FOURTH , ALLOWED_OPTIONS ))
220+ .and (() -> validateOriginScaleAndOffset (value .dataType ()))
221+ .and (this ::validateTypeOption );
217222 }
218223
219224 private TypeResolution validateValue () {
@@ -222,34 +227,100 @@ private TypeResolution validateValue() {
222227 );
223228 }
224229
225- private TypeResolution validateOriginAndScale (DataType valueType ) {
230+ private TypeResolution validateOriginScaleAndOffset (DataType valueType ) {
226231 if (isSpatialPoint (valueType )) {
227232 boolean isGeoPoint = isGeoPoint (valueType );
228233
229- return validateOriginAndScale (
234+ return validateOriginScaleAndOffset (
230235 DataType ::isSpatialPoint ,
231236 "spatial point" ,
232237 isGeoPoint ? DataType ::isString : DataType ::isNumeric ,
238+ isGeoPoint ? "keyword or text" : "numeric" ,
239+ isGeoPoint ? DataType ::isString : DataType ::isNumeric ,
233240 isGeoPoint ? "keyword or text" : "numeric"
234241 );
235242 } else if (isMillisOrNanos (valueType )) {
236- return validateOriginAndScale (DataType ::isMillisOrNanos , "datetime or date_nanos" , DataType ::isTimeDuration , "time_duration" );
243+ return validateOriginScaleAndOffset (
244+ DataType ::isMillisOrNanos ,
245+ "datetime or date_nanos" ,
246+ DataType ::isTimeDuration ,
247+ "time_duration" ,
248+ DataType ::isTimeDuration ,
249+ "time_duration"
250+ );
237251 } else {
238- return validateOriginAndScale (DataType ::isNumeric , "numeric" , DataType ::isNumeric , "numeric" );
252+ return validateOriginScaleAndOffset (
253+ DataType ::isNumeric ,
254+ "numeric" ,
255+ DataType ::isNumeric ,
256+ "numeric" ,
257+ DataType ::isNumeric ,
258+ "numeric"
259+ );
239260 }
240261 }
241262
242- private TypeResolution validateOriginAndScale (
263+ private TypeResolution validateOriginScaleAndOffset (
243264 Predicate <DataType > originPredicate ,
244265 String originDesc ,
245266 Predicate <DataType > scalePredicate ,
246- String scaleDesc
267+ String scaleDesc ,
268+ Predicate <DataType > offsetPredicate ,
269+ String offsetDesc
247270 ) {
271+ if (options != null ) {
272+ Expression offset = ((MapExpression ) options ).keyFoldedMap ().get (OFFSET );
273+ if (offset != null && offset .dataType () != NULL && offsetPredicate .test ((offset ).dataType ()) == false ) {
274+ return new TypeResolution (
275+ format (null , "{} option has invalid type, expected [{}], found [{}]" , OFFSET , offsetDesc , offset .dataType ().typeName ())
276+ );
277+ }
278+ }
279+
248280 return isNotNull (origin , sourceText (), SECOND ).and (isType (origin , originPredicate , sourceText (), SECOND , originDesc ))
249281 .and (isNotNull (scale , sourceText (), THIRD ))
250282 .and (isType (scale , scalePredicate , sourceText (), THIRD , scaleDesc ));
251283 }
252284
285+ private TypeResolution validateTypeOption () {
286+ if (options == null ) {
287+ return TypeResolution .TYPE_RESOLVED ;
288+ }
289+
290+ Expression decayType = ((MapExpression ) options ).keyFoldedMap ().get (TYPE );
291+
292+ if (decayType == null || decayType .dataType () == NULL ) {
293+ return TypeResolution .TYPE_RESOLVED ;
294+ }
295+
296+ if (decayType .dataType () != KEYWORD ) {
297+ return new TypeResolution (
298+ format (
299+ null ,
300+ "{} option has invalid type, expected [{}], found [{}]" ,
301+ TYPE ,
302+ KEYWORD .typeName (),
303+ decayType .dataType ().typeName ()
304+ )
305+ );
306+ }
307+
308+ String decayTypeName = BytesRefs .toString (decayType .fold (FoldContext .small ())).toLowerCase (Locale .ROOT );
309+
310+ if (DecayFunction .BY_NAME .containsKey (decayTypeName ) == false ) {
311+ return new TypeResolution (
312+ format (
313+ null ,
314+ "{} option has invalid value, expected one of [gauss, linear, exp], found [{}]" ,
315+ TYPE ,
316+ decayType .source ().text ()
317+ )
318+ );
319+ }
320+
321+ return TypeResolution .TYPE_RESOLVED ;
322+ }
323+
253324 @ Override
254325 public Expression replaceChildren (List <Expression > newChildren ) {
255326 return new Decay (source (), newChildren .get (0 ), newChildren .get (1 ), newChildren .get (2 ), options != null ? newChildren .get (3 ) : null );
0 commit comments