Skip to content

core: ProtoPlanConverter reads every PlanRel as a root, so a plan with a bare relation is rejected whole #1297

Description

@alexandrefimov

ProtoPlanConverter.from reads every PlanRel as a root without asking which arm of the oneof is set, so a plan carrying a bare relation is rejected whole. ProtoPlanConverter.java:97-99 at v0.103.0, and the same three lines on main at ee9f3d20:

for (PlanRel planRel : plan.getRelationsList()) {
  io.substrait.proto.RelRoot root = planRel.getRoot();
  Rel rel = relConverter.from(root.getInput());

On the rel arm getRoot() returns the default instance, so relConverter is handed an empty Rel and reports that faithfully. The message names RELTYPE_NOT_SET because that is what it was given, not what the plan holds:

ROOT   -> 1 root(s)
REL    -> UnsupportedOperationException: Unsupported RelTypeCase of RELTYPE_NOT_SET

substrait-go produces this shape: its Plan.ToProto calls ToProtoPlanRel per relation and emits PlanRel_Rel for every relation that is not a root, at plan/plan.go:132-135. So this is two implementations that cannot exchange a plan, rather than a shape only a hand-built proto reaches.

Only reading is affected. PlanProtoConverter walks plan.getRoots() and always calls setRoot, so substrait-java never writes the arm it cannot read.

The fix worth agreeing on before writing it: skipping bare relations would turn today's loud failure into a quiet one, dropping relations and leaving any ReferenceRel pointing at them dangling. Checking the arm and refusing with an accurate message is smaller and loses nothing. Holding them properly means somewhere for them to live in io.substrait.plan.Plan, which today has only roots. Which of the two would you want?

Reproducer

import io.substrait.proto.*;
import io.substrait.plan.ProtoPlanConverter;

public final class Repro {
  public static void main(String[] args) {
    Rel read = Rel.newBuilder().setRead(ReadRel.newBuilder()
        .setNamedTable(ReadRel.NamedTable.newBuilder().addNames("t"))).build();
    for (PlanRel pr : new PlanRel[] {
        PlanRel.newBuilder().setRoot(RelRoot.newBuilder().setInput(read)).build(),
        PlanRel.newBuilder().setRel(read).build()}) {
      Plan p = Plan.newBuilder().addRelations(pr).build();
      try {
        System.out.printf("%-6s -> %d root(s)%n", pr.getRelTypeCase(),
            new ProtoPlanConverter().from(p).getRoots().size());
      } catch (Throwable t) {
        System.out.printf("%-6s -> %s: %s%n", pr.getRelTypeCase(),
            t.getClass().getSimpleName(), t.getMessage());
      }
    }
  }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions