Skip to content

isthmus: a NamedScan's declared schema is not reconciled with the catalog it resolves against #1294

Description

@alexandrefimov

visit(NamedScan) builds the relation from the name alone:

RelNode node = relBuilder.scan(namedScan.getNames()).build();

When no catalog is supplied, SchemaCollector builds one out of the plan and reads getInitialSchema() for it; the comparison it makes there is between two reads of the same table within the plan, not between the plan and a catalog. When a catalog is supplied that path is not taken, and the catalog-backed conversion does not compare the catalog row type with the declared schema.

The spec makes the declared schema — base_schema in the proto — required, and gives it a job: "Direct Schema — Defines the schema of the output of the read (before any projection or emit remapping/hiding)". What it does not say is what a consumer should do when it resolves the name against a catalog whose schema differs.

Today it does nothing, quietly. Reproduced with Isthmus 0.103.0 using Impala's catalog reader, one plan throughout, declaring t_rn(c0 i64 not null, c1 i64) and naming both columns in Plan.Root.names:

{"relations":[{"root":{"input":{"read":{"baseSchema":{"names":["c0","c1"],
"struct":{"types":[{"i64":{"nullability":"NULLABILITY_REQUIRED"}},
{"i64":{"nullability":"NULLABILITY_NULLABLE"}}],"nullability":"NULLABILITY_REQUIRED"}},
"namedTable":{"names":["t_rn"]}}},"names":["c0","c1"]}}],
"version":{"minorNumber":102,"producer":"case-corpus"}}

The catalog is Impala's own, holding one table per row of the table below:

// t_rn is created in the catalog, then Impala's frontend is asked to analyse a
// statement naming it, which is where its CalciteCatalogReader comes from.
addTestTable("create table " + db + ".t_rn (c0 bigint, c1 bigint) stored as parquet");
CalciteCatalogReader reader = analyse("select 1 from t_rn", db).getCatalogReader();

// The provider's type factory is Impala's; getRelBuilder is overridden to build on
// Impala's RelOptCluster, so the relations are the ones its planner expects.
RelRoot root = new SubstraitToCalcite(provider, reader)
    .convert(new ProtoPlanConverter().from(protoPlan).getRoots().get(0));
root.rel.getRowType();

visit(NamedScan) is unchanged on main at ee9f3d2, and this plan carries no projection, so #1280 does not touch it.

catalog root.rel.getRowType()
(c0 bigint, c1 bigint) [c0 BIGINT, c1 BIGINT] — the catalog's nullable c0 is accepted despite the plan declaring it required
(c0 string, c1 bigint) [c0 VARCHAR, c1 BIGINT]
(c0 bigint) [c0 BIGINT]
(x0 bigint, x1 bigint) [x0 BIGINT, x1 BIGINT]; root.validatedRowType and root.fields carry c0, c1, applied by position
(c0 bigint, c1 bigint, c2 bigint) IndexOutOfBoundsException: Index 2 out of bounds for length 2, from renameFields

Only the last row says anything, and what it says is an index error out of the root renaming rather than a disagreement about a schema.

A consumer can compare the two itself, and the one I am measuring with does. But whether Isthmus should — reconcile, refuse, or hand the caller both and let it decide — seems worth settling once rather than in each consumer.

Related but distinct: #1204 (a read's filter and projection are dropped) and #1178 (the two renameFields walkers).

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