-
Notifications
You must be signed in to change notification settings - Fork 1.9k
IGNITE-29031 SQL Calcite: Support byte[] in UDF and UDTF parameters and results #13543
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
base: master
Are you sure you want to change the base?
Changes from all commits
e353bca
f73b7e7
4578951
cea8805
64b0419
0ac6b0b
3937734
0bc8c0a
66bb687
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| import java.util.List; | ||
| import java.util.UUID; | ||
| import org.apache.calcite.adapter.enumerable.RexImpTable; | ||
| import org.apache.calcite.avatica.util.ByteString; | ||
| import org.apache.calcite.linq4j.tree.ConstantExpression; | ||
| import org.apache.calcite.linq4j.tree.ConstantUntypedNull; | ||
| import org.apache.calcite.linq4j.tree.Expression; | ||
|
|
@@ -38,6 +39,8 @@ | |
| import org.apache.calcite.util.BuiltInMethod; | ||
| import org.apache.calcite.util.Util; | ||
| import org.apache.ignite.internal.processors.query.calcite.util.Commons; | ||
| import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils; | ||
| import org.jetbrains.annotations.Nullable; | ||
|
|
||
| /** */ | ||
| public class ConverterUtils { | ||
|
|
@@ -80,11 +83,6 @@ else if (targetType == Long.class) | |
| /** Converts from internal representation to JDBC representation used by | ||
| * arguments of user-defined functions. For example, converts date values from | ||
| * {@code int} to {@link java.sql.Date}. */ | ||
| private static Expression fromInternal(Expression operand, Type targetType) { | ||
| return fromInternal(operand, operand.getType(), targetType); | ||
| } | ||
|
|
||
| /** */ | ||
| private static Expression fromInternal(Expression operand, | ||
| Type fromType, Type targetType) { | ||
| if (operand == ConstantUntypedNull.INSTANCE) | ||
|
|
@@ -111,6 +109,9 @@ else if (targetType == java.sql.Timestamp.class) { | |
| if (isA(fromType, Primitive.LONG)) | ||
| return Expressions.call(BuiltInMethod.INTERNAL_TO_TIMESTAMP.method, operand); | ||
| } | ||
| else if (targetType == byte[].class && fromType == ByteString.class) | ||
| return Expressions.call(BuiltInMethod.BYTE_STRING_TO_BYTE_ARRAY.method, operand); | ||
|
|
||
| if (Primitive.is(operand.type) | ||
| && Primitive.isBox(targetType)) { | ||
| // E.g. operand is "int", target is "Long", generate "(long) operand". | ||
|
|
@@ -123,28 +124,73 @@ else if (targetType == java.sql.Timestamp.class) { | |
| /** */ | ||
| static List<Expression> fromInternal(Class<?>[] targetTypes, | ||
| List<Expression> expressions) { | ||
| final List<Expression> list = new ArrayList<>(); | ||
| return fromInternal(null, targetTypes, expressions); | ||
| } | ||
|
|
||
| /** */ | ||
| static List<Expression> fromInternal(@Nullable Expression root, | ||
| Class<?>[] targetTypes, | ||
| List<Expression> expressions | ||
| ) { | ||
| final List<Expression> list = new ArrayList<>(expressions.size()); | ||
|
|
||
| if (targetTypes.length == expressions.size()) { | ||
| for (int i = 0; i < expressions.size(); i++) | ||
| list.add(fromInternal(expressions.get(i), targetTypes[i])); | ||
| list.add(fromInternal(root, expressions.get(i), targetTypes[i])); | ||
| } | ||
| else { | ||
| int j = 0; | ||
| for (int i = 0; i < expressions.size(); i++) { | ||
| Class<?> type; | ||
|
|
||
| for (Expression expression : expressions) { | ||
| Class<?> targetType; | ||
|
|
||
| if (!targetTypes[j].isArray()) { | ||
| type = targetTypes[j]; | ||
| targetType = targetTypes[j]; | ||
| j++; | ||
| } | ||
| else | ||
| type = targetTypes[j].getComponentType(); | ||
| targetType = targetTypes[j].getComponentType(); | ||
|
|
||
| list.add(fromInternal(expressions.get(i), type)); | ||
| list.add(fromInternal(root, expression, targetType)); | ||
| } | ||
| } | ||
|
|
||
| return list; | ||
| } | ||
|
|
||
| /** */ | ||
| private static Expression fromInternal(@Nullable Expression root, Expression operand, Type targetType) { | ||
| // Preserve Calcite conversions when no execution context is available, including temporal conversions. | ||
| if (root == null) | ||
| return fromInternal(operand, operand.getType(), targetType); | ||
|
|
||
| // Let the Java method call box compatible primitives instead of generating a reference cast. | ||
| if (Types.isAssignableFrom(targetType, operand.getType()) | ||
| || Types.isAssignableFrom(targetType, Primitive.box(operand.getType()))) | ||
| return operand; | ||
|
|
||
| if (!TypeUtils.isConvertableType(targetType)) | ||
| return targetType == BigDecimal.class ? fromInternal(operand, operand.getType(), targetType) : | ||
| convert(operand, operand.getType(), targetType); | ||
|
|
||
| if (Primitive.is(operand.getType())) | ||
| operand = Expressions.box(operand); | ||
|
|
||
| Expression converted = Expressions.call( | ||
| TypeUtils.class, | ||
| "fromInternal", | ||
| root, | ||
| operand, | ||
| Expressions.constant(targetType) | ||
| ); | ||
|
|
||
| Primitive primitive = Primitive.of(targetType); | ||
|
|
||
| return primitive == null | ||
| ? Expressions.convert_(converted, targetType) | ||
| : Expressions.unbox(Expressions.convert_(converted, primitive.boxClass), primitive); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. redundant ? |
||
| } | ||
|
|
||
| /** */ | ||
| private static Type toInternal(RelDataType type) { | ||
| return toInternal(type, false); | ||
|
|
@@ -230,6 +276,12 @@ public static Expression convert(Expression operand, Type fromType, Type toType) | |
| if (toType == BigDecimal.class) | ||
| throw new AssertionError("For conversion to decimal, ConverterUtils#convertToDecimal method should be used instead."); | ||
|
|
||
| if (fromType == byte[].class && toType == ByteString.class) | ||
| return Expressions.call(BuiltInMethod.BYTE_ARRAY_TO_BYTE_STRING.method, operand); | ||
|
|
||
| if (fromType == ByteString.class && toType == byte[].class) | ||
| return Expressions.call(BuiltInMethod.BYTE_STRING_TO_BYTE_ARRAY.method, operand); | ||
|
Comment on lines
+282
to
+283
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these conversion still has no test coverage, isn`t it ? |
||
|
|
||
| // E.g. from "Short" to "int". | ||
| // Generate "x.intValue()". | ||
| final Primitive toPrimitive = Primitive.of(toType); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.ignite.internal.processors.query.calcite.exec.exp; | ||
|
|
||
| import java.util.List; | ||
| import org.apache.calcite.adapter.java.JavaTypeFactory; | ||
| import org.apache.calcite.rel.type.RelDataType; | ||
| import org.apache.calcite.rel.type.RelDataTypeFactory; | ||
| import org.apache.calcite.schema.FunctionParameter; | ||
| import org.apache.calcite.sql.type.SqlTypeName; | ||
| import org.apache.ignite.internal.processors.query.calcite.type.OtherType; | ||
|
|
||
| /** | ||
| * Reflective Java function parameter represented with a SQL type. | ||
| * | ||
| * <p>The SQL representation is required to validate user-defined function arguments and to convert literal arguments | ||
| * while deriving a table function row type. | ||
| */ | ||
| final class IgniteFunctionParameter implements FunctionParameter { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems all tests are passed if you completelly remote this class, am i right ? |
||
| /** Original function parameter. */ | ||
| private final FunctionParameter delegate; | ||
|
|
||
| /** */ | ||
| private IgniteFunctionParameter(FunctionParameter delegate) { | ||
| this.delegate = delegate; | ||
| } | ||
|
|
||
| /** Returns function parameters represented with SQL types. */ | ||
| static List<FunctionParameter> toSql(List<FunctionParameter> parameters) { | ||
| return parameters.stream().map(IgniteFunctionParameter::toSql).toList(); | ||
| } | ||
|
|
||
| /** Returns a function parameter represented with a SQL type. */ | ||
| static FunctionParameter toSql(FunctionParameter parameter) { | ||
| return new IgniteFunctionParameter(parameter); | ||
| } | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override public int getOrdinal() { | ||
| return delegate.getOrdinal(); | ||
| } | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override public String getName() { | ||
| return delegate.getName(); | ||
| } | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override public RelDataType getType(RelDataTypeFactory typeFactory) { | ||
| JavaTypeFactory tf = (JavaTypeFactory)typeFactory; | ||
| RelDataType type = tf.toSql(delegate.getType(typeFactory)); | ||
|
|
||
| // Prevent the validator from replacing OTHER with a structured type derived from a dynamic parameter value. | ||
| return type.getSqlTypeName() == SqlTypeName.OTHER ? new OtherType(type.isNullable()) : type; | ||
|
zstan marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override public boolean isOptional() { | ||
| return delegate.isOptional(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dead code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reverse branch in fromInternal is reachable through
RexImpTable.defineReflective. I verified this with an operator backed bybinaryLength(byte[]): with the conversion, a binary literal works; without it, generated code fails to compile because it passesByteStringto a method expectingbyte[]. I suggest keeping this branch.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, but still miss it ( do we have a test for it ? suggest it plz ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a
org.apache.ignite.internal.processors.query.calcite.integration.OperatorsExtensionIntegrationTest#testByteArrayFunctionsthat reproduces the issue if this is removed.