From 7262641e426e3cce0f99c3fce972da5611783097 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/Query.java | 6 +-- .../java/org/apache/jena/query/ResultSet.java | 4 ++ .../apache/jena/sparql/algebra/Algebra.java | 19 ++++--- .../jena/sparql/algebra/TableFactory.java | 27 ++++++++-- .../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 | 53 +++++++++++++++---- .../apache/jena/sparql/exec/QueryExec.java | 6 +++ .../jena/sparql/exec/QueryExecBuilder.java | 28 +++++++++- .../org/apache/jena/sparql/exec/RowSet.java | 4 ++ .../jena/sparql/exec/TestQueryExecution.java | 36 +++++++++---- .../apache/jena/tdb1/solver/BindingTDB.java | 17 +++++- .../apache/jena/tdb2/solver/BindingTDB.java | 11 ++++ 25 files changed, 270 insertions(+), 55 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/query/Query.java b/jena-arq/src/main/java/org/apache/jena/query/Query.java index ed1546e18e6..26bd0212465 100644 --- a/jena-arq/src/main/java/org/apache/jena/query/Query.java +++ b/jena-arq/src/main/java/org/apache/jena/query/Query.java @@ -27,7 +27,7 @@ import org.apache.jena.atlas.logging.Log ; import org.apache.jena.graph.Node ; import org.apache.jena.sparql.ARQConstants ; -import org.apache.jena.sparql.algebra.table.TableData ; +import org.apache.jena.sparql.algebra.table.TableN; import org.apache.jena.sparql.core.* ; import org.apache.jena.sparql.engine.binding.Binding ; import org.apache.jena.sparql.expr.Expr ; @@ -98,7 +98,7 @@ public class Query extends Prologue implements Cloneable, Printable public static final int ORDER_UNKNOW = -3 ; // VALUES trailing clause - protected TableData valuesDataBlock = null ; + protected TableN valuesDataBlock = null ; protected boolean strictQuery = true ; @@ -601,7 +601,7 @@ public Expr allocAggregate(Aggregator agg) public void setValuesDataBlock(List variables, List values) { checkDataBlock(variables, values) ; - valuesDataBlock = new TableData(variables, values) ; + valuesDataBlock = new TableN(variables, values) ; } private static void checkDataBlock(List variables, List values) 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..2113e31bc13 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 toRowSet() { + 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..d97b8eaeb20 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,8 +18,10 @@ package org.apache.jena.sparql.algebra; +import java.util.Iterator; import java.util.List ; +import org.apache.jena.atlas.iterator.Iter; import org.apache.jena.graph.Node ; import org.apache.jena.sparql.algebra.table.Table1 ; import org.apache.jena.sparql.algebra.table.TableEmpty ; @@ -27,31 +29,46 @@ 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 = rs.getResultVars(); + Iterator it = Iter.map(rs, Binding::detach); + return create(vars, it); + } + + public static Table create(List vars, Iterator bindings) { + List list = Iter.toList(bindings); + 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..24734ab8cd1 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..97b1c9a86e2 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,13 +29,26 @@ 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.op.OpAssign; +import org.apache.jena.sparql.algebra.op.OpBGP; +import org.apache.jena.sparql.algebra.op.OpDatasetNames; +import org.apache.jena.sparql.algebra.op.OpGraph; +import org.apache.jena.sparql.algebra.op.OpPath; +import org.apache.jena.sparql.algebra.op.OpQuadPattern; +import org.apache.jena.sparql.algebra.op.OpService; +import org.apache.jena.sparql.algebra.op.OpTable; +import org.apache.jena.sparql.algebra.op.OpTriple; import org.apache.jena.sparql.algebra.table.Table1; -import org.apache.jena.sparql.algebra.table.TableN; -import org.apache.jena.sparql.core.*; +import org.apache.jena.sparql.algebra.table.TableData; +import org.apache.jena.sparql.core.BasicPattern; +import org.apache.jena.sparql.core.Substitute; +import org.apache.jena.sparql.core.TriplePath; +import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.core.VarExprList; import org.apache.jena.sparql.engine.ExecutionContext; import org.apache.jena.sparql.engine.QueryIterator; import org.apache.jena.sparql.engine.binding.Binding; @@ -292,17 +307,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<>(); 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/QueryExec.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExec.java index fb8236d465c..3d502bb5116 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExec.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExec.java @@ -54,6 +54,12 @@ public static QueryExecBuilder graph(Graph graph) { return QueryExecDatasetBuilder.create().graph(graph); } + /** Create a {@link QueryExecBuilder} initialized with an empty dataset. */ + public static QueryExecBuilder emptyDataset() { + DatasetGraph empty = DatasetGraphFactory.create(); + return dataset(empty); + } + /** Create a {@link QueryExecBuilder} for a remote endpoint. */ public static QueryExecBuilder service(String serviceURL) { return QueryExecHTTPBuilder.create().endpoint(serviceURL); 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..5778923c282 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 toResultSet() { + return ResultSet.adapt(this); + } } 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..26490aac770 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,13 +20,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.apache.jena.sparql.algebra.Table; +import org.apache.jena.sparql.sse.SSE; import org.junit.Test; -import org.apache.jena.graph.Node; -import org.apache.jena.sparql.core.DatasetGraph; -import org.apache.jena.sparql.core.DatasetGraphFactory; -import org.apache.jena.sparql.engine.binding.Binding; - /** Miscellaneous tests, e.g. from reports. */ public class TestQueryExecution { @Test public void lateral_with_join() { @@ -40,11 +37,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.emptyDataset().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.emptyDataset().query(qsReport).table(); + assertEquals(expected, actual); } } 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."); + } }