TypeExpressionEvaluator narrows a derived type parameter with Math.toIntExact, which checks only that the value fits in 32 bits. Nothing checks it against the domain the type class actually allows, and io.substrait.type.Type has no @Value.Check on any of these classes, so TypeCreator builds the type and TypeProtoConverter serializes it straight into the plan.
site/docs/types/type_classes.md requires VARCHAR<L>, FIXEDCHAR<L> and FIXEDBINARY<L> to have L within [1..2,147,483,647], and DECIMAL<P, S> to have P <= 38 with 0 <= S <= P.
Measured on main at cf581f4, with a declared argument varchar<L> bound to varchar(10):
| Return expression |
Derived type |
varchar<0> |
VarChar{length=0} |
decimal<100, 0> |
Decimal{precision=100, scale=0} |
decimal<10, 20> |
Decimal{precision=10, scale=20} |
varchar<L - 20> |
VarChar{length=-10} |
varchar<L - 10> |
VarChar{length=0} |
decimal<L * 10, L * 2> |
Decimal{precision=100, scale=20} |
interval_day<0 - L> |
IntervalDay{precision=-10} |
precision_timestamp<L * 10> |
PrecisionTimestamp{precision=100} |
The first three need no arithmetic and predate #1288; the rest became reachable with it, because before that a parameter could only be a literal or a value bound from an actual argument type, which is in range by construction. No shipped catalog declaration produces an out-of-range result — every decimal formula clamps with min(..., 38) — so the exposure is hand-written and future declarations, and any already-invalid type read back from proto.
A @Value.Check on Type.Decimal, Type.VarChar, Type.FixedChar, Type.FixedBinary and the precision-carrying temporal classes would cover the proto-read path too, where a check in the evaluator covers only that one caller. Note the interval classes are the subject of #1129, and the isthmus side of the same defect class is #1253; this is about io.substrait.type.Type itself.
TypeExpressionEvaluatornarrows a derived type parameter withMath.toIntExact, which checks only that the value fits in 32 bits. Nothing checks it against the domain the type class actually allows, andio.substrait.type.Typehas no@Value.Checkon any of these classes, soTypeCreatorbuilds the type andTypeProtoConverterserializes it straight into the plan.site/docs/types/type_classes.mdrequiresVARCHAR<L>,FIXEDCHAR<L>andFIXEDBINARY<L>to have L within [1..2,147,483,647], andDECIMAL<P, S>to have P <= 38 with 0 <= S <= P.Measured on
mainat cf581f4, with a declared argumentvarchar<L>bound tovarchar(10):varchar<0>VarChar{length=0}decimal<100, 0>Decimal{precision=100, scale=0}decimal<10, 20>Decimal{precision=10, scale=20}varchar<L - 20>VarChar{length=-10}varchar<L - 10>VarChar{length=0}decimal<L * 10, L * 2>Decimal{precision=100, scale=20}interval_day<0 - L>IntervalDay{precision=-10}precision_timestamp<L * 10>PrecisionTimestamp{precision=100}The first three need no arithmetic and predate #1288; the rest became reachable with it, because before that a parameter could only be a literal or a value bound from an actual argument type, which is in range by construction. No shipped catalog declaration produces an out-of-range result — every decimal formula clamps with
min(..., 38)— so the exposure is hand-written and future declarations, and any already-invalid type read back from proto.A
@Value.CheckonType.Decimal,Type.VarChar,Type.FixedChar,Type.FixedBinaryand the precision-carrying temporal classes would cover the proto-read path too, where a check in the evaluator covers only that one caller. Note the interval classes are the subject of #1129, and the isthmus side of the same defect class is #1253; this is aboutio.substrait.type.Typeitself.