Jerome Isaac Haltom created CALCITE-7690:
--------------------------------------------
Summary: DELETE on a single-column table fails with "Cannot cast
java.lang.Object to int"
Key: CALCITE-7690
URL: https://issues.apache.org/jira/browse/CALCITE-7690
Project: Calcite
Issue Type: Bug
Affects Versions: 1.43.0
Reporter: Jerome Isaac Haltom
DELETE against a table with exactly one NOT NULL column of a primitive type
fails at runtime. Reproduced on master (fc95bb0e6) and on 1.43.0-SNAPSHOT;
1.42.0 is unaffected.
{code:sql}
create table t (i int not null);
insert into t values (1);
delete from t where i = 1;
{code}
{noformat}
java.lang.RuntimeException: Error while compiling generated Java code:
...
Caused by: org.codehaus.commons.compiler.CompileException: Cannot cast
"java.lang.Object" to "int"
{noformat}
h3. Cause
{{EnumerableTableModify.deleteFromCollection}} declares the sink row as
{{Object}} and then casts it to the table's Java row type:
{code:java}
final ParameterExpression sinkRow = Expressions.parameter(Object.class,
"sinkRow");
final Expression typedSinkRow =
Expressions.convert_(sinkRow, tablePhysType.getJavaRowType());
{code}
For a single-column table that row type is a primitive.
{{EnumerableTableScan.deduceFormat}} returns ARRAY, because the table's element
type is {{Object[]}}, and the optimising {{PhysTypeImpl.of}} then rewrites
ARRAY to SCALAR for a one-field row type, so {{getJavaRowType()}} is {{int}}.
The generated source is therefore {{(int) sinkRow}}.
{{(int) someObject}} is legal Java -- JLS 5.5 permits a narrowing reference
conversion followed by an unboxing conversion, and javac compiles it -- but
Janino does not implement it, and Janino is what compiles the generated code.
Measured against the Janino on the classpath:
{noformat}
(int) o -> Cannot cast "java.lang.Object" to "int"
(java.lang.Integer) o -> compiles
((java.lang.Integer) o).intValue() -> compiles
{noformat}
h3. Suggested fix
Box the target type:
{code:java}
final Expression typedSinkRow =
Expressions.convert_(sinkRow,
Primitive.box(tablePhysType.getJavaRowType()));
{code}
{{Primitive.box}} leaves {{Object[]}} unchanged, so the multi-column case
generates exactly what it generates today. The sink values are read with a
storage type of {{Object}}, so boxing loses nothing.
h3. Notes
Introduced by CALCITE-7510. Every test that change added uses a two-column
table ({{create table t (i int not null, j int not null)}}), so the
single-column shape was never exercised. A nullable single column is
unaffected, because its Java row type is already {{Integer}}.
I have a patch with tests in {{ServerTest}} and will open a PR.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)