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).
visit(NamedScan)builds the relation from the name alone:When no catalog is supplied,
SchemaCollectorbuilds one out of the plan and readsgetInitialSchema()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_schemain 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 inPlan.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:
visit(NamedScan)is unchanged on main at ee9f3d2, and this plan carries no projection, so #1280 does not touch it.root.rel.getRowType()(c0 bigint, c1 bigint)[c0 BIGINT, c1 BIGINT]— the catalog's nullablec0is 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.validatedRowTypeandroot.fieldscarryc0,c1, applied by position(c0 bigint, c1 bigint, c2 bigint)IndexOutOfBoundsException: Index 2 out of bounds for length 2, fromrenameFieldsOnly 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
filterandprojectionare dropped) and #1178 (the tworenameFieldswalkers).