Skip to content

Commit ec7ce78

Browse files
committed
Simplify datetime-string compare logic with implict coercion
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent 42fd079 commit ec7ce78

4 files changed

Lines changed: 58 additions & 107 deletions

File tree

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
import static org.opensearch.sql.ast.expression.SpanUnit.NONE;
1212
import static org.opensearch.sql.ast.expression.SpanUnit.UNKNOWN;
1313
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.TYPE_FACTORY;
14-
import static org.opensearch.sql.utils.DateTimeUtils.findCastType;
15-
import static org.opensearch.sql.utils.DateTimeUtils.transferCompareForDateRelated;
1614

1715
import java.math.BigDecimal;
1816
import java.util.ArrayList;
@@ -215,11 +213,8 @@ public RexNode visitIn(In node, CalcitePlanContext context) {
215213

216214
@Override
217215
public RexNode visitCompare(Compare node, CalcitePlanContext context) {
218-
RexNode leftCandidate = analyze(node.getLeft(), context);
219-
RexNode rightCandidate = analyze(node.getRight(), context);
220-
SqlTypeName castTarget = findCastType(leftCandidate, rightCandidate);
221-
final RexNode left = transferCompareForDateRelated(leftCandidate, context, castTarget);
222-
final RexNode right = transferCompareForDateRelated(rightCandidate, context, castTarget);
216+
RexNode left = analyze(node.getLeft(), context);
217+
RexNode right = analyze(node.getRight(), context);
223218
return PPLFuncImpTable.INSTANCE.resolve(context.rexBuilder, node.getOperator(), left, right);
224219
}
225220

core/src/main/java/org/opensearch/sql/expression/function/CoercionUtils.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.calcite.rex.RexBuilder;
1212
import org.apache.calcite.rex.RexNode;
1313
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
14+
import org.opensearch.sql.data.type.ExprCoreType;
1415
import org.opensearch.sql.data.type.ExprType;
1516
import org.opensearch.sql.data.type.WideningTypeRule;
1617
import org.opensearch.sql.exception.ExpressionEvaluationException;
@@ -121,12 +122,22 @@ public class CoercionUtils {
121122
for (int i = 1; i < arguments.size(); i++) {
122123
var type = OpenSearchTypeFactory.convertRelDataTypeToExprType(arguments.get(i).getType());
123124
try {
124-
widestType = WideningTypeRule.max(widestType, type);
125+
if (areDateAndTime(widestType, type)) {
126+
// If one is date and the other is time, we consider timestamp as the widest type
127+
widestType = ExprCoreType.TIMESTAMP;
128+
} else {
129+
widestType = WideningTypeRule.max(widestType, type);
130+
}
125131
} catch (ExpressionEvaluationException e) {
126132
// the two types are not compatible, return null
127133
return null;
128134
}
129135
}
130136
return widestType;
131137
}
138+
139+
private static boolean areDateAndTime(ExprType type1, ExprType type2) {
140+
return (type1 == ExprCoreType.DATE && type2 == ExprCoreType.TIME)
141+
|| (type1 == ExprCoreType.TIME && type2 == ExprCoreType.DATE);
142+
}
132143
}

core/src/main/java/org/opensearch/sql/expression/function/PPLTypeChecker.java

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.stream.IntStream;
1616
import lombok.RequiredArgsConstructor;
1717
import org.apache.calcite.rel.type.RelDataType;
18+
import org.apache.calcite.rel.type.RelDataTypeField;
1819
import org.apache.calcite.sql.SqlIntervalQualifier;
1920
import org.apache.calcite.sql.parser.SqlParserPos;
2021
import org.apache.calcite.sql.type.CompositeOperandTypeChecker;
@@ -25,6 +26,7 @@
2526
import org.apache.calcite.sql.type.SqlTypeFamily;
2627
import org.apache.calcite.sql.type.SqlTypeName;
2728
import org.apache.calcite.sql.type.SqlTypeUtil;
29+
import org.apache.calcite.util.Pair;
2830
import org.opensearch.sql.calcite.type.ExprIPType;
2931
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
3032
import org.opensearch.sql.calcite.utils.UserDefinedFunctionUtils;
@@ -257,28 +259,56 @@ public boolean checkOperandTypes(List<RelDataType> types) {
257259
for (int i = 0; i < types.size() - 1; i++) {
258260
// TODO: Binary, Array UDT?
259261
// DATETIME, NUMERIC, BOOLEAN will be regarded as comparable
260-
// with strings in SqlTypeUtil.isComparable
262+
// with strings in isComparable
261263
RelDataType type_l = types.get(i);
262264
RelDataType type_r = types.get(i + 1);
263-
if (!SqlTypeUtil.isComparable(type_l, type_r)) {
264-
return false;
265-
}
266-
// Disallow coercing between strings and numeric, boolean
267-
if ((type_l.getFamily() == SqlTypeFamily.CHARACTER
268-
&& cannotConvertStringInCompare((SqlTypeFamily) type_r.getFamily()))
269-
|| (type_r.getFamily() == SqlTypeFamily.CHARACTER
270-
&& cannotConvertStringInCompare((SqlTypeFamily) type_l.getFamily()))) {
265+
if (!isComparable(type_l, type_r)) {
271266
return false;
272267
}
273268
}
274269
return true;
275270
}
276271

277-
private static boolean cannotConvertStringInCompare(SqlTypeFamily typeFamily) {
278-
return switch (typeFamily) {
279-
case BOOLEAN, INTEGER, NUMERIC, EXACT_NUMERIC, APPROXIMATE_NUMERIC -> true;
280-
default -> false;
281-
};
272+
/**
273+
* Modified from {@link SqlTypeUtil#isComparable(RelDataType, RelDataType)} to
274+
*
275+
* @param type1 first type
276+
* @param type2 second type
277+
* @return true if the two types are comparable, false otherwise
278+
*/
279+
private static boolean isComparable(RelDataType type1, RelDataType type2) {
280+
if (type1.isStruct() != type2.isStruct()) {
281+
return false;
282+
}
283+
284+
if (type1.isStruct()) {
285+
int n = type1.getFieldCount();
286+
if (n != type2.getFieldCount()) {
287+
return false;
288+
}
289+
for (Pair<RelDataTypeField, RelDataTypeField> pair :
290+
Pair.zip(type1.getFieldList(), type2.getFieldList())) {
291+
if (!isComparable(pair.left.getType(), pair.right.getType())) {
292+
return false;
293+
}
294+
}
295+
return true;
296+
}
297+
298+
// Numeric types are comparable without the need to cast
299+
if (SqlTypeUtil.isNumeric(type1) && SqlTypeUtil.isNumeric(type2)) {
300+
return true;
301+
}
302+
303+
ExprType exprType1 = OpenSearchTypeFactory.convertRelDataTypeToExprType(type1);
304+
ExprType exprType2 = OpenSearchTypeFactory.convertRelDataTypeToExprType(type2);
305+
306+
if (!exprType1.shouldCast(exprType2)) {
307+
return true;
308+
}
309+
310+
// If one of the arguments is of type 'ANY', return true.
311+
return type1.getFamily() == SqlTypeFamily.ANY || type2.getFamily() == SqlTypeFamily.ANY;
282312
}
283313

284314
@Override

core/src/main/java/org/opensearch/sql/utils/DateTimeUtils.java

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55

66
package org.opensearch.sql.utils;
77

8-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_DATE;
9-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_TIME;
10-
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.ExprUDT.EXPR_TIMESTAMP;
11-
128
import java.time.Instant;
139
import java.time.LocalDate;
1410
import java.time.LocalDateTime;
@@ -19,18 +15,11 @@
1915
import java.time.format.DateTimeParseException;
2016
import java.time.temporal.ChronoUnit;
2117
import java.util.Locale;
22-
import java.util.Objects;
2318
import java.util.regex.Pattern;
2419
import lombok.experimental.UtilityClass;
25-
import org.apache.calcite.rex.RexNode;
26-
import org.apache.calcite.sql.type.SqlTypeName;
27-
import org.opensearch.sql.calcite.CalcitePlanContext;
28-
import org.opensearch.sql.calcite.type.ExprSqlType;
29-
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
3020
import org.opensearch.sql.data.model.ExprTimeValue;
3121
import org.opensearch.sql.data.model.ExprValue;
3222
import org.opensearch.sql.expression.function.FunctionProperties;
33-
import org.opensearch.sql.expression.function.PPLBuiltinOperators;
3423

3524
@UtilityClass
3625
public class DateTimeUtils {
@@ -305,78 +294,4 @@ private static String normalizeUnit(String rawUnit) {
305294
}
306295
}
307296
}
308-
309-
/**
310-
* The function add cast for date-related target node
311-
*
312-
* @param candidate The candidate node
313-
* @param context calcite context
314-
* @param castTarget the target cast type
315-
* @return the rexnode after casting
316-
*/
317-
public static RexNode transferCompareForDateRelated(
318-
RexNode candidate, CalcitePlanContext context, SqlTypeName castTarget) {
319-
if (!(Objects.isNull(castTarget))) {
320-
switch (castTarget) {
321-
case DATE:
322-
if (!(candidate.getType() instanceof ExprSqlType
323-
&& ((ExprSqlType) candidate.getType()).getUdt() == EXPR_DATE)) {
324-
return context.rexBuilder.makeCall(PPLBuiltinOperators.DATE, candidate);
325-
}
326-
break;
327-
case TIME:
328-
if (!(candidate.getType() instanceof ExprSqlType
329-
&& ((ExprSqlType) candidate.getType()).getUdt() == EXPR_TIME)) {
330-
return context.rexBuilder.makeCall(PPLBuiltinOperators.TIME, candidate);
331-
}
332-
break;
333-
case TIMESTAMP:
334-
if (!(candidate.getType() instanceof ExprSqlType
335-
&& ((ExprSqlType) candidate.getType()).getUdt() == EXPR_TIMESTAMP)) {
336-
return context.rexBuilder.makeCall(PPLBuiltinOperators.TIMESTAMP, candidate);
337-
}
338-
break;
339-
default:
340-
return candidate;
341-
}
342-
}
343-
return candidate;
344-
}
345-
346-
/**
347-
* The function find the target cast type according to the left and right node. When the two node
348-
* are both related to date with different type, cast to timestamp
349-
*
350-
* @param left
351-
* @param right
352-
* @return
353-
*/
354-
public static SqlTypeName findCastType(RexNode left, RexNode right) {
355-
SqlTypeName leftType = returnCorrespondingSqlType(left);
356-
SqlTypeName rightType = returnCorrespondingSqlType(right);
357-
if (leftType != null && rightType != null && rightType != leftType) {
358-
return SqlTypeName.TIMESTAMP;
359-
}
360-
return leftType == null ? rightType : leftType;
361-
}
362-
363-
/**
364-
* Find corresponding cast type according to the node's type. If they're not related to the date,
365-
* return null
366-
*
367-
* @param node the candidate node
368-
* @return the sql type name
369-
*/
370-
public static SqlTypeName returnCorrespondingSqlType(RexNode node) {
371-
if (node.getType() instanceof ExprSqlType) {
372-
OpenSearchTypeFactory.ExprUDT udt = ((ExprSqlType) node.getType()).getUdt();
373-
return switch (udt) {
374-
case EXPR_DATE -> SqlTypeName.DATE;
375-
case EXPR_TIME -> SqlTypeName.TIME;
376-
case EXPR_TIMESTAMP -> SqlTypeName.TIMESTAMP;
377-
default -> null;
378-
};
379-
}
380-
return null;
381-
}
382297
}

0 commit comments

Comments
 (0)