ProtoRelConverter.newJoin decodes rel.getExpression() without checking hasExpression() (core/src/main/java/io/substrait/relation/ProtoRelConverter.java:1015), while Join.getCondition() is Optional and RelProtoConverter leaves expression unset when it is empty. A Join built without a condition therefore serializes to a JoinRel that substrait-java cannot read back:
Join join =
Join.builder()
.left(t1)
.right(t2)
.joinType(Join.JoinType.LEFT)
.postJoinFilter(sb.bool(true))
.build();
io.substrait.proto.Rel proto = relProtoConverter.toProto(join); // getJoin().hasExpression() == false
protoRelConverter.from(proto); // IllegalArgumentException: Unknown type: REXTYPE_NOT_SET
The spec makes the join expression required ("Required. Can be the literal True.", site/docs/relations/logical_relations.md, v0.102.0), so the encoder is producing an out-of-spec plan. Either emit literal true when the condition is empty, or make the condition non-optional. newLateralJoin guards the same field and round-trips.
Measured on main at ca8315b. Found while reviewing #1260.
ProtoRelConverter.newJoindecodesrel.getExpression()without checkinghasExpression()(core/src/main/java/io/substrait/relation/ProtoRelConverter.java:1015), whileJoin.getCondition()isOptionalandRelProtoConverterleavesexpressionunset when it is empty. AJoinbuilt without a condition therefore serializes to aJoinRelthat substrait-java cannot read back:The spec makes the join expression required ("Required. Can be the literal True.",
site/docs/relations/logical_relations.md, v0.102.0), so the encoder is producing an out-of-spec plan. Either emit literaltruewhen the condition is empty, or make the condition non-optional.newLateralJoinguards the same field and round-trips.Measured on
mainat ca8315b. Found while reviewing #1260.