feat(isthmus)!: make unquoted identifier casing configurable in ConverterProvider#983
Conversation
a3f5945 to
2285cd1
Compare
…rterProvider Add constructor-based configuration of unquoted SQL identifier casing to ConverterProvider, so that isthmus consumers can control how unquoted identifiers are cased during parsing. The default remains Casing.TO_UPPER (no behaviour change). Previously the only way to change this was to subclass ConverterProvider and override getSqlParserConfig() — as IsthmusEntryPoint already did with an anonymous class. That workaround is now replaced by a first-class constructor parameter. Changes to ConverterProvider: - unquotedCasing is a new final field, consistent with executionBehavior - getUnquotedCasing() getter - getSqlParserConfig() reads unquotedCasing instead of hard-coding TO_UPPER - new ConverterProvider(Casing) and ConverterProvider(extensions, typeFactory, Casing) for the common cases; the existing 7-arg constructor gains Casing as an 8th parameter; all narrower constructors default to Casing.TO_UPPER Propagation through the pipeline — casing is applied consistently across both CREATE TABLE parsing and query parsing: - SubstraitSqlToCalcite: new convertQueries(sql, catalog, ConverterProvider, operatorTable) overload passes getSqlParserConfig() to the statement parser - SqlToSubstrait: convert(sql, catalog) uses the ConverterProvider overload; the legacy convert(sql, catalog, SqlDialect) overload is deprecated and now delegates to it (the SqlDialect argument is ignored — casing is controlled by the ConverterProvider) - SubstraitCreateStatementParser: new processCreateStatements(ConverterProvider, sql) and processCreateStatementsToCatalog(ConverterProvider, ...) overloads; SqlParser.Config stays an internal detail - SqlExpressionToSubstrait: uses processCreateStatements(converterProvider, tableDef) - IsthmusEntryPoint: uses new ConverterProvider(unquotedCasing); anonymous ConverterProvider subclass removed Examples: - FromSql: replaced the deprecated convert(sql, catalog, SqlDialect) call with a shared ConverterProvider(Casing.UNCHANGED) used for both schema and query parsing, preserving the lower-case identifiers as written BREAKING CHANGE: The SqlParser.Config-based parsing entry points have been removed in favour of ConverterProvider overloads: - SubstraitSqlStatementParser.parseStatements(String, SqlParser.Config) - SubstraitSqlToCalcite.convertQueries(String, CatalogReader, SqlParser.Config) - SubstraitSqlToCalcite.convertQueries(String, CatalogReader, SqlValidator, RelOptCluster, SqlParser.Config) Callers should pass a ConverterProvider (configured for the desired casing, or subclassed with an overridden getSqlParserConfig() for fully custom parser settings) instead of a SqlParser.Config.
2285cd1 to
283d871
Compare
| .withUnquotedCasing(unquotedCasing) | ||
| .withParserFactory(SqlDdlParserImpl.FACTORY) | ||
| .withConformance(SqlConformanceEnum.LENIENT); | ||
| } |
There was a problem hiding this comment.
I don't think we should have an unquotedCasing option like this on the ConverterProvider.
SqlParser.Config has a ton of options on it. Historically, the issue that we ran into was that we added flags to toggle bits and pieces as we needed.
The reason this is structured like this to return a full SqlParser.Config is to allow users to extend the ConverterProvider to return their system specific parsing config.
Left a code comment to this effect, but this actually not a feature I think we should support. SQL parsing is very much a Calcite concern, and for users that need to customize it I would push them to handle their parsing purely in Calcite and then hand the Calcite plans to the Isthmus converters. Is there a specific usecase that you want this for? |
|
TL;DR: isthmus hard-codes Hi Victor — thanks, both points are fair. Here's the concrete use case that drove this, and why casing specifically feels different from the general "one more The specific use case: Substrait plans generated by isthmus don't round-trip against engines that don't fold to upper-case. isthmus hard-codes This isn't hypothetical — I ran into it head-on while prototyping a native Substrait consumer for Presto (Substrait → Presto
The integration itself isn't upstream and isn't a dependency here — I'm raising it only as evidence that the mismatch is real: any consumer targeting a lower-casing engine has to reinvent that bridge, purely because the plan-generation side has no way to say "don't upper-case." The examples already demonstrate the same inconsistency. On "push users to parse in Calcite themselves": for most of On the API shape (your inline comment): your flag-creep worry is legitimate, and I don't want to relitigate the history of bolting individual parser toggles onto the API — Happy to reshape to whatever you're comfortable with. A couple of options:
I'd lean toward (2)-without-the-breaking-removal, but I'm glad to go with (1) if you'd rather not grow |
So what you're saying is that if we generate plans with upper-cased identifiers, that only works on some systems, and the same thing with lower-cased identifiers. This sounds like a deeper problem with the spec, because now we have plans that are basically identical except for the casing of names that don't work across engines. Making it easier to control the casing in the output papers over the issue here. |
From my perspective names in the spec are case-sensitive aka quoted identifiers in SQL-speak. We only have the problem when we parse SQL statements into Substrait plans. Currently, the |
That does feel reasonable. Anything else is a can-of-worms, and we might want to open it one day but it doesn't need to be today.
Gotcha, from this I think it's perfectly reasonable to inject the ConvertProvider into the statement parsers.
How are we expecting users to parse SQL? Are they doing so through the isthmus CLI, or are they integrating with the full Java library? |
I would say both currently. For the Presto integration I used isthmus (the lib) to take their existing SQL test suite and create Substrait plans on the fly which are in Presto SQL dialect and may or may not use unquoted identifiers. We are also doing that for some of our internal work with a number of pre-existing SQL based benchmarks which may have been created with various SQL dialects. The CLI is then just a thin wrapper around the lib which allows one to use a subset of the isthmus features without requiring Java. |
Summary
Adds constructor-based configuration of unquoted SQL identifier casing to
ConverterProvider, so that isthmus consumers can control how unquoted identifiers are cased during parsing. The default remainsCasing.TO_UPPER(no behaviour change).Previously the only way to change this was to subclass
ConverterProviderand overridegetSqlParserConfig()— asIsthmusEntryPointalready did with an anonymous class. That workaround is now replaced by a first-class constructor parameter.Breaking change
The
SqlParser.Config-based parsing entry points (public inv0.94.0) have been removed in favour ofConverterProvideroverloads:SubstraitSqlStatementParser.parseStatements(String, SqlParser.Config)SubstraitSqlToCalcite.convertQueries(String, CatalogReader, SqlParser.Config)SubstraitSqlToCalcite.convertQueries(String, CatalogReader, SqlValidator, RelOptCluster, SqlParser.Config)Callers should pass a
ConverterProvider(constructed with the desiredCasing, or subclassed with an overriddengetSqlParserConfig()for fully custom parser settings) instead of aSqlParser.Config.Changes
ConverterProviderunquotedCasingis a newfinalfield, consistent withexecutionBehaviorgetUnquotedCasing()— gettergetSqlParserConfig()readsunquotedCasinginstead of hard-codingCasing.TO_UPPERConverterProvider(Casing)andConverterProvider(extensions, typeFactory, Casing)for the common cases; the existing 7-arg all-components constructor gainsCasingas an 8th parameter. All narrower constructors default toCasing.TO_UPPER.ConverterProvider.DEFAULT— a shared constant for the default (all-system-defaults) provider, used at every call site that previously wrotenew ConverterProvider().Propagation through the pipeline
The casing setting is applied consistently across both CREATE TABLE parsing and query parsing, so that the table name stored in a
NamedScanmatches the configured casing end-to-end.SubstraitSqlStatementParserparseStatements(String, ConverterProvider)now owns theSqlParserinstantiation directly; theSqlParser.Configoverload is removed — callers needing fully custom config should subclassConverterProviderand overridegetSqlParserConfig()SubstraitSqlToCalciteconvertQueries(sql, catalog, ConverterProvider)andconvertQueries(sql, catalog, ConverterProvider, operatorTable)overloads; all internal overloads now route throughConverterProvider; theSqlParser.Configoverloads are removedSubstraitCreateStatementParserprocessCreateStatements(ConverterProvider, sql)andprocessCreateStatementsToCatalog(ConverterProvider, ...)overloadsSqlToSubstraitconvert(sql, catalog)now uses theConverterProviderpath;convert(sql, catalog, SqlDialect)is@Deprecated— theSqlDialectargument is ignored and it simply delegates toconvert(sql, catalog)SqlExpressionToSubstraitprocessCreateStatements(converterProvider, tableDef)SubstraitToSqlConverterProvider.DEFAULTIsthmusEntryPointnew ConverterProvider(unquotedCasing); anonymousConverterProvidersubclass removedFromSql(example)convert(sql, catalog, SqlDialect)call with a singleConverterProvider(Casing.UNCHANGED)shared across both the schema build and the query conversion, preserving the lower-case identifiers as writtenTest
UnquotedCasingTestverifies:TO_UPPERand is reflected ingetSqlParserConfig()new ConverterProvider(Casing)sets the casing correctly for all threeCasingvaluesTO_UPPERa plan built fromCREATE TABLE employees … / SELECT … FROM employeesproduces aNamedScanwith nameEMPLOYEES; withUNCHANGEDit producesemployeesExisting tests
DdlToSubstraitConversionTestandDdlToSubstraitConversionWithOptimizationTestare updated to use theConverterProviderAPI instead of the removedSqlParser.Configoverloads.Notes
For consumers who need a fully custom parser configuration beyond what
ConverterProviderexposes (e.g. a different parser factory), the supported extension point is subclassingConverterProviderand overridinggetSqlParserConfig(). TheSqlParser.Configoverloads onSubstraitSqlStatementParserandSubstraitSqlToCalcitehave been removed since they are entirely superseded by this.