Skip to content

fix(isthmus)!: use schema types for virtual table literals - #1065

Open
alexandrefimov wants to merge 2 commits into
substrait-io:mainfrom
alexandrefimov:codex/isthmus-values-schema-literals
Open

fix(isthmus)!: use schema types for virtual table literals#1065
alexandrefimov wants to merge 2 commits into
substrait-io:mainfrom
alexandrefimov:codex/isthmus-values-schema-literals

Conversation

@alexandrefimov

@alexandrefimov alexandrefimov commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • convert LogicalValues tuple literals using the complete row-schema field type;
  • preserve the existing literal-based behavior for other expression conversions;
  • add a regression test for a TINYINT tuple literal in an INTEGER field.

VirtualTableScan requires row field types to exactly match its schema. Calcite
may infer a narrower type for a tuple literal than for the corresponding
LogicalValues row field — Values.assertRowType() only asserts that the field
type canAssignFrom the literal type, so a TINYINT literal is a legal member of
an INTEGER row. Previously Isthmus copied only schema nullability, so the
conversion could construct an I8 row under an I32 schema and fail its own
validation.

LiteralConverter.convert now takes the schema RelDataType, and derives
nullability from it rather than from a separate flag. Existing callers continue to
derive the result type from the literal itself.

Fixes #1064

BREAKING CHANGE: io.substrait.isthmus.expression.LiteralConverter#convert(RexLiteral, boolean) is removed. Use convert(RexLiteral, RelDataType), which takes nullability from the supplied Calcite type; callers needing a nullability other than the literal's own should widen the type with RelDataTypeFactory#createTypeWithNullability first.

@alexandrefimov

Copy link
Copy Markdown
Contributor Author

@nielspardon would you mind taking a look at this one? Small isthmus fix for #1064: VirtualTableScan requires row field types to match its schema exactly, but Calcite can infer a narrower type for a tuple literal than for the corresponding LogicalValues row field — so the conversion could build an I8 row under an I32 schema and then fail its own validation.

@@ -108,14 +107,34 @@ public Expression.Literal convert(RexLiteral literal) {
* @return the converted Substrait Literal
*/
public Expression.Literal convert(RexLiteral literal, boolean nullable) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would advocate for getting rid of this method entirely actually.

It's not used internally after your changes, and any user that wants to set the nullability explicitly on the Calcite type can use the RelDataTypeFactory#createTypeWithNullability to set it, and then pass it to the new method below.

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.

Removed. It has been public since #684, so the PR is now fix(isthmus)! with a footer pointing at convert(RexLiteral, RelDataType).

Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/LiteralConverter.java Outdated
@vbarua

vbarua commented Aug 6, 2026

Copy link
Copy Markdown
Member

To confirm two things.

In your test, you construct a LogicalValue containing literals with different but compatible(?) types than the row type of the LogicalValue relation, and that ends up being valid because Calcite checks if the type can be assigned to value?
https://github.com/apache/calcite/blob/052a35015c56db2927727afd7e08ed7a18a26ace/core/src/main/java/org/apache/calcite/rel/core/Values.java#L169-L190. The issue that you've identified is that Calcite isn't as strict as Substrait, so when we convert from Calcite we can't rely on the literal types and need to override them with the row type?

In Substrait, we assume that all literals in VirtualTables have the same types as given in the schema. When we convert from Substrait to Calcite, do the tuple values in the Calcite LogicalValues relation this, or is that conversion also potentially lossy?

@alexandrefimov alexandrefimov changed the title fix(isthmus): use schema types for virtual table literals fix(isthmus)!: use schema types for virtual table literals Aug 7, 2026
@alexandrefimov

Copy link
Copy Markdown
Contributor Author

@vbarua both confirmed.

On strictness — the asymmetry is canAssignFrom against VirtualTableScan's @Value.Check, which requires rowFieldType.equals(schemaFieldType), nullability included.

The reverse direction isn't lossy, and structurally so: that same check makes a mismatched VirtualTableScan unconstructible, so nothing reaches the converter to lose. visit(VirtualTableScan) then derives the row type and every literal from the same TypeConverter over the same Substrait types. Checked i8, i32, i64, decimal(10,2), varchar(5), char(3) and string — every literal type is equal to its row field type, not merely assignable.

Both inline suggestions applied; the javadoc now points at createTypeWithNullability for callers wanting a nullability other than the literal's own. That does make it an API-visible removal — convert(RexLiteral, boolean) has been public since #684, so by the standard just applied in #1014 it's breaking. Retitled fix(isthmus)! with a footer; releaseRules maps that to a minor bump. Say the word if you'd rather split the removal out and keep this one non-breaking.

@alexandrefimov

alexandrefimov commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@vbarua ping when you get a chance. The overload is gone, so the PR is now fix(isthmus)! with a footer pointing at convert(RexLiteral, RelDataType) — that thread is left open because the call on removing a public API is yours.

Use the complete LogicalValues row-schema field type when converting tuple literals so virtual table rows match their schema. Preserve literal-derived behavior for other conversions and cover TINYINT-to-INTEGER widening.

Fixes substrait-io#1064
convert(RexLiteral, boolean) has no callers left now that virtual table
literals carry the schema type, and its nullability argument duplicates
what the Calcite result type already expresses.

BREAKING CHANGE: io.substrait.isthmus.expression.LiteralConverter#convert(RexLiteral, boolean)
is removed. Use convert(RexLiteral, RelDataType), which takes nullability from the supplied
Calcite type; callers needing a nullability other than the literal's own should widen the type
with RelDataTypeFactory#createTypeWithNullability first.
@alexandrefimov
alexandrefimov force-pushed the codex/isthmus-values-schema-literals branch from 697ad4e to 4940dd8 Compare August 10, 2026 20:14
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.

LogicalValues conversion fails when tuple literal type is narrower than row schema

2 participants