77package org .elasticsearch .xpack .esql .expression .function ;
88
99import org .elasticsearch .common .lucene .BytesRefs ;
10+ import org .elasticsearch .core .Nullable ;
1011import org .elasticsearch .xpack .esql .EsqlIllegalArgumentException ;
12+ import org .elasticsearch .xpack .esql .common .Failure ;
1113import org .elasticsearch .xpack .esql .common .Failures ;
1214import org .elasticsearch .xpack .esql .core .expression .Expression ;
1315import org .elasticsearch .xpack .esql .core .expression .Literal ;
1618import static org .elasticsearch .xpack .esql .common .Failure .fail ;
1719
1820public class FunctionUtils {
21+ /**
22+ * A utility class to validate the type resolution of expressions before and after folding.
23+ * If null is passed for Failures to the constructor, it means we are only type resolution.
24+ * This is usually called when doing pre-folding validation.
25+ * If a {@link Failures} instance is passed, it means we are doing post-folding validation as well.
26+ * This is usually called after folding is done, from during {@link org.elasticsearch.xpack.esql.capabilities.PostOptimizationVerificationAware} verification
27+ */
28+ public static class TypeResolutionValidator {
29+
30+ Expression .TypeResolution typeResolution = Expression .TypeResolution .TYPE_RESOLVED ;
31+ @ Nullable
32+ private final Failures postFoldingFailures ; // null means we are doing pre-folding validation only
33+ private final Expression field ;
34+
35+ public TypeResolutionValidator (Expression field , Failures failures ) {
36+ this .field = field ;
37+ this .postFoldingFailures = failures ;
38+ }
39+
40+ public void reportPostFoldingFailure (Failure failure ) {
41+ if (postFoldingFailures != null ) {
42+ postFoldingFailures .add (failure );
43+ }
44+ }
45+
46+ public void reportPreFoldingFailure (Expression .TypeResolution message ) {
47+ typeResolution = message ;
48+ if (postFoldingFailures != null ) {
49+ postFoldingFailures .add (fail (field , message .message ()));
50+ }
51+ }
52+
53+ public Expression .TypeResolution getResolvedType () {
54+ return typeResolution ;
55+ }
56+ }
57+
1958 public static Integer limitValue (Expression limitField , String sourceText ) {
2059 if (limitField instanceof Literal literal ) {
2160 Object value = literal .value ();
@@ -28,71 +67,63 @@ public static Integer limitValue(Expression limitField, String sourceText) {
2867
2968 /**
3069 * We check that the limit is not null and that if it is a literal, it is a positive integer
31- * We will do a more thorough check in the postOptimizationVerification once folding is done.
70+ * During postOptimizationVerification folding is already done, so we also verify that it is definitively a literal
3271 */
33- public static Expression .TypeResolution resolveTypeLimit (Expression limitField , String sourceText ) {
72+ public static Expression .TypeResolution resolveTypeLimit (Expression limitField , String sourceText , Failures failures ) {
73+ TypeResolutionValidator validator = new TypeResolutionValidator (limitField , failures );
3474 if (limitField == null ) {
35- return new Expression . TypeResolution (
36- format (null , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField )
75+ validator . reportPreFoldingFailure (
76+ new Expression . TypeResolution ( format (null , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField ) )
3777 );
38- }
39- if (limitField instanceof Literal literal ) {
78+ } else if (limitField instanceof Literal literal ) {
4079 if (literal .value () == null ) {
41- return new Expression .TypeResolution (
42- format (null , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField )
80+ validator .reportPreFoldingFailure (
81+ new Expression .TypeResolution (
82+ format (null , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField )
83+ )
4384 );
44- }
45- int value = (Integer ) literal .value ();
46- if (value <= 0 ) {
47- return new Expression .TypeResolution (format (null , "Limit must be greater than 0 in [{}], found [{}]" , sourceText , value ));
48- }
49- }
50- return Expression .TypeResolution .TYPE_RESOLVED ;
51- }
52-
53- public static void postOptimizationVerificationLimit (Failures failures , Expression limitField , String sourceText ) {
54- if (limitField == null ) {
55- failures .add (fail (limitField , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField ));
56- }
57- if (limitField instanceof Literal literal ) {
58- int value = (Integer ) literal .value ();
59- if (value <= 0 ) {
60- failures .add (fail (limitField , "Limit must be greater than 0 in [{}], found [{}]" , sourceText , value ));
85+ } else {
86+ int value = (Integer ) literal .value ();
87+ if (value <= 0 ) {
88+ validator .reportPreFoldingFailure (
89+ new Expression .TypeResolution (format (null , "Limit must be greater than 0 in [{}], found [{}]" , sourceText , value ))
90+ );
91+ }
6192 }
6293 } else {
6394 // it is expected that the expression is a literal after folding
6495 // we fail if it is not a literal
65- failures .add (fail (limitField , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField ));
96+ validator .reportPostFoldingFailure (
97+ fail (limitField , "Limit must be a constant integer in [{}], found [{}]" , sourceText , limitField )
98+ );
6699 }
100+ return validator .getResolvedType ();
67101 }
68102
69- public static Expression .TypeResolution resolveTypeQuery (Expression queryField , String sourceText ) {
103+ /**
104+ * We check that the query is not null and that if it is a literal, it is a string
105+ * During postOptimizationVerification folding is already done, so we also verify that it is definitively a literal
106+ */
107+ public static Expression .TypeResolution resolveTypeQuery (Expression queryField , String sourceText , Failures failures ) {
108+ TypeResolutionValidator validator = new TypeResolutionValidator (queryField , failures );
70109 if (queryField == null ) {
71- return new Expression .TypeResolution (format (null , "Query must be a valid string in [{}], found [{}]" , sourceText , queryField ));
72- }
73- if (queryField instanceof Literal literal ) {
110+ validator .reportPreFoldingFailure (
111+ new Expression .TypeResolution (format (null , "Query must be a valid string in [{}], found [{}]" , sourceText , queryField ))
112+ );
113+ } else if (queryField instanceof Literal literal ) {
74114 if (literal .value () == null ) {
75- return new Expression . TypeResolution (
76- format (null , "Query value cannot be null in [{}], but got [{}]" , sourceText , queryField )
115+ validator . reportPreFoldingFailure (
116+ new Expression . TypeResolution ( format (null , "Query value cannot be null in [{}], but got [{}]" , sourceText , queryField ) )
77117 );
78118 }
79- }
80- return Expression .TypeResolution .TYPE_RESOLVED ;
81- }
82-
83- public static void postOptimizationVerificationQuery (Failures failures , Expression queryField , String sourceText ) {
84- if (queryField == null ) {
85- failures .add (fail (queryField , "Query must be a valid string in [{}], found [{}]" , sourceText , queryField ));
86- }
87- if (queryField instanceof Literal literal ) {
88- if (literal .value () == null ) {
89- failures .add (fail (queryField , "Invalid query value in [{}], found [{}]" , sourceText , literal .value ()));
90- }
91119 } else {
92120 // it is expected that the expression is a literal after folding
93121 // we fail if it is not a literal
94- failures .add (fail (queryField , "Query must be a valid string in [{}], found [{}]" , sourceText , queryField ));
122+ validator .reportPostFoldingFailure (
123+ fail (queryField , "Query must be a valid string in [{}], found [{}]" , sourceText , queryField )
124+ );
95125 }
126+ return validator .getResolvedType ();
96127 }
97128
98129 public static Object queryAsObject (Expression queryField , String sourceText ) {
0 commit comments