Skip to content

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

Open
nielspardon wants to merge 1 commit into
mainfrom
feat/builder-unquoted-casing
Open

feat(isthmus): configurable unquoted identifier casing via the ConverterProvider builder#1037
nielspardon wants to merge 1 commit into
mainfrom
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.

Split of #983

Per @vbarua's request, #983 was 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 (merged)
  2. feat(isthmus): introduce a ConverterProvider builder #1036 — introduce the ConverterProvider builder (merged)
  3. feat(isthmus): configurable unquoted identifier casing via the ConverterProvider builder #1037 — configurable unquoted identifier casing via the builder (this PR)

Both dependencies have merged, so this PR is now rebased onto main and is the last commit in the
stack — no longer blocked on anything.

🤖 Generated with AI

@nielspardon
nielspardon requested a review from vbarua July 24, 2026 06:30
Base automatically changed from feat/converterprovider-builder-api to main July 31, 2026 08:38
nielspardon added a commit that referenced this pull request Jul 31, 2026
## Summary

Add `ConverterProvider.builder()` and a nested `Builder` — a readable,
forward-compatible way to
configure a `ConverterProvider`, most importantly to supply a full
Calcite `SqlParser.Config` now
that the parsers consume the provider's config (#1035).
`ConverterProvider.DEFAULT_SQL_PARSER_CONFIG`
(`SqlParser.Config.DEFAULT` + `TO_UPPER` + `SqlDdlParserImpl.FACTORY` +
`LENIENT`) is exposed as the
base for customized configs, e.g.
`DEFAULT_SQL_PARSER_CONFIG.withUnquotedCasing(Casing.UNCHANGED)`.

## Sole construction path + subclassing seam

All public `ConverterProvider` constructors are `@Deprecated` in favour
of `builder()`, and `DEFAULT`
is `builder().build()`. Subclassing routes through a new `protected
ConverterProvider(Builder)` seam
— the single place instances are created — so adding a component later
touches only the builder and
this constructor, never a subclass `super(...)` call.
`DynamicConverterProvider` and
`AutomaticDynamicFunctionMappingConverterProvider` construct via
`super(builder)`.

Now that the provider is fully configurable, call sites that still used
global defaults are routed
through it: the builder's `typeConverter` reaches the
scalar/aggregate/window converters, and the
CREATE-statement catalog reader uses the provider's type factory and
connection config.

## Compatibility

Source-compatible — no public API removed, deprecated constructors still
delegate to the builder,
and behavior is unchanged for the default provider.

## Stack (#983 split, merge bottom-up)

1. #1035 — inject `ConverterProvider` into the SQL statement parsers
(merged)
2. **#1036 — this PR**
3. #1037 — configurable unquoted identifier casing via the builder
…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).
@nielspardon
nielspardon force-pushed the feat/builder-unquoted-casing branch from 3cda7ed to 130e81f Compare July 31, 2026 08:50
@nielspardon

Copy link
Copy Markdown
Member Author

Rebased onto main now that #1035 and #1036 have merged. The branch previously carried pre-squash copies of both, which is where the conflicts came from — it is now a single commit on top of main.

The one real conflict was positional: #1036 gained review changes that regrouped the Builder members, so unquotedCasing(...) now sits directly after sqlParserConfig(...) (with the parser-config settings) rather than ahead of the derived function converters. Verified with :isthmus:check, :isthmus:javadoc, :isthmus-cli:compileJava and :examples:isthmus-api:build — all 6 UnquotedCasingTest tests pass. Ready for review.

public Builder unquotedCasing(Casing unquotedCasing) {
this.sqlParserConfig = this.sqlParserConfig.withUnquotedCasing(unquotedCasing);
return this;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two Qs:

  1. How much is this utility needed instead of just pushing folks to use sqlParserConfig directly as that's much easier now.
  2. If we include this, do we need to protect against users calling both sqlParserConfig and unquotedCasing in the same builder? I could see that causing confusion if unquotedCasing override parts of an already set sqlParserConfig.

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.

2 participants