Skip to content

IGNITE-29031 SQL Calcite: Support byte[] in UDF and UDTF parameters and results - #13543

Open
tkalkirill wants to merge 9 commits into
masterfrom
ignite-29031
Open

IGNITE-29031 SQL Calcite: Support byte[] in UDF and UDTF parameters and results#13543
tkalkirill wants to merge 9 commits into
masterfrom
ignite-29031

Conversation

@tkalkirill

Copy link
Copy Markdown
Contributor

@zstan

zstan commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

First of all you need to follow the common process [1] i.e. make a PR from your own ignite mirror, not from origin = https://github.com/apache but from: https://github.com/tkalkirill/ignite

[1] https://cwiki.apache.org/confluence/spaces/IGNITE/pages/177047163/How+to+Contribute#HowtoContribute-GITworkflow

@tkalkirill

Copy link
Copy Markdown
Contributor Author

@zstan Okay, subsequent tickets will do as described.

if (isA(fromType, Primitive.LONG))
return Expressions.call(BuiltInMethod.INTERNAL_TO_TIMESTAMP.method, operand);
}
else if (targetType == byte[].class && fromType == ByteString.class)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dead code

Copy link
Copy Markdown
Contributor Author

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 by binaryLength(byte[]): with the conversion, a binary literal works; without it, generated code fails to compile because it passes ByteString to a method expecting byte[]. I suggest keeping this branch.

Copy link
Copy Markdown
Contributor

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 ?

Copy link
Copy Markdown
Contributor Author

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#testByteArrayFunctions that reproduces the issue if this is removed.

assertNotSame(row, res);
assertEquals(1, res.length);
assertSame(val, res[0]);
assertSame(val, row[0]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

redundant

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why?

if (isA(fromType, Primitive.LONG))
return Expressions.call(BuiltInMethod.INTERNAL_TO_TIMESTAMP.method, operand);
}
else if (targetType == byte[].class && fromType == ByteString.class)

Copy link
Copy Markdown
Contributor

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 ?

}

/** */
static List<Expression> fromInternal(RexToLixTranslator translator,

@zstan zstan Sep 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Look now you have two identical code, differs only with "RexToLixTranslator translator" param
i mean:
ConverterUtils#fromInternal(java.lang.Class[], List) and ConverterUtils#fromInternal(RexToLixTranslator, java.lang.Class[], java.util.List)

seems you can just rewrite your code like :

    private static Expression fromInternal(@Nullable Expression root, Expression operand, Type targetType) {
        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);

        Primitive primitive = Primitive.of(targetType);

        if (Primitive.is(operand.getType()))
            operand = Expressions.box(operand);

        Expression converted = Expressions.call(
            TypeUtils.class,
            "fromInternal",
            root,//translator.getRoot(),
            operand,
            Expressions.constant(targetType)
        );

        return primitive == null
            ? Expressions.convert_(converted, targetType)
            : Expressions.unbox(Expressions.convert_(converted, primitive.boxClass), primitive);
    }

probably some assertions check are helpful, it`s just a prototype
I run this approach through all calcite tests and it`s ok
wdyt ? props: more readable code, less code base

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I did it a bit differently, and the duplication went away.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

One or more issues must be addressed before approval.

Pull request overview

Adds SQL Calcite support for byte[] UDF/UDTF parameters and results, with broader conversion coverage for temporal, primitive, collection, and custom types.

Changes:

  • Adds internal/public binary and temporal value conversions.
  • Updates reflective scalar and table functions to expose SQL-compatible types.
  • Expands integration and execution tests.
File summaries
File Description
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java Updated as part of this pull request.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java Updated as part of this pull request.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteTableFunction.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteScalarFunction.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteReflectiveFunctionBase.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java Updated as part of this pull request.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java Updated as part of this pull request.
Review details

Suppressed comments (1)

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java:73

  • Using an exact-class check rejects valid array row holders whose runtime type is a subtype of Object[] (for example String[] or byte[][]). These values satisfy the method's documented Object[] row contract but now fail with IgniteSQLException before column conversion; use an Object[] assignability/instanceof check while still cloning the array.
        if (rowContainer.getClass() != Object[].class && !Collection.class.isAssignableFrom(rowContainer.getClass()))
            throw new IgniteSQLException("Unable to process table function data: row type is neither Collection or Object[].");

        Object[] rowArr = rowContainer.getClass() == Object[].class
            ? ((Object[])rowContainer).clone()
  • Files reviewed: 12/12 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +282 to +283
if (fromType == ByteString.class && toType == byte[].class)
return Expressions.call(BuiltInMethod.BYTE_STRING_TO_BYTE_ARRAY.method, operand);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these conversion still has no test coverage, isn`t it ?

* <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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 ?


return primitive == null
? Expressions.convert_(converted, targetType)
: Expressions.unbox(Expressions.convert_(converted, primitive.boxClass), primitive);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

redundant ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants