From b356b7c933256edcf218abdfd15964354e42f409 Mon Sep 17 00:00:00 2001 From: Claus Stadler Date: Tue, 7 Jan 2025 00:08:52 +0100 Subject: [PATCH] GH-2924: Lateral - fixed injection for tables, enhanced QueryExec API for easier testing. --- .../java/org/apache/jena/query/ResultSet.java | 4 + .../apache/jena/sparql/algebra/Algebra.java | 19 ++-- .../jena/sparql/algebra/TableFactory.java | 25 ++++- .../jena/sparql/algebra/table/TableData.java | 8 +- .../jena/sparql/algebra/table/TableN.java | 17 ++-- .../jena/sparql/engine/binding/Binding.java | 7 ++ .../jena/sparql/engine/binding/Binding0.java | 5 + .../jena/sparql/engine/binding/Binding1.java | 5 + .../jena/sparql/engine/binding/Binding2.java | 5 + .../jena/sparql/engine/binding/Binding3.java | 5 + .../jena/sparql/engine/binding/Binding4.java | 5 + .../sparql/engine/binding/BindingBase.java | 15 +++ .../sparql/engine/binding/BindingOverMap.java | 5 + .../sparql/engine/binding/BindingProject.java | 13 +++ .../engine/binding/BindingProjectBase.java | 6 +- .../engine/binding/BindingProjectNamed.java | 13 +++ .../sparql/engine/binding/BindingRoot.java | 5 + .../engine/iterator/QueryIterLateral.java | 37 ++++++-- .../jena/sparql/exec/QueryExecBuilder.java | 28 +++++- .../org/apache/jena/sparql/exec/RowSet.java | 4 + .../query/TestQueryCloningCornerCases.java | 4 + .../jena/sparql/exec/TestQueryExecution.java | 37 +++++--- .../apache/jena/sparql/graph/GraphsTests.java | 94 ++++++++++++------- .../apache/jena/tdb1/solver/BindingTDB.java | 17 +++- .../apache/jena/tdb2/solver/BindingTDB.java | 11 +++ 25 files changed, 311 insertions(+), 83 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java b/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java index 44db088a05e..ee64b1d07e0 100644 --- a/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java +++ b/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java @@ -103,4 +103,8 @@ public default ResultSet materialise() { } public void close(); + + default RowSet asRowSet() { + return RowSet.adapt(this); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java index 72f5d87f3a1..af4bf49fa98 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java @@ -161,22 +161,27 @@ public static Binding merge(Binding bindingLeft, Binding bindingRight) { // If compatible, merge. Iterate over variables in right but not in left. BindingBuilder b = Binding.builder(bindingLeft); - for ( Iterator vIter = bindingRight.vars() ; vIter.hasNext() ; ) { - Var v = vIter.next(); - Node n = bindingRight.get(v); + bindingRight.forEach((v, n) -> { if ( !bindingLeft.contains(v) ) b.add(v, n); - } + }); return b.build(); } public static boolean compatible(Binding bindingLeft, Binding bindingRight) { // Test to see if compatible: Iterate over variables in left - for ( Iterator vIter = bindingLeft.vars() ; vIter.hasNext() ; ) { - Var v = vIter.next(); + return compatible(bindingLeft, bindingRight, bindingLeft.vars()); + } + + /** Test to see if bindings are compatible for all variables of the provided iterator. */ + public static boolean compatible(Binding bindingLeft, Binding bindingRight, Iterator vars) { + while (vars.hasNext() ) { + Var v = vars.next(); Node nLeft = bindingLeft.get(v); - Node nRight = bindingRight.get(v); + if ( nLeft == null ) + continue; + Node nRight = bindingRight.get(v); if ( nRight != null && !nRight.equals(nLeft) ) return false; } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java index 3f5a5e07e3b..f1d26a5823d 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java @@ -18,6 +18,7 @@ package org.apache.jena.sparql.algebra; +import java.util.ArrayList; import java.util.List ; import org.apache.jena.graph.Node ; @@ -27,31 +28,45 @@ import org.apache.jena.sparql.algebra.table.TableUnit ; import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.engine.QueryIterator ; +import org.apache.jena.sparql.engine.binding.Binding ; +import org.apache.jena.sparql.exec.RowSet ; public class TableFactory { public static Table createUnit() { return new TableUnit() ; } - + public static Table createEmpty() { return new TableEmpty() ; } public static Table create() { return new TableN() ; } - + public static Table create(List vars) { return new TableN(vars) ; } - + public static Table create(QueryIterator queryIterator) - { + { if ( queryIterator.isJoinIdentity() ) { queryIterator.close(); return createUnit() ; } - + return new TableN(queryIterator) ; } public static Table create(Var var, Node value) { return new Table1(var, value) ; } + + /** Creates a mutable table from the detached bindings of the row set. */ + public static Table create(RowSet rs) + { + List vars = new ArrayList<>(rs.getResultVars()); + List list = new ArrayList<>(); + rs.forEach(row -> { + Binding b = row.detach(); + list.add(b); + }); + return new TableN(vars, list); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java index 99d5fe5c15f..ca7c9f686cb 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java @@ -18,23 +18,21 @@ package org.apache.jena.sparql.algebra.table ; +import java.util.Collections; import java.util.List ; import org.apache.jena.sparql.ARQException ; import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.engine.binding.Binding ; +/** Immutable table. */ public class TableData extends TableN { public TableData(List variables, List rows) { - super(variables, rows) ; + super(Collections.unmodifiableList(variables), Collections.unmodifiableList(rows)) ; } @Override public void addBinding(Binding binding) { throw new ARQException("Can't add bindings to an existing data table") ; } - - public List getRows() { - return rows ; - } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java index 4d8887116c6..f4bac649c8f 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java @@ -21,6 +21,7 @@ import java.util.ArrayList ; import java.util.Iterator ; import java.util.List ; +import java.util.Objects; import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.engine.ExecutionContext ; @@ -28,6 +29,7 @@ import org.apache.jena.sparql.engine.binding.Binding ; import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper ; +/** Mutable table. */ public class TableN extends TableBase { protected List rows = new ArrayList<>() ; protected List vars = new ArrayList<>() ; @@ -48,16 +50,13 @@ public TableN(QueryIterator qIter) { materialize(qIter) ; } - protected TableN(List variables, List rows) { - this.vars = variables ; - this.rows = rows ; + public TableN(List variables, List rows) { + this.vars = Objects.requireNonNull(variables) ; + this.rows = Objects.requireNonNull(rows) ; } private void materialize(QueryIterator qIter) { - while (qIter.hasNext()) { - Binding binding = qIter.nextBinding() ; - addBinding(binding) ; - } + qIter.forEachRemaining(this::addBinding); qIter.close() ; } @@ -105,4 +104,8 @@ public List getVarNames() { public List getVars() { return vars ; } + + public List getRows() { + return rows; + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java index d7c7efbc5c8..ec59a63cc64 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java @@ -90,4 +90,11 @@ public default boolean contains(String varName) { @Override public boolean equals(Object other); + + /** + * Returns a binding which is guaranteed to be independent of + * any resources such as an ongoing query execution or a disk-based dataset. + * May return itself if it is already detached. + */ + public Binding detach(); } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java index 7833f9e2420..ca06ee4c742 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java @@ -51,4 +51,9 @@ protected void forEach1(BiConsumer action) { } @Override protected Node get1(Var var) { return null; } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return new Binding0(newParent); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java index ba21241c0b3..7d8a8703b6a 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java @@ -70,4 +70,9 @@ protected Node get1(Var v) { return value; return null; } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return new Binding1(newParent, var, value); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java index 9ad3bc5af0c..42ca53313f4 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java @@ -77,4 +77,9 @@ protected Node get1(Var v) return value2; return null; } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return new Binding2(newParent, var1, value1, var2, value2); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java index c52eb07c39b..144cbf40e12 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java @@ -132,4 +132,9 @@ protected Node get1(Var var) { return null; } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return new Binding3(newParent, var1, value1, var2, value2, var3, value3); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java index 5ec9e398248..0d71a9ea890 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java @@ -154,4 +154,9 @@ protected Node get1(Var var) { return null; } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return new Binding4(newParent, var1, value1, var2, value2, var3, value3, var4, value4); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java index 8952425640d..d3544f859c6 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java @@ -202,4 +202,19 @@ public static int hashCode(Binding bind) { } return hash; } + + @Override + public Binding detach() { + Binding newParent = (parent == null) ? null : parent.detach(); + Binding result = (newParent == parent) + ? detachWithOriginalParent() + : detachWithNewParent(newParent); + return result; + } + + protected Binding detachWithOriginalParent() { + return this; + } + + protected abstract Binding detachWithNewParent(Binding newParent); } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java index 04db856512f..555d3f353d8 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java @@ -61,4 +61,9 @@ protected int size1() { protected boolean isEmpty1() { return map.isEmpty(); } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return new BindingOverMap(newParent, map); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java index ab37542a02c..d84f09d6635 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java @@ -35,4 +35,17 @@ public BindingProject(Collection vars, Binding bind) { protected boolean accept(Var var) { return projectionVars.contains(var) ; } + + @Override + public Binding detach() { + Binding b = binding.detach(); + return b == binding + ? this + : new BindingProject(projectionVars, b); + } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + throw new UnsupportedOperationException("Should never be called."); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java index 1364f57d17b..a2156f82fdf 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java @@ -25,13 +25,13 @@ import org.apache.jena.graph.Node ; import org.apache.jena.sparql.core.Var ; -/** Common framework for projection; +/** Common framework for projection; * the projection policy is provided by - * abstract method {@link #accept(Var)} + * abstract method {@link #accept(Var)} */ public abstract class BindingProjectBase extends BindingBase { private List actualVars = null ; - private final Binding binding ; + protected final Binding binding ; public BindingProjectBase(Binding bind) { super(null) ; diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java index aaca87cc1ac..ef956682db2 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java @@ -33,4 +33,17 @@ public BindingProjectNamed(Binding bind) { protected boolean accept(Var var) { return var.isNamedVar() ; } + + @Override + public Binding detach() { + Binding b = binding.detach(); + return b == binding + ? this + : new BindingProjectNamed(b); + } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + throw new UnsupportedOperationException("Should never be called."); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java index 47381b0f7e4..d7743eca12e 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java @@ -33,4 +33,9 @@ private BindingRoot() { public void format1(StringBuilder sBuff) { sBuff.append("[Root]"); } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + return this; + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java index 9ce2ce85a1f..047725019c5 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java @@ -19,6 +19,8 @@ package org.apache.jena.sparql.engine.iterator; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -27,12 +29,13 @@ import org.apache.jena.atlas.lib.SetUtils; import org.apache.jena.graph.Node; import org.apache.jena.graph.Triple; +import org.apache.jena.sparql.algebra.Algebra; import org.apache.jena.sparql.algebra.Op; import org.apache.jena.sparql.algebra.Table; import org.apache.jena.sparql.algebra.TransformCopy; import org.apache.jena.sparql.algebra.op.*; import org.apache.jena.sparql.algebra.table.Table1; -import org.apache.jena.sparql.algebra.table.TableN; +import org.apache.jena.sparql.algebra.table.TableData; import org.apache.jena.sparql.core.*; import org.apache.jena.sparql.engine.ExecutionContext; import org.apache.jena.sparql.engine.QueryIterator; @@ -292,17 +295,33 @@ public Op transform(OpTable opTable) { // By the assignment restriction, the binding only needs to be added to each row of the table. Table table = opTable.getTable(); // Table vars. - List vars = new ArrayList<>(table.getVars()); - binding.vars().forEachRemaining(vars::add); - TableN table2 = new TableN(vars); + List tableVars = table.getVars(); + List vars = new ArrayList<>(tableVars); + + // Track variables that appear both in the table and the binding. + List commonVars = new ArrayList<>(); + + // Index variables in a set if there are more than a few of them. + Collection tableVarsIndex = tableVars.size() > 4 ? new HashSet<>(tableVars) : tableVars; + binding.vars().forEachRemaining(v -> { + if (tableVarsIndex.contains(v)) { + commonVars.add(v); + } else { + vars.add(v); + } + }); + + List bindings = new ArrayList<>(table.size()); BindingBuilder builder = BindingFactory.builder(); table.iterator(null).forEachRemaining(row->{ - builder.reset(); - builder.addAll(row); - builder.addAll(binding); - table2.addBinding(builder.build()); + if (Algebra.compatible(row, binding, commonVars.iterator())) { + builder.reset(); + builder.addAll(row); + binding.forEach(builder::set); + bindings.add(builder.build()); + } }); - return OpTable.create(table2); + return OpTable.create(new TableData(vars, bindings)); } private Triple applyReplacement(Triple triple, Function replacement) { diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java index 43aa09b10fa..ddfba85abc0 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java @@ -25,6 +25,9 @@ import org.apache.jena.query.ARQ; import org.apache.jena.query.Query; import org.apache.jena.query.Syntax; +import org.apache.jena.riot.rowset.RowSetOnClose; +import org.apache.jena.sparql.algebra.Table; +import org.apache.jena.sparql.algebra.TableFactory; import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.engine.binding.Binding; import org.apache.jena.sparql.util.Context; @@ -81,9 +84,16 @@ public default QueryExecBuilder substitution(String var, Node value) { // build-and-use short cuts - /** Build and execute as a SELECT query. */ + /** + * Build and execute as a SELECT query. + * The caller must eventually close the returned RowSet + * in order to free any associated resources. + * Use {@link #table()} to obtain an independent in-memory copy of the row set. + */ public default RowSet select() { - return build().select(); + QueryExec qExec = build(); + RowSet core = qExec.select(); + return new RowSetOnClose(core, qExec::close); } /** Build and execute as a CONSTRUCT query. */ @@ -106,4 +116,18 @@ public default boolean ask() { return qExec.ask(); } } + + /** + * Build and execute as a SELECT query. + * Creates and returns an independent in-memory table by materializing the underlying row set. + * Subsequently, {@link Table#toRowSet()} can be used to obtain a fresh row set view over the table. + */ + public default Table table() { + Table result; + try (QueryExec qExec = build()) { + RowSet rowSet = qExec.select(); + result = TableFactory.create(rowSet); + } + return result; + } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java index 4ac53a69314..cb50ae5e66c 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java @@ -93,4 +93,8 @@ public default Stream stream() { public static RowSet create(QueryIterator qIter, List vars) { return RowSetStream.create(vars, qIter); } + + default ResultSet asResultSet() { + return ResultSet.adapt(this); + } } diff --git a/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java b/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java index 3cc72454afc..99dcbb39592 100644 --- a/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java +++ b/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java @@ -78,7 +78,10 @@ public void testCloneOfValuesDataBlock() { // from those from the original query { Query clone = TestQueryCloningEssentials.checkedClone(query); + Assert.assertEquals(query.getValuesData(), clone.getValuesData()); + // The values block is no longer mutable since jena-5.3.0 + /* clone.getValuesData().clear(); Assert.assertEquals(0, clone.getValuesData().size()); Assert.assertNotEquals(0, query.getValuesData().size()); @@ -86,6 +89,7 @@ public void testCloneOfValuesDataBlock() { clone.getValuesVariables().clear(); Assert.assertEquals(0, clone.getValuesVariables().size()); Assert.assertNotEquals(0, query.getValuesVariables().size()); + */ } } } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java index 258a3a003fc..7c926a204b7 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java @@ -20,12 +20,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; - -import org.apache.jena.graph.Node; -import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.algebra.Table; import org.apache.jena.sparql.core.DatasetGraphFactory; -import org.apache.jena.sparql.engine.binding.Binding; +import org.apache.jena.sparql.sse.SSE; +import org.junit.Test; /** Miscellaneous tests, e.g. from reports. */ public class TestQueryExecution { @@ -40,11 +38,28 @@ public class TestQueryExecution { } } """; - DatasetGraph dsg = DatasetGraphFactory.empty(); - RowSet rowSet = QueryExec.dataset(dsg).query(qsReport).select(); - Binding row = rowSet.next(); - row.contains("xOut"); - Node x = row.get("xOut"); - assertEquals("x", x.getLiteralLexicalForm()); + + Table expected = SSE.parseTable("(table (row (?xIn 'x') (?x 1) (?xOut 'x') ) )"); + Table actual = QueryExec.dataset(DatasetGraphFactory.empty()).query(qsReport).table(); + assertEquals(expected, actual); + } + + @Test public void lateral_with_nesting() { + // GH-2924 + String qsReport = """ + SELECT * { + BIND(1 AS ?s) + LATERAL { + BIND(?s AS ?x) + LATERAL { + BIND(?s AS ?y) + } + } + } + """; + + Table expected = SSE.parseTable("(table (row (?s 1) (?x 1) (?y 1) ) )"); + Table actual = QueryExec.dataset(DatasetGraphFactory.empty()).query(qsReport).table(); + assertEquals(expected, actual); } } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java b/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java index 90f18dbed13..b37e8d6e273 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java @@ -28,8 +28,12 @@ import org.apache.jena.query.* ; import org.apache.jena.rdf.model.Model ; import org.apache.jena.rdf.model.ModelFactory ; +import org.apache.jena.sparql.algebra.Table ; import org.apache.jena.sparql.core.Quad ; +import org.apache.jena.sparql.exec.QueryExec ; +import org.apache.jena.sparql.exec.QueryExecBuilder ; import org.apache.jena.sparql.sse.SSE ; +import org.apache.jena.system.Txn; import org.junit.Test ; /** Test API use of models, including some union graph cases : see also DatasetGraphTests */ @@ -40,12 +44,12 @@ public abstract class GraphsTests protected static final String graph1 = "http://example/g1" ; protected static final String graph2 = "http://example/g2" ; protected static final String graph3 = "http://example/g3" ; - + private Dataset dataset ; private Model calcUnion = ModelFactory.createDefaultModel() ; protected abstract Dataset createDataset() ; - + protected Dataset getDataset() { if ( dataset == null ) @@ -55,17 +59,17 @@ protected Dataset getDataset() } return dataset ; } - + protected void fillDataset(Dataset dataset) { // Load default model. // Load graph 1 // Load graph 2. dataset.getDefaultModel().getGraph().add(SSE.parseTriple("(

'Default graph')")) ; - + Model m1 = dataset.getNamedModel(graph1) ; m1.getGraph().add(SSE.parseTriple("(

'Graph 1')")) ; m1.getGraph().add(SSE.parseTriple("(

'ZZZ')")) ; - + Model m2 = dataset.getNamedModel(graph2) ; m2.getGraph().add(SSE.parseTriple("(

'Graph 2')")) ; m2.getGraph().add(SSE.parseTriple("(

'ZZZ')")) ; @@ -74,30 +78,30 @@ protected void fillDataset(Dataset dataset) { } String queryString = "SELECT * {?s ?p ?o}" ; - - @Test public void graph1() + + @Test public void graph1() { Dataset ds = getDataset() ; int x = query(queryString, ds.getDefaultModel()) ; assertEquals(1,x) ; } - - @Test public void graph2() + + @Test public void graph2() { Dataset ds = getDataset() ; int x = query(queryString, ds.getNamedModel(graph1)) ; assertEquals(2,x) ; } - @Test public void graph3() + @Test public void graph3() { Dataset ds = getDataset() ; int x = query(queryString, ds.getNamedModel(graph3)) ; assertEquals(0,x) ; } - - @Test public void graph4() + + @Test public void graph4() { Dataset ds = getDataset() ; int x = query(queryString, ds.getNamedModel(Quad.unionGraph.getURI())) ; @@ -106,56 +110,80 @@ protected void fillDataset(Dataset dataset) { m.isIsomorphicWith(calcUnion) ; } - @Test public void graph5() + @Test public void graph5() { Dataset ds = getDataset() ; int x = query(queryString, ds.getNamedModel(Quad.defaultGraphIRI.getURI())) ; assertEquals(1,x) ; } - @Test public void graph6() + @Test public void graph6() { Dataset ds = getDataset() ; int x = query(queryString, ds.getNamedModel(Quad.defaultGraphNodeGenerated.getURI())) ; assertEquals(1,x) ; } - @Test public void graph_count1() + /** Test that checks that {@link QueryExecBuilder#table()} correctly detaches the bindings such that they remain + * valid even after the query execution and the data set have been closed. */ + @Test public void table1() + { + // Use a transaction if the reference data set is in one. + Dataset ref = getDataset() ; + + Table expected = SSE.parseTable("(table (row (?s ) (?p

) (?o \"Default graph\") ) )") ; + Table actual ; + Dataset ds = createDataset() ; + try { + if (ref.isInTransaction()) { + Txn.executeWrite(ds, () -> fillDataset(ds)) ; + actual = Txn.calculateRead(ds, () -> QueryExec.dataset(ds.asDatasetGraph()).query(queryString).table()) ; + } else { + fillDataset(ds) ; + actual = QueryExec.dataset(ds.asDatasetGraph()).query(queryString).table() ; + } + } finally { + ds.close() ; + } + assertEquals(expected, actual) ; + } + + @Test public void graph_count1() { Dataset ds = getDataset() ; long x = count(ds.getDefaultModel()) ; assertEquals(1,x) ; } - @Test public void graph_count2() + @Test public void graph_count2() { Dataset ds = getDataset() ; long x = count(ds.getNamedModel(graph1)) ; assertEquals(2,x) ; } - @Test public void graph_count3() + @Test public void graph_count3() { Dataset ds = getDataset() ; long x = count(ds.getNamedModel(graph3)) ; assertEquals(0,x) ; } - - @Test public void graph_count4() + + @Test public void graph_count4() { Dataset ds = getDataset() ; long x = count(ds.getNamedModel(Quad.unionGraph.getURI())) ; assertEquals(3,x) ; } - - @Test public void graph_count5() + + @Test public void graph_count5() { Dataset ds = getDataset() ; long x = count(ds.getNamedModel(Quad.defaultGraphIRI.getURI())) ; assertEquals(1,x) ; } - @Test public void graph_count6() + @Test public void graph_count6() { Dataset ds = getDataset() ; long x = count(ds.getNamedModel(Quad.defaultGraphNodeGenerated.getURI())) ; @@ -170,29 +198,29 @@ protected void fillDataset(Dataset dataset) { assertEquals(0, x) ; } - @Test public void graph_api1() + @Test public void graph_api1() { Dataset ds = getDataset() ; int x = api(ds.getDefaultModel()) ; assertEquals(1,x) ; } - - @Test public void graph_api2() + + @Test public void graph_api2() { Dataset ds = getDataset() ; int x = api(ds.getNamedModel(graph1)) ; assertEquals(2,x) ; } - @Test public void graph_api3() + @Test public void graph_api3() { Dataset ds = getDataset() ; int x = api(ds.getNamedModel(graph3)) ; assertEquals(0,x) ; } - - @Test public void graph_api4() + + @Test public void graph_api4() { Dataset ds = getDataset() ; int x = api(ds.getNamedModel(Quad.unionGraph.getURI())) ; @@ -201,20 +229,20 @@ protected void fillDataset(Dataset dataset) { m.isIsomorphicWith(calcUnion) ; } - @Test public void graph_api5() + @Test public void graph_api5() { Dataset ds = getDataset() ; int x = api(ds.getNamedModel(Quad.defaultGraphIRI.getURI())) ; assertEquals(1,x) ; } - @Test public void graph_api6() + @Test public void graph_api6() { Dataset ds = getDataset() ; int x = api(ds.getNamedModel(Quad.defaultGraphNodeGenerated.getURI())) ; assertEquals(1,x) ; } - + private int query(String str, Model model) { Query q = QueryFactory.create(str, Syntax.syntaxARQ) ; @@ -223,14 +251,14 @@ private int query(String str, Model model) return ResultSetFormatter.consume(rs) ; } } - + private int api(Model model) { Iterator iter = model.getGraph().find(Node.ANY, Node.ANY, Node.ANY) ; int x = (int)Iter.count(iter) ; return x ; } - + private long count(Model model) { return model.size() ; diff --git a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java index b07bac36255..deeadaa042f 100644 --- a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java +++ b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java @@ -18,7 +18,11 @@ package org.apache.jena.tdb1.solver; -import java.util.* ; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.apache.jena.atlas.logging.Log ; import org.apache.jena.graph.Node ; @@ -26,6 +30,7 @@ import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.engine.binding.Binding ; import org.apache.jena.sparql.engine.binding.BindingBase ; +import org.apache.jena.sparql.engine.binding.BindingFactory; import org.apache.jena.tdb1.TDB1Exception; import org.apache.jena.tdb1.store.NodeId; import org.apache.jena.tdb1.store.nodetable.NodeTable; @@ -159,4 +164,14 @@ protected void fmtVar(StringBuilder sbuff, Var var) String tmp = NodeFmtLib.displayStr(node) ; sbuff.append("( ?"+var.getVarName()+extra+" = "+tmp+" )") ; } + + @Override + public Binding detach() { + return BindingFactory.copy(this); + } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + throw new UnsupportedOperationException("Should never be called."); + } } diff --git a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java index dc913d1c041..5a77907c857 100644 --- a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java +++ b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java @@ -26,6 +26,7 @@ import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.engine.binding.Binding; import org.apache.jena.sparql.engine.binding.BindingBase; +import org.apache.jena.sparql.engine.binding.BindingFactory; import org.apache.jena.tdb2.TDBException; import org.apache.jena.tdb2.store.NodeId; import org.apache.jena.tdb2.store.nodetable.NodeTable; @@ -159,4 +160,14 @@ protected void fmtVar(StringBuilder sbuff, Var var) String tmp = NodeFmtLib.displayStr(node); sbuff.append("( ?"+var.getVarName()+extra+" = "+tmp+" )"); } + + @Override + public Binding detach() { + return BindingFactory.copy(this); + } + + @Override + protected Binding detachWithNewParent(Binding newParent) { + throw new UnsupportedOperationException("Should never be called."); + } }