afs commented on code in PR #2413:
URL: https://github.com/apache/jena/pull/2413#discussion_r1567764379


##########
jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java:
##########
@@ -329,12 +336,43 @@ public void visit(OpExtend opExtend) {
 
         private void processAssignVarExprList(VarExprList varExprList) {
             varExprList.forEachVarExpr((v,e)-> {
-                defines.add(v) ; // Expression may eval to error -> unset?
-                if ( e != null )
+                if ( e != null ) {
+                    boolean isLikelyToBeDefined = isLikelyToBeDefined(e);
+                    if (isLikelyToBeDefined) {
+                        defines.add(v) ; // Expression may eval to error -> 
unset?
+                    }
                     ExprVars.nonOpVarsMentioned(assignMentions, e);
+                }
             }) ;
         }
 
+        /**
+         * Heuristic to check whether the expression's evaluation result is 
likely
+         * to always be defined (i.e. never null / type error).
+         * Does not detect runtime type errors, such as when
+         * expr is (?x < 1) and ?x is assigned a string.
+         */
+        private boolean isLikelyToBeDefined(Expr expr) {
+            boolean result;
+            if (expr.isVariable()) {
+                result = defines.contains(expr.asVar());
+            } else if (expr.isConstant() || expr instanceof E_Bound) {
+                result = true;
+            } else if (expr.isFunction()) {
+                ExprFunction fn = expr.getFunction();
+                List<Expr> args = fn.getArgs();
+                if (fn instanceof E_Coalesce) {
+                    result = args.stream().anyMatch(this::isLikelyToBeDefined);
+                } else {
+                    result = args.stream().allMatch(this::isLikelyToBeDefined);

Review Comment:
   ```suggestion
                       result = Iter.anyMatch(args.iterator(), 
this::isLikelyToBeDefined);
   ```



##########
jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java:
##########
@@ -351,9 +389,26 @@ public void visit(OpProject opProject) {
             assignMentions.addAll(subUsage.assignMentions);
         }
 
+
         @Override
         public void visit(OpTable opTable) {
-            defines.addAll(opTable.getTable().getVars());
+            Table table = opTable.getTable();
+            Set<Var> definedVars = new HashSet<>(table.getVars());
+            List<Var> optVars = new ArrayList<>();
+            Iterator<Binding> rowIt = table.rows();
+            while (rowIt.hasNext()) {
+                Binding row = rowIt.next();
+                Iterator<Var> varIt = definedVars.iterator();
+                while (varIt.hasNext()) {
+                    Var var = varIt.next();
+                    if (!row.contains(var)) {
+                        varIt.remove();

Review Comment:
   This method took me a long time to work out! especially `varIt.remove()`.  
But being forced into old style code seems necessary.
   



##########
jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java:
##########
@@ -329,12 +336,43 @@ public void visit(OpExtend opExtend) {
 
         private void processAssignVarExprList(VarExprList varExprList) {
             varExprList.forEachVarExpr((v,e)-> {
-                defines.add(v) ; // Expression may eval to error -> unset?
-                if ( e != null )
+                if ( e != null ) {
+                    boolean isLikelyToBeDefined = isLikelyToBeDefined(e);
+                    if (isLikelyToBeDefined) {
+                        defines.add(v) ; // Expression may eval to error -> 
unset?
+                    }
                     ExprVars.nonOpVarsMentioned(assignMentions, e);
+                }
             }) ;
         }
 
+        /**
+         * Heuristic to check whether the expression's evaluation result is 
likely
+         * to always be defined (i.e. never null / type error).
+         * Does not detect runtime type errors, such as when
+         * expr is (?x < 1) and ?x is assigned a string.
+         */
+        private boolean isLikelyToBeDefined(Expr expr) {
+            boolean result;
+            if (expr.isVariable()) {
+                result = defines.contains(expr.asVar());
+            } else if (expr.isConstant() || expr instanceof E_Bound) {
+                result = true;
+            } else if (expr.isFunction()) {
+                ExprFunction fn = expr.getFunction();
+                List<Expr> args = fn.getArgs();
+                if (fn instanceof E_Coalesce) {
+                    result = args.stream().anyMatch(this::isLikelyToBeDefined);

Review Comment:
   Minor tweak: Streams involve quite a few objects.
   
   ```suggestion
                       result = Iter.anyMatch(args.iteator(), 
this::isLikelyToBeDefined);
   ```
   It's lighter weight than `stream().anyMatch` and args is likely small.



##########
jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java:
##########
@@ -329,12 +336,43 @@ public void visit(OpExtend opExtend) {
 
         private void processAssignVarExprList(VarExprList varExprList) {
             varExprList.forEachVarExpr((v,e)-> {
-                defines.add(v) ; // Expression may eval to error -> unset?
-                if ( e != null )
+                if ( e != null ) {
+                    boolean isLikelyToBeDefined = isLikelyToBeDefined(e);
+                    if (isLikelyToBeDefined) {
+                        defines.add(v) ; // Expression may eval to error -> 
unset?

Review Comment:
   Yes - that seems likely. 
   Not putting it in is quite safe.
   Doing so and tests all pass.



##########
jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestDynamicDataset.java:
##########
@@ -204,7 +204,17 @@ public void dynamic_dft_7() {
                   "{ GRAPH <urn:x-arq:DefaultGraph> { ?s <uri:p> ?o . FILTER ( 
?o IN ( 1, 2) ) } }",
                   2, dataset);
     }
-    
+
+    @Test

Review Comment:
   It does feel odd. Testing at this level (whole query) is mainly in the 
scripted tests.
   
   Please do start a new `Test*` such as `TestQueryExec`.
   
   



##########
jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java:
##########
@@ -351,9 +389,26 @@ public void visit(OpProject opProject) {
             assignMentions.addAll(subUsage.assignMentions);
         }
 
+
         @Override
         public void visit(OpTable opTable) {
-            defines.addAll(opTable.getTable().getVars());
+            Table table = opTable.getTable();
+            Set<Var> definedVars = new HashSet<>(table.getVars());
+            List<Var> optVars = new ArrayList<>();
+            Iterator<Binding> rowIt = table.rows();
+            while (rowIt.hasNext()) {
+                Binding row = rowIt.next();
+                Iterator<Var> varIt = definedVars.iterator();
+                while (varIt.hasNext()) {
+                    Var var = varIt.next();
+                    if (!row.contains(var)) {
+                        varIt.remove();
+                        optVars.add(var);
+                    }
+                }
+            }
+            defines.addAll(definedVars);
+            optDefines.addAll(optVars);

Review Comment:
   No problem.



-- 
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: pr-unsubscr...@jena.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscr...@jena.apache.org
For additional commands, e-mail: pr-h...@jena.apache.org

Reply via email to