@@ -258,7 +258,7 @@ public static String getLegacyTypeName(RelDataType relDataType, QueryType queryT
258258
259259 /** Converts a Calcite data type to OpenSearch ExprCoreType. */
260260 public static ExprType convertRelDataTypeToExprType (RelDataType type ) {
261- if (isUserDefinedType (type )) {
261+ if (OpenSearchTypeUtil . isUserDefinedType (type )) {
262262 AbstractExprRelDataType <?> udt = (AbstractExprRelDataType <?>) type ;
263263 return udt .getExprType ();
264264 }
@@ -331,128 +331,57 @@ public Type getJavaClass(RelDataType type) {
331331
332332 @ Override
333333 public @ Nullable RelDataType leastRestrictive (List <RelDataType > types ) {
334+ // Handle UDTs separately, otherwise the least restrictive type will become VARCHAR
335+ if (types .stream ().anyMatch (OpenSearchTypeUtil ::isUserDefinedType )) {
336+ int nullCount = 0 ;
337+ int anyCount = 0 ;
338+ int nullableCount = 0 ;
339+ int dateCount = 0 ;
340+ int timeCount = 0 ;
341+ int ipCount = 0 ;
342+ int binaryCount = 0 ;
343+ for (RelDataType t : types ) {
344+ if (t .isNullable ()) {
345+ nullableCount ++;
346+ }
347+ if (t .getSqlTypeName () == SqlTypeName .NULL ) {
348+ nullCount ++;
349+ } else if (t .getSqlTypeName () == SqlTypeName .ANY ) {
350+ anyCount ++;
351+ }
352+ if (OpenSearchTypeUtil .isDate (t )) {
353+ dateCount ++;
354+ } else if (OpenSearchTypeUtil .isTime (t )) {
355+ timeCount ++;
356+ } else if (OpenSearchTypeUtil .isIp (t )) {
357+ ipCount ++;
358+ } else if (OpenSearchTypeUtil .isBinary (t )) {
359+ binaryCount ++;
360+ }
361+ }
362+ if (nullCount == 0 && anyCount == 0 ) {
363+ RelDataType udt ;
364+ if (dateCount == types .size ()) {
365+ udt = createUDT (ExprUDT .EXPR_DATE , nullableCount > 0 );
366+ } else if (timeCount == types .size ()) {
367+ udt = createUDT (ExprUDT .EXPR_TIME , nullableCount > 0 );
368+ } else if (ipCount == types .size ()) {
369+ udt = createUDT (ExprUDT .EXPR_IP , nullableCount > 0 );
370+ } else if (binaryCount == types .size ()) {
371+ udt = createUDT (ExprUDT .EXPR_BINARY , nullableCount > 0 );
372+ } else if (binaryCount == 0 && ipCount == 0 ) {
373+ udt = createUDT (ExprUDT .EXPR_TIMESTAMP , nullableCount > 0 );
374+ } else {
375+ udt = createSqlType (SqlTypeName .VARCHAR , nullableCount > 0 );
376+ }
377+ return udt ;
378+ }
379+ }
334380 RelDataType type = leastRestrictive (types , PplTypeCoercionRule .assignmentInstance ());
335381 // Convert CHAR(precision) to VARCHAR so that results won't be padded
336382 if (type != null && SqlTypeName .CHAR .equals (type .getSqlTypeName ())) {
337383 return createSqlType (SqlTypeName .VARCHAR , type .isNullable ());
338384 }
339385 return type ;
340386 }
341-
342- /**
343- * Whether a given RelDataType is a user-defined type (UDT)
344- *
345- * @param type the RelDataType to check
346- * @return true if the type is a user-defined type, false otherwise
347- */
348- public static boolean isUserDefinedType (RelDataType type ) {
349- return type instanceof AbstractExprRelDataType <?>;
350- }
351-
352- /**
353- * Checks if the RelDataType represents a numeric type. Supports standard SQL numeric types
354- * (INTEGER, BIGINT, SMALLINT, TINYINT, FLOAT, DOUBLE, DECIMAL, REAL), OpenSearch UDT numeric
355- * types, and string types (VARCHAR, CHAR).
356- *
357- * @param fieldType the RelDataType to check
358- * @return true if the type is numeric or string, false otherwise
359- */
360- public static boolean isNumericType (RelDataType fieldType ) {
361- // Check standard SQL numeric types
362- SqlTypeName sqlType = fieldType .getSqlTypeName ();
363- if (sqlType == SqlTypeName .INTEGER
364- || sqlType == SqlTypeName .BIGINT
365- || sqlType == SqlTypeName .SMALLINT
366- || sqlType == SqlTypeName .TINYINT
367- || sqlType == SqlTypeName .FLOAT
368- || sqlType == SqlTypeName .DOUBLE
369- || sqlType == SqlTypeName .DECIMAL
370- || sqlType == SqlTypeName .REAL ) {
371- return true ;
372- }
373-
374- // Check string types (VARCHAR, CHAR)
375- if (sqlType == SqlTypeName .VARCHAR || sqlType == SqlTypeName .CHAR ) {
376- return true ;
377- }
378-
379- // Check for OpenSearch UDT numeric types
380- if (isUserDefinedType (fieldType )) {
381- AbstractExprRelDataType <?> exprType = (AbstractExprRelDataType <?>) fieldType ;
382- ExprType udtType = exprType .getExprType ();
383- return ExprCoreType .numberTypes ().contains (udtType );
384- }
385-
386- return false ;
387- }
388-
389- /**
390- * Checks if the RelDataType represents a time-based field (timestamp, date, or time). Supports
391- * both standard SQL time types (including TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE, DATE, TIME,
392- * and their timezone variants) and OpenSearch UDT time types.
393- *
394- * @param fieldType the RelDataType to check
395- * @return true if the type is time-based, false otherwise
396- */
397- public static boolean isDatetime (RelDataType fieldType ) {
398- // Check standard SQL time types
399- // TODO: Optimize with SqlTypeUtil.isDatetime
400- SqlTypeName sqlType = fieldType .getSqlTypeName ();
401- if (sqlType == SqlTypeName .TIMESTAMP
402- || sqlType == SqlTypeName .TIMESTAMP_WITH_LOCAL_TIME_ZONE
403- || sqlType == SqlTypeName .DATE
404- || sqlType == SqlTypeName .TIME
405- || sqlType == SqlTypeName .TIME_WITH_LOCAL_TIME_ZONE ) {
406- return true ;
407- }
408-
409- // Check for OpenSearch UDT types (EXPR_TIMESTAMP mapped to VARCHAR)
410- if (isUserDefinedType (fieldType )) {
411- AbstractExprRelDataType <?> exprType = (AbstractExprRelDataType <?>) fieldType ;
412- ExprType udtType = exprType .getExprType ();
413- return udtType == ExprCoreType .TIMESTAMP
414- || udtType == ExprCoreType .DATE
415- || udtType == ExprCoreType .TIME ;
416- }
417-
418- // Fallback check if type string contains EXPR_TIMESTAMP
419- return fieldType .toString ().contains ("EXPR_TIMESTAMP" );
420- }
421-
422- /**
423- * Checks whether a {@link RelDataType} represents a time type.
424- *
425- * <p>This method returns true for both Calcite's built-in {@link SqlTypeName#TIME} type and
426- * OpenSearch's user-defined time type {@link ExprUDT#EXPR_TIME}.
427- *
428- * @param type the type to check
429- * @return true if the type is a time type (built-in or user-defined), false otherwise
430- */
431- public static boolean isTime (RelDataType type ) {
432- if (isUserDefinedType (type )) {
433- if (((AbstractExprRelDataType <?>) type ).getUdt () == ExprUDT .EXPR_TIME ) {
434- return true ;
435- }
436- }
437- SqlTypeName typeName = type .getSqlTypeName ();
438- if (typeName == null ) {
439- return false ;
440- }
441- return type .getSqlTypeName () == SqlTypeName .TIME ;
442- }
443-
444- /**
445- * This method should be used in place for {@link SqlTypeUtil#isCharacter(RelDataType)} because
446- * user-defined types also have VARCHAR as their SqlTypeName.
447- */
448- public static boolean isCharacter (RelDataType type ) {
449- return !isUserDefinedType (type ) && SqlTypeUtil .isCharacter (type );
450- }
451-
452- public static boolean isIp (RelDataType type ) {
453- if (isUserDefinedType (type )) {
454- return ((AbstractExprRelDataType <?>) type ).getUdt () == ExprUDT .EXPR_IP ;
455- }
456- return false ;
457- }
458387}
0 commit comments