Skip to content

isthmus: the configured type system does not reach the SQL-generation or cluster legs #1220

Description

@nielspardon

SubstraitTypeSystem is the type system isthmus configures, and ConverterProvider.Builder.typeFactory(RelDataTypeFactory) lets a caller supply their own. Two legs of isthmus consult neither, so a limit the configured type system states is not the limit those legs apply.

Substrait → SQL still caps at Calcite's default. SubstraitSqlDialect.DEFAULT_CONTEXT is SqlDialect.EMPTY_CONTEXT, and Calcite's emptyContext() pins RelDataTypeSystemImpl.DEFAULT; SqlDialect.getCastSpec then clips a CHAR/VARCHAR precision to getTypeSystem().getMaxPrecision(...), i.e. 65536. So a varchar<100000> renders as CAST(… AS VARCHAR(65536)) — a truncating cast that declares a width the plan does not. getCastSpec does not switch on BINARY, so fixed_binary<100000> renders faithfully as BINARY(100000) and the three length-carrying types disagree with each other. DECIMAL has the same shape and predates any of this: decimal<38,10> renders as DECIMAL(19,10), since getCastSpec reads getMaxPrecision/getMaxScale from the default system there too.

The fix is a context carrying the type system isthmus already has:

public static final SqlDialect.Context DEFAULT_CONTEXT =
    SqlDialect.EMPTY_CONTEXT.withDataTypeSystem(SubstraitTypeSystem.TYPE_SYSTEM);

The SQL → Substrait cluster ignores the provider's factory. SubstraitSqlToCalcite.createDefaultRelOptCluster() is static, takes no argument, and hardcodes new RexBuilder(new JavaTypeFactoryImpl(SubstraitTypeSystem.TYPE_SYSTEM)). The provider-aware overloads call it without the provider, while the validator does get the provider's factory, through the catalog reader. The two therefore derive types under different type systems for the same query. SqlConverterBase does build a cluster from the provider's factory, but that field is consumed only by the extended-expression path, so SqlToSubstrait.convert gets the hardcoded one.

While the two type systems agree on a bound this is invisible. Once they do not, SqlToRelConverter.checkConvertedType compares the validated and converted row types and throws:

java.lang.AssertionError: Conversion to relational algebra failed to preserve datatypes:
validated type:   RecordType(VARCHAR EXPR$0) NOT NULL
converted type:   RecordType(VARCHAR(80000) EXPR$0) NOT NULL

for SELECT a || b FROM t over t(a VARCHAR(40000), b VARCHAR(40000)) — no wide type anywhere — with a provider built on new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT). That is an explicit throw new AssertionError, not a bare assert, so it is not -ea-dependent. This half is live only once getMaxPrecision diverges between the two, which #1169 is what does; it is raised on that PR and may be fixed there, in which case only the dialect half of this issue remains.

Same shape as #1219, which is the type mapper rather than the type system: a decision taken on one leg with no hook on the other. Not folded in — the mapper has its own interface question.

ConverterProviderBuilderTest never calls .typeFactory(...), so nothing currently covers a provider whose type system is not Substrait's; that is the gap that leaves both halves invisible.

Checked on #1169's head 880090ae and its base.

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