Skip to content
Open
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 @@ -341,6 +341,54 @@
return expressionHandlingSafe(convert3, safe, targetType);
}

/** Converts a ROW value to another ROW type, field by field. */
private Expression getRowConvertExpression(
RelDataType sourceType,
RelDataType targetType,
Expression operand,
ConstantExpression format) {
if (sourceType.getSqlTypeName() == SqlTypeName.NULL) {
return Expressions.constant(null);
}
assert sourceType.getSqlTypeName() == SqlTypeName.ROW;
List<RelDataTypeField> targetTypes = targetType.getFieldList();
List<RelDataTypeField> sourceTypes = sourceType.getFieldList();
assert targetTypes.size() == sourceTypes.size();
List<Expression> fields = new ArrayList<>();
for (int i = 0; i < targetTypes.size(); i++) {
RelDataTypeField targetField = targetTypes.get(i);
RelDataTypeField sourceField = sourceTypes.get(i);
Expression field = Expressions.arrayIndex(operand, Expressions.constant(i));
// In the generated Java code 'field' is an Object,
// we need to also cast it to the correct type to enable correct method dispatch in Java.
// We force the type to be nullable; this way, instead of (int) we get (Integer).
// Casting an object to an int is not legal.
RelDataType nullableSourceFieldType =
typeFactory.createTypeWithNullability(sourceField.getType(), true);
Type javaType = typeFactory.getJavaClass(nullableSourceFieldType);
if (nullableSourceFieldType.isStruct()) {
// A struct field is represented as Object[] at runtime;
// the recursive conversion below indexes into the field, which
// requires an array-typed operand.
field = Expressions.convert_(field, Object[].class);
} else if (!javaType.getTypeName().equals("java.lang.Void")) {
// Cannot cast to Void - this is the type of NULL literals.
field = Expressions.convert_(field, javaType);
}
Expression convert =
getConvertExpression(sourceField.getType(), targetField.getType(), field, format);
if (sourceField.getType().isNullable()) {
// field == null ? field : convert
convert =
Expressions.condition(
Expressions.equal(field, Expressions.constant(null)),
Expressions.constant(null), convert);
}
fields.add(convert);
}
return Expressions.call(BuiltInMethod.ARRAY.method, fields);
}

private Expression getConvertExpression(
RelDataType sourceType,
RelDataType targetType,
Expand All @@ -366,32 +414,7 @@
}

if (targetType.getSqlTypeName() == SqlTypeName.ROW) {
assert sourceType.getSqlTypeName() == SqlTypeName.ROW;
List<RelDataTypeField> targetTypes = targetType.getFieldList();
List<RelDataTypeField> sourceTypes = sourceType.getFieldList();
assert targetTypes.size() == sourceTypes.size();
List<Expression> fields = new ArrayList<>();
for (int i = 0; i < targetTypes.size(); i++) {
RelDataTypeField targetField = targetTypes.get(i);
RelDataTypeField sourceField = sourceTypes.get(i);
Expression field = Expressions.arrayIndex(operand, Expressions.constant(i));
// In the generated Java code 'field' is an Object,
// we need to also cast it to the correct type to enable correct method dispatch in Java.
// We force the type to be nullable; this way, instead of (int) we get (Integer).
// Casting an object ot an int is not legal.
RelDataType nullableSourceFieldType =
typeFactory.createTypeWithNullability(sourceField.getType(), true);
Type javaType = typeFactory.getJavaClass(nullableSourceFieldType);
if (!javaType.getTypeName().equals("java.lang.Void")
&& !nullableSourceFieldType.isStruct()) {
// Cannot cast to Void - this is the type of NULL literals.
field = Expressions.convert_(field, javaType);
}
Expression convert =
getConvertExpression(sourceField.getType(), targetField.getType(), field, format);
fields.add(convert);
}
return Expressions.call(BuiltInMethod.ARRAY.method, fields);
return getRowConvertExpression(sourceType, targetType, operand, format);
}

switch (targetType.getSqlTypeName()) {
Expand Down Expand Up @@ -703,7 +726,7 @@
}
}

private static Expression checkExpressionPadTruncate(

Check warning on line 729 in core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "Brain Method" was detected. Refactor it to reduce at least one of the following metrics: LOC from 77 to 64, Complexity from 33 to 14, Nesting Level from 3 to 2, Number of Variables from 12 to 6.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ-mQtAcFOTIYb4JvNo5&open=AZ-mQtAcFOTIYb4JvNo5&pullRequest=5128
Expression operand,
RelDataType sourceType,
RelDataType targetType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,33 @@
final SyntheticRecordType syntheticType =
new SyntheticRecordType(type, name);
for (final RelDataTypeField recordField : type.getFieldList()) {
final Type javaClass = getJavaClass(recordField.getType());
final Type fieldClass = getJavaClass(recordField.getType());
// A field whose type has no real Java class is stored as Object[] at
// runtime, like all rows in enumerable convention. For example, the
// element type of ARRAY[ROW(ROW(1, 'a'), 10), NULL] becomes a "synthetic" type
// named Record2_0
// public static class Record2_0 implements java.io.Serializable {

Check warning on line 373 in core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ-mQs5rFOTIYb4JvNo4&open=AZ-mQs5rFOTIYb4JvNo4&pullRequest=5128
// public Object[] EXPR$0; // the nested row, e.g. {1, 'a'}
// public Integer EXPR$1;
// ...equals, hashCode, compareTo, toString...
// }
// If EXPR$0 would also have a synthetic type,
// this would generate nested synthetic classes, which
// EnumerableRelImplementor#classDecl cannot emit.
//
// A field whose type maps to a real Java class (e.g. a bean from a
// ReflectiveSchema) uses its own Java class.
//
// 'instanceof Class' distinguishes the two cases: getJavaClass
// returns a java.lang.reflect.Type, which is a loaded
// java.lang.Class for most SQL types. For a record type with no
// Java class (here the nested row's type, which maps to its own
// Record2_N), it is a SyntheticRecordType: a description of a class
// that is only generated and compiled together with the query, so no
// Class object exists for it.
final Type javaClass = fieldClass instanceof Class
? fieldClass
: Object[].class;
syntheticType.fields.add(
new RecordFieldImpl(
syntheticType,
Expand Down
48 changes: 48 additions & 0 deletions core/src/test/resources/sql/cast.iq
Original file line number Diff line number Diff line change
Expand Up @@ -2031,4 +2031,52 @@ values (cast(multiset[null] as integer multiset));

!ok

# Tests for [CALCITE-7677] CAST between ROW types fails at runtime
# https://issues.apache.org/jira/browse/CALCITE-7677
!use scott

SELECT ARRAY[ROW(1, 'Alice'), ROW(NULL, 'Dan')] AS people
FROM (VALUES (0)) AS t(zero);
+---------------------------+
| PEOPLE |
+---------------------------+
| [{1, Alice}, {null, Dan}] |
+---------------------------+
(1 row)

!ok

SELECT CAST(ROW(ROW(2, 'b'), 20) AS ROW(a ROW(x INTEGER, y CHAR(1)), b INTEGER)) AS r
FROM (VALUES (0)) AS t(zero);
+--------------+
| R |
+--------------+
| {{2, b}, 20} |
+--------------+
(1 row)

!ok

SELECT CAST(ROW(NULL, 30) AS ROW(a ROW(x INTEGER, y CHAR(1)), b INTEGER)) AS r
FROM (VALUES (0)) AS t(zero);
+------------+
| R |
+------------+
| {null, 30} |
+------------+
(1 row)

!ok

SELECT ARRAY[ROW(ROW(1, 'a'), 10), NULL] AS xs
FROM (VALUES (0)) AS t(zero);
+----------------------+
| XS |
+----------------------+
| [{{1, a}, 10}, null] |
+----------------------+
(1 row)

!ok

# End cast.iq
Loading