Repository navigation
Hibernate 7: give criterion handlers the query context and let a handler order by an expression - #16696
Conversation
Converts the 24 Java sources of the spring-orm subproject (the Hibernate 7 copies of Spring's HibernateTemplate, HibernateTransactionManager, LocalSessionFactoryBean/Builder, SessionFactoryUtils, SpringBeanContainer, the exception translators, OpenSessionInViewInterceptor and the TransactionResources pair) to statically compiled Groovy under src/main/groovy, and adds the Groovy plugin, the Groovy dependency and the code-style plugin to its build so the new sources are compiled and Checkstyle/CodeNarc-checked like every other framework module. Maintainers asked in #16351 for the Java-to-Groovy migration to land one Gradle project at a time. This is the first slice: it transplants the end state of the 9.0.x-java-to-groovy branch for this project (original commits 2416bdd..0c2e2c2) onto 9.0.x unchanged, so that the native domain binding work can be rebuilt on top of Groovy sources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Converts the 207 Java main sources of grails-data-hibernate7-core (HibernateDatastore, GrailsHibernateTemplate, the criteria builder, the query package, the cfg/domainbinding subtree with its binders, generators and second passes, the connections, proxy, access, support and exceptions packages) to statically compiled Groovy, and adds two Spock specs written while reviewing the conversion. The behaviour is unchanged: the module's own 3657-test suite passes on the converted sources, and the review of the original branch caught and fixed the Groovy semantics that differ from Java (reference identity in equals(), Optional.map null-collapse, closure spread, isX/getX accessor ambiguity). Maintainers asked in #16351 for the Java-to-Groovy migration to land one Gradle project at a time. This transplants the end state of the 9.0.x-java-to-groovy branch for this project (original commits 98c59ed..25dde13) onto 9.0.x unchanged. It depends on the Groovy spring-orm subproject committed just before it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Converts the 35 Java main sources, 19 Java tests and 29 Java test fixtures of the dbmigration-core subproject (the liquibase-hibernate fork: the HibernateDatabase family, the snapshot and diff generators, the custom metadata factory and the harness) to statically compiled Groovy. The HibernateDatabase classes are annotated with @pojo as well as @CompileStatic, because their public getProperty(String) API would otherwise become the GroovyObject property hook and hijack dynamic property access in the dbmigration plugin's Spock specs. Lombok is dropped from the build, since the fixtures no longer use it, and jboss-logging is added as compileOnly because Groovydoc resolves the documented Hibernate types with Class.forName and hibernate-core exposes it at runtime only. The RAT exclusions in gradle/rat-root-config.gradle that name the forked third-party files are repointed from the old .java paths to the new .groovy paths, so the files stay excluded and the rat task stays green. Maintainers asked in #16351 for the Java-to-Groovy migration to land one Gradle project at a time. This transplants the end state of the 9.0.x-java-to-groovy branch for this project (original commits 44f4279..e2ba1e6) onto 9.0.x unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o Groovy Converts the 5 Java sources of the Hibernate 7 Grails plugin subproject (HibernatePersistenceContextInterceptor, GrailsOpenSessionInViewInterceptor, AggregatePersistenceContextInterceptor and its abstract multi-data-source base, SessionFactoryAwarePersistenceContextInterceptor) to statically compiled Groovy. With this commit no Java source is left under grails-data-hibernate7; the dbmigration, boot-plugin and docs subprojects were already pure Groovy and are unchanged. Maintainers asked in #16351 for the Java-to-Groovy migration to land one Gradle project at a time. This transplants the end state of the 9.0.x-java-to-groovy branch for this project (original commits ee295ea..06fb6a1) onto 9.0.x unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comparing the javap -public output of the four Hibernate 7 modules built from origin/9.0.x (Java originals) with this branch, plus a source sweep of every deleted .java file, found that the conversion changed what Java callers see: - 67 constants lost their public static final field. A Groovy static final without public is a property (private field plus static getter), so Type.CONSTANT no longer compiled from Java. Restored in HibernateCriteriaBuilder (37), GrailsHibernateUtil (14), ClosureEventTriggeringInterceptor (10), IdentityEnumType (2), BackticksRemover, RootPersistentClassCommonValuesBinder, SessionFactoryUtils and OpenSessionInViewInterceptor. - HibernateAlias lost its two argument constructor that defaults to JoinType.INNER; the Groovy record generated a same-signature constructor that left joinType null. - CollectionHolder was no longer a record (no Record supertype, equals, hashCode or toString). - configureNamingStrategy on GrailsDomainBinder and NamingStrategyProvider declared ReflectiveOperationException instead of InstantiationException and IllegalAccessException. - The jspecify Nullable and NonNull annotations were dropped (about 130 usages, mostly spring-orm: HibernateCallback.doInHibernate and the HibernateOperations/HibernateTemplate nullable results, parameters and fields). They are restored as type annotations, which Groovy emits as RuntimeVisibleTypeAnnotations like javac. Three binders had swapped jspecify NonNull for jakarta Nonnull; that is reverted. Remaining javap differences are Groovy artifacts: extra generated record constructors and tuple accessors, one covariant bridge method on HibernateQuery.JpaProjectionList, an inherited bridge on ChildHibernateDatastore, and the package-private liquibase IdentifierGeneratorSupport now being public. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reflection spec over the public surface Java callers rely on: constants are public static final fields, the two argument HibernateAlias constructor joins with INNER, CollectionHolder is a record, configureNamingStrategy declares its checked exceptions, and the nullable results of HibernateCallback and HibernateOperations keep their Nullable annotation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Carries #16533 (unique groups on enum and collection properties) and #16539 (nested composite foreign key column order) over to the Groovy sources by hand: HibernateToManyProperty.isBoundAsCollection, CreateKeyForProps, EnumTypeBinder, GrailsPropertyBinder, PropertyFromValueCreator, BidirectionalOneToManyLinker, CompositeIdentifierToManyToOneBinder, GrailsHibernatePersistentEntity, GrailsDomainBinder, and CompositeForeignKeySecondPass converted to Groovy. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…inator, schema and catalog getDiscriminatorColumnName returned null for a root that maps only a discriminator value (discriminator 'animal'), where the Java Optional chain fell back to 'class'; a one-to-many to a table-per-hierarchy subclass then filtered on 'null' and loaded nothing. getDiscriminatorValue, getSchema and getCatalog used ?: and also fell back on an empty string, which Optional.orElse did not. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, keep newInstance's exceptions - HibernateCriteriaBuilder: @slf4j did nothing on the Java class and now adds a log field that takes over 'log' inside criteria closures; remove it. - HibernateClassicDatabase and JpaPersistenceDatabase: carry @pojo like the rest of the HibernateDatabase family so dynamic getProperty calls reach the connection property. - NamingStrategyProvider: a strategy without a no-arg constructor is an InstantiationException again, and an exception from its constructor arrives unwrapped, as with Class.newInstance(). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e conversion widened Members with no modifier in Java are public in Groovy. Put them back with @PackageScope or protected/private: HibernateSession, HqlQueryContext helpers, GrailsHibernateTemplate, HibernateMappingContextConfiguration, CollectionBinder, MapSecondPassBinder, PredicateGenerator, ProjectionPredicate, SoftKey, the query delegates, IdentityEnumType.BidiEnumMap, and the IdentifierGeneratorSupport class itself, which reads a private Hibernate field and is no API. HqlQueryContext keeps the mutating/nativeQuery component names, with a shorter comment: under @CompileStatic a record component named isX cannot be called as isX() from another class (Groovy 6.0.0). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… callers saw - records: no getAt/toList/toMap/size and no default-argument constructors - SelectHqlQuery: keep the protected field name 'delegate' and read it with this.@DeleGate - HibernateQuery: write projections and max straight to the field; protected JpaProjectionList constructor - try-with-resources again in HibernateDatastore, HibernateConnectionSource and HibernateConnectionSourceFactory, so a failing close() is wrapped or suppressed as before - unmodifiable lists where Java returned Stream.toList() - HibernateQueryConstants is @CompileStatic - JavaFacingApiSpec asserts something when nothing matches; JavaCallerPin is a Java class compiled with the Groovy sources, so a constant that stops being a field or an exception that is no longer declared fails the build Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The five-argument constructor the specs use delegates to the six-argument one, which takes the CreateKeyForProps; the merge had folded the two together. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…to-groovy-grails-data-hibernate7 # Conflicts: # gradle/rat-root-config.gradle # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPass.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPass.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/SetSecondPass.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HqlQueryContext.java
…nding (31 files) Rebuild the 31 converted classes under cfg/domainbinding (binder, generator, hibernate, secondpass, util) from their .java originals so git detects each as a rename: 28 of 31 now sit at R054-R090 (were R033-R050). Only the mechanical inverse transforms were applied (public dropped, def <T> on generic methods, list-form @SuppressWarnings); semicolons and the original double-quoted strings stay under scoped CodeNarc suppressions with a TODO for the stacked groovyism-cleanup PR. Every stage-1 deviation was listed first and preserved, proven by a normalised diff: @CompileStatic everywhere, the nested-if rewrites of pattern-matching instanceof, closures and loops for the Optional/stream pipelines, the IdentifierPart record as a class, super.getIdentity()/getVersion() to avoid self-recursion, @NonNull/@nonnull placements and the restored package-private visibility. @serial is back on the serialVersionUID fields (no import needed). origin/10.0.x has moved past this branch's merge-base a3f8355 for GrailsNativeGenerator (ce72ecc), ListSecondPass and MapSecondPass (47b836c); those three were rebuilt from the merge-base .java so no upstream logic is merged silently here. Still below 50%: IndexBinder R042 (the body is one static-imported Optional pipeline that had to become if-guards), NamingStrategyWrapper R045 (three Optional chains), GrailsNativeGenerator R046 against origin/10.0.x but R081 against the merge-base. Verification: :grails-data-hibernate7-core compileGroovy and codenarcMain clean; core tests 3757 run, 22 skipped, 0 failures, 0 errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…event + plugin (27 files) Rebuild the remaining 27 converted classes of grails-data-hibernate7-core (query, proxy, event, multitenancy, support, access, HibernateSession, CriteriaMethodInvoker, IdentityEnumType) and the plugin's HibernatePersistenceContextInterceptor from their .java originals so git detects them as renames: 26 of 27 at R053-R082 (were R034-R049). Mechanical inverse transforms only; semicolons and double-quoted strings kept under scoped suppressions with the groovyism-cleanup TODO; every stage-1 deviation preserved and proven by a normalised diff (@CompileStatic, 15 @PackageScope, 28 this.@field accesses, the five .is() identity checks, def <T> methods, closures for lambdas and streams, @nullable placements, the existing rawtypes/PMD suppressions). A stray `};` after an if/else chain in HqlQueryContext, which CodeNarc's SpaceAfterClosingBrace rejects, was removed. ByteBuddyGroovyProxyFactory, GrailsBytecodeProvider and HqlQueryContext have drifted on origin/10.0.x since the merge-base a3f8355; they were rebuilt from the merge-base .java so nothing upstream is merged here. Still below 50%: HibernateQueryExecutor R043, a Java record that stage 1 turned into a Groovy record with @RecordOptions and a different body. Verification: compileGroovy and codenarcMain clean for :grails-data-hibernate7-core and :grails-data-hibernate7; core tests 3757 run, 22 skipped, 0 failures; plugin tests 39 run, 0 failures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…src/main + spring-orm (31 files) Rebuild the 30 liquibase.ext.hibernate classes of dbmigration-core's main source set (moved from src/main/java to src/main/groovy by stage 1) and spring-orm's AsyncRequestInterceptor from their .java originals so git detects them as renames: 26 of 31 at R050-R097 (were R002-R049). Mechanical inverse transforms only; semicolons and double-quoted strings kept under scoped suppressions with the groovyism-cleanup TODO; every stage-1 deviation preserved and proven by a normalised diff (@CompileStatic, the @pojo Database classes, @PackageScope, this.@dialect, the loop and if-chain rewrites of the Optional/stream pipelines, the ENVERS_* constants). HibernateConnectionMetadata keeps `public` on its 176 DatabaseMetaData signatures under an UnnecessaryPublicModifier suppression, which takes it from R047 to R097. Still below 50%: ExtendedSnapshotGenerator R023 and HibernateGenericDialect R039 (a dozen lines each, the header dominates), HibernateChangedIndex- ChangeGenerator R040 (stage 1 added the Apache header the original lacked), HibernateSpringBeanDatabase R043 and JpaPersistenceDatabase R046 (stream pipelines rewritten as loops). Verification: compileGroovy and codenarcMain clean for :grails-data-hibernate7-dbmigration-core and :grails-data-hibernate7-spring-orm; dbmigration-core tests 41 run, 0 failures (testcontainers PostgreSQL). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…test and testFixtures (42 files) Rebuild dbmigration-core's 16 test classes and 26 test fixtures (moved from src/test/java and src/testFixtures/java to the groovy source sets by stage 1) from their .java originals so git detects them as renames: 30 of 42 at R051-R097 (were R001-R044). Mechanical inverse transforms only, semicolons and double-quoted strings kept; no CodeNarc suppressions are added because the test source sets are not analysed. Every stage-1 deviation is preserved and proven by a normalised diff (@CompileStatic, explicit JUnit static imports, the closure rewrites, the escaped `$` in Bid.toString(), the ElementType/RetentionPolicy imports in SnowflakeId). Authors, Posts and the two Name fixtures keep their explicit public accessors. Still below 50% (12 files): the Lombok fixtures, whose `@Getter @Setter private X y;` lines must become Groovy properties because Lombok does not run under groovyc, and the five customid/columnremarks files whose stage-1 Apache header dominates a tiny body. Verification: compileTestGroovy and compileTestFixturesGroovy clean; dbmigration-core tests 41 run, 0 failures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings in the 260 upstream commits since the merge-base a3f8355. Because the converted files now rename-detect against their .java originals, git paired upstream's Java edits with the Groovy files and the conflicts were content conflicts, resolved by hand: - gradle/rat-root-config.gradle: the Groovy test paths stay, upstream's renamed Procfile entry is taken. - CollectionBinder, SetSecondPass, ListSecondPass, MapSecondPass (47b836c): each collection of an embedded entity is bound with its own second pass. - GrailsNativeGenerator (ce72ecc): the sequences of the default native generator are created on sequence-based databases; the reflection fallback upstream removed is gone here too. - GrailsBytecodeProvider, GrailsProxyFactoryFactory, ByteBuddyGroovyProxyFactory, ByteBuddyGroovyInterceptor (0e9dc2c, 776c6d9, 41cea2f): one shared ByteBuddyState, the interfaces kept on the interceptor, serialisation through SerializableGroovyProxy. - HqlQueryContext (7008f7f, f0f8729, 85d7c86): indexOfSelect, indexOfFrom and indexOfTopLevel, resolveHql no longer normalises, native and update queries run as written. Upstream's new proxy classes (GroovyProxyClassBuilder, GroovyProxyMetaClassDispatcher, SerializableGroovyProxy) arrive as Java in this commit and are converted in the next one. Verification: compileGroovy and codenarcMain clean for core, the plugin and dbmigration-core; core tests 3812 run, 22 skipped, 0 failures (one transient failure, the Oracle testcontainer OOM-killed by Docker on this machine, passed on rerun); plugin tests 39 run, 0 failures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d upstream to Groovy
GroovyProxyClassBuilder, GroovyProxyMetaClassDispatcher and
SerializableGroovyProxy came in with the 10.0.x merge as Java. They are
converted in the same stage-2 style as the rest of the module: semicolons
and double-quoted strings kept under scoped suppressions with the
groovyism-cleanup TODO, @CompileStatic, public dropped, the .class
literals and the pattern-matching instanceof rewritten, the groovy.lang,
java.util and java.io imports dropped.
One change is not mechanical. GroovyProxyClassBuilder's delegation is now
filtered on named("intercept"): a Groovy dispatcher also declares
getMetaClass(), and ByteBuddy's name-equality resolver would bind the
proxy's getMetaClass() to that instead of intercept(..), so the wrapper
would answer the dispatcher's MetaClass. GroovyProxyClassBuilderSpec
catches it.
Verification: compile and codenarc clean; GroovyProxyClassBuilderSpec 6,
SerializableGroovyProxySpec 2, GroovyProxyMetaClassDispatcherSpec 2,
ByteBuddyGroovyProxyFactorySpec 7, ByteBuddyGroovyInterceptorSpec 13,
GrailsBytecodeProviderSpec 8, TraitInitializerProxySpec 5, all passing;
core suite 3812 run, 0 failures.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
….0.x/10.0.x syncs) The remote branch carried two sync merges (origin/9.0.x on Oct 14, origin/10.0.x on Oct 15) whose upstream parents are both already inside the 10.0.x merged in b80a903. The four conflicts (List/Map/SetSecondPass, HqlQueryContext) differ only in stage-1 vs stage-2 style over the same upstream change; the stage-2 versions are kept, so the tree is identical to d7cb780. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oovy-grails-data-hibernate7
…nless it shares a join table Ports the 8.0.x fix for #16665 to the Groovy conversion of CollectionKeyColumnUpdater by hand: the key is non-updatable only when another unidirectional to-many property of the owner uses the same collection table (issue 10811), so addTo on an owner with several hasMany to different classes is written to its join table instead of being discarded. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…oovy-grails-data-hibernate7
Hibernate 7 resolves the type name of a BasicValue through TypeDefinition, which accepts a BasicType or a UserType only, so a property mapped with `type: MonetaryAmountType` where that class implements CompositeUserType failed the SessionFactory build with "Named type [...] did not implement BasicType nor UserType". Hibernate binds a @CompositeType as a Component whose type name is the user type, with one property per attribute of the embeddable() class in alphabetical order, read through getPropertyValue(value, index) and rebuilt through instantiate(ValueAccess). GrailsPropertyBinder now routes a property whose type, or the user-type registered for its class, implements CompositeUserType to the new CompositeUserTypeBinder, which builds that Component: the columns of the property mapping are assigned to the attributes in alphabetical order and bound through ColumnBinder, an attribute without a column is named after the property and the attribute, and more columns than attributes is a MappingException. typeParams are logged as ignored, since Hibernate instantiates a composite user type itself. Documents composite user types in the GORM guide, the `type` mapping reference and the Grails 8 upgrade guide. Fixes #16688
… handler order by an expression A custom criterion handler could not resolve a property path the way the built-in criteria do: it got the query, its root and the criteria builder, but not the JpaQueryContext that holds the joins and aliases GORM created, so a handler had to join an association itself, and a GORM criterion on the same alias then joined it again and the two could match different rows. CriterionHandler gains a default five-parameter handle(..., JpaQueryContext context) that PredicateGenerator dispatches to; its default calls the four-parameter method, so existing handlers and closures keep working. ContextualCriterionHandler is the five-parameter functional form, through which JpaQueryContext.getFullyQualifiedPath resolves alias.property or association.property to the join GORM made for it. Ordering by an expression had no hook at all: assignOrderBy ordered by property paths only, so a plugin had to insert into the query's order list from a criterion and hope the orders were assigned first. A Query.Order subclass is now converted by a registered OrderHandler, looked up by its exact class before the property path handling, so it keeps its place among the orders and can return null to leave the order out. registerOrderHandler, orderHandler, clearCustomOrderHandlers and an OrderHandlerProvider ServiceLoader SPI mirror the criterion ones. The criteria-extension example app gains PriceDistanceOrder with a provider discovered through META-INF/services, an orderByPriceDistance extension and a service method, and its NumberLike handler now resolves its property through the context. The Hibernate 7 guide documents extending the criteria builder, and the What's New notes it. Fixes #16563
… PR (revert before merge) Pushes, rebases and force-pushes to this stacked PR start no jobs; the PR closest to the release branch keeps CI. Merging with this commit is a blocker.
…hether a key is updatable Port of 7138500 (#16686) to the Groovy conversion. CollectionKeyColumnUpdater asked every unidirectional to-many of the owner for its Hibernate collection; a to-many mapped with type: 'serializable' or with a user type is bound as a column and has none, and getCollection() throws a MappingException rather than returning null, so such an owner failed the session factory build. Filter the siblings with HibernateToManyProperty.isBoundAsCollection. CollectionKeyColumnUpdaterSpec and MultipleUnidirectionalHasManySpec carry the cases of #16686.
…oovy-grails-data-hibernate7
…at/criteria-handler-context
jamesfredley
left a comment
There was a problem hiding this comment.
Not approving. Reviewed only the delta on top of the Java-to-Groovy Hibernate 7 branch, not the conversion. The four-argument handler contract and ordinary order translation look right, and there is no request-scoped context stored on a static.
Two problems block this. Provider discovery publishes the initialized flag before the order handlers are loaded, so a second request during first use can miss a new order handler and sort by the property name instead. And this delta sets 13 workflow pull_request filters to branches: ['CHANGEME-FOR-ACCEPTANCE'], so this pull request and ordinary release-targeted pull requests no longer run those checks.
The new translator tests call PredicateGenerator.getPredicates() and JpaCriteriaQueryCreator.createQuery() directly. That is not blocking on its own. Please also cover the order handler through withCriteria or DetachedCriteria.list().
| - '[0-9]+.[0-9]+.x' | ||
| pull_request: | ||
| # CHANGEME FOR ACCEPTANCE: PR triggers disabled on this stacked branch; revert before merge | ||
| branches: ['CHANGEME-FOR-ACCEPTANCE'] |
There was a problem hiding this comment.
branches here filters the pull request base. CHANGEME-FOR-ACCEPTANCE matches no release branch, so this workflow does not run for this pull request or for a pull request targeting 9.0.x / 8.0.x / 7.0.x.
The same placeholder is in the other 12 workflow files in this delta: benchmark, build-health, codeanalysis, codeql, codestyle, coverage, end-to-end, groovy-snapshot-canary, rat, sitemesh2, validate-actions, and vulnerability-scan.
Restore those trigger blocks to the base versions before merge.
There was a problem hiding this comment.
Restored: the base merge of 2026-10-22 brought the policy workflows (#16673) and the 13 trigger blocks are identical to the base branch again; the placeholder is gone from this branch.
…oovy-grails-data-hibernate7 # Conflicts: # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/HibernateDatastore.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ManyToOneValuesBinder.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ToManyEntityMultiTenantFilterBinder.groovy # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/multitenancy/MultiTenantEventListener.groovy
…at/criteria-handler-context # Conflicts: # .github/workflows/benchmark.yml # .github/workflows/build-health.yml # .github/workflows/codeanalysis.yml # .github/workflows/codeql.yml # .github/workflows/codestyle.yml # .github/workflows/coverage.yml # .github/workflows/end-to-end.yml # .github/workflows/gradle.yml # .github/workflows/groovy-snapshot-canary.yml # .github/workflows/rat.yml # .github/workflows/sitemesh2.yml # .github/workflows/validate-actions.yml # .github/workflows/vulnerability-scan.yml
- Handler discovery waits for the providers. `loadServiceProviders` loads under a lock and publishes its flag only after both ServiceLoader loops, so a query that arrives during the discovery waits for it instead of deciding, with the handlers still unregistered, that an order has no handler. A handler registered in code stays ahead of a discovered one. OrderHandlerDiscoverySpec blocks a thread inside a provider found through META-INF/services and shows a second lookup waiting for the handler; it fails with the previous flag. - Handlers see a `QueryPathContext`, not the translation state. The new interface has `getRoot()`, `getFullyQualifiedPath(String)` and `getFullyQualifiedExpression(String)`; `JpaQueryContext` implements it, and `CriterionHandler`, `ContextualCriterionHandler`, `OrderHandler` and the guide depend on the interface only. - A count query leaves its orders out, handled ones too (the fix of the 8.0.x count query, carried here until it merges up), so the guide no longer asks an order handler to return null for a count, and a `count()` with a handled order counts every row. - Tests: the custom criterion before the association block resolves to the same join; the order handler through `DetachedCriteria.list()`.
|
Follow-up pushed as b39597e: the order handler is also covered through |
|
Yes: this goes to 8.x first. I am porting it to 8.1.x as a minor feature ( |
|
The 8.1.x version is #16733. It carries the same changes as the latest push here, in Java, with |
jamesfredley
left a comment
There was a problem hiding this comment.
The first-use discovery race is fixed. Both provider types load under one lock, and the initialized flag is published after that. Not approving. This pull request still carries no ci, so the build jobs are skipped, and a custom order cannot see a sqlProjection alias.
…cords under its alias A custom order that names a sqlProjection alias could not reach that expression: the translator records it with registerSelectionAlias, and getFullyQualifiedPath and getFullyQualifiedExpression resolve property paths only. QueryPathContext now exposes getSelectionAlias(String), which JpaQueryContext already had, so an order handler orders by a projected value the way the built-in alias order does; predicate property resolution is unchanged. HibernateCriteriaBuilderSqlProjectionSpec orders by a sqlProjection alias through a handler and asserts the rows come in the projection's order and the SQL orders by the select item, not by a column. The guide names the accessor.
…commit): same tree, so this records the new history only
194cd43 to
68ff6d8
Compare
jamesfredley
left a comment
There was a problem hiding this comment.
Approving the alias lookup. A custom order can call getSelectionAlias and get the expression a sqlProjection recorded. The discovery-race thread stays resolved. This pull request still has the no ci label, so GitHub will not run the build until that label is removed.
…at/criteria-handler-context Claude-Session: https://claude.ai/code/session_01MDHn7fC3hsGJ27vFHD2o1p # Conflicts: # grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaCriteriaQueryCreator.groovy # grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy # grails-doc/src/en/guide/introduction/whatsNew.adoc
…sources Ports what #16733 changed after the first port, from 1e47f7e to 3bdd024, into the converted classes: a closure registers as a criterion handler without as (five parameters make it contextual), the order handler map is private, discovery and registration are documented as putIfAbsent and put, getFullyQualifiedExpression resolves like the built-in predicates and points to getSelectionAlias for the column alias of a sqlProjection, QueryPathContext has the ASF header, and the specs and the guide follow. The count-only check is the base's; the duplicate this branch carried is gone. Claude-Session: https://claude.ai/code/session_01MDHn7fC3hsGJ27vFHD2o1p
94ba143
into
refactor/java-to-groovy-grails-data-hibernate7
…ler order by an expression Resolves apache#16563 on 8.x. A plugin or an application can extend the Hibernate 7 criteria builder with its own criteria and orders. - A criterion handler can take the query's QueryPathContext, a small interface that JpaQueryContext implements (getRoot, getFullyQualifiedPath, getFullyQualifiedExpression). Through it a handler resolves alias.property or association.property to the join GORM created for the association, so a custom criterion and a built-in one on the same association restrict the same row instead of each joining it again. The four-argument handler keeps working; ContextualCriterionHandler is the five-argument form, and a closure with five parameters is coerced to it. - An OrderHandler turns a Query.Order subclass into a JPA order by any expression, in its place among the query's orders, registered in code or through a META-INF/services OrderHandlerProvider. - Handler discovery waits for the providers: loadServiceProviders loads under a lock and publishes its flag only after both ServiceLoader loops, so a query that arrives during the discovery no longer decides, with the handlers still unregistered, that an order has no handler. A handler registered in code stays ahead of a discovered one. - A count query leaves its orders out, handled ones too (the fix of the previous commit), so no handler runs for a count. Covered by HibernateCriteriaBuilderDirectSpec (a contextual handler on an association path before and after the association block, an order handler through the builder and DetachedCriteria.list(), a count with a handled order), OrderHandlerDiscoverySpec (a lookup during a blocked discovery waits for the handler), PredicateGeneratorSpec and JpaCriteriaQueryCreatorSpec. Documented in the Hibernate 7 guide and the what's new; the criteria-extension example exercises both handler kinds through providers. This is the 8.1.x version of apache#16696, which becomes its merge-up onto the Java-to-Groovy Hibernate 7 branch.
Fixes #16563.
Two additive extension points for the Hibernate 7 criteria builder, asked for by grails-postgresql-extensions' Hibernate 7 port (gpc/grails-postgresql-extensions#136), which today has to guess which join GORM made for an alias and smuggle an
order bythrough a criterion.Resolving property paths in a criterion handler
PredicateGenerator.CriterionHandlergains a default five-parameterhandle(query, root, cb, criterion, context)thatPredicateGeneratornow dispatches to; its default delegates to the four-parameter method, so every existing handler, including closures coerced to the interface, keeps working unchanged. A handler that needs the context implements the newContextualCriterionHandler(five-parameter abstract method, so a five-parameter closure coerces to it). ThroughJpaQueryContext.getFullyQualifiedPath(String)the handler resolvesalias.propertyorassociation.propertyto the join GORM created for it, so a custom criterion and a GORM criterion on the same association restrict the same row rather than each joining the association again.Ordering by an expression
A
Query.Ordersubclass can now be converted by a registeredPredicateGenerator.OrderHandler(handle(query, root, cb, order, context)returning a JPAOrder, ornullto leave the order out).JpaCriteriaQueryCreator.assignOrderBylooks the handler up by the order's exact class before the property path handling, so the order keeps its place among the query's orders.registerOrderHandler,orderHandler(type),clearCustomOrderHandlersand anOrderHandlerProviderServiceLoader SPI (META-INF/services/org.grails.orm.hibernate.query.PredicateGenerator$OrderHandlerProvider) mirror the criterion ones;loadServiceProvidersloads both kinds. AQuery.Ordersubclass already reaches the creator untouched throughHibernateCriteriaBuilder.order(Query.Order)→HibernateQuery.order→DetachedCriteria.order.Tests
HibernateCriteriaBuilderDirectSpec: a contextual handler onchildren.namenext tochildren { eq('name', 'Kid') }finds the parent whose child matches both, and finds nothing when two children match one criterion each, which proves the handler used GORM's join and not a second one; an order handler's order comes before a property order and anullreturn leaves it out.JpaCriteriaQueryCreatorSpec: the handler's JPA order is first inorderList, the handler receives the query's context, and anullreturn leaves only the property order.PredicateGeneratorSpec: a contextual handler receives the very context passed togetPredicates; a four-parameter handler is still called.grails-test-examples/hibernate7/criteria-extension:PriceDistanceOrderwith anOrderHandlerProviderdiscovered throughMETA-INF/services, anorderByPriceDistanceextension andProductSearchService.findNearestPrice, with three integration cases (nearest first, position among orders, through the service);NumberLikeCriterionHandlerProvidernow resolves its property through the context.Locally (JDK 21): the three core specs 98/98,
codenarcMain/codenarcTestclean ongrails-data-hibernate7-core, the example app 7 unit + 18 integration specs green, and the full:grails-data-hibernate7-core:testrun: 3,821 tests, 3,820 passed, 22 skipped. The one red result isSqlRestrictionHibernate7Spec > sqlRestriction works with Oracle, whose Oracle container was killed 27 s after starting on its first attempt and passed on the retry the build configures; the same image starts by hand in 15 s, and the three other Oracle specs passed in the run, so that is the local Docker environment rather than the change.Docs
New section Extending the Criteria Builder in the GORM for Hibernate 7 guide (
querying/criteria.adoc, before Method Reference), covering custom criteria and orders, the context, programmatic and ServiceLoader registration; What's New entry Hibernate 7 Criteria Queries Can Be Extended.Base and status
This is the merge-up of #16733, the 8.1.x version of the change (
@since 8.1), onto the Java-to-Groovy rewrite of the module (#16572). The two carry the same changes, this one in Groovy; review happens on #16733 first, and what lands there is merged up here, so a merge later is a hand-port into the converted files rather than a rewrite.CI on this PR
This pull request carries the
no cilabel, like every open pull request except the one under review at a time, while the ASF runner pool is under pressure (#16673 gates the workflows). The temporary workflow placeholder of the first push is gone: the 2026-10-22 base merge restored the 13 trigger blocks to the base branch's. #16733 runs the full CI on 8.1.x; here,:grails-data-hibernate7-core:testand CodeNarc are green locally at every push, and the label comes off for a run before merge.