Skip to content

fix(isthmus)!: cast operands until the function's declaration binds them - #1348

Merged
nielspardon merged 3 commits into
substrait-io:mainfrom
alexandrefimov:issue-1117-operand-casts
Oct 1, 2026
Merged

nielspardon merged 3 commits into
substrait-io:mainfrom
alexandrefimov:issue-1117-operand-casts

Conversation

@alexandrefimov

@alexandrefimov alexandrefimov commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Some calls had operands their declaration does not bind: decimal(7,2) * INT cast the integer to decimal(12,2), d BETWEEN 0.99 AND 1.49 mixed two decimal precisions under one any1, and char(n) or unbounded varchar reached declarations for another string type. Operands are now cast just far enough to bind, trying each matching variant in turn and skipping one that would truncate a string result, such as concat:vchar for CHAR(10) || CHAR(10). In the TPC tests, non-binding calls go from 59 to 2.

Part of #1117.

BREAKING CHANGE: calls converted from Calcite get different operand casts, and some bind another variant, for example concat:str for c || c over chars and like:str_str for LIKE over an unbounded varchar. Consumers matching the old shape have to accept the new ones.

An integer operand of decimal arithmetic was cast to the least
restrictive decimal, whose scale it does not have, so decimal(7,2) * INT
multiplied by a decimal(12,2). It now becomes the decimal that holds its
type, decimal(10,0) for an INT. Where the operands share a parameter,
like the any1 of gte(any1, any1), and do not bind it, they take the
least restrictive type exactly; before, decimals of two precisions were
left as they were. A char(n) operand of a varchar declaration becomes a
varchar(n).
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: substrait-io/substrait-java/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 7f5914a9-54ee-448c-a208-dcac2da2bdcf

📥 Commits

Reviewing files that changed from the base of the PR and between 50a4505 and 486c612.

📒 Files selected for processing (2)
  • isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java
  • isthmus/src/test/java/io/substrait/isthmus/OperandCoercionTest.java

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved SQL function matching when multiple compatible variants are available, including calls with string and fixed-length character arguments.
    • Comparisons involving decimals of different precisions and decimal multiplication with integer values now resolve with compatible argument types.
    • Decimal bounds retain their scale when arguments are coerced, and string results are checked against the length required by the call.

Walkthrough

FunctionConverter now checks multiple matching function declarations and tries alternate operand coercions. It validates declaration argument types and, when resolvable, string result length. Tests cover decimal and character-string operand conversions.

Changes

Function operand coercion

Layer / File(s) Summary
Operand coercion and declaration binding
isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java
Matching collects candidate declarations and checks their bindings against coerced operands. It tries fixed-character-to-varchar and declared-string conversions. Least-restrictive matching can retry with a lossless common type; decimal precision is limited to 38. Binding checks validate declared argument types and, when resolvable, require the declaration result string length to be at least the call result length.
Operand coercion tests
isthmus/src/test/java/io/substrait/isthmus/OperandCoercionTest.java
Tests inspect converted argument types for decimal comparisons and multiplication, BETWEEN, LIKE, concatenation, and REPLACE. Selected tests also check declaration resolution.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: nielspardon

Merge Risk: ⚪ Minimal · up to 486c6

The change improves decimal and string operand binding. No actionable merge-blocking issue remains; merge after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 486c6

The main risk is compatibility with consumers that depend on the previous generated plan shape. The inspected conversion path remains limited to already supplied function declarations. Downstream execution permissions and deployment-specific exposure were not established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Caller-provided expression types influence casts and selection among declarations already supplied to the converter. The supported immediate exposure is changed generated invocation shape, not new execution authority. Tenant, environment, and data-store exposure depend on downstream executors that were not established by this review.

Trust Boundaries and Controls

  • observed — Function availability remains determined by constructor-supplied declarations and registered Calcite signatures. Alternate matching does not populate a new registry; scalar, aggregate, and window converters retain their existing invocation-construction ownership. These are conversion constraints, not evidence of executor-side authorization.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.87% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, specific, and uses valid Conventional Commit syntax. It accurately describes the main change: casting operands until the function declaration binds them.
Description check ✅ Passed The description explains the rationale, summarizes the behavior changes with concrete examples, identifies the breaking-change impact, and references the related issue. It satisfies the provided templ…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…s them

Calcite's least restrictive decimal gives up scale past precision 38, so
TPC-H Q6's l_discount, a bare DECIMAL, met 0.03 - 0.01 at decimal(38,0)
and the bound became 0. The common decimal is now built from the most
integer digits and the largest scale, and operands are left as they are
when that needs more than 38 digits.

@nielspardon nielspardon left a comment

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.

Please mark this fix(isthmus)!: with a BREAKING CHANGE: footer, as in #1346 and #1347: it changes the casts in plans that already converted.

Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java Outdated
Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java Outdated
Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java Outdated
Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java Outdated
Comment thread isthmus/src/main/java/io/substrait/isthmus/expression/FunctionConverter.java Outdated
Comment thread isthmus/src/test/java/io/substrait/isthmus/OperandCoercionTest.java Outdated
…ating

A signature match took the first variant whatever it bound. Each matching
variant is now tried with the operands as they are, with char(n) as
varchar(n), and with char and varchar as string where the declaration
takes a string, and the first that binds is taken. Binding now also
requires a concretely declared argument to have exactly its type, and a
string result no shorter than the call's, so CHAR(10) || CHAR(10) binds
concat:str rather than a concat:vchar that types it varchar(10).

An integer operand's digits come from its Calcite type.
@alexandrefimov alexandrefimov changed the title fix(isthmus): cast operands until the function's declaration binds them fix(isthmus)!: cast operands until the function's declaration binds them Sep 30, 2026

@nielspardon nielspardon left a comment

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.

LGTM

@nielspardon
nielspardon merged commit bac6b43 into substrait-io:main Oct 1, 2026
14 of 16 checks passed
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.

2 participants