Skip to content

refactor(datastore): one internal declaration of the supported SQL databases - #16877

Open
borinquenkid wants to merge 7 commits into
8.0.xfrom
refactor/supported-database-8.0.x
Open

borinquenkid wants to merge 7 commits into
8.0.xfrom
refactor/supported-database-8.0.x

Conversation

@borinquenkid

@borinquenkid borinquenkid commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Three readers of the database each kept their own list of Hibernate dialect classes and their own copy of the lookup: SqlSyntax, RegexDialectPattern and UniqueIndexNulls on Hibernate 7, and the instanceof chain of RlikeExpression on Hibernate 5. A new database or a new dialect meant finding all of them.

Change

  • SupportedDatabase is the one declaration of the supported SQL databases in each Hibernate module, keyed on the Hibernate dialect. Hibernate 7 declares sqlSyntax(), regexPattern() and uniqueIndexNulls(); Hibernate 5 declares regexPattern(). SqlSyntax, GrailsRLikeFunctionContributor, UniqueIndexNulls and RlikeExpression ask it. The two modules declare their own because the two Hibernate versions render different SQL for some databases.
  • ClassKeyed in grails-datastore-core replaces DialectKeyed. It names no database, so a store that is not SQL declares its own definitions the same way, and core holds only the universal lookup.
  • Both are internal API, and their Javadoc says that they carry no compatibility promise.

Behavior that changes

  • rlike with Hibernate 5 on a database with no regular expression operator that Grails uses (SQL Server, Sybase, CockroachDB, Db2 for z/OS) falls back to like, as with Hibernate 7, where it used to fail with rlike is not supported with the configured dialect.

The criteria reference and section 91 of the Grails 8 upgrade guide describe it.

Not changed here: rlike on PostgreSQL is ~* (case-insensitive) with Hibernate 5 and ~ (case-sensitive) with Hibernate 7. The Hibernate 7 rendering is kept so this refactor changes no rows; the difference is tracked in #16882. Running every supported database in Testcontainers is #16881.

Tests

  • ClassKeyedSpec, and a SupportedDatabaseSpec in each module with every dialect, the default, the regular expression pattern, the syntax and the unique index reading.
  • RlikeExpressionSpec for the fallback, and RLikeHibernate7Spec runs rlike against H2 and the Testcontainers databases and expects the rows Hibernate 5 returns on each. The Db2 row is skipped on arm machines and its case expectation has not been run.

Related

#16583 (the unique constraint with a null value, 9.0.x) adds its unique-null properties to these declarations and drops its own copies of the dialect lists. This is the part of it that does not depend on that feature, so that it can land on 8.0.x first.

https://claude.ai/code/session_01Dmki4Tgxp7dLBhzpe2xT93

…tabases

Three readers of the database each held their own list of Hibernate dialect classes and
their own copy of the lookup: SqlSyntax, RegexDialectPattern and UniqueIndexNulls on
Hibernate 7, and RlikeExpression's instanceof chain on Hibernate 5. SupportedDatabase is
now the one declaration per module, keyed on the dialect, and each of them asks it.
DialectKeyed becomes ClassKeyed in grails-datastore-core, which names no database, so a
store that is not SQL can declare its own definitions the same way. Both are marked as
internal API in their Javadoc.

Two readings change, to agree with each other and with Hibernate 5: rlike on PostgreSQL with
Hibernate 7 uses ~*, and with Hibernate 5 a database with no regular expression operator
falls back to like instead of failing. The criteria reference and the upgrade guide say so.

Claude-Session: https://claude.ai/code/session_01Dmki4Tgxp7dLBhzpe2xT93
Copilot AI balanced review requested due to automatic review settings November 6, 2026 01:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@jamesfredley jamesfredley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not approved. PostgreSQL rlike becomes case-insensitive. MariaDB is classified as MySQL, and Sybase is classified as SQL Server.

// MariaDB's dialect extends MySQL's, and both read RLIKE
MYSQL(SqlSyntax.MYSQL, "?1 RLIKE ?2", UniqueIndexNulls.DISTINCT, MySQLDialect.class),

POSTGRESQL(SqlSyntax.POSTGRESQL, "?1 ~* ?2", UniqueIndexNulls.DISTINCT, PostgreSQLDialect.class),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

findAllByNameRlike('^al.*') on PostgreSQL used to render ~ and return only alice. This renders ~* and also returns Alice. Keep the case-sensitive operator.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hibernate 7 keeps the case-sensitive ~, as before this PR. Hibernate 5 has always rendered ~* (RlikeExpression on 8.0.x already does), so the two differ today; #16882 tracks that. This PR changes neither.

public enum SupportedDatabase implements ClassKeyed<SupportedDatabase> {

// MariaDB's dialect extends MySQL's, and both read RLIKE
MYSQL(SqlSyntax.MYSQL, "?1 RLIKE ?2", UniqueIndexNulls.DISTINCT, MySQLDialect.class),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MariaDBDialect extends a MySQL dialect and this list has no MariaDB entry, so forDialect returns MYSQL. SybaseDialect is listed under SQL_SERVER, so a Sybase database is reported as SQL Server. Keep each product as itself.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same as the reply below: MARIADB is declared before MYSQL on both versions, and SYBASE has its own entry.

@borinquenkid

Copy link
Copy Markdown
Member Author

Pushed da3870e, by point:

  • PostgreSQL rlike: you are right, ~* changed rows in a refactor. It is ~ again, as before. The Hibernate 5 difference (~* there, ~ on Hibernate 7) is its own issue, rlike on PostgreSQL is case-insensitive on Hibernate 5 and case-sensitive on Hibernate 7 #16882, and I took the ~* bullet out of the upgrade guide and the PR body.
  • MariaDB: it is its own constant again, ?1 REGEXP ?2 as the old RegexDialectPattern had it, declared before MySQL because its dialect extends MySQL's.
  • Sybase: its own constant again, with SQL Server's syntax and the like fallback, as before.

SupportedDatabaseSpec has a row for each. The full grails-data-hibernate7-core suite and codeStyle pass, including the rlike rows on the PostgreSQL, MySQL, MariaDB and Oracle containers. Testcontainers for every supported database is #16881.

@jamesfredley jamesfredley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not approved. Hibernate 5 still classifies MariaDB as MySQL. Hibernate 7 now keeps PostgreSQL rlike case-sensitive, and keeps MariaDB and Sybase as their own databases.

The Hibernate 5 SupportedDatabase had no MariaDB entry, so forDialect returned MYSQL for
MariaDB. MARIADB is declared before MYSQL, as the Hibernate 7 declaration is, and matches a
regular expression the same way MySQL does.

Claude-Session: https://claude.ai/code/session_01Dmki4RzWwreEC2GkkyFnvL
@borinquenkid

Copy link
Copy Markdown
Member Author

@jamesfredley Fixed in 78ca94c, on top of da3870e. The Hibernate 5 SupportedDatabase now has MARIADB, declared before MYSQL as the Hibernate 7 declaration is, keyed on MariaDBDialect, so a MariaDB database is reported as MariaDB.

It matches a regular expression with rlike, as MySQL does on Hibernate 5, so the SQL a MariaDB database gets is the one it got before. SupportedDatabaseSpec has rows for MariaDB103Dialect and MariaDBDialect and for the pattern; they fail without the entry. The H5 core suite and codeStyle pass.

@jamesfredley jamesfredley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MariaDB is now classified as MariaDB. Thread PRRT_kwDOAAfRJ860nBMd resolved. Not approving. rlike still rewrites a mapped column that already contains ?2.


private boolean isOracleDialect(Dialect dialect) {
return (dialect instanceof Oracle8iDialect);
return SupportedDatabase.forDialect(dialect).regexPattern().replace("?1", columns[0]).replace("?2", "?");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Input: rlike on a property whose mapped column already contains ?2. The column SQL is rewritten, so the query names a column that does not exist. A formula that contains the literal ?2 is changed too. Replace ?2 before inserting the column SQL.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 71a4286: the value marker is replaced in the pattern before the column SQL goes in, so a column or formula containing ?2 stays as written. Spec added.

…16877)

The regular expression pattern of a database is filled with the column first and with the value marker second, so a mapped column or formula that contains the text ?2 was changed as well and the query named a column that does not exist. The value marker is now replaced in the pattern before the column is put in.

Claude-Session: https://claude.ai/code/session_01Dmki4RzWwreEC2GkkyFnvL
@borinquenkid

Copy link
Copy Markdown
Member Author

@jamesfredley Fixed in 71a4286. The value marker ?2 is replaced in the pattern before the column goes in, so a column or formula that contains ?2 or ?1 is kept as written. RlikeExpressionSpec has a case for each.

@jamesfredley jamesfredley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rlike no longer rewrites a mapped column that already contains ?2.

The upgrade guide entry about the Hibernate rlike rendering follows the entry about exception mappings.

Claude-Session: https://claude.ai/code/session_01Dmki4RzWwreEC2GkkyFnvL

@jamesfredley jamesfredley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rlike no longer rewrites a mapped column that already contains ?2.

@jamesfredley jamesfredley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rlike no longer rewrites a mapped column that already contains ?2.

This branch has not been deployed

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

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants