This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch merge-hibernate6
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 056b8019ce1e783be82f9804c8097112473ad50e
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Fri Aug 22 22:01:58 2025 -0500

    fixed HQL queries except 1
---
 .../orm/hibernate/query/HibernateHqlQuery.java     | 139 ++++++++++++++++++---
 1 file changed, 125 insertions(+), 14 deletions(-)

diff --git 
a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java
 
b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java
index bb580c23c0..d0a4e48c68 100644
--- 
a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java
+++ 
b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateHqlQuery.java
@@ -70,31 +70,142 @@ public class HibernateHqlQuery extends Query {
             , String sqlString
             , boolean isNative
         ) {
-        var q = isNative ? session.createNativeQuery(sqlString, Object.class) 
: session.createQuery(sqlString, Object.class);
+        var clazz = getTarget(sqlString,persistentEntity.getJavaClass());
+        var q = isNative ? session.createNativeQuery(sqlString,clazz) : 
session.createQuery(sqlString, clazz);
         var hibernateSession = new HibernateSession( dataStore, 
sessionFactory);
         HibernateHqlQuery hibernateHqlQuery = new 
HibernateHqlQuery(hibernateSession, null, q);
         hibernateHqlQuery.setFlushMode(session.getHibernateFlushMode());
         return hibernateHqlQuery;
     }
 
-    public static HibernateHqlQuery createHqlQuery(org.hibernate.Session 
session
-            , org.hibernate.query.Query q
-            , HibernateDatastore dataStore
-            , SessionFactory sessionFactory
-            , PersistentEntity persistentEntity
-    ) {
-        var hibernateSession = new HibernateSession( dataStore, 
sessionFactory);
-        switch (session.getHibernateFlushMode()) {
-            case AUTO, ALWAYS:
-                hibernateSession.setFlushMode(FlushModeType.AUTO);
-                break;
+    /**
+     * Determine the number of top-level projections in the HQL query.
+     * Returns 0 if there is no explicit SELECT clause (implicit entity 
projection),
+     * 1 if there is a single top-level projection expression (including 
constructs like DISTINCT x or NEW map(...)),
+     * and 2 if there are two or more top-level projection expressions (e.g. 
"select a, b from ...").
+     *
+     * Notes:
+     * - Commas within parentheses or string literals are ignored.
+     * - Constructor expressions like "new map(a as n, b as m)" count as a 
single projection.
+     * - Aggregate and function calls with commas in their argument lists are 
handled by parentheses tracking.
+     */
+    static int countHqlProjections(CharSequence hql) {
+        if (hql == null) return 0;
+        String s = hql.toString().trim();
+        if (s.isEmpty()) return 0;
+        // Find select and from in a case-insensitive way
+        String lower = s.toLowerCase();
+        int selectIdx = lower.indexOf("select ");
+        if (selectIdx < 0) {
+            // no explicit select -> implicit single entity projection 
following "from"
+            return 0;
+        }
+        // Ensure this select occurs before the corresponding from
+        int fromIdx = lower.indexOf(" from ", selectIdx);
+        if (fromIdx < 0) {
+            // malformed or incomplete query; treat as one projection if 
select exists
+            fromIdx = s.length();
+        }
+        int selectStart = selectIdx + "select".length();
+        // Extract the select clause between 'select' and 'from'
+        String sel = s.substring(selectStart, fromIdx).trim();
+        if (sel.isEmpty()) return 0;
+        // Strip leading DISTINCT/ALL keywords
+        String selLower = sel.toLowerCase();
+        if (selLower.startsWith("distinct ")) {
+            sel = sel.substring("distinct ".length()).trim();
+            selLower = sel.toLowerCase();
+        } else if (selLower.startsWith("all ")) {
+            sel = sel.substring("all ".length()).trim();
+            selLower = sel.toLowerCase();
+        }
+        // Now count top-level commas ignoring those within parentheses and 
string literals
+        int depth = 0;
+        boolean inSingleQuote = false;
+        boolean inDoubleQuote = false;
+        int topLevelCommas = 0;
+        char singleQuote = '\'';
+        char doubleQuote = '"';
+        char leftParen = '(';        // Left parenthesis
+        char rightParen = ')';       // Right parenthesis
+        char comma = ',';            // Comma
+
+
+
+        for (int i = 0; i < sel.length(); i++) {
+            char c = sel.charAt(i);
+            // handle quotes (simple handling: toggle on quote not escaped by 
another same quote)
+            if (!inDoubleQuote && c ==singleQuote) {
+                // handle doubled single quotes inside strings
+                if (inSingleQuote) {
+                    if (i + 1 < sel.length() && sel.charAt(i + 1) == 
singleQuote) {
+                        i++ ;// skip escaped quote
+                        continue;
+                    } else {
+                        inSingleQuote = false;
+                        continue;
+                    }
+                } else {
+                    inSingleQuote = true;
+                    continue;
+                }
+            }
+            if (!inSingleQuote && c == doubleQuote) {
+                inDoubleQuote = !inDoubleQuote;
+                continue;
+            }
+            if (inSingleQuote || inDoubleQuote) continue;
+            if (c == leftParen) { depth++ ; continue ;}
+            if (c == rightParen && depth > 0) { depth-- ; continue; }
+            if (c == comma && depth == 0) { topLevelCommas++; }
+        }
+        if (topLevelCommas == 0) return 1;
+        return 2;
+    }
+
+
+    static Class getTarget(CharSequence hql, Class clazz) {
+        int projections = countHqlProjections(hql);
+        switch(projections) {
+            case 0:
+                return clazz; // No explicit SELECT - implicit entity 
projection
+            case 1:
+                // Single projection - check if it's a property access 
(contains dot)
+                if (isPropertyProjection(hql)) {
+                    return Object.class; // For scalar results like "select 
h.name"
+                } else {
+                    return clazz; // For entity projections like "select h"
+                }
             default:
-                hibernateSession.setFlushMode(FlushModeType.COMMIT);
+                return Object[].class; // Multiple projections
+        }
+    }
+
+    private static boolean isPropertyProjection(CharSequence hql) {
+        String s = hql.toString().toLowerCase().trim();
+        int selectIdx = s.indexOf("select ");
+        if (selectIdx < 0) return false;
+
+        int fromIdx = s.indexOf(" from ", selectIdx);
+        if (fromIdx < 0) fromIdx = s.length();
+
+        String selectClause = s.substring(selectIdx + "select ".length(), 
fromIdx).trim();
+
+        // Remove DISTINCT/ALL if present
+        if (selectClause.startsWith("distinct ")) {
+            selectClause = selectClause.substring("distinct ".length()).trim();
+        } else if (selectClause.startsWith("all ")) {
+            selectClause = selectClause.substring("all ".length()).trim();
         }
-        return new HibernateHqlQuery(hibernateSession, persistentEntity, q);
+
+        // Only return true for clear property projections (containing dots)
+        // This is the safest approach - only treat selections with dots as 
scalar projections
+        return selectClause.contains(".");
     }
 
 
+
+
     public void setFlushMode(FlushMode flushMode) {
         session.setFlushMode(flushMode == FlushMode.AUTO || flushMode == 
FlushMode.ALWAYS ?
                 FlushModeType.AUTO : FlushModeType.COMMIT);

Reply via email to