Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support TYPEOF function with Calcite #3446

Merged
merged 8 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@
import org.apache.calcite.tools.RelBuilder;
import org.opensearch.sql.ast.expression.UnresolvedExpression;
import org.opensearch.sql.calcite.utils.CalciteToolsHelper;
import org.opensearch.sql.executor.QueryType;

public class CalcitePlanContext {

public FrameworkConfig config;
public final Connection connection;
public final RelBuilder relBuilder;
public final ExtendedRexBuilder rexBuilder;
public final QueryType queryType;

@Getter @Setter private boolean isResolvingJoinCondition = false;
@Getter @Setter private boolean isResolvingExistsSubquery = false;
private final Stack<RexCorrelVariable> correlVar = new Stack<>();

private CalcitePlanContext(FrameworkConfig config) {
private CalcitePlanContext(FrameworkConfig config, QueryType queryType) {
this.config = config;
this.queryType = queryType;
this.connection = CalciteToolsHelper.connect(config, TYPE_FACTORY);
this.relBuilder = CalciteToolsHelper.create(config, TYPE_FACTORY, connection);
this.rexBuilder = new ExtendedRexBuilder(relBuilder.getRexBuilder());
Expand Down Expand Up @@ -67,7 +70,7 @@ public Optional<RexCorrelVariable> peekCorrelVar() {
}
}

public static CalcitePlanContext create(FrameworkConfig config) {
return new CalcitePlanContext(config);
public static CalcitePlanContext create(FrameworkConfig config, QueryType queryType) {
return new CalcitePlanContext(config, queryType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.opensearch.sql.ast.expression.SpanUnit.NONE;
import static org.opensearch.sql.ast.expression.SpanUnit.UNKNOWN;
import static org.opensearch.sql.calcite.utils.BuiltinFunctionUtils.translateArgument;
import static org.opensearch.sql.calcite.utils.PlanUtils.intervalUnitToSpanUnit;

import java.math.BigDecimal;
import java.util.List;
Expand All @@ -19,6 +20,7 @@
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.SqlTypeName;
Expand All @@ -35,6 +37,7 @@
import org.opensearch.sql.ast.expression.EqualTo;
import org.opensearch.sql.ast.expression.Function;
import org.opensearch.sql.ast.expression.In;
import org.opensearch.sql.ast.expression.Interval;
import org.opensearch.sql.ast.expression.Let;
import org.opensearch.sql.ast.expression.Literal;
import org.opensearch.sql.ast.expression.Not;
Expand Down Expand Up @@ -116,6 +119,15 @@ public RexNode visitLiteral(Literal node, CalcitePlanContext context) {
}
}

@Override
public RexNode visitInterval(Interval node, CalcitePlanContext context) {
RexNode value = analyze(node.getValue(), context);
SqlIntervalQualifier intervalQualifier =
context.rexBuilder.createIntervalUntil(intervalUnitToSpanUnit(node.getUnit()));
return context.rexBuilder.makeIntervalLiteral(
new BigDecimal(value.toString()), intervalQualifier);
}

@Override
public RexNode visitAnd(And node, CalcitePlanContext context) {
final RelDataType booleanType =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.calcite.udf.systemUDF;

import org.opensearch.sql.calcite.udf.UserDefinedFunction;

public class TypeOfFunction implements UserDefinedFunction {

@Override
public Object eval(Object... args) {
return args[0];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious here returns the argument value instead of its type?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.opensearch.sql.calcite.utils;

import static java.lang.Math.E;
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.getLegacyTypeName;
import static org.opensearch.sql.calcite.utils.UserDefinedFunctionUtils.*;

import java.math.BigDecimal;
Expand All @@ -30,6 +31,7 @@
import org.opensearch.sql.calcite.udf.mathUDF.EulerFunction;
import org.opensearch.sql.calcite.udf.mathUDF.ModFunction;
import org.opensearch.sql.calcite.udf.mathUDF.SqrtFunction;
import org.opensearch.sql.calcite.udf.systemUDF.TypeOfFunction;
import org.opensearch.sql.calcite.udf.textUDF.LocateFunction;
import org.opensearch.sql.calcite.udf.textUDF.ReplaceFunction;

Expand Down Expand Up @@ -197,6 +199,10 @@ static SqlOperator translate(String op) {
return SqlStdOperatorTable.IS_NOT_NULL;
case "IS NULL":
return SqlStdOperatorTable.IS_NULL;
case "TYPEOF":
// TODO optimize this function to ImplementableFunction
return TransferUserDefinedFunction(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe leave a TODO here to optimize this function to ImplementableFunction

Copy link
Collaborator

@qianheng-aws qianheng-aws Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about return RexLiteral directly instead RexCall for TypeOf function? It also requires simple refactoring of this utils.

 context.rexBuilder.makeLiteral(
      OpenSearchTypeFactory.convertRelDataTypeToExprType(
               argList.getFirst().getType()).legacyTypeName());

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

TypeOfFunction.class, "typeof", ReturnTypes.VARCHAR_2000_NULLABLE);
// TODO Add more, ref RexImpTable
default:
throw new IllegalArgumentException("Unsupported operator: " + op);
Expand Down Expand Up @@ -268,6 +274,11 @@ static List<RexNode> translateArgument(
throw new IllegalArgumentException("Log cannot accept argument list: " + argList);
}
return LogArgs;
case "TYPEOF":
return List.of(
context.rexBuilder.makeLiteral(
getLegacyTypeName(
argList.getFirst().getType().getSqlTypeName(), context.queryType)));
default:
return argList;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
import static org.opensearch.sql.data.type.ExprCoreType.TIME;
import static org.opensearch.sql.data.type.ExprCoreType.TIMESTAMP;
import static org.opensearch.sql.data.type.ExprCoreType.UNDEFINED;
import static org.opensearch.sql.data.type.ExprCoreType.UNKNOWN;
import static org.opensearch.sql.executor.QueryType.PPL;
import static org.opensearch.sql.lang.PPLLangSpec.PPL_SPEC;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
import org.apache.calcite.rel.type.RelDataType;
Expand All @@ -35,6 +39,7 @@
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.data.type.ExprType;
import org.opensearch.sql.executor.OpenSearchTypeSystem;
import org.opensearch.sql.executor.QueryType;
import org.opensearch.sql.storage.Table;

/** This class is used to create RelDataType and map RelDataType to Java data type */
Expand Down Expand Up @@ -134,9 +139,13 @@ public static RelDataType convertExprTypeToRelDataType(ExprType fieldType, boole
}
}

/** Converts a Calcite data type to OpenSearch ExprCoreType. */
public static ExprType convertRelDataTypeToExprType(RelDataType type) {
switch (type.getSqlTypeName()) {
/**
* Usually, {@link this#createSqlType(SqlTypeName, boolean)} is used to create RelDataType, then
* convert it to ExprType. This is a util to convert when you don't have typeFactory. So they are
* all ExprCoreType.
*/
public static ExprType convertSqlTypeNameToExprType(SqlTypeName sqlTypeName) {
switch (sqlTypeName) {
case TINYINT:
return BYTE;
case SMALLINT:
Expand All @@ -145,6 +154,7 @@ public static ExprType convertRelDataTypeToExprType(RelDataType type) {
return INTEGER;
case BIGINT:
return LONG;
case FLOAT:
case REAL:
return FLOAT;
case DOUBLE:
Expand All @@ -157,16 +167,25 @@ public static ExprType convertRelDataTypeToExprType(RelDataType type) {
case DATE:
return DATE;
case TIME:
case TIME_TZ:
case TIME_WITH_LOCAL_TIME_ZONE:
return TIME;
case TIMESTAMP:
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
case TIMESTAMP_TZ:
return TIMESTAMP;
case GEOMETRY:
return IP;
case INTERVAL_YEAR:
case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH:
case INTERVAL_DAY:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY_MINUTE:
case INTERVAL_DAY_SECOND:
case INTERVAL_HOUR:
case INTERVAL_HOUR_MINUTE:
case INTERVAL_HOUR_SECOND:
case INTERVAL_MINUTE:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND:
return INTERVAL;
case ARRAY:
Expand All @@ -176,9 +195,33 @@ public static ExprType convertRelDataTypeToExprType(RelDataType type) {
case NULL:
return UNDEFINED;
default:
throw new IllegalArgumentException(
"Unsupported conversion for Relational Data type: " + type.getSqlTypeName());
return UNKNOWN;
}
}

/** Get legacy name for a SqlTypeName. */
public static String getLegacyTypeName(SqlTypeName sqlTypeName, QueryType queryType) {
switch (sqlTypeName) {
case BINARY:
case VARBINARY:
return "BINARY";
case GEOMETRY:
return "GEO_POINT";
default:
ExprType type = convertSqlTypeNameToExprType(sqlTypeName);
return (queryType == PPL ? PPL_SPEC.typeName(type) : type.legacyTypeName())
.toUpperCase(Locale.ROOT);
}
}

/** Converts a Calcite data type to OpenSearch ExprCoreType. */
public static ExprType convertRelDataTypeToExprType(RelDataType type) {
ExprType exprType = convertSqlTypeNameToExprType(type.getSqlTypeName());
if (exprType == UNKNOWN) {
throw new IllegalArgumentException(
"Unsupported conversion for Relational Data type: " + type.getSqlTypeName());
}
return exprType;
}

public static ExprValue getExprValueByExprType(ExprType type, Object value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.calcite.utils;

import org.opensearch.sql.ast.expression.IntervalUnit;
import org.opensearch.sql.ast.expression.SpanUnit;

public interface PlanUtils {

static SpanUnit intervalUnitToSpanUnit(IntervalUnit unit) {
return switch (unit) {
case MICROSECOND -> SpanUnit.MILLISECOND;
case SECOND -> SpanUnit.SECOND;
case MINUTE -> SpanUnit.MINUTE;
case HOUR -> SpanUnit.HOUR;
case DAY -> SpanUnit.DAY;
case WEEK -> SpanUnit.WEEK;
case MONTH -> SpanUnit.MONTH;
case QUARTER -> SpanUnit.QUARTER;
case YEAR -> SpanUnit.YEAR;
case UNKNOWN -> SpanUnit.UNKNOWN;
default -> throw new UnsupportedOperationException("Unsupported interval unit: " + unit);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlUserDefinedAggFunction;
Expand Down Expand Up @@ -63,7 +64,12 @@ public static SqlOperator TransferUserDefinedFunction(
SqlIdentifier udfLtrimIdentifier =
new SqlIdentifier(Collections.singletonList(functionName), null, SqlParserPos.ZERO, null);
return new SqlUserDefinedFunction(
udfLtrimIdentifier, SqlKind.OTHER_FUNCTION, returnType, null, null, udfFunction);
udfLtrimIdentifier,
SqlKind.OTHER_FUNCTION,
returnType,
InferTypes.ANY_NULLABLE,
null,
udfFunction);
}

public static SqlReturnTypeInference getReturnTypeInferenceForArray() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void execute(
(PrivilegedAction<Void>)
() -> {
final FrameworkConfig config = buildFrameworkConfig();
final CalcitePlanContext context = CalcitePlanContext.create(config);
final CalcitePlanContext context = CalcitePlanContext.create(config, queryType);
executePlanByCalcite(analyze(plan, context), context, listener);
return null;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

package org.opensearch.sql.calcite.remote.nonfallback;

import org.junit.Ignore;
import org.opensearch.sql.calcite.remote.fallback.CalciteSystemFunctionIT;

@Ignore("https://github.com/opensearch-project/sql/issues/3418")
public class NonFallbackCalciteSystemFunctionIT extends CalciteSystemFunctionIT {
@Override
public void init() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.IOException;
import org.json.JSONObject;
import org.junit.Ignore;
import org.junit.jupiter.api.Test;

public class CalcitePPLBuiltinFunctionIT extends CalcitePPLIntegTestCase {
Expand Down Expand Up @@ -106,6 +107,43 @@ public void testAtanAndAtan2WithSort() {
verifyDataRowsInOrder(actual, rows("Hello", 30, 4), rows("Jake", 70, 4));
}

@Test
public void testTypeOfBasic() {
JSONObject result =
executeQuery(
String.format(
"""
source=%s
| eval `typeof(1)` = typeof(1)
| eval `typeof(true)` = typeof(true)
| eval `typeof(2.0)` = typeof(2.0)
| eval `typeof("2.0")` = typeof("2.0")
| eval `typeof(name)` = typeof(name)
| eval `typeof(country)` = typeof(country)
| eval `typeof(age)` = typeof(age)
| eval `typeof(interval)` = typeof(INTERVAL 2 DAY)
| fields `typeof(1)`, `typeof(true)`, `typeof(2.0)`, `typeof("2.0")`, `typeof(name)`, `typeof(country)`, `typeof(age)`, `typeof(interval)`
| head 1
""",
TEST_INDEX_STATE_COUNTRY));
verifyDataRows(
result, rows("INT", "BOOLEAN", "DOUBLE", "STRING", "STRING", "STRING", "INT", "INTERVAL"));
}

@Ignore("https://github.com/opensearch-project/sql/issues/3400")
public void testTypeOfDateTime() {
JSONObject result =
executeQuery(
String.format(
"""
source=%s
| eval `typeof(date)` = typeof(DATE('2008-04-14'))
| eval `typeof(now())` = typeof(now())
| fields `typeof(date)`, `typeof(now())`
""",
TEST_INDEX_STATE_COUNTRY));
}

@Test
public void testCeilingAndFloor() {
JSONObject actual =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public void typeof_opensearch_types() throws IOException {
+ " | fields `text`, `date`, `date_nanos`, `boolean`, `object`, `keyword`,"
+ " `ip`, `binary`, `geo_point`",
TEST_INDEX_DATATYPE_NONNUMERIC));
// TODO https://github.com/opensearch-project/sql/issues/3322
// TO support IP, we need support UDT.
verifyDataRows(
response,
rows(
Expand All @@ -91,7 +93,7 @@ public void typeof_opensearch_types() throws IOException {
"BOOLEAN",
"STRUCT",
"STRING",
"IP",
isCalciteEnabled() ? "STRING" : "IP",
"BINARY",
"GEO_POINT"));
}
Expand Down
Loading
Loading