Skip to content

Commit 502dbb0

Browse files
committed
Handle least restrictive for UDTs
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent bc38f9f commit 502dbb0

13 files changed

Lines changed: 263 additions & 155 deletions

File tree

core/src/main/java/org/opensearch/sql/calcite/ExtendedRexBuilder.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.opensearch.sql.ast.expression.SpanUnit;
2727
import org.opensearch.sql.calcite.type.AbstractExprRelDataType;
2828
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
29+
import org.opensearch.sql.calcite.utils.OpenSearchTypeUtil;
2930
import org.opensearch.sql.data.type.ExprCoreType;
3031
import org.opensearch.sql.exception.ExpressionEvaluationException;
3132
import org.opensearch.sql.exception.SemanticCheckException;
@@ -150,7 +151,7 @@ public RexNode makeCast(
150151
// SqlStdOperatorTable.NOT_EQUALS,
151152
// ImmutableList.of(exp, makeZeroLiteral(sourceType)));
152153
}
153-
} else if (OpenSearchTypeFactory.isUserDefinedType(type)) {
154+
} else if (OpenSearchTypeUtil.isUserDefinedType(type)) {
154155
if (RexLiteral.isNullLiteral(exp)) {
155156
return super.makeCast(pos, type, exp, matchNullability, safe, format);
156157
}
@@ -210,10 +211,9 @@ public RelDataType deriveReturnType(SqlOperator op, List<? extends RexNode> expr
210211
if (op.getKind().belongsTo(SqlKind.BINARY_ARITHMETIC) && exprs.size() == 2) {
211212
final RelDataType type1 = exprs.get(0).getType();
212213
final RelDataType type2 = exprs.get(1).getType();
213-
if (OpenSearchTypeFactory.isNumericType(type1) && OpenSearchTypeFactory.isCharacter(type2)) {
214+
if (SqlTypeUtil.isNumeric(type1) && OpenSearchTypeUtil.isCharacter(type2)) {
214215
return type1;
215-
} else if (OpenSearchTypeFactory.isCharacter(type1)
216-
&& OpenSearchTypeFactory.isNumericType(type2)) {
216+
} else if (OpenSearchTypeUtil.isCharacter(type1) && SqlTypeUtil.isNumeric(type2)) {
217217
return type2;
218218
}
219219
}

core/src/main/java/org/opensearch/sql/calcite/utils/OpenSearchTypeFactory.java

Lines changed: 47 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.calcite.utils;
7+
8+
import lombok.experimental.UtilityClass;
9+
import org.apache.calcite.rel.type.RelDataType;
10+
import org.apache.calcite.sql.type.SqlTypeName;
11+
import org.apache.calcite.sql.type.SqlTypeUtil;
12+
import org.opensearch.sql.calcite.type.AbstractExprRelDataType;
13+
import org.opensearch.sql.data.type.ExprCoreType;
14+
import org.opensearch.sql.data.type.ExprType;
15+
16+
/**
17+
* Utility methods for to derive types, containing special handling logics for user-defined-types.
18+
*
19+
* @see SqlTypeUtil utilities used during SQL validation or type derivation.
20+
*/
21+
@UtilityClass
22+
public class OpenSearchTypeUtil {
23+
/**
24+
* Whether a given RelDataType is a user-defined type (UDT)
25+
*
26+
* @param type the RelDataType to check
27+
* @return true if the type is a user-defined type, false otherwise
28+
*/
29+
public static boolean isUserDefinedType(RelDataType type) {
30+
return type instanceof AbstractExprRelDataType<?>;
31+
}
32+
33+
/**
34+
* Checks if the RelDataType represents a numeric type. Supports standard SQL numeric types
35+
* (INTEGER, BIGINT, SMALLINT, TINYINT, FLOAT, DOUBLE, DECIMAL, REAL), OpenSearch UDT numeric
36+
* types, and string types (VARCHAR, CHAR).
37+
*
38+
* @param fieldType the RelDataType to check
39+
* @return true if the type is numeric or string, false otherwise
40+
*/
41+
public static boolean isNumericOrCharacter(RelDataType fieldType) {
42+
// Check standard SQL numeric types & string types (VARCHAR, CHAR)
43+
if (SqlTypeUtil.isNumeric(fieldType) || SqlTypeUtil.isCharacter(fieldType)) {
44+
return true;
45+
}
46+
47+
// Check for OpenSearch UDT numeric types
48+
if (isUserDefinedType(fieldType)) {
49+
AbstractExprRelDataType<?> exprType = (AbstractExprRelDataType<?>) fieldType;
50+
ExprType udtType = exprType.getExprType();
51+
return ExprCoreType.numberTypes().contains(udtType);
52+
}
53+
54+
return false;
55+
}
56+
57+
/**
58+
* Checks if the RelDataType represents a time-based field (timestamp, date, or time). Supports
59+
* both standard SQL time types (including TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE, DATE, TIME,
60+
* and their timezone variants) and OpenSearch UDT time types.
61+
*
62+
* @param fieldType the RelDataType to check
63+
* @return true if the type is time-based, false otherwise
64+
*/
65+
public static boolean isDatetime(RelDataType fieldType) {
66+
// Check standard SQL time types
67+
if (SqlTypeUtil.isDatetime(fieldType)) {
68+
return true;
69+
}
70+
71+
// Check for OpenSearch UDT types (EXPR_TIMESTAMP mapped to VARCHAR)
72+
if (isUserDefinedType(fieldType)) {
73+
AbstractExprRelDataType<?> exprType = (AbstractExprRelDataType<?>) fieldType;
74+
ExprType udtType = exprType.getExprType();
75+
return udtType == ExprCoreType.TIMESTAMP
76+
|| udtType == ExprCoreType.DATE
77+
|| udtType == ExprCoreType.TIME;
78+
}
79+
80+
// Fallback check if type string contains EXPR_TIMESTAMP
81+
return fieldType.toString().contains("EXPR_TIMESTAMP");
82+
}
83+
84+
/**
85+
* Checks whether a {@link RelDataType} represents a date type.
86+
*
87+
* <p>This method returns true for both Calcite's built-in {@link SqlTypeName#DATE} type and
88+
* OpenSearch's user-defined date type {@link OpenSearchTypeFactory.ExprUDT#EXPR_DATE}.
89+
*
90+
* @param type the type to check
91+
* @return true if the type is a date type (built-in or user-defined), false otherwise
92+
*/
93+
public static boolean isDate(RelDataType type) {
94+
if (isUserDefinedType(type)) {
95+
if (((AbstractExprRelDataType<?>) type).getUdt() == OpenSearchTypeFactory.ExprUDT.EXPR_DATE) {
96+
return true;
97+
}
98+
}
99+
return SqlTypeName.DATE.equals(type.getSqlTypeName());
100+
}
101+
102+
/**
103+
* Checks whether a {@link RelDataType} represents a timestamp type.
104+
*
105+
* <p>This method returns true for both Calcite's built-in {@link SqlTypeName#TIMESTAMP} type and
106+
* OpenSearch's user-defined timestamp type {@link OpenSearchTypeFactory.ExprUDT#EXPR_TIMESTAMP}.
107+
*
108+
* @param type the type to check
109+
* @return true if the type is a timestamp type (built-in or user-defined), false otherwise
110+
*/
111+
public static boolean isTimestamp(RelDataType type) {
112+
if (isUserDefinedType(type)) {
113+
if (((AbstractExprRelDataType<?>) type).getUdt()
114+
== OpenSearchTypeFactory.ExprUDT.EXPR_TIMESTAMP) {
115+
return true;
116+
}
117+
}
118+
return SqlTypeName.TIMESTAMP.equals(type.getSqlTypeName());
119+
}
120+
121+
/**
122+
* Checks whether a {@link RelDataType} represents a time type.
123+
*
124+
* <p>This method returns true for both Calcite's built-in {@link SqlTypeName#TIME} type and
125+
* OpenSearch's user-defined time type {@link OpenSearchTypeFactory.ExprUDT#EXPR_TIME}.
126+
*
127+
* @param type the type to check
128+
* @return true if the type is a time type (built-in or user-defined), false otherwise
129+
*/
130+
public static boolean isTime(RelDataType type) {
131+
if (isUserDefinedType(type)) {
132+
if (((AbstractExprRelDataType<?>) type).getUdt() == OpenSearchTypeFactory.ExprUDT.EXPR_TIME) {
133+
return true;
134+
}
135+
}
136+
return SqlTypeName.TIME.equals(type.getSqlTypeName());
137+
}
138+
139+
/**
140+
* This method should be used in place for {@link SqlTypeUtil#isCharacter(RelDataType)} because
141+
* user-defined types also have VARCHAR as their SqlTypeName.
142+
*/
143+
public static boolean isCharacter(RelDataType type) {
144+
return !isUserDefinedType(type) && SqlTypeUtil.isCharacter(type);
145+
}
146+
147+
/**
148+
* Checks whether a {@link RelDataType} represents an IP address type.
149+
*
150+
* <p>This method returns true only for OpenSearch's user-defined IP type {@link
151+
* OpenSearchTypeFactory.ExprUDT#EXPR_IP}.
152+
*
153+
* @param type the type to check
154+
* @return true if the type is an IP address type, false otherwise
155+
*/
156+
public static boolean isIp(RelDataType type) {
157+
if (isUserDefinedType(type)) {
158+
return ((AbstractExprRelDataType<?>) type).getUdt() == OpenSearchTypeFactory.ExprUDT.EXPR_IP;
159+
}
160+
return false;
161+
}
162+
163+
/**
164+
* Checks whether a {@link RelDataType} represents a binary type.
165+
*
166+
* <p>This method returns true for both Calcite's built-in binary types (BINARY, VARBINARY) and
167+
* OpenSearch's user-defined binary type {@link OpenSearchTypeFactory.ExprUDT#EXPR_BINARY}.
168+
*
169+
* @param type the type to check
170+
* @return true if the type is a binary type (built-in or user-defined), false otherwise
171+
*/
172+
public static boolean isBinary(RelDataType type) {
173+
if (isUserDefinedType(type)) {
174+
return ((AbstractExprRelDataType<?>) type).getUdt()
175+
== OpenSearchTypeFactory.ExprUDT.EXPR_BINARY;
176+
}
177+
return SqlTypeName.BINARY_TYPES.contains(type.getSqlTypeName());
178+
}
179+
}

0 commit comments

Comments
 (0)