55
66package org .opensearch .sql .calcite .validate ;
77
8+ import static java .util .Objects .requireNonNull ;
89import static org .opensearch .sql .calcite .validate .ValidationUtils .createUDTWithAttributes ;
910
1011import java .util .List ;
1112import java .util .Map ;
1213import java .util .Set ;
1314import java .util .stream .IntStream ;
15+ import org .apache .calcite .adapter .java .JavaTypeFactory ;
1416import org .apache .calcite .rel .type .RelDataType ;
1517import org .apache .calcite .rel .type .RelDataTypeFactory ;
18+ import org .apache .calcite .rel .type .RelDataTypeFactoryImpl ;
19+ import org .apache .calcite .sql .SqlCall ;
1620import org .apache .calcite .sql .SqlCallBinding ;
21+ import org .apache .calcite .sql .SqlDynamicParam ;
1722import org .apache .calcite .sql .SqlNode ;
23+ import org .apache .calcite .sql .fun .SqlStdOperatorTable ;
24+ import org .apache .calcite .sql .type .SqlTypeCoercionRule ;
1825import org .apache .calcite .sql .type .SqlTypeFamily ;
1926import org .apache .calcite .sql .type .SqlTypeMappingRule ;
2027import org .apache .calcite .sql .type .SqlTypeName ;
2431import org .apache .calcite .sql .validate .implicit .TypeCoercionImpl ;
2532import org .checkerframework .checker .nullness .qual .Nullable ;
2633import org .opensearch .sql .calcite .utils .OpenSearchTypeFactory ;
34+ import org .opensearch .sql .data .type .ExprCoreType ;
35+ import org .opensearch .sql .data .type .ExprType ;
36+ import org .opensearch .sql .expression .function .PPLBuiltinOperators ;
2737
2838/**
2939 * Custom type coercion implementation for PPL that extends Calcite's default type coercion with
@@ -86,13 +96,16 @@ private boolean isBlacklistedCoercion(RelDataType operandType, SqlTypeFamily exp
8696 @ Override
8797 public @ Nullable RelDataType implicitCast (RelDataType in , SqlTypeFamily expected ) {
8898 RelDataType casted = super .implicitCast (in , expected );
99+ if (casted == null ) {
100+ // String -> DATETIME is converted to String -> TIMESTAMP
101+ if (OpenSearchTypeFactory .isCharacter (in ) && expected == SqlTypeFamily .DATETIME ) {
102+ return createUDTWithAttributes (factory , in , OpenSearchTypeFactory .ExprUDT .EXPR_TIMESTAMP );
103+ }
104+ return null ;
105+ }
89106 return switch (casted .getSqlTypeName ()) {
90- case SqlTypeName .DATE ->
91- createUDTWithAttributes (factory , in , OpenSearchTypeFactory .ExprUDT .EXPR_DATE );
92- case SqlTypeName .TIME ->
93- createUDTWithAttributes (factory , in , OpenSearchTypeFactory .ExprUDT .EXPR_TIME );
94- case SqlTypeName .TIMESTAMP ->
95- createUDTWithAttributes (factory , in , OpenSearchTypeFactory .ExprUDT .EXPR_TIMESTAMP );
107+ case SqlTypeName .DATE , SqlTypeName .TIME , SqlTypeName .TIMESTAMP , SqlTypeName .BINARY ->
108+ createUDTWithAttributes (factory , in , casted .getSqlTypeName ());
96109 default -> casted ;
97110 };
98111 }
@@ -106,9 +119,90 @@ protected boolean needToCast(
106119 SqlValidatorScope scope , SqlNode node , RelDataType toType , SqlTypeMappingRule mappingRule ) {
107120 boolean need = super .needToCast (scope , node , toType , mappingRule );
108121 RelDataType fromType = validator .deriveType (scope , node );
109- if (OpenSearchTypeFactory .isUserDefinedType (toType ) && SqlTypeUtil .isCharacter (fromType )) {
122+ if (OpenSearchTypeFactory .isUserDefinedType (toType )
123+ && OpenSearchTypeFactory .isCharacter (fromType )) {
110124 need = true ;
111125 }
112126 return need ;
113127 }
128+
129+ @ Override
130+ protected boolean dateTimeStringEquality (
131+ SqlCallBinding binding , RelDataType left , RelDataType right ) {
132+ if (OpenSearchTypeFactory .isCharacter (left ) && OpenSearchTypeFactory .isDatetime (right )) {
133+ // Use user-defined types in place of inbuilt datetime types
134+ RelDataType r =
135+ OpenSearchTypeFactory .isUserDefinedType (right )
136+ ? right
137+ : ValidationUtils .createUDTWithAttributes (factory , right , right .getSqlTypeName ());
138+ return coerceOperandType (binding .getScope (), binding .getCall (), 0 , r );
139+ }
140+ if (OpenSearchTypeFactory .isCharacter (right ) && OpenSearchTypeFactory .isDatetime (left )) {
141+ RelDataType l =
142+ OpenSearchTypeFactory .isUserDefinedType (left )
143+ ? left
144+ : ValidationUtils .createUDTWithAttributes (factory , left , left .getSqlTypeName ());
145+ return coerceOperandType (binding .getScope (), binding .getCall (), 1 , l );
146+ }
147+ return false ;
148+ }
149+
150+ @ Override
151+ protected @ Nullable RelDataType commonTypeForComparison (List <RelDataType > dataTypes ) {
152+ return super .commonTypeForComparison (dataTypes );
153+ }
154+
155+ /**
156+ * Cast operand at index {@code index} to target type. we do this base on the fact that validate
157+ * happens before type coercion.
158+ */
159+ protected boolean coerceOperandType (
160+ @ Nullable SqlValidatorScope scope , SqlCall call , int index , RelDataType targetType ) {
161+ // Transform the JavaType to SQL type because the SqlDataTypeSpec
162+ // does not support deriving JavaType yet.
163+ if (RelDataTypeFactoryImpl .isJavaType (targetType )) {
164+ targetType = ((JavaTypeFactory ) factory ).toSql (targetType );
165+ }
166+
167+ SqlNode operand = call .getOperandList ().get (index );
168+ if (operand instanceof SqlDynamicParam ) {
169+ // Do not support implicit type coercion for dynamic param.
170+ return false ;
171+ }
172+ requireNonNull (scope , "scope" );
173+ RelDataType operandType = validator .deriveType (scope , operand );
174+ if (coerceStringToArray (call , operand , index , operandType , targetType )) {
175+ return true ;
176+ }
177+
178+ // Check it early.
179+ if (!needToCast (scope , operand , targetType , SqlTypeCoercionRule .lenientInstance ())) {
180+ return false ;
181+ }
182+ // Fix up nullable attr.
183+ RelDataType targetType1 = ValidationUtils .syncAttributes (factory , operandType , targetType );
184+ SqlNode desired = castTo (operand , targetType1 );
185+ call .setOperand (index , desired );
186+ updateInferredType (desired , targetType1 );
187+ return true ;
188+ }
189+
190+ private static SqlNode castTo (SqlNode node , RelDataType type ) {
191+ if (OpenSearchTypeFactory .isDatetime (type )) {
192+ ExprType exprType = OpenSearchTypeFactory .convertRelDataTypeToExprType (type );
193+ return switch (exprType ) {
194+ case ExprCoreType .DATE ->
195+ PPLBuiltinOperators .DATE .createCall (node .getParserPosition (), node );
196+ case ExprCoreType .TIMESTAMP ->
197+ PPLBuiltinOperators .TIMESTAMP .createCall (node .getParserPosition (), node );
198+ case ExprCoreType .TIME ->
199+ PPLBuiltinOperators .TIME .createCall (node .getParserPosition (), node );
200+ default -> throw new UnsupportedOperationException ("Unsupported type: " + exprType );
201+ };
202+ }
203+ return SqlStdOperatorTable .CAST .createCall (
204+ node .getParserPosition (),
205+ node ,
206+ SqlTypeUtil .convertTypeToSpec (type ).withNullable (type .isNullable ()));
207+ }
114208}
0 commit comments