Skip to content

feat(isthmus): configurable unquoted identifier casing via the ConverterProvider builder - #1037

Open
nielspardon wants to merge 3 commits into
feat/converterprovider-builder-apifrom
feat/builder-unquoted-casing
Open

feat(isthmus): configurable unquoted identifier casing via the ConverterProvider builder#1037
nielspardon wants to merge 3 commits into
feat/converterprovider-builder-apifrom
feat/builder-unquoted-casing

Conversation

@nielspardon

@nielspardon nielspardon commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Add ConverterProvider.Builder.unquotedCasing(Casing) — a convenience for the common case of
overriding only the unquoted-identifier casing, layered on top of the current parser configuration
(equivalent to sqlParserConfig(current.withUnquotedCasing(casing))).

Motivation

TO_UPPER is the ANSI/Calcite default, but a large class of engines fold unquoted identifiers to
lower-case or preserve them (Presto/Trino, Spark, DuckDB, Postgres), so a plan generated from
SELECT … FROM orders comes out with a NamedScan named ORDERS that those engines can't resolve.
This gives library and CLI users a one-liner to control it, while the full sqlParserConfig(...)
path (previous PR) remains for anything else.

Changes

  • ConverterProvider.Builder.unquotedCasing(Casing) convenience.
  • IsthmusEntryPoint (CLI): --unquotedcasing now maps to
    builder().unquotedCasing(...).
  • FromSql example: uses builder().unquotedCasing(Casing.UNCHANGED) (matching the lower-case
    identifiers in its CREATE TABLE statements), replacing the deprecated
    convert(sql, catalog, SqlDialect) call.
  • New UnquotedCasingTest: default casing; the convenience for each Casing; the full
    sqlParserConfig(...) path (verifying LENIENT conformance is retained); and end-to-end — a plan
    built from CREATE TABLE employees … / SELECT … FROM employees produces a NamedScan named
    EMPLOYEES under TO_UPPER and employees under UNCHANGED.

Split of #983

Per @vbarua's request, #983 is split into three stacked PRs (review/merge bottom-up):

  1. feat(isthmus)!: inject ConverterProvider into the SQL statement parsers #1035 — inject ConverterProvider into the SQL statement parsers
  2. feat(isthmus): introduce a ConverterProvider builder #1036 — introduce the ConverterProvider builder
  3. feat(isthmus): configurable unquoted identifier casing via the ConverterProvider builder #1037 — configurable unquoted identifier casing via the builder (this PR)

Stacked on #1036 (and #1035) — review/merge those first.

🤖 Generated with AI

Thread a single ConverterProvider through both the CREATE-statement parsing that
builds the Calcite catalog and the query parsing, so one parser configuration
(the provider's getSqlParserConfig()) is applied consistently end-to-end. Add a
shared ConverterProvider.DEFAULT instance for the no-arg parsing entry points.

The SqlParser.Config-based parsing entry points (public since v0.94.0) are
removed in favour of passing a ConverterProvider:
- SubstraitSqlStatementParser.parseStatements(String, SqlParser.Config)
- SubstraitSqlToCalcite.convertQueries(..., SqlParser.Config) overloads

SqlToSubstrait.convert(String, CatalogReader, SqlDialect) is deprecated: the
SqlDialect argument no longer influences parsing and it delegates to
convert(String, CatalogReader). Parser configuration is customised by subclassing
ConverterProvider and overriding getSqlParserConfig().
Add ConverterProvider.builder() and a nested Builder that carries a full Calcite
SqlParser.Config alongside extensions, typeFactory, the function converters,
typeConverter and executionBehavior. getSqlParserConfig() now returns the stored
config, and DEFAULT_SQL_PARSER_CONFIG (the isthmus default: DEFAULT + TO_UPPER +
SqlDdlParserImpl.FACTORY + LENIENT) is exposed as the recommended base for
customization.

Make the builder the sole public construction path: a protected
ConverterProvider(Builder) is the stable subclassing seam (build() and the
delegating public constructors route through it), and all public constructors are
@deprecated in favour of builder(). DEFAULT is now builder().build(). The two
subclasses, DynamicConverterProvider and
AutomaticDynamicFunctionMappingConverterProvider, gain a public (Builder)
constructor calling super(builder) and deprecate their positional constructors.
Callers, tests and examples are migrated to builder()/DEFAULT.
…builder

Add Builder.unquotedCasing(Casing) as a convenience for the common case of
overriding only the unquoted-identifier casing, layered on top of the current
parser configuration. Route the isthmus CLI's --unquotedcasing option through it,
use it in the FromSql example (Casing.UNCHANGED, matching the lower-case
identifiers in the example's CREATE TABLE statements), and add UnquotedCasingTest
covering the default casing, the convenience for each Casing, the full
sqlParserConfig path (retaining LENIENT conformance), and end-to-end NamedScan
naming (EMPLOYEES under TO_UPPER, employees under UNCHANGED).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant