jdaugherty commented on code in PR #16505:
URL: https://github.com/apache/grails-core/pull/16505#discussion_r4178328800


##########
grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java:
##########
@@ -1112,6 +1115,46 @@ public Criteria sizeLt(String propertyName, int size) {
         return this;
     }
 
+    /**
+     * Builds a junction, whose block {@link DetachedCriteria} resolves its 
calls against, with
+     * {@code sqlRestriction} added to the detached criteria of the block and 
of the association blocks inside it.
+     */
+    private void inJunction(Runnable junction) {
+        GroovyCategorySupport.use(SqlRestrictionCategory.class, new 
Closure<Object>(this) {

Review Comment:
   **[P1] The category scope invalidates every Groovy call site in the JVM, on 
every junction.** In Groovy 5.1.3, `GroovyCategorySupport.use` calls 
`VMPluginFactory.getPlugin().invalidateCallSites()` when it enters the scope 
and again when it leaves it (`ThreadCategoryInfo.newScope` and `endScope`). 
That replaces the global `SwitchPoint` that guards every indy call site. Only 
the method lookup is limited to the current thread. So every Hibernate 7 
criteria query with an `and`, `or` or `not` block now makes the call sites of 
every thread relink, and deoptimizes the compiled code that inlined them, 
whether or not it uses `sqlRestriction`.
   
   Measured on JDK 21/H2:
   - `IndyInterface.switchPoint` before and after `createCriteria().list { or { 
eq('title', 'Alpha'); eq('title', 'Beta') } }` is invalidated at edc5f687cb and 
still valid at the base 620d549caa. The same query without the `or` leaves it 
valid on both.
   - A Groovy method with 300 dynamic call sites, timed after each query 
(median of 1,500 runs), takes about 60 µs after either query at the base. At 
edc5f687cb it takes about 900 µs after the `or` query, and 150-170 µs after 
plain queries once `or` queries have run.
   
   The category's methods work unchanged as a Groovy extension module, which 
Groovy loads once and which needs no scope. I tried registering 
`SqlRestrictionCategory` in 
`META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule` and building the 
junctions directly again. All of `HibernateCriteriaBuilderSqlRestrictionSpec` 
passed, including `or { and { chapters { ... } } }`, the switch point stayed 
valid, and the 300 call sites took about 60 µs after both queries. 
`grails-test-examples/hibernate7/criteria-extension` adds criteria methods the 
same way.
   
   One difference: `sqlRestriction` is then also callable on any detached 
criteria, for example `new DetachedCriteria(Book).build { sqlRestriction(...) 
}`, not only inside the builder's junctions. If you keep that, please document 
and test it. Otherwise, any approach without a category scope works.



##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java:
##########
@@ -448,6 +453,38 @@ private Predicate handlePropertyCriterion(
         throw new UnsupportedOperationException("Unsupported criterion: " + 
pc.getClass().getName());
     }
 
+    /**
+     * Returns a column of the entity's own table, whose table alias replaces 
{@code {alias}} in a SQL restriction.
+     */
+    private static Expression<?> aliasColumn(From<?, ?> root, 
GrailsHibernatePersistentEntity entity) {
+        PersistentProperty identity = entity.getIdentity();
+        if (identity == null) {
+            HibernatePersistentProperty[] compositeIdentity = 
entity.getCompositeIdentity();
+            if (compositeIdentity == null || compositeIdentity.length == 0) {
+                throw new ConfigurationException("Cannot use sqlRestriction on 
class [" + entity.getJavaClass().getName() + "] without an identifier");
+            }
+            identity = compositeIdentity[0];
+        }
+        Path<?> path = root.get(identity.getName());
+        if (identity instanceof Association<?> association && 
association.getAssociatedEntity() != null) {
+            path = 
path.get(association.getAssociatedEntity().getIdentity().getName());

Review Comment:
   Verified at edc5f687cb: the `Printing`/`Edition` mapping now works with and 
without `{alias}`.



##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/SqlRestriction.java:
##########
@@ -0,0 +1,59 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.grails.orm.hibernate.query;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.grails.datastore.mapping.query.Query;
+
+/**
+ * Criterion that restricts the results with a native SQL condition, created 
by {@code sqlRestriction} in a
+ * criteria query. In the SQL, {@code {alias}} stands for the table alias of 
the queried entity, and each
+ * {@code ?} for one of the values, which are bound as parameters.
+ *
+ * @param sql the SQL condition
+ * @param values the values of the {@code ?} placeholders, in order
+ * @since 8.0.0
+ */
+public record SqlRestriction(String sql, List<?> values) implements 
Query.Criterion {
+
+    public SqlRestriction {
+        if (sql == null) {
+            throw new IllegalArgumentException("The SQL of a sqlRestriction 
must not be null");
+        }
+        if (values == null) {
+            values = List.of();
+        }
+        List<Object> copy = new ArrayList<>(values.size());
+        for (Object value : values) {
+            if (value == null) {
+                throw new IllegalArgumentException("The values of a 
sqlRestriction must not be null: " + sql);
+            }
+            copy.add(value instanceof CharSequence ? value.toString() : value);
+        }
+        long placeholders = sql.chars().filter(c -> c == '?').count();
+        if (placeholders != copy.size()) {
+            throw new IllegalArgumentException("The SQL of a sqlRestriction 
has " + placeholders +
+                    " ? placeholders but " + copy.size() + " values: " + sql);

Review Comment:
   Verified at edc5f687cb: `{alias}.title like '%?' and {alias}.pages > ?` with 
`[50]` now returns the matching book, through the shared scan in validation and 
rendering.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to