Skip to content

fix(model): the H2 adapter doesn't quote identifiers, so reserved-word column names fail #4440

Closed

Description

@bpamiri

The H2 model adapter doesn't quote identifiers. H2Model extends wheels.databaseAdapters.Base, whose $quoteIdentifier() returns the name unchanged. The MySQL, PostgreSQL, SQL Server, Oracle and SQLite adapters each override it. So on H2, generated SQL that names a column which is a reserved word (order, group, user, value, ...) is invalid. A model backed by such a table can't be queried or ordered by that column, whether the order is written order="column" or order="table.column".

Repro (H2 datasource): a table with a column named order, then model("X").findAll(order="order DESC"). The generated ORDER BY order DESC is a syntax error in H2.

The core spec OrderReservedWordSpec (#4392) is skipped on H2 for this reason.

Why this isn't a one-line override: H2 folds unquoted identifiers to upper case, while a quoted identifier is case-sensitive. Quoting every identifier would change how existing H2 schemas resolve. For example, a table created unquoted as users is stored as USERS, and a quoted "users" would no longer match it. Adding quoting needs its own design: match the stored case, quote only reserved words, or make it opt-in. It also needs H2 runs across the model and migrator suites.

Expected: reserved-word column names work on H2 the way they do on the other adapters, without changing how existing H2 schemas resolve.

Activity

  1. added this to the v4.3.0 milestone on Oct 5, 2026
  2. added
    deferredExplicitly deferred past the next release (reason in a comment)
    on Oct 5, 2026
  3. bpamiri commented on Oct 11, 2026

    @bpamiri
    CollaboratorAuthor

    H2 edge case, not a regression in 4.2.0; targeted for a later release.

  4. removed
    deferredExplicitly deferred past the next release (reason in a comment)
    on Oct 14, 2026
  5. bpamiri commented on Oct 14, 2026

    @bpamiri
    CollaboratorAuthor

    Moved to v4.3.0. Quoting H2 identifiers has to follow how each H2 datasource is configured:

    • which identifiers are reserved depends on the H2 version (1.3.x vs 2.x);
    • whether, and in which case, unquoted names are folded depends on DATABASE_TO_UPPER / DATABASE_TO_LOWER.

    Quoting with fixed rules breaks names that work unquoted today on some configurations. For example, on H2 2.x with DATABASE_TO_UPPER=FALSE, users, first$name and today work unquoted but fail as "USERS", "FIRST$NAME" and "TODAY". A fix needs to detect the version and those settings per datasource. It is also blocked on running H2 2.x in the test images, so it can be covered by the suite. #4599 holds a first attempt as a draft.

  6. added
    deferredExplicitly deferred past the next release (reason in a comment)
    on Oct 14, 2026
  7. removed this from the v4.3.0 milestone on Oct 21, 2026
  8. bpamiri commented on Oct 21, 2026

    @bpamiri
    CollaboratorAuthor

    Fixed on develop in #4628 (merge commit e7437cd), superseding the closed draft #4599.

    What now works on H2. The adapter probes the datasource's identifier fold case once (from INFORMATION_SCHEMA.SETTINGS, handling the H2 2.x SETTING_NAME/SETTING_VALUE and 1.3.x NAME/VALUE shapes) and quotes table and column identifiers in that case. Reserved-word columns (order, group, value, …) work in findAll(order=…), where=, selects and writes. Default folding quotes upper ("ORDER"); DATABASE_TO_LOWER=TRUE quotes lower. OrderReservedWordSpec is unskipped on H2 and passes.

    What deliberately does not change. With folding disabled (DATABASE_TO_UPPER=FALSE) the adapter emits exactly the unquoted SQL it emitted before — reserved-word columns remain unsupported there, and schemas are untouched. Quoted lower/mixed-case schemas on a default-folding database were unsupported before and remain so. H2Migrator still emits unquoted DDL, and reserved-word table names on writes are still unquoted on every adapter.

    Evidence. Full core suite on H2 2.1.214: 7287 passed / 0 failed / 0 errors (lucee7 + h2 CI leg green); SQLite: 1501 model + 189 database specs passed; Adobe 2023 and BoxLang legs green.

    Follow-ups from independent review are tracked separately (migrator DDL quoting, tenant JOIN memo key, classifier unit coverage).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugdeferredExplicitly deferred past the next release (reason in a comment)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions