Skip to content

fix!: keep explicit dialects across withConfig and fail fast on ambiguous dialect resolution - #438

Merged
zantvoort merged 2 commits into
mainfrom
fix/dialect-config-resolution
Aug 10, 2026
Merged

fix!: keep explicit dialects across withConfig and fail fast on ambiguous dialect resolution#438
zantvoort merged 2 commits into
mainfrom
fix/dialect-config-resolution

Conversation

@zantvoort

Copy link
Copy Markdown
Collaborator

Fixes #404.

Problem

Two related resolution defects:

  1. SqlTemplateImpl.withConfig re-resolved the dialect from the classpath, so SqlTemplate.PS.withDialect(x).withConfig(c) silently lost x.
  2. Providers.getSqlDialect(StormConfig) picked the first enabled dialect provider with no ambiguity guard, unlike the unique selection used for connection and transaction providers, so the winner on a classpath with several dialect modules depended on classpath order.

Changes

Providers.getSqlDialect(StormConfig) now routes through the same selectUnique path as the connection and transaction providers: an ambiguous resolution throws a PersistenceException naming the candidates, with withDialect(...) or database binding as the remedy. Ordered classpaths resolve exactly as before: DefaultSqlDialectProviderImpl is @AfterAny and MariaDB is @Before(MySQL), so the default provider plus a single dialect module still resolves uniquely to the module's dialect.

SqlTemplateImpl tracks the explicitly set dialect separately from the resolved one. withConfig retains an explicit dialect and re-resolves a classpath-resolved one under the new configuration (dialects capture their configuration at construction). The other with* methods pass the explicit dialect through, so derived templates keep their resolution mode.

Ambient resolution is lazy. SqlTemplate.PS/JPA resolved a dialect in their static initializers, and every database-bound template derives from them via withConfig(...) before applying the dialect resolved for its database. With an eager fail-fast guard, class initialization would have failed on multi-dialect classpaths that are perfectly valid under the per-database resolution rules of #359. The dialect, the dialect-keyed template cache, and the template preparation are now initialized on first use, so the ambiguity error surfaces only when an ambient dialect is actually used, at query time, consistent with #359.

JpaTemplateImpl defers its classpath fallback the same way: it previously resolved in the constructor, which would have made the withProviderFilter(...) remedy unreachable on an ambiguous classpath. The fallback dialect and the derived SqlTemplate are now lazy; the data-source path still resolves by database product at construction.

Tests

  • SqlDialectProviderResolutionTest (new): unordered peers fail fast naming both candidates; single and ordered provider sets resolve; ambient resolution is lazy (an explicit dialect works on an ambiguous classpath while dialect() on an ambient template fails fast). Uses a context class loader that substitutes the dialect service registrations.
  • SqlTemplateImplTest: explicit dialect survives withConfig and resolver customization; classpath-resolved dialects are re-resolved under the new configuration, asserted in both ANSI escaping directions since the surefire run sets -Dstorm.ansi_escaping=true.

Full reactor green (30 modules, zero failures).

…uous dialect resolution

withConfig re-resolved the dialect from the classpath, silently discarding
a dialect set via withDialect. The template now tracks the explicitly set
dialect and retains it across withConfig; a classpath-resolved dialect is
re-resolved under the new configuration, as dialects capture their
configuration at construction.

Providers.getSqlDialect(StormConfig) now selects its provider through the
same unique-selection path as the connection and transaction providers, so
an ambiguous classpath fails with an error naming the candidates instead
of picking by classpath order. Ambient dialect resolution is deferred to
first use: SqlTemplate.PS/JPA no longer resolve a dialect during class
initialization, and database-bound templates never trigger classpath
resolution at all. The JPA classpath fallback is deferred the same way so
a provider filter can still be applied on an ambiguous classpath.
@zantvoort zantvoort added this to the 1.14.0 milestone Aug 10, 2026
LazySupplier.get() performed a compare-and-set and a volatile store on
every call, including after the value was resolved. Suppliers shared
across threads on per-query paths, such as the lazily resolved dialect
and template preparation, turned that into contended cache-line writes.
A resolved value is now returned after a single volatile read.
@zantvoort
zantvoort merged commit 24791df into main Aug 10, 2026
7 checks passed
@zantvoort
zantvoort deleted the fix/dialect-config-resolution branch August 10, 2026 14:28
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.11111% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ava/st/orm/core/template/impl/JpaTemplateImpl.java 62.50% 2 Missing and 1 partial ⚠️
...ava/st/orm/core/template/impl/SqlTemplateImpl.java 90.90% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

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.

withConfig discards an explicitly set dialect, and getSqlDialect(StormConfig) resolves first-match

1 participant