diff --git a/coral-common/src/main/java/com/linkedin/coral/common/transformers/JsonTransformSqlCallTransformer.java b/coral-common/src/main/java/com/linkedin/coral/common/transformers/JsonTransformSqlCallTransformer.java deleted file mode 100644 index 1ff907821..000000000 --- a/coral-common/src/main/java/com/linkedin/coral/common/transformers/JsonTransformSqlCallTransformer.java +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright 2023 LinkedIn Corporation. All rights reserved. - * Licensed under the BSD-2 Clause license. - * See LICENSE in the project root for license information. - */ -package com.linkedin.coral.common.transformers; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; - -import org.apache.calcite.sql.SqlCall; -import org.apache.calcite.sql.SqlIdentifier; -import org.apache.calcite.sql.SqlNode; -import org.apache.calcite.sql.SqlOperator; -import org.apache.calcite.sql.SqlWriter; -import org.apache.calcite.sql.fun.SqlStdOperatorTable; -import org.apache.calcite.sql.parser.SqlParserPos; -import org.apache.calcite.sql.type.OperandTypes; -import org.apache.calcite.sql.type.ReturnTypes; -import org.apache.calcite.sql.validate.SqlUserDefinedFunction; - -import com.linkedin.coral.com.google.common.base.Preconditions; -import com.linkedin.coral.common.functions.FunctionReturnTypes; - -import static com.linkedin.coral.common.calcite.CalciteUtil.*; - - -/** - * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} which transforms the parts (e.g. name, operator and operands) - * of the target operator according to multiple rules defined in JSON format respectively. - */ -public class JsonTransformSqlCallTransformer extends SourceOperatorMatchSqlCallTransformer { - private static final Map OP_MAP = new HashMap<>(); - - // Operators allowed in the transformation - static { - OP_MAP.put("+", SqlStdOperatorTable.PLUS); - OP_MAP.put("-", SqlStdOperatorTable.MINUS); - OP_MAP.put("*", SqlStdOperatorTable.MULTIPLY); - OP_MAP.put("/", SqlStdOperatorTable.DIVIDE); - OP_MAP.put("^", SqlStdOperatorTable.POWER); - OP_MAP.put("%", SqlStdOperatorTable.MOD); - OP_MAP.put("date", new SqlUserDefinedFunction(new SqlIdentifier("date", SqlParserPos.ZERO), ReturnTypes.DATE, null, - OperandTypes.STRING, null, null)); - OP_MAP.put("timestamp", new SqlUserDefinedFunction(new SqlIdentifier("timestamp", SqlParserPos.ZERO), - FunctionReturnTypes.TIMESTAMP, null, OperandTypes.STRING, null, null) { - @Override - public void unparse(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec) { - // for timestamp operator, we need to construct `CAST(x AS TIMESTAMP)` - Preconditions.checkState(call.operandCount() == 1); - final SqlWriter.Frame frame = writer.startFunCall("CAST"); - call.operand(0).unparse(writer, 0, 0); - writer.sep("AS"); - writer.literal("TIMESTAMP"); - writer.endFunCall(frame); - } - }); - OP_MAP.put("hive_pattern_to_trino", - new SqlUserDefinedFunction(new SqlIdentifier("hive_pattern_to_trino", SqlParserPos.ZERO), - FunctionReturnTypes.STRING, null, OperandTypes.STRING, null, null)); - } - - public static final String OPERATOR = "op"; - public static final String OPERANDS = "operands"; - /** - * For input node: - * - input equals 0 refers to the result - * - input great than 0 refers to the index of source operand (starting from 1) - */ - public static final String INPUT = "input"; - public static final String VALUE = "value"; - public static final String REGEX = "regex"; - public static final String NAME = "name"; - - private final SqlOperator targetOperator; - public List operandTransformers; - public JsonObject resultTransformer; - public List operatorTransformers; - - public JsonTransformSqlCallTransformer(String fromOperatorName, int numOperands, SqlOperator targetOperator) { - super(fromOperatorName, numOperands); - this.targetOperator = targetOperator; - } - - public JsonTransformSqlCallTransformer(SqlOperator coralOp, int numOperands, String targetOpName, - String operandTransformers, String resultTransformer, String operatorTransformers) { - this(coralOp.getName(), numOperands, createSqlOperator(targetOpName, coralOp.getReturnTypeInference())); - if (operandTransformers != null) { - this.operandTransformers = parseJsonObjectsFromString(operandTransformers); - } - if (resultTransformer != null) { - this.resultTransformer = new JsonParser().parse(resultTransformer).getAsJsonObject(); - } - if (operatorTransformers != null) { - this.operatorTransformers = parseJsonObjectsFromString(operatorTransformers); - } - } - - @Override - protected boolean condition(SqlCall sqlCall) { - return sourceOpName.equalsIgnoreCase(sqlCall.getOperator().getName()) - && sqlCall.getOperandList().size() == numOperands; - } - - @Override - protected SqlCall transform(SqlCall sqlCall) { - List sourceOperands = sqlCall.getOperandList(); - final SqlOperator newTargetOperator = transformTargetOperator(targetOperator, sourceOperands); - if (newTargetOperator == null || newTargetOperator.getName().isEmpty()) { - String operands = sourceOperands.stream().map(SqlNode::toString).collect(Collectors.joining(",")); - throw new IllegalArgumentException( - String.format("An equivalent operator in the target IR was not found for the function call: %s(%s)", - sourceOpName, operands)); - } - final List newOperands = transformOperands(sourceOperands); - final SqlCall newCall = createCall(newTargetOperator, newOperands, SqlParserPos.ZERO); - return (SqlCall) transformResult(newCall, sourceOperands); - } - - private List transformOperands(List sourceOperands) { - if (operandTransformers == null) { - return sourceOperands; - } - final List sources = new ArrayList<>(); - // Add a dummy expression for input 0 - sources.add(null); - sources.addAll(sourceOperands); - final List results = new ArrayList<>(); - for (JsonObject operandTransformer : operandTransformers) { - results.add(transformExpression(operandTransformer, sources)); - } - return results; - } - - private SqlNode transformResult(SqlNode result, List sourceOperands) { - if (resultTransformer == null) { - return result; - } - final List sources = new ArrayList<>(); - // Result will be input 0 - sources.add(result); - sources.addAll(sourceOperands); - return transformExpression(resultTransformer, sources); - } - - /** - * Performs a single transformer. - */ - private SqlNode transformExpression(JsonObject transformer, List sourceOperands) { - if (transformer.get(OPERATOR) != null) { - final List inputOperands = new ArrayList<>(); - for (JsonElement inputOperand : transformer.getAsJsonArray(OPERANDS)) { - if (inputOperand.isJsonObject()) { - inputOperands.add(transformExpression(inputOperand.getAsJsonObject(), sourceOperands)); - } - } - final String operatorName = transformer.get(OPERATOR).getAsString(); - final SqlOperator op = OP_MAP.get(operatorName); - if (op == null) { - throw new UnsupportedOperationException("Operator " + operatorName + " is not supported in transformation"); - } - return createCall(op, inputOperands, SqlParserPos.ZERO); - } - if (transformer.get(INPUT) != null) { - int index = transformer.get(INPUT).getAsInt(); - if (index < 0 || index >= sourceOperands.size() || sourceOperands.get(index) == null) { - throw new IllegalArgumentException( - "Invalid input value: " + index + ". Number of source operands: " + sourceOperands.size()); - } - return sourceOperands.get(index); - } - final JsonElement value = transformer.get(VALUE); - if (value == null) { - throw new IllegalArgumentException("JSON node for transformation should be either op, input, or value"); - } - if (!value.isJsonPrimitive()) { - throw new IllegalArgumentException("Value should be of primitive type: " + value); - } - - final JsonPrimitive primitive = value.getAsJsonPrimitive(); - if (primitive.isString()) { - return createStringLiteral(primitive.getAsString(), SqlParserPos.ZERO); - } - if (primitive.isBoolean()) { - return createLiteralBoolean(primitive.getAsBoolean(), SqlParserPos.ZERO); - } - if (primitive.isNumber()) { - return createLiteralNumber(value.getAsBigDecimal().longValue(), SqlParserPos.ZERO); - } - - throw new UnsupportedOperationException("Invalid JSON literal value: " + primitive); - } - - /** - * Returns a SqlOperator with a function name based on the value of the source operands. - */ - private SqlOperator transformTargetOperator(SqlOperator operator, List sourceOperands) { - if (operatorTransformers == null) { - return operator; - } - - for (JsonObject operatorTransformer : operatorTransformers) { - if (!operatorTransformer.has(REGEX) || !operatorTransformer.has(INPUT) || !operatorTransformer.has(NAME)) { - throw new IllegalArgumentException( - "JSON node for target operator transformer must have a matcher, input and name"); - } - // We use the same convention as operand and result transformers. - // Therefore, we start source index values at index 1 instead of index 0. - // Acceptable index values are set to be [1, size] - int index = operatorTransformer.get(INPUT).getAsInt() - 1; - if (index < 0 || index >= sourceOperands.size()) { - throw new IllegalArgumentException( - String.format("Index is not within the acceptable range [%d, %d]", 1, sourceOperands.size())); - } - String functionName = operatorTransformer.get(NAME).getAsString(); - if (functionName.isEmpty()) { - throw new IllegalArgumentException("JSON node for transformation must have a non-empty name"); - } - String matcher = operatorTransformer.get(REGEX).getAsString(); - - if (Pattern.matches(matcher, sourceOperands.get(index).toString())) { - return createSqlOperator(functionName, operator.getReturnTypeInference()); - } - } - return operator; - } - - /** - * Creates an ArrayList of JsonObjects from a string input. - * The input string must be a serialized JSON array. - */ - private static List parseJsonObjectsFromString(String s) { - List objects = new ArrayList<>(); - JsonArray transformerArray = new JsonParser().parse(s).getAsJsonArray(); - for (JsonElement object : transformerArray) { - objects.add(object.getAsJsonObject()); - } - return objects; - } -} diff --git a/coral-common/src/main/java/com/linkedin/coral/common/transformers/SourceOperatorMatchSqlCallTransformer.java b/coral-common/src/main/java/com/linkedin/coral/common/transformers/SourceOperatorMatchSqlCallTransformer.java index d924a7087..3f115c957 100644 --- a/coral-common/src/main/java/com/linkedin/coral/common/transformers/SourceOperatorMatchSqlCallTransformer.java +++ b/coral-common/src/main/java/com/linkedin/coral/common/transformers/SourceOperatorMatchSqlCallTransformer.java @@ -5,7 +5,18 @@ */ package com.linkedin.coral.common.transformers; +import com.google.common.base.Preconditions; + import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.SqlWriter; +import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.type.OperandTypes; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.validate.SqlUserDefinedFunction; + +import com.linkedin.coral.common.functions.FunctionReturnTypes; import static com.linkedin.coral.common.calcite.CalciteUtil.*; @@ -16,6 +27,23 @@ * matches the target values in the condition function. */ public abstract class SourceOperatorMatchSqlCallTransformer extends SqlCallTransformer { + public static final SqlOperator TIMESTAMP_OPERATOR = + new SqlUserDefinedFunction(new SqlIdentifier("timestamp", SqlParserPos.ZERO), FunctionReturnTypes.TIMESTAMP, null, + OperandTypes.STRING, null, null) { + @Override + public void unparse(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec) { + // for timestamp operator, we need to construct `CAST(x AS TIMESTAMP)` + Preconditions.checkState(call.operandCount() == 1); + final SqlWriter.Frame frame = writer.startFunCall("CAST"); + call.operand(0).unparse(writer, 0, 0); + writer.sep("AS"); + writer.literal("TIMESTAMP"); + writer.endFunCall(frame); + } + }; + + protected final SqlOperator DATE_OPERATOR = new SqlUserDefinedFunction(new SqlIdentifier("date", SqlParserPos.ZERO), + ReturnTypes.DATE, null, OperandTypes.STRING, null, null); protected final String sourceOpName; protected final int numOperands; diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/CoralToTrinoSqlCallConverter.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/CoralToTrinoSqlCallConverter.java index fadce197a..9e7923567 100644 --- a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/CoralToTrinoSqlCallConverter.java +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/CoralToTrinoSqlCallConverter.java @@ -15,13 +15,21 @@ import org.apache.calcite.sql.util.SqlShuttle; import com.linkedin.coral.common.functions.Function; -import com.linkedin.coral.common.transformers.JsonTransformSqlCallTransformer; import com.linkedin.coral.common.transformers.OperatorRenameSqlCallTransformer; import com.linkedin.coral.common.transformers.SqlCallTransformers; import com.linkedin.coral.hive.hive2rel.functions.StaticHiveFunctionRegistry; import com.linkedin.coral.trino.rel2trino.transformers.CoralRegistryOperatorRenameSqlCallTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.DateAddOperatorTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.DateDiffOperatorTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.DateSubOperatorTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.DecodeOperatorTransformer; import com.linkedin.coral.trino.rel2trino.transformers.GenericCoralRegistryOperatorRenameSqlCallTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.ModOperatorTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.RandomIntegerOperatorWithTwoOperandsTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.RandomOperatorWithOneOperandTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.RegexpExtractOperatorTransformer; import com.linkedin.coral.trino.rel2trino.transformers.ToDateOperatorTransformer; +import com.linkedin.coral.trino.rel2trino.transformers.TruncateOperatorTransformer; import static com.linkedin.coral.trino.rel2trino.CoralTrinoConfigKeys.*; @@ -43,45 +51,22 @@ public CoralToTrinoSqlCallConverter(Map configs) { new OperatorRenameSqlCallTransformer(SqlStdOperatorTable.SUBSTRING, 3, "SUBSTR"), // math functions new OperatorRenameSqlCallTransformer(SqlStdOperatorTable.RAND, 0, "RANDOM"), - new JsonTransformSqlCallTransformer(SqlStdOperatorTable.RAND, 1, "RANDOM", "[]", null, null), + new RandomOperatorWithOneOperandTransformer(), new OperatorRenameSqlCallTransformer(SqlStdOperatorTable.RAND_INTEGER, 1, "RANDOM"), - new JsonTransformSqlCallTransformer(SqlStdOperatorTable.RAND_INTEGER, 2, "RANDOM", "[{\"input\":2}]", null, - null), - new JsonTransformSqlCallTransformer(SqlStdOperatorTable.TRUNCATE, 2, "TRUNCATE", - "[{\"op\":\"*\",\"operands\":[{\"input\":1},{\"op\":\"^\",\"operands\":[{\"value\":10},{\"input\":2}]}]}]", - "{\"op\":\"/\",\"operands\":[{\"input\":0},{\"op\":\"^\",\"operands\":[{\"value\":10},{\"input\":2}]}]}", - null), + new RandomIntegerOperatorWithTwoOperandsTransformer(), new TruncateOperatorTransformer(), // string functions new OperatorRenameSqlCallTransformer(SqlStdOperatorTable.SUBSTRING, 2, "SUBSTR"), // JSON functions new CoralRegistryOperatorRenameSqlCallTransformer("get_json_object", 2, "json_extract"), // map various hive functions - new JsonTransformSqlCallTransformer(hiveToCoralSqlOperator("pmod"), 2, "mod", - "[{\"op\":\"+\",\"operands\":[{\"op\":\"%\",\"operands\":[{\"input\":1},{\"input\":2}]},{\"input\":2}]},{\"input\":2}]", - null, null), - new CoralRegistryOperatorRenameSqlCallTransformer("base64", 1, "to_base64"), + new ModOperatorTransformer(), new CoralRegistryOperatorRenameSqlCallTransformer("base64", 1, "to_base64"), new CoralRegistryOperatorRenameSqlCallTransformer("unbase64", 1, "from_base64"), new CoralRegistryOperatorRenameSqlCallTransformer("hex", 1, "to_hex"), new CoralRegistryOperatorRenameSqlCallTransformer("unhex", 1, "from_hex"), new CoralRegistryOperatorRenameSqlCallTransformer("array_contains", 2, "contains"), - new JsonTransformSqlCallTransformer(hiveToCoralSqlOperator("regexp_extract"), 3, "regexp_extract", - "[{\"input\": 1}, {\"op\": \"hive_pattern_to_trino\", \"operands\":[{\"input\": 2}]}, {\"input\": 3}]", - null, null), - new CoralRegistryOperatorRenameSqlCallTransformer("instr", 2, "strpos"), - new JsonTransformSqlCallTransformer(hiveToCoralSqlOperator("decode"), 2, - "[{\"regex\":\"(?i)('utf-8')\", \"input\":2, \"name\":\"from_utf8\"}]", "[{\"input\":1}]", null, null), - new JsonTransformSqlCallTransformer(hiveToCoralSqlOperator("date_add"), 2, "date_add", - "[{\"value\": 'day'}, {\"input\": 2}, " - + "{\"op\": \"date\", \"operands\":[{\"op\": \"timestamp\", \"operands\":[{\"input\": 1}]}]}]", - null, null), - new JsonTransformSqlCallTransformer(hiveToCoralSqlOperator("date_sub"), 2, "date_add", - "[{\"value\": 'day'}, " + "{\"op\": \"*\", \"operands\":[{\"input\": 2}, {\"value\": -1}]}, " - + "{\"op\": \"date\", \"operands\":[{\"op\": \"timestamp\", \"operands\":[{\"input\": 1}]}]}]", - null, null), - new JsonTransformSqlCallTransformer(hiveToCoralSqlOperator("datediff"), 2, "date_diff", - "[{\"value\": 'day'}, {\"op\": \"date\", \"operands\":[{\"op\": \"timestamp\", \"operands\":[{\"input\": 2}]}]}, " - + "{\"op\": \"date\", \"operands\":[{\"op\": \"timestamp\", \"operands\":[{\"input\": 1}]}]}]", - null, null), + new RegexpExtractOperatorTransformer(), new CoralRegistryOperatorRenameSqlCallTransformer("instr", 2, "strpos"), + new DecodeOperatorTransformer(), new DateAddOperatorTransformer(), new DateSubOperatorTransformer(), + new DateDiffOperatorTransformer(), new ToDateOperatorTransformer(configs.getOrDefault(AVOID_TRANSFORM_TO_DATE_UDF, false)), // LinkedIn specific functions @@ -102,7 +87,7 @@ public CoralToTrinoSqlCallConverter(Map configs) { new GenericCoralRegistryOperatorRenameSqlCallTransformer()); } - private SqlOperator hiveToCoralSqlOperator(String functionName) { + public static SqlOperator hiveToCoralSqlOperator(String functionName) { Collection lookup = HIVE_FUNCTION_REGISTRY.lookup(functionName); return lookup.iterator().next().getSqlOperator(); } diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateAddOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateAddOperatorTransformer.java new file mode 100644 index 000000000..394544951 --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateAddOperatorTransformer.java @@ -0,0 +1,56 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.common.calcite.CalciteUtil.*; +import static com.linkedin.coral.trino.rel2trino.CoralToTrinoSqlCallConverter.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} which transforms a Coral SqlCall of "date_add" + * operator with 2 operands into a Trino SqlCall of an operator named "date_add" + */ +public class DateAddOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "date_add"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("date_add", hiveToCoralSqlOperator("date_add").getReturnTypeInference()); + + public DateAddOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = new ArrayList<>(); + newOperands.add(createStringLiteral("day", SqlParserPos.ZERO)); + newOperands.add(sourceOperands.get(1)); + + List timestampOperatorOperands = new ArrayList<>(); + timestampOperatorOperands.add(sourceOperands.get(0)); + SqlCall timestampSqlCall = + TIMESTAMP_OPERATOR.createCall(new SqlNodeList(timestampOperatorOperands, SqlParserPos.ZERO)); + + List dateOperatorOperands = new ArrayList<>(); + dateOperatorOperands.add(timestampSqlCall); + SqlCall dateOpSqlCall = DATE_OPERATOR.createCall(new SqlNodeList(dateOperatorOperands, SqlParserPos.ZERO)); + newOperands.add(dateOpSqlCall); + + return TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateDiffOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateDiffOperatorTransformer.java new file mode 100644 index 000000000..0d2090da7 --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateDiffOperatorTransformer.java @@ -0,0 +1,57 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.common.calcite.CalciteUtil.*; +import static com.linkedin.coral.trino.rel2trino.CoralToTrinoSqlCallConverter.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} which transforms a Coral SqlCall of "datediff" operator + * with 2 operands into a Trino SqlCall of an operator named "date_diff" + */ +public class DateDiffOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "datediff"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("date_diff", hiveToCoralSqlOperator("datediff").getReturnTypeInference()); + + public DateDiffOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = new ArrayList<>(); + newOperands.add(createStringLiteral("day", SqlParserPos.ZERO)); + newOperands.add(createDateTimestampSqlCall(sourceOperands, 1)); + newOperands.add(createDateTimestampSqlCall(sourceOperands, 0)); + return TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } + + private SqlCall createDateTimestampSqlCall(List sourceOperands, int idx) { + List timestampOperatorOperands = new ArrayList<>(); + timestampOperatorOperands.add(sourceOperands.get(idx)); + SqlCall timestampSqlCall = + TIMESTAMP_OPERATOR.createCall(new SqlNodeList(timestampOperatorOperands, SqlParserPos.ZERO)); + + List dateOperatorOperands = new ArrayList<>(); + dateOperatorOperands.add(timestampSqlCall); + return DATE_OPERATOR.createCall(new SqlNodeList(dateOperatorOperands, SqlParserPos.ZERO)); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateSubOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateSubOperatorTransformer.java new file mode 100644 index 000000000..1fbe8eece --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DateSubOperatorTransformer.java @@ -0,0 +1,63 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.common.calcite.CalciteUtil.*; +import static com.linkedin.coral.trino.rel2trino.CoralToTrinoSqlCallConverter.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} which transforms a Coral SqlCall of "date_sub" operator + * with 2 operands into a Trino SqlCall of an operator named "date_add" + */ +public class DateSubOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "date_sub"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("date_add", hiveToCoralSqlOperator("date_sub").getReturnTypeInference()); + + public DateSubOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = new ArrayList<>(); + newOperands.add(createStringLiteral("day", SqlParserPos.ZERO)); + + List multiplyOperands = new ArrayList<>(); + multiplyOperands.add(sourceOperands.get(1)); + multiplyOperands.add(createLiteralNumber(-1, SqlParserPos.ZERO)); + SqlCall multiplySqlCall = + SqlStdOperatorTable.MULTIPLY.createCall(new SqlNodeList(multiplyOperands, SqlParserPos.ZERO)); + newOperands.add(multiplySqlCall); + + List timestampOperatorOperands = new ArrayList<>(); + timestampOperatorOperands.add(sourceOperands.get(0)); + SqlCall timestampSqlCall = + TIMESTAMP_OPERATOR.createCall(new SqlNodeList(timestampOperatorOperands, SqlParserPos.ZERO)); + + List dateOperatorOperands = new ArrayList<>(); + dateOperatorOperands.add(timestampSqlCall); + SqlCall dateOpSqlCall = DATE_OPERATOR.createCall(new SqlNodeList(dateOperatorOperands, SqlParserPos.ZERO)); + newOperands.add(dateOpSqlCall); + + return TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DecodeOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DecodeOperatorTransformer.java new file mode 100644 index 000000000..4adca9ff6 --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/DecodeOperatorTransformer.java @@ -0,0 +1,44 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.trino.rel2trino.CoralToTrinoSqlCallConverter.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} which transforms a Coral SqlCall of "decode" operator + * with 2 operands into a Trino SqlCall of an operator named "[{\"regex\":\"(?i)('utf-8')\", \"input\":2, \"name\":\"from_utf8\"}]" + */ +public class DecodeOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "decode"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("[{\"regex\":\"(?i)('utf-8')\", \"input\":2, \"name\":\"from_utf8\"}]", + hiveToCoralSqlOperator("decode").getReturnTypeInference()); + + public DecodeOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = new ArrayList<>(); + newOperands.add(sourceOperands.get(0)); + return TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/ModOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/ModOperatorTransformer.java new file mode 100644 index 000000000..bf1ca8ead --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/ModOperatorTransformer.java @@ -0,0 +1,57 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.trino.rel2trino.CoralToTrinoSqlCallConverter.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} transforms a Coral SqlCall of "pmod" operator + * with 2 operands into a Trino SqlCall of an operator named "mod" + */ +public class ModOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "pmod"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("mod", hiveToCoralSqlOperator(FROM_OPERATOR_NAME).getReturnTypeInference()); + + public ModOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = transformOperands(sourceOperands); + return TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } + + private List transformOperands(List sourceOperands) { + List newTopLevelOperands = new ArrayList<>(); + + SqlNode modOpSqlNode = SqlStdOperatorTable.MOD.createCall(new SqlNodeList(sourceOperands, SqlParserPos.ZERO)); + List operandsOfPlusOp = new ArrayList<>(); + operandsOfPlusOp.add(modOpSqlNode); + operandsOfPlusOp.add(sourceOperands.get(1)); + SqlNode plusOpSqlNode = SqlStdOperatorTable.PLUS.createCall(new SqlNodeList(operandsOfPlusOp, SqlParserPos.ZERO)); + + newTopLevelOperands.add(plusOpSqlNode); + newTopLevelOperands.add(sourceOperands.get(1)); + return newTopLevelOperands; + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RandomIntegerOperatorWithTwoOperandsTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RandomIntegerOperatorWithTwoOperandsTransformer.java new file mode 100644 index 000000000..2e567b0e6 --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RandomIntegerOperatorWithTwoOperandsTransformer.java @@ -0,0 +1,42 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.common.calcite.CalciteUtil.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} transforms a Coral SqlCall of "RAND_INTEGER" operator + * with 2 operands into a Trino SqlCall of an operator named "RANDOM" + */ +public class RandomIntegerOperatorWithTwoOperandsTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "RAND_INTEGER"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("RANDOM", SqlStdOperatorTable.RAND_INTEGER.getReturnTypeInference()); + + public RandomIntegerOperatorWithTwoOperandsTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List newOperands = new ArrayList<>(); + newOperands.add(sqlCall.getOperandList().get(1)); + return createCall(TARGET_OPERATOR, newOperands, SqlParserPos.ZERO); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RandomOperatorWithOneOperandTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RandomOperatorWithOneOperandTransformer.java new file mode 100644 index 000000000..641d770cb --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RandomOperatorWithOneOperandTransformer.java @@ -0,0 +1,38 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.Collections; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.common.calcite.CalciteUtil.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} transforms a Coral SqlCall of "RAND" operator + * with 1 operand into a Trino SqlCall of an operator named "RANDOM" + */ +public class RandomOperatorWithOneOperandTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "RAND"; + private static final int OPERAND_NUM = 1; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("RANDOM", SqlStdOperatorTable.RAND.getReturnTypeInference()); + + public RandomOperatorWithOneOperandTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + return createCall(TARGET_OPERATOR, Collections.emptyList(), SqlParserPos.ZERO); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RegexpExtractOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RegexpExtractOperatorTransformer.java new file mode 100644 index 000000000..16bb44d63 --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/RegexpExtractOperatorTransformer.java @@ -0,0 +1,58 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.type.OperandTypes; +import org.apache.calcite.sql.validate.SqlUserDefinedFunction; + +import com.linkedin.coral.common.functions.FunctionReturnTypes; +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + +import static com.linkedin.coral.trino.rel2trino.CoralToTrinoSqlCallConverter.*; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} transforms a Coral SqlCall of "regexp_extract" operator + * with 3 operands into a Trino SqlCall of an operator named "regexp_extract" + */ +public class RegexpExtractOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "regexp_extract"; + private static final int OPERAND_NUM = 3; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("regexp_extract", hiveToCoralSqlOperator("regexp_extract").getReturnTypeInference()); + + private static final SqlOperator HIVE_PATTERN_TO_TRINO_OPERATOR = + new SqlUserDefinedFunction(new SqlIdentifier("hive_pattern_to_trino", SqlParserPos.ZERO), + FunctionReturnTypes.STRING, null, OperandTypes.STRING, null, null); + + public RegexpExtractOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = new ArrayList<>(); + newOperands.add(sourceOperands.get(0)); + + List hivePatternToTrinoOperands = new ArrayList<>(); + hivePatternToTrinoOperands.add(sourceOperands.get(1)); + newOperands + .add(HIVE_PATTERN_TO_TRINO_OPERATOR.createCall(new SqlNodeList(hivePatternToTrinoOperands, SqlParserPos.ZERO))); + + newOperands.add(sourceOperands.get(2)); + return TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } +} diff --git a/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/TruncateOperatorTransformer.java b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/TruncateOperatorTransformer.java new file mode 100644 index 000000000..82400b874 --- /dev/null +++ b/coral-trino/src/main/java/com/linkedin/coral/trino/rel2trino/transformers/TruncateOperatorTransformer.java @@ -0,0 +1,72 @@ +/** + * Copyright 2023 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.coral.trino.rel2trino.transformers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.parser.SqlParserPos; + +import com.linkedin.coral.common.transformers.SourceOperatorMatchSqlCallTransformer; + + +/** + * This class is a subclass of {@link SourceOperatorMatchSqlCallTransformer} transforms a Coral SqlCall of "TRUNCATE" operator + * with 2 operands into a Trino SqlCall of an operator named "TRUNCATE" + */ +public class TruncateOperatorTransformer extends SourceOperatorMatchSqlCallTransformer { + private static final String FROM_OPERATOR_NAME = "TRUNCATE"; + private static final int OPERAND_NUM = 2; + private static final SqlOperator TARGET_OPERATOR = + createSqlOperator("TRUNCATE", SqlStdOperatorTable.TRUNCATE.getReturnTypeInference()); + + public TruncateOperatorTransformer() { + super(FROM_OPERATOR_NAME, OPERAND_NUM); + } + + @Override + protected SqlCall transform(SqlCall sqlCall) { + List sourceOperands = sqlCall.getOperandList(); + List newOperands = transformOperands(sourceOperands); + SqlCall newSqlCall = TARGET_OPERATOR.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + return transformResult(newSqlCall, sourceOperands); + } + + private List transformOperands(List sourceOperands) { + SqlNode powerOpSqlNode = createPowerOpSqlNode(sourceOperands); + + List operandsOfMultiplyOp = new ArrayList<>(); + operandsOfMultiplyOp.add(sourceOperands.get(0)); + operandsOfMultiplyOp.add(powerOpSqlNode); + SqlNode multiplyOpSqlNode = + SqlStdOperatorTable.MULTIPLY.createCall(new SqlNodeList(operandsOfMultiplyOp, SqlParserPos.ZERO)); + + List topLevelOperands = new ArrayList<>(); + topLevelOperands.add(multiplyOpSqlNode); + return topLevelOperands; + } + + private SqlCall transformResult(SqlNode result, List sourceOperands) { + List newOperands = new ArrayList<>(); + newOperands.add(result); + SqlNode powerOpSqlNode = createPowerOpSqlNode(sourceOperands); + newOperands.add(powerOpSqlNode); + return SqlStdOperatorTable.DIVIDE.createCall(new SqlNodeList(newOperands, SqlParserPos.ZERO)); + } + + private SqlCall createPowerOpSqlNode(List sourceOperands) { + List operandsOfPowerOp = new ArrayList<>(); + operandsOfPowerOp.add(SqlLiteral.createExactNumeric(String.valueOf(10), SqlParserPos.ZERO)); + operandsOfPowerOp.add(sourceOperands.get(1)); + return SqlStdOperatorTable.POWER.createCall(new SqlNodeList(operandsOfPowerOp, SqlParserPos.ZERO)); + } +}