Repository navigation
fix(model): quote H2 identifiers in the datasource's fold case - #4628
Merged
Merged
Conversation
H2's adapter inherited Base's no-op $quoteIdentifier(), so reserved-word columns such as `order` and `group` failed on SELECT, WHERE, ORDER BY and writes. Probe INFORMATION_SCHEMA.SETTINGS once per datasource to learn whether H2 folds identifiers to upper or lower, or not at all, and quote every table and column in that fold case, doubling embedded quotes. The probe result is cached in the application scope per datasource. Datasources with case folding disabled (DATABASE_TO_UPPER=FALSE) keep today's unquoted SQL byte-for-byte. The migrator still emits H2 DDL names unquoted; that stays a follow-up, as with the PostgreSQL and Oracle migrators. Refs #4440 Signed-off-by: Peter Amiri <peter@alurium.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
H2ModelinheritedBase's no-op$quoteIdentifier(), so H2's generated SQL left table and column names unquoted. H2 only accepts reserved-word identifiers (order,group,value, …) when they are quoted, soORDER BY order,where=on such a column, and writes failed.The H2 adapter now probes once per datasource which case the datasource folds unquoted identifiers to — from
INFORMATION_SCHEMA.SETTINGS(DATABASE_TO_UPPER/DATABASE_TO_LOWER; H2 2.xSETTING_NAME/SETTING_VALUE, H2 1.3.xNAME/VALUE) — and quotes every table and column identifier in that fold case, doubling embedded":DATABASE_TO_LOWER=TRUE"users","order"DATABASE_TO_LOWER)"USERS","ORDER"DATABASE_TO_UPPER=FALSEThe result is cached in the application scope per effective datasource (
application.wheels.h2IdentifierCase, besideschemaColumnCache), so a tenant datasource gets its own value, a reload re-probes, and there is no per-identifier or per-query cost. Table aliases and select-list aliases are deliberately not quoted, and no new quoting call sites were added — the shared model SQL builder already routes every model identifier through$quoteIdentifier/$quoteColumn/$quotedTableColumn.Datasources with case folding disabled (
DATABASE_TO_UPPER=FALSE) are byte-for-byte unchanged — the adapter emits exactly the unquoted SQL it emitted before this change.OrderReservedWordSpecno longer skips H2: its reserved-word table is created with the adapter's own quoting, and its two ordering tests now execute on H2. A new H2-only block pins the probe (upperon the suite datasource) and proves quoted-upper still resolves against an unquoted-created schema with a mixed-case column fororder=,where=andsave().Scope / follow-ups
H2Migrator.quoteTableName/quoteColumnNamestill emit unquoted names, like the PostgreSQL, Oracle and CockroachDB migrators. This change targets the model SQL path, which is used against schemas created by hand, raw SQL, or the app's own migrations — the way the regression spec's fixture table is built.INSERT INTO <table>,MERGE INTO <table>) and are out of scope.Refs #4440
Verification
bash tools/test-local.sh databasebash tools/test-local.sh modeltools/test-matrix.sh lucee7 h2Per-spec proof from the H2 run (
/wheels/core/tests?db=h2&directory=wheels.tests.specs.model):OrderReservedWordSpec— 4 specs, 4 passed, 0 skipped, includingH2 identifier case (#4440) > probes the default fold case as upperand… > resolves reads and writes against an unquoted-created table under default folding.AdapterSqlGenerationSpecon H2: 51 passed, 0 skipped, including the new upper/lower/none quoting cases.Unit coverage for all three fold modes is in
AdapterSqlGenerationSpecvia the newH2CaseProbeasset (upper/lower/none, embedded-quote doubling in both quoted modes).Deviation from the implementation brief
The brief's default-folding regression proposed a new
c_o_r_e_caseprobetable plus a new test model. The task's write scope allowed no new model asset, so the spec reuses the existingc_o_r_e_casepreservationfixture (tests/populate.cfmcreates it unquoted with a mixed-caseisHiddencolumn) and the existingCasePreservationmodel. Same property under test, no extra files.Residual risk
NAME/VALUEfallback and its "noDATABASE_TO_LOWER⇒ upper" rule are probe-verified in the brief but not covered by CI.DATABASE_TO_UPPER=FALSE) andDATABASE_TO_LOWER=TRUEare covered by unit tests only — the suite has one H2 folding configuration. Optional follow-up: add aDATABASE_TO_UPPER=FALSEH2 datasource to the Lucee CFConfig.$columnAlias,$removeColumnAliasesInOrderClause,$addColumnsToSelectAndGroupBy,$moveAggregateToHaving).