This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new b99997c083 GH-2412: Improved JoinClassifier for unbound values
b99997c083 is described below
commit b99997c083fd9723b0580f7c8e436603e2188ad7
Author: Claus Stadler <[email protected]>
AuthorDate: Thu Apr 11 18:46:28 2024 +0200
GH-2412: Improved JoinClassifier for unbound values
---
.../jena/sparql/engine/main/JoinClassifier.java | 9 +-
.../apache/jena/sparql/engine/main/VarFinder.java | 73 ++++++++++-
.../org/apache/jena/sparql/algebra/TS_Algebra.java | 1 +
.../apache/jena/sparql/algebra/TestClassify.java | 84 ++++++++----
.../apache/jena/sparql/algebra/TestVarFinder2.java | 144 +++++++++++++++++++++
.../sparql/core/AbstractTestDynamicDataset.java | 65 ++++------
.../jena/sparql/core/AbstractTestQueryExec.java | 78 +++++++++++
.../java/org/apache/jena/sparql/core/TS_Core.java | 1 +
.../apache/jena/sparql/core/TestQueryExecMem.java | 59 +++++++++
.../org/apache/jena/sparql/exec/TS_ExecSPARQL.java | 2 +-
.../jena/sparql/exec/TestQueryExecDataset.java | 1 +
.../java/org/apache/jena/tdb1/store/TS_Store.java | 1 +
.../apache/jena/tdb1/store/TestQueryExecTDB.java | 29 +++--
.../java/org/apache/jena/tdb2/store/TS_Store.java | 1 +
.../apache/jena/tdb2/store/TestQueryExecTDB.java | 27 ++--
15 files changed, 485 insertions(+), 90 deletions(-)
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
index 2855857b43..3fd325bd87 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/JoinClassifier.java
@@ -70,16 +70,17 @@ public class JoinClassifier
// -- pre check for ops we can't handle in a linear fashion.
// These are the negation patterns (minus and diff)
// FILTER NOT EXISTS is safe - it's defined by iteration like the linear
execution algorithm.
- private static class UnsafeLineraOpException extends RuntimeException {
+ private static class UnsafeLinearOpException extends RuntimeException {
@Override public Throwable fillInStackTrace() { return this; }
}
private static OpVisitor checkForUnsafeVisitor = new OpVisitorBase() {
- @Override public void visit(OpMinus opMinus) { throw new
UnsafeLineraOpException(); }
- @Override public void visit(OpDiff opDiff) { throw new
UnsafeLineraOpException(); }
+ @Override public void visit(OpMinus opMinus) { throw new
UnsafeLinearOpException(); }
+ @Override public void visit(OpDiff opDiff) { throw new
UnsafeLinearOpException(); }
};
+
private static boolean isSafeForLinear(Op op) {
try { OpWalker.walk(op, checkForUnsafeVisitor); return true; }
- catch (UnsafeLineraOpException e) { return false; }
+ catch (UnsafeLinearOpException e) { return false; }
}
// --
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
index 71b2403e68..882fa9b93d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/main/VarFinder.java
@@ -25,18 +25,26 @@ import static
org.apache.jena.sparql.util.VarUtils.addVarsFromTriple ;
import static org.apache.jena.sparql.util.VarUtils.addVarsFromTriplePath ;
import java.io.PrintStream ;
+import java.util.ArrayList;
import java.util.HashSet ;
+import java.util.Iterator;
import java.util.List ;
import java.util.Set ;
+import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.atlas.lib.SetUtils ;
import org.apache.jena.sparql.algebra.Op ;
import org.apache.jena.sparql.algebra.OpVisitor ;
+import org.apache.jena.sparql.algebra.Table ;
import org.apache.jena.sparql.algebra.op.* ;
import org.apache.jena.sparql.core.BasicPattern ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.core.VarExprList ;
+import org.apache.jena.sparql.engine.binding.Binding ;
+import org.apache.jena.sparql.expr.E_Bound;
+import org.apache.jena.sparql.expr.E_Coalesce;
import org.apache.jena.sparql.expr.Expr ;
+import org.apache.jena.sparql.expr.ExprFunction;
import org.apache.jena.sparql.expr.ExprList ;
import org.apache.jena.sparql.expr.ExprVars;
import org.apache.jena.sparql.util.VarUtils ;
@@ -329,12 +337,45 @@ public class VarFinder
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?
+ } else {
+ optDefines.add(v);
+ }
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 = Iter.anyMatch(args.iterator(),
this::isLikelyToBeDefined);
+ } else {
+ result = Iter.allMatch(args.iterator(),
this::isLikelyToBeDefined);
+ }
+ } else {
+ // Should never happen
+ result = false;
+ }
+ return result;
+ }
+
@Override
public void visit(OpProject opProject) {
List<Var> vars = opProject.getVars();
@@ -351,9 +392,35 @@ public class VarFinder
assignMentions.addAll(subUsage.assignMentions);
}
+
@Override
public void visit(OpTable opTable) {
- defines.addAll(opTable.getTable().getVars());
+ Table table = opTable.getTable();
+
+ // Start with a a copy of the table's declared variables.
+ // Then sieve out those variables which are unbound in any row.
+ Set<Var> remainingDefinedVars = new HashSet<>(table.getVars());
+
+ // The list of detected unbound variables.
+ // Tracked in a separate collection for clarity - could be added
to optDefines directly.
+ List<Var> optVars = new ArrayList<>();
+
+ Iterator<Binding> rowIt = table.rows();
+ while (rowIt.hasNext()) {
+ Binding row = rowIt.next();
+
+ // Only the remaining defined variables need to be checked for
unbound values
+ Iterator<Var> varIt = remainingDefinedVars.iterator();
+ while (varIt.hasNext()) {
+ Var var = varIt.next();
+ if (!row.contains(var)) {
+ varIt.remove(); // Removes the current var from
remainingDefinedVars
+ optVars.add(var);
+ }
+ }
+ }
+ defines.addAll(remainingDefinedVars);
+ optDefines.addAll(optVars);
}
@Override
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TS_Algebra.java
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TS_Algebra.java
index 4e848bbd0b..d75e39ec17 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TS_Algebra.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TS_Algebra.java
@@ -33,6 +33,7 @@ import org.junit.runners.Suite ;
, TestPattern2Join.class
, TestTransformQuads.class
, TestVarFinder.class
+ , TestVarFinder2.class
, TestTransformOpExt.class
})
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
index fe55d00459..24010f89e3 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestClassify.java
@@ -33,56 +33,56 @@ import org.junit.Test ;
public class TestClassify
{
@Test public void testClassify_Join_01()
- { classifyJ("{?s :p :o . { ?s :p :o FILTER(true) } }", true) ; }
+ { classifyJ("{?s :p :o . { ?s :p :o FILTER(true) } }", true) ; }
@Test public void testClassify_Join_02()
- { classifyJ("{?s :p :o . { ?s :p :o FILTER(?s) } }", true) ; }
+ { classifyJ("{?s :p :o . { ?s :p :o FILTER(?s) } }", true) ; }
@Test public void testClassify_Join_03()
- { classifyJ("{?s :p :o . { ?s :p ?o FILTER(?o) } }", true) ; }
+ { classifyJ("{?s :p :o . { ?s :p ?o FILTER(?o) } }", true) ; }
// JENA-1167
// This safe; ?o is not an input to the filter block.
@Test public void testClassify_Join_04()
- { classifyJ("{?s :p :o . { ?s :p :o FILTER(?o) } }", true) ; }
+ { classifyJ("{?s :p :o . { ?s :p :o FILTER(?o) } }", true) ; }
@Test public void testClassify_Join_05()
- { classifyJ("{?s :p :o . { ?x :p :o FILTER(?s) } }", false) ; }
+ { classifyJ("{?s :p :o . { ?x :p :o FILTER(?s) } }", false) ; }
@Test public void testClassify_Join_06()
- { classifyJ("{ { ?s :p :o FILTER(true) } ?s :p :o }", true) ; }
+ { classifyJ("{ { ?s :p :o FILTER(true) } ?s :p :o }", true) ; }
- @Test public void testClassify_Join_07()
- { classifyJ("{ { ?s :p :o FILTER(?s) } ?s :p :o }", true) ; }
+ @Test public void testClassify_Join_07()
+ { classifyJ("{ { ?s :p :o FILTER(?s) } ?s :p :o }", true) ; }
- @Test public void testClassify_Join_08()
- { classifyJ("{ { ?s :p ?o FILTER(?o) } ?s :p :o }", true) ; }
+ @Test public void testClassify_Join_08()
+ { classifyJ("{ { ?s :p ?o FILTER(?o) } ?s :p :o }", true) ; }
- @Test public void testClassify_Join_09()
- { classifyJ("{ { ?s :p :o FILTER(?o) } ?s :p :o }", true) ; }
+ @Test public void testClassify_Join_09()
+ { classifyJ("{ { ?s :p :o FILTER(?o) } ?s :p :o }", true) ; }
// Actually, this is safe IF executed left, then streamed to right.
- @Test public void testClassify_Join_10()
- { classifyJ("{ { ?x :p :o FILTER(?s) } ?s :p :o }", true) ; }
+ @Test public void testClassify_Join_10()
+ { classifyJ("{ { ?x :p :o FILTER(?s) } ?s :p :o }", true) ; }
// OPTIONAL nested inside {} so it is a join of the LHS and the {}-RHS.
// Not safe: ?s
// Other parts of RHS may restrict ?s to things that can't match the LHS.
- @Test public void testClassify_Join_11()
- { classifyJ("{?s :p :o . { OPTIONAL { ?s :p :o } } }", false) ; }
+ @Test public void testClassify_Join_11()
+ { classifyJ("{?s :p :o . { OPTIONAL { ?s :p :o } } }", false) ; }
// Not safe: ?s
- @Test public void testClassify_Join_12()
- { classifyJ("{?s :p :o . { OPTIONAL { ?s :p :o FILTER(?s) } } }",
false) ; }
+ @Test public void testClassify_Join_12()
+ { classifyJ("{?s :p :o . { OPTIONAL { ?s :p :o FILTER(?s) } } }", false) ;
}
- @Test public void testClassify_Join_13()
- { classifyJ("{?s :p :o . { ?x :p :o OPTIONAL { :s :p :o FILTER(?x) } }
}", true) ; }
+ @Test public void testClassify_Join_13()
+ { classifyJ("{?s :p :o . { ?x :p :o OPTIONAL { :s :p :o FILTER(?x) } } }",
true) ; }
- // See testClassify_Join_04()
- @Test public void testClassify_Join_14()
- { classifyJ("{?s :p :o . { OPTIONAL { :s :p :o FILTER(?o) } } }", true)
; }
+ // See testClassify_Join_04()
+ @Test public void testClassify_Join_14()
+ { classifyJ("{?s :p :o . { OPTIONAL { :s :p :o FILTER(?o) } } }", true) ; }
@Test public void testClassify_Join_14a()
{ classifyJ("{?s :p :o . { OPTIONAL { :s :p ?o FILTER(?o) } } }", true) ; }
@@ -91,7 +91,7 @@ public class TestClassify
{ classifyJ("{?s :p ?o . { OPTIONAL { :s :p :o FILTER(?o) } } }", false) ;
}
@Test public void testClassify_Join_15()
- { classifyJ("{?s :p :o . { OPTIONAL { ?x :p :o FILTER(?s) } } }",
false) ; }
+ { classifyJ("{?s :p :o . { OPTIONAL { ?x :p :o FILTER(?s) } } }", false) ;
}
@Test public void testClassify_Join_20()
{ classifyJ("{ {?s :p ?x } . { {} OPTIONAL { :s :p ?x } } }", false) ; }
@@ -173,6 +173,42 @@ public class TestClassify
TestClassify.classifyJ(x1, false);
}
+ /** Can't linearize because rhs does not bind ?x */
+ @Test public void testClassify_Join_70a() {
+ String x1 = "{ VALUES ?x { UNDEF } VALUES ?x { UNDEF } }";
+ TestClassify.classifyJ(x1, false);
+ }
+
+ /** Can linearize because rhs binds ?x*/
+ @Test public void testClassify_Join_70b() {
+ String x1 = "{ VALUES ?x { UNDEF } VALUES ?x { 0 } }";
+ TestClassify.classifyJ(x1, true);
+ }
+
+ /** Can't linearize because rhs does not bind ?x */
+ @Test public void testClassify_Join_71() {
+ String x1 = "{ VALUES ?x { 0 } VALUES ?x { UNDEF } }";
+ TestClassify.classifyJ(x1, false);
+ }
+
+ /** Can't linearize because rhs does not bind ?x */
+ @Test public void testClassify_Join_80() {
+ String x1 = "{ BIND(0 AS ?x) { VALUES ?x { UNDEF } FILTER(bound(?x)) }
}";
+ TestClassify.classifyJ(x1, false);
+ }
+
+ /** Can't linearize because rhs does not bind ?x */
+ @Test public void testClassify_Join_81() {
+ String x1 = "{ BIND(0 AS ?x) { BIND(coalesce() AS ?x)
FILTER(bound(?x)) } }";
+ TestClassify.classifyJ(x1, false);
+ }
+
+ /** Can linearize because rhs binds ?x*/
+ @Test public void testClassify_Join_82() {
+ String x1 = "{ BIND('x' AS ?x) { BIND('y' AS ?x) FILTER(?x < 1) } }";
+ TestClassify.classifyJ(x1, true);
+ }
+
public static void classifyJ(String pattern, boolean expected)
{
String qs1 = "PREFIX : <http://example/>\n" ;
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder2.java
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder2.java
new file mode 100644
index 0000000000..3009819293
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestVarFinder2.java
@@ -0,0 +1,144 @@
+/*
+ * 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
+ *
+ * http://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.apache.jena.sparql.algebra;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.util.Set ;
+
+import org.apache.jena.sparql.algebra.op.OpTable;
+import org.apache.jena.sparql.algebra.table.TableN;
+import org.apache.jena.sparql.core.Var ;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.engine.main.VarFinder ;
+import org.apache.jena.sparql.sse.SSE ;
+import org.junit.Test ;
+
+public class TestVarFinder2
+{
+ private static Binding row_xy = SSE.parseBinding("(row (?x 1) (?y 2))");
+ private static Binding row_x = SSE.parseBinding("(row (?x 1))");
+ private static Binding row_y = SSE.parseBinding("(row (?y 2))");
+ private static Binding row0 = SSE.parseBinding("(row)");
+ private static Var var_x = Var.alloc("x");
+ private static Var var_y = Var.alloc("y");
+ private static Var[] empty = {};
+
+ @Test public void varfind_table_1() {
+ OpTable opTable = createTable(row_xy);
+ VarFinder vf = VarFinder.process(opTable);
+ assertSetVars(vf.getFixed(), var_x, var_y);
+ assertSetVars(vf.getOpt(), empty);
+
+ assertDifferentVars(vf.getFixed(), var_x);
+ assertDifferentVars(vf.getFixed(), var_y);
+ assertDifferentVars(vf.getFixed(), empty);
+ }
+
+ @Test public void varfind_table_2() {
+ OpTable opTable = createTable(row_xy, row_x);
+ VarFinder vf = VarFinder.process(opTable);
+ assertSetVars(vf.getFixed(), var_x);
+ assertSetVars(vf.getOpt(), var_y);
+ }
+
+ @Test public void varfind_table_3() {
+ OpTable opTable = createTable(row_xy, row_x, row_y);
+ VarFinder vf = VarFinder.process(opTable);
+ assertSetVars(vf.getFixed(), empty);
+ assertSetVars(vf.getOpt(), var_x, var_y);
+ }
+
+ @Test public void varfind_table_4() {
+ OpTable opTable = createTable(row_x, row0);
+ VarFinder vf = VarFinder.process(opTable);
+ assertSetVars(vf.getFixed(), empty);
+ assertSetVars(vf.getOpt(), var_x);
+ }
+
+ /** Create a table of rows */
+ private static OpTable createTable(Binding... rows) {
+ Table table = new TableN();
+ for(Binding b : rows) {
+ table.addBinding(b);
+ }
+ return OpTable.create(table);
+ }
+
+ @Test public void varfind_extend_1() {
+ Op op = SSE.parseOp("(extend (?x 1) (table))");
+ VarFinder vf = VarFinder.process(op);
+ assertSetVars(vf.getFixed(), var_x);
+ }
+
+ @Test public void varfind_extend_2() {
+ Op op = SSE.parseOp("(extend (?x (coalesce 1 2)) (table))");
+ VarFinder vf = VarFinder.process(op);
+ assertSetVars(vf.getFixed(), var_x);
+ }
+
+ @Test public void varfind_extend_3() {
+ Op op = SSE.parseOp("(extend (?x (coalesce ?y 2)) (table))");
+ VarFinder vf = VarFinder.process(op);
+ assertSetVars(vf.getFixed(), var_x);
+ }
+
+ @Test public void varfind_extend_4() {
+ Op op = SSE.parseOp("(extend (?x (coalesce ?y)) (table))");
+ VarFinder vf = VarFinder.process(op);
+ assertSetVars(vf.getFixed(), empty);
+ assertSetVars(vf.getOpt(), var_x);
+ }
+
+ @Test public void varfind_extend_5() {
+ Op op = SSE.parseOp("""
+ (extend ((?x (coalesce ?y)))
+ (extend ((?y 1))
+ (table unit)
+ ))
+ """);
+ VarFinder vf = VarFinder.process(op);
+ assertSetVars(vf.getFixed(), var_x, var_y);
+ }
+
+ @Test public void varfind_extend_6() {
+ Op op = SSE.parseOp("""
+ (extend ((?x (coalesce ?z)))
+ (extend ((?y 1))
+ (table unit)
+ ))
+ """);
+ VarFinder vf = VarFinder.process(op);
+ assertSetVars(vf.getFixed(), var_y);
+ assertSetVars(vf.getOpt(), var_x);
+ }
+
+ /* Assert that a set contains exactly the given vars. */
+ private static void assertSetVars(Set<Var> vars, Var... expectedVars) {
+ Set<Var> expected = Set.of(expectedVars);
+ assertEquals(expected, vars);
+ }
+
+ /* Assert that a set differs from exactly the given vars. */
+ private static void assertDifferentVars(Set<Var> vars, Var...
notExpectedVars) {
+ Set<Var> expected = Set.of(notExpectedVars);
+ assertNotEquals(expected, vars);
+ }
+}
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestDynamicDataset.java
b/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestDynamicDataset.java
index 152e8a7021..bc247c4ebd 100644
---
a/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestDynamicDataset.java
+++
b/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestDynamicDataset.java
@@ -18,8 +18,6 @@
package org.apache.jena.sparql.core;
-import static org.junit.Assert.assertEquals;
-
import org.apache.jena.graph.Triple ;
import org.apache.jena.query.* ;
import org.apache.jena.rdf.model.Model ;
@@ -28,12 +26,14 @@ import org.junit.After ;
import org.junit.Before ;
import org.junit.Test ;
+import static org.apache.jena.sparql.core.AbstractTestQueryExec.testCount ;
+
public abstract class AbstractTestDynamicDataset
{
protected abstract Dataset createDataset() ;
protected abstract void releaseDataset(Dataset ds) ;
protected Dataset dataset ;
-
+
@Before public void before()
{
dataset = createDataset() ;
@@ -43,15 +43,15 @@ public abstract class AbstractTestDynamicDataset
// Default model.
Model m = dataset.getDefaultModel() ;
Triple t1 = SSE.parseTriple("(<uri:x> <uri:p> 0)") ;
- Triple t2 = SSE.parseTriple("(<uri:y> <uri:q> 'ABC')") ;
- Triple t3 = SSE.parseTriple("(<uri:z> <uri:property> 'DEF')") ;
+ Triple t2 = SSE.parseTriple("(<uri:y> <uri:q> 'ABC')") ;
+ Triple t3 = SSE.parseTriple("(<uri:z> <uri:property> 'DEF')") ;
m.getGraph().add(t1) ;
m.getGraph().add(t2) ;
m.getGraph().add(t3) ;
}
-
+
@After public void after() {
- releaseDataset(dataset) ;
+ releaseDataset(dataset) ;
}
private static void addGraph(Dataset dataset, int i)
@@ -60,77 +60,77 @@ public abstract class AbstractTestDynamicDataset
String x = "graph:"+i ;
Model m = dataset.getNamedModel(x) ;
Triple t1 = SSE.parseTriple("(<uri:x> <uri:p> "+i+")") ;
- Triple t2 = SSE.parseTriple("(<uri:y> <uri:q> 'ABC')") ;
+ Triple t2 = SSE.parseTriple("(<uri:y> <uri:q> 'ABC')") ;
m.getGraph().add(t1) ;
m.getGraph().add(t2) ;
}
-
+
@Test public void dynamic01() { testCount("SELECT * {?s ?p ?o}", 3,
dataset) ; }
-
+
@Test public void dynamic02() { testCount("SELECT ?g { GRAPH ?g {} }",
5, dataset) ; }
-
+
@Test public void dynamic03() { testCount("SELECT * FROM <graph:1> {?s
<uri:p> ?o}", 1, dataset) ; }
@Test public void dynamic04() { testCount("SELECT * FROM <graph:1> {
GRAPH ?g { ?s ?p ?o} }", 0, dataset) ; }
-
+
@Test public void dynamic05() { testCount("SELECT * FROM <graph:1> FROM
<graph:2> {?s <uri:p> ?o}", 2, dataset) ; }
// Duplicate surpression
@Test public void dynamic06() { testCount("SELECT ?s FROM <graph:1>
FROM <graph:2> {?s <uri:q> ?o}", 1, dataset) ; }
-
+
@Test public void dynamic07() { testCount("SELECT ?s FROM NAMED
<graph:1> {?s <uri:q> ?o}", 0, dataset) ; }
-
+
@Test public void dynamic08() { testCount("SELECT ?s FROM <graph:2>
FROM NAMED <graph:1> {?s <uri:q> ?o}", 1, dataset) ; }
@Test public void dynamic09() { testCount("SELECT * "+
"FROM <graph:1> FROM <graph:2>
"+
"FROM NAMED <graph:3> FROM
NAMED <graph:4> "+
"{ GRAPH ?g { ?s <uri:q> ?o
}}",
- 2, dataset) ;
+ 2, dataset) ;
}
-
+
@Test public void dynamic10() { testCount("SELECT * "+
"FROM <graph:1> FROM
<graph:2>"+
"FROM NAMED <graph:3> FROM
NAMED <graph:4> "+
"{ GRAPH ?g { ?s <uri:q> ?o
}}",
- 2, dataset) ;
+ 2, dataset) ;
}
@Test public void dynamic11() { testCount("SELECT * "+
"FROM <x:unknown>"+
"{ GRAPH ?g { ?s <uri:q> ?o
}}",
- 0, dataset) ;
+ 0, dataset) ;
}
@Test public void dynamic12() { testCount("SELECT * "+
"FROM <graph:1>"+
"{ GRAPH ?g { }}",
- 0, dataset) ;
+ 0, dataset) ;
}
@Test public void dynamic13() { testCount("SELECT * "+
"FROM NAMED <graph:1>"+
"{ GRAPH ?g { }}",
- 1, dataset) ;
+ 1, dataset) ;
}
@Test public void dynamic14() { testCount("SELECT * "+
"FROM NAMED <graph:1> FROM
NAMED <graph:2>"+
"FROM <graph:3> "+
"{ GRAPH ?g { }}",
- 2, dataset) ;
+ 2, dataset) ;
}
// -- Union graph.
-
+
// No FROM <union> the underlying dataset.
- @Test public void dynamic_union_1() {
- testCount("SELECT * FROM <urn:x-arq:UnionGraph> { ?s <uri:p> ?o }", 5,
dataset) ;
- }
-
+ @Test public void dynamic_union_1() {
+ testCount("SELECT * FROM <urn:x-arq:UnionGraph> { ?s <uri:p> ?o }", 5,
dataset) ;
+ }
+
// Should be able to see two graphs in the union.
@Test public void dynamic_union_2() {
- testCount("SELECT * FROM NAMED <graph:1> FROM NAMED <graph:2> FROM
<graph:3>" +
+ testCount("SELECT * FROM NAMED <graph:1> FROM NAMED <graph:2> FROM
<graph:3>" +
"{ GRAPH <urn:x-arq:UnionGraph> { ?s <uri:p> ?o } }",
2, dataset);
}
@@ -138,7 +138,7 @@ public abstract class AbstractTestDynamicDataset
@Test public void dynamic_union_3() {
testCount("SELECT * FROM NAMED <urn:x-arq:UnionGraph> { GRAPH
<urn:x-arq:UnionGraph> { } }", 1, dataset);
}
-
+
// The union graph isn't in the named set, even if placed there explicitly.
@Test
public void dynamic_union_4() {
@@ -204,13 +204,4 @@ public abstract class AbstractTestDynamicDataset
"{ GRAPH <urn:x-arq:DefaultGraph> { ?s <uri:p> ?o . FILTER (
?o IN ( 1, 2) ) } }",
2, dataset);
}
-
- private static void testCount(String queryString, int expected, Dataset ds)
- {
- Query query = QueryFactory.create(queryString) ;
- QueryExecution qExec = QueryExecutionFactory.create(query, ds) ;
- ResultSet rs = qExec.execSelect() ;
- int n = ResultSetFormatter.consume(rs) ;
- assertEquals(expected, n) ;
- }
}
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestQueryExec.java
b/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestQueryExec.java
new file mode 100644
index 0000000000..751988d19c
--- /dev/null
+++
b/jena-arq/src/test/java/org/apache/jena/sparql/core/AbstractTestQueryExec.java
@@ -0,0 +1,78 @@
+/**
+ * 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
+ *
+ * http://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.apache.jena.sparql.core;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.Query;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.query.QueryExecutionFactory;
+import org.apache.jena.query.QueryFactory;
+import org.apache.jena.query.ResultSet;
+import org.apache.jena.query.ResultSetFormatter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Test cases that operate on the query execution level (whole query). */
+public abstract class AbstractTestQueryExec {
+ protected abstract Dataset createDataset() ;
+ protected abstract void releaseDataset(Dataset ds) ;
+
+ protected Dataset dataset ;
+
+ @Before public void before()
+ {
+ dataset = createDataset() ;
+ }
+
+ @After public void after() {
+ releaseDataset(dataset) ;
+ }
+
+ /* join skew tests based on unbound values. See also:
AbstractTestInnerJoin */
+
+ /** GH-2412 */
+ @Test
+ public void join_skew_01() {
+ testCount("SELECT * { VALUES ?x { 0 } { VALUES ?x { UNDEF }
FILTER(bound(?x)) } }", 0, dataset);
+ }
+
+ /** GH-2412 */
+ @Test
+ public void join_skew_02() {
+ testCount("SELECT * { BIND(0 AS ?x) { BIND(coalesce() AS ?x)
FILTER(bound(?x)) } }", 0, dataset);
+ }
+
+ /**
+ * Execute the given query on the given dataset and assert that the number
of result rows
+ * matches the expected value.
+ *
+ * This method has package visibility because it is also called from
{@link AbstractTestDynamicDataset}.
+ */
+ static void testCount(String queryString, int expected, Dataset ds)
+ {
+ Query query = QueryFactory.create(queryString) ;
+ QueryExecution qExec = QueryExecutionFactory.create(query, ds) ;
+ ResultSet rs = qExec.execSelect() ;
+ int n = ResultSetFormatter.consume(rs) ;
+ assertEquals(expected, n) ;
+ }
+}
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/core/TS_Core.java
b/jena-arq/src/test/java/org/apache/jena/sparql/core/TS_Core.java
index 9dbd198258..6a18490e72 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/core/TS_Core.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/core/TS_Core.java
@@ -27,6 +27,7 @@ import org.junit.runners.Suite ;
, TestDatasetGraphMem.class
, TestDatasetGraphMemTriplesQuads.class
, TestDatasetGeneral.class
+ , TestQueryExecMem.class
, TestDynamicDatasetMem.class
, TestDatasetGraphsRegular.class
, TestDatasetGraphLink.class
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/core/TestQueryExecMem.java
b/jena-arq/src/test/java/org/apache/jena/sparql/core/TestQueryExecMem.java
new file mode 100644
index 0000000000..f1391290af
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/core/TestQueryExecMem.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
+ *
+ * http://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.apache.jena.sparql.core;
+
+import java.util.Arrays ;
+import java.util.Collection ;
+
+import org.apache.jena.atlas.lib.Creator ;
+import org.apache.jena.query.Dataset ;
+import org.apache.jena.query.DatasetFactory ;
+import org.junit.runner.RunWith ;
+import org.junit.runners.Parameterized ;
+import org.junit.runners.Parameterized.Parameters ;
+
+@RunWith(Parameterized.class)
+public class TestQueryExecMem extends AbstractTestQueryExec
+{
+ @Parameters(name = "{index}: {0}")
+ public static Collection<Object[]> data() {
+ Creator<Dataset> datasetGeneralMaker = ()->
DatasetFactory.createGeneral() ;
+ Creator<Dataset> datasetTxnMemMaker = ()->
DatasetFactory.createTxnMem() ;
+ return Arrays.asList(new Object[][] {
+ { "General", datasetGeneralMaker },
+ { "TxnMem", datasetTxnMemMaker} });
+ }
+
+ private final Creator<Dataset> maker;
+
+ public TestQueryExecMem(String name, Creator<Dataset> maker) {
+ this.maker = maker ;
+ }
+
+ @Override
+ protected Dataset createDataset()
+ {
+ return maker.create() ;
+ }
+
+ @Override
+ protected void releaseDataset(Dataset ds) {}
+
+}
+
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
index 8ccf2418e1..6ae60d2e42 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
@@ -26,7 +26,7 @@ import org.junit.runners.Suite;
TestExecEnvironment.class
, TestQueryExecDataset.class
} )
-
+
public class TS_ExecSPARQL {
}
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecDataset.java
b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecDataset.java
index 8e7f6d5365..dd0bce2e1b 100644
---
a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecDataset.java
+++
b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecDataset.java
@@ -32,6 +32,7 @@ import org.junit.Test;
public class TestQueryExecDataset {
// Most QueryExec testing gets tested via QueryExecution usage
+ // See also AbstractTestQueryExec
@Test public void queryExec_substitution_01() {
QueryExec queryExec = QueryExec.dataset(DatasetGraphFactory.empty())
diff --git a/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TS_Store.java
b/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TS_Store.java
index 652a909f89..ced984c335 100644
--- a/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TS_Store.java
+++ b/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TS_Store.java
@@ -39,6 +39,7 @@ import org.junit.runners.Suite ;
, TestLoader.class
, Test_SPARQL_TDB.class
, TestConcurrentAccess.class
+ , TestQueryExecTDB.class
, TestDynamicDatasetTDB.class
, TestStoreConnectionsDirect.class
, TestStoreConnectionsMapped.class
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
b/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TestQueryExecTDB.java
similarity index 61%
copy from jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
copy to jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TestQueryExecTDB.java
index 8ccf2418e1..45abbf86d3 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
+++ b/jena-tdb1/src/test/java/org/apache/jena/tdb1/store/TestQueryExecTDB.java
@@ -1,4 +1,4 @@
-/*
+/**
* 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
@@ -16,17 +16,24 @@
* limitations under the License.
*/
-package org.apache.jena.sparql.exec;
+package org.apache.jena.tdb1.store;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import org.apache.jena.query.Dataset ;
+import org.apache.jena.sparql.core.AbstractTestQueryExec;
+import org.apache.jena.tdb1.TDB1Factory;
+import org.apache.jena.tdb1.sys.TDBInternal;
-@RunWith(Suite.class)
[email protected]( {
- TestExecEnvironment.class
- , TestQueryExecDataset.class
-} )
-
-public class TS_ExecSPARQL {
+public class TestQueryExecTDB extends AbstractTestQueryExec
+{
+ @Override
+ protected Dataset createDataset()
+ {
+ return TDB1Factory.createDataset() ;
+ }
+ @Override
+ protected void releaseDataset(Dataset ds) {
+ TDBInternal.expel(ds.asDatasetGraph());
+ }
}
+
diff --git a/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TS_Store.java
b/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TS_Store.java
index dce1b44568..2ca65c8acb 100644
--- a/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TS_Store.java
+++ b/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TS_Store.java
@@ -42,6 +42,7 @@ import org.junit.runners.Suite;
, TestDatasetTDB.class
, TestDatasetTDBPersist.class
, Test_SPARQL_TDB.class
+ , TestQueryExecTDB.class
, TestDynamicDatasetTDB.class
, TestStoreConnectionMem.class
, TestStoreConnectionDirect.class
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
b/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TestQueryExecTDB.java
similarity index 60%
copy from jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
copy to jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TestQueryExecTDB.java
index 8ccf2418e1..a3dffb0e49 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TS_ExecSPARQL.java
+++ b/jena-tdb2/src/test/java/org/apache/jena/tdb2/store/TestQueryExecTDB.java
@@ -16,17 +16,24 @@
* limitations under the License.
*/
-package org.apache.jena.sparql.exec;
+package org.apache.jena.tdb2.store;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.ReadWrite;
+import org.apache.jena.sparql.core.AbstractTestQueryExec;
+import org.apache.jena.tdb2.junit.TL;
-@RunWith(Suite.class)
[email protected]( {
- TestExecEnvironment.class
- , TestQueryExecDataset.class
-} )
-
-public class TS_ExecSPARQL {
+public class TestQueryExecTDB extends AbstractTestQueryExec
+{
+ @Override
+ protected Dataset createDataset()
+ {
+ Dataset ds = TL.createTestDatasetMem();
+ ds.begin(ReadWrite.WRITE);
+ return ds;
+ }
+ @Override
+ protected void releaseDataset(Dataset ds) { ds.abort(); TL.expel(ds); }
}
+