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

afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit 12d0ea19fd0513add976685d2ec6790efd66f936
Author: Andy Seaborne <[email protected]>
AuthorDate: Mon May 4 20:46:45 2026 +0100

    GH-3879: Include GROUP BY variables in scope testing
---
 .../org/apache/jena/sparql/lang/SPARQLParser.java  | 11 +++--
 .../apache/jena/sparql/lang/SyntaxVarScope.java    | 57 ++++++++++++++--------
 2 files changed, 43 insertions(+), 25 deletions(-)

diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/lang/SPARQLParser.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/lang/SPARQLParser.java
index ac192bc1bd..6a8a92883e 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/SPARQLParser.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/SPARQLParser.java
@@ -25,11 +25,12 @@ import org.apache.jena.query.Query;
 import org.apache.jena.query.QueryParseException;
 import org.apache.jena.query.Syntax;
 
-/** This class provides the root of lower level access to all the parsers.
- *  Each subclass hides the details of the per-language exception handlers and 
other
- *  javacc details to provide a methods that deal with setting up Query objects
- *  and using QueryException exceptions for problems. */
-
+/**
+ * This class provides the root of lower level access to all the parsers. Each
+ * subclass hides the details of the per-language exception handlers and other 
javacc
+ * details to provide a methods that deal with setting up Query objects and 
using
+ * {@link QueryParseException} exceptions for problems.
+ */
 public abstract class SPARQLParser
 {
     public final Query parse(Query query, String queryString) throws 
QueryParseException {
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java
index da33813e91..99370edb0d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/SyntaxVarScope.java
@@ -42,18 +42,21 @@ public class SyntaxVarScope {
      *
      * Syntax Form In-scope variables
      * <pre>
-     * Basic Graph Pattern (BGP) v occurs in the BGP
-     * Path                      v occurs in the path
-     * Group { P1 P2 ... }       v is in-scope if in-scope in one or more of 
P1, P2, ...
-     * GRAPH term { P }          v is term or v is in-scope in P
-     * { P1 } UNION { P2 }       v is in-scope in P1 or in-scope in P2
-     * OPTIONAL {P}              v is in-scope in P
-     * SERVICE term {P}          v is term or v is in-scope in P
-     * (expr AS v) for BIND, SELECT and GROUP BY   v is in-scope
-     * SELECT ..v .. { P }       v is in-scope if v is mentioned as a project 
variable
-     * SELECT * { P }            v is in-scope in P
-     * VALUES var (values)       v is in-scope
-     * VALUES varlist (values)   v is in-scope if v is in varlist
+     * Basic Graph Pattern (BGP)  v occurs in the BGP
+     * Path                       v occurs in the path
+     * Group { P1 P2 ... }        v is in-scope if in-scope in one or more of 
P1, P2, ...
+     * GRAPH term { P }           v is term or v is in-scope in P
+     * { P1 } UNION { P2 }        v is in-scope in P1 or in-scope in P2
+     * OPTIONAL {P}               v is in-scope in P
+     * SERVICE term {P}           v is term or v is in-scope in P
+     * BIND (expr AS v)           v is in-scope
+     * SELECT .. v .. { P }       v is in-scope
+     * SELECT ... (expr AS v)     v is in-scope
+     * SELECT * { P }             v is in-scope in P
+     * GROUP BY ... v             v is in-scope
+     * GROUP BY ... (expr AS v)   v is in-scope
+     * VALUES var (values)        v is in-scope
+     * VALUES varlist (values)    v is in-scope if v is in varlist
      * </pre>
      */
     //@formatter:on
@@ -75,10 +78,14 @@ public class SyntaxVarScope {
         // Does not include variables in trailing VALUES.
         // A trailing VALUES is joined to the query results, including SELECT 
clause,
         // and so does not affect scope at this level.
-        Collection<Var> vars = PatternVars.vars(query.getQueryPattern());
+        Collection<Var> scopeSelectClause = query.hasGroupBy()
+                ? query.getGroupBy().getVars()
+                : PatternVars.vars(query.getQueryPattern());
 
-        // SELECT expressions
-        checkExprListAssignment(vars, query.getProject());
+        // Catches SELECT (AS ?z) WHERE { ?z } GROUP ?z
+        // and reuse SELECT (AS ?z) (AS ?z) WHERE { ... }
+        // "Variable used when already in-scope"
+        checkExprListAssignment(scopeSelectClause, query.getProject());
 
         // Check for SELECT * GROUP BY
         // Legal in ARQ, not in SPARQL 1.1, 1.2
@@ -87,7 +94,9 @@ public class SyntaxVarScope {
                 throw new QueryParseException("SELECT * not legal with GROUP 
BY", -1, -1);
         }
 
+        // Non-group variable passed into GROUP expression and used in SELECT
         // Check any variable in an expression is in scope (if GROUP BY)
+        // SELECT  WHERE { ... } GROUP BY (expression)
         checkExprVarGroupBy(query);
     }
 
@@ -126,23 +135,31 @@ public class SyntaxVarScope {
         });
     }
 
+    // When there is a GROUP BY, check the SELECT usage of variables
+    // from inside a GROUP BY that are not group variables.
+    // These are the aggregate restrictions from section 11.4
     private static void checkExprVarGroupBy(Query query) {
         if ( query.hasGroupBy() ) {
             VarExprList groupKey = query.getGroupBy();
-
-            // Copy - we need to add variables
-            // SELECT (count(*) AS ?C) (?C+1 as ?D)
+            // Mutated so copy.
             List<Var> inScopeVars = new ArrayList<>(groupKey.getVars());
             VarExprList exprList = query.getProject();
 
+            // Look in the SELECT expressions
             for ( Var v : exprList.getVars() ) {
-                // In scope?
+                // Check expression, if any.
                 Expr e = exprList.getExpr(v);
                 if ( e == null ) {
-                    if ( !inScopeVars.contains(v) ) {
+                    // No expression - it's a projection of a group variable.
+                    if ( ! inScopeVars.contains(v) ) {
                         throw new QueryParseException("Non-group key variable 
in SELECT: " + v, -1, -1);
                     }
                 } else {
+                    // (expression AS v) - can't assign in-use.
+                    if ( inScopeVars.contains(v) )
+                        // BeSame test as above, better error message
+                        throw new QueryParseException("Assignment to an 
in-scope variable in SELECT: " + v, -1, -1);
+                    // Check expression
                     Set<Var> eVars = e.getVarsMentioned();
                     for ( Var v2 : eVars ) {
                         if ( !inScopeVars.contains(v2) ) {

Reply via email to