SqlExpressionToSubstrait builds one base_schema from the concatenation of every registered table, keyed by bare column name, so two tables that share any column name are rejected outright — even when the column the expression actually references is unambiguous. There is no qualified-reference escape hatch: validateParameterizedExpression builds a ParameterScope over EmptyScope and resolves every identifier through nameToNodeMap alone, so the rootSchema.add(t.getName(), t) registration is never consulted.
Both halves reproduce:
// (a) an unambiguous reference, rejected because an *unreferenced* column name collides
new SqlExpressionToSubstrait().convert(
"X", List.of("CREATE TABLE A (ID BIGINT, X BIGINT)", "CREATE TABLE B (ID BIGINT, Y BIGINT)"));
// java.lang.IllegalArgumentException: There is no support for duplicate column names: ID
// (b) the natural workaround
new SqlExpressionToSubstrait().convert(
"A.X", List.of("CREATE TABLE A (X BIGINT)", "CREATE TABLE B (Y BIGINT)"));
// org.apache.calcite.runtime.CalciteContextException: At line 1, column 1: Unknown identifier 'A'
Between them, the commonest real schema pair — two tables each carrying ID, or NAME — cannot be used at all. This is reachable from the CLI as well, where -c/--create is a repeatable option. The error message also names only the colliding column and neither table it came from, which is the part a caller needs in order to act.
Measured on main at ade72bc.
Worth deciding what the intended contract is: reject only a collision the expression actually references, support qualified references, or state the global-uniqueness requirement in convert's Javadoc. Related: #1256 covers a separate gap in the same method's base schema.
SqlExpressionToSubstraitbuilds onebase_schemafrom the concatenation of every registered table, keyed by bare column name, so two tables that share any column name are rejected outright — even when the column the expression actually references is unambiguous. There is no qualified-reference escape hatch:validateParameterizedExpressionbuilds aParameterScopeoverEmptyScopeand resolves every identifier throughnameToNodeMapalone, so therootSchema.add(t.getName(), t)registration is never consulted.Both halves reproduce:
Between them, the commonest real schema pair — two tables each carrying
ID, orNAME— cannot be used at all. This is reachable from the CLI as well, where-c/--createis a repeatable option. The error message also names only the colliding column and neither table it came from, which is the part a caller needs in order to act.Measured on
mainat ade72bc.Worth deciding what the intended contract is: reject only a collision the expression actually references, support qualified references, or state the global-uniqueness requirement in
convert's Javadoc. Related: #1256 covers a separate gap in the same method's base schema.