diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/Table1.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/Table1.java index a7cc31ea1ae..1f21ad409b0 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/Table1.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/Table1.java @@ -24,7 +24,6 @@ import org.apache.jena.atlas.iterator.Iter ; import org.apache.jena.graph.Node ; -import org.apache.jena.riot.out.NodeFmtLib ; import org.apache.jena.sparql.core.Var ; import org.apache.jena.sparql.engine.ExecutionContext ; import org.apache.jena.sparql.engine.QueryIterator ; @@ -34,25 +33,24 @@ /** A table of one row of one binding */ public class Table1 extends TableBase { - private Var var ; - private Node value ; + private final Binding row; public Table1(Var var, Node value) { - this.var = var ; - this.value = value ; + this.row = BindingFactory.binding(var, value); + } + + public Table1(Binding row) { + this.row = row; } @Override public Iterator rows() { - Binding b = BindingFactory.binding(var, value) ; - return Iter.singletonIterator(b) ; + return Iter.singletonIterator(row) ; } @Override public QueryIterator iterator(ExecutionContext execCxt) { - // Root binding? - Binding binding = BindingFactory.binding(var, value) ; - QueryIterator qIter = QueryIterSingleton.create(null, var, value, execCxt) ; + QueryIterator qIter = QueryIterSingleton.create(row, execCxt) ; return qIter ; } @@ -61,15 +59,15 @@ public void closeTable() {} @Override public List getVars() { - List x = new ArrayList<>() ; - x.add(var) ; - return x ; + List x = new ArrayList<>(); + row.forEach((v,n)->x.add(v)); + return x; } @Override public List getVarNames() { List x = new ArrayList<>() ; - x.add(var.getVarName()) ; + row.forEach((v,n)->x.add(v.getName())); return x ; } @@ -85,6 +83,6 @@ public boolean isEmpty() { @Override public String toString() { - return "Table1(" + var + "," + NodeFmtLib.displayStr(value) + ")" ; + return "Table1(" + row + ")" ; } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java index 19cbadc2963..e741351417c 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java @@ -49,7 +49,7 @@ public class Substitute { /** * Inject takes an {@link Op} to transform using a {Binding binding}. The - * transformation assumes the Ope structure is legal for the operation. The + * transformation assumes the Op structure is legal for the operation. The * transformation is to wrap each place a variable is used (BGP, GRAPH, Path and * some equivalent operations) with a {@code BIND} to restrict the vartibale to a specific value * while still retaining the variable (e.g for FILETERs). @@ -71,7 +71,7 @@ public class Substitute { */ public static Op inject(Op opInput, Binding binding) { Set injectVars = binding.varsMentioned(); - Transform transform = new QueryIterLateral.TransformInject(injectVars, binding::get); + Transform transform = new QueryIterLateral.TransformInject(injectVars, binding); Op opOutput = Transformer.transform(transform, opInput); return opOutput; } 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 1162c6cb802..9ce2ce85a1f 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 @@ -18,6 +18,7 @@ package org.apache.jena.sparql.engine.iterator; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -27,8 +28,11 @@ import org.apache.jena.graph.Node; import org.apache.jena.graph.Triple; 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.core.*; import org.apache.jena.sparql.engine.ExecutionContext; import org.apache.jena.sparql.engine.QueryIterator; @@ -74,14 +78,14 @@ public static class TransformInject extends TransformCopy { private final Set injectVars; private final Set varsAsNodes; private final Function replacement; + private final Binding binding; private static final boolean substitute = true; - // Replacement becomes binding.?? - // Or "op call injection"!! - public TransformInject(Set injectVars, Function replacement) { + public TransformInject(Set injectVars, Binding binding) { this.injectVars = injectVars; this.varsAsNodes = Set.copyOf(injectVars); - this.replacement = replacement; + this.replacement = binding::get; + this.binding = binding; } @Override @@ -215,6 +219,7 @@ public Op transform(OpDatasetNames opDatasetNames) { // Basic Graph Pattern Matching // Property Path Patterns // evaluation of algebra form Graph(var,P) involving a variable (from the syntax GRAPH ?variable {…}) + // and also nested (table unit) inside (extend) @Override public Op transform(OpPath opPath) { @@ -270,6 +275,36 @@ public Op transform(OpTriple opTriple) { return opExec; } + private OpTable tableUnitTransformed = null; + + @Override + public Op transform(OpTable opTable) { + // Unit table. + if ( opTable.isJoinIdentity() ) { + if ( tableUnitTransformed == null ) { + Table table2 = new Table1(binding); + // Multiple assignment does not matter! + tableUnitTransformed = OpTable.create(table2); + } + return tableUnitTransformed; + } + + // 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); + BindingBuilder builder = BindingFactory.builder(); + table.iterator(null).forEachRemaining(row->{ + builder.reset(); + builder.addAll(row); + builder.addAll(binding); + table2.addBinding(builder.build()); + }); + return OpTable.create(table2); + } + private Triple applyReplacement(Triple triple, Function replacement) { Node s2 = applyReplacement(triple.getSubject(), replacement); Node p2 = applyReplacement(triple.getPredicate(), replacement); 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 6ae60d2e420..6a1739a19a7 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 @@ -25,8 +25,9 @@ @Suite.SuiteClasses( { TestExecEnvironment.class , TestQueryExecDataset.class + , TestQueryExecution.class } ) - + public class TS_ExecSPARQL { } 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 new file mode 100644 index 00000000000..258a3a003fc --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java @@ -0,0 +1,50 @@ +/* + * 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.exec; + +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.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() { + // GH-2896 LATERAL + String qsReport = """ + SELECT * { + BIND( 'x' AS ?xIn ) + LATERAL { + VALUES ?x { 1 } + { SELECT ?xIn ?xOut { BIND(?xIn AS ?xOut) } } + } + } + """; + 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()); + } +} diff --git a/jena-cmds/src/main/java/arq/cmdline/ModEngine.java b/jena-cmds/src/main/java/arq/cmdline/ModEngine.java index c278ef2f45e..bb6b4b60286 100644 --- a/jena-cmds/src/main/java/arq/cmdline/ModEngine.java +++ b/jena-cmds/src/main/java/arq/cmdline/ModEngine.java @@ -44,24 +44,25 @@ public class ModEngine extends ModBase private boolean timing = false ; @Override - public void registerWith(CmdGeneral cmdLine) - { - cmdLine.getUsage().startCategory("Query Engine") ; - cmdLine.add(engineDecl, "--engine=EngineName", "Register another engine factory[ref]") ; - cmdLine.add(unEngineDecl, "--unengine=EngineName", "Unregister an engine factory") ; + public void registerWith(CmdGeneral cmdLine) { + cmdLine.getUsage().startCategory("Query Engine"); + cmdLine.add(engineDecl, "--engine=EngineName", "Register another engine factory[ref]"); + cmdLine.add(unEngineDecl, "--unengine=EngineName", "Unregister an engine factory"); } - public void checkCommandLine(CmdGeneral cmdLine) - {} + public void resetRegistrations() { + QueryEngineRef.unregister(); + QueryEngineRefQuad.unregister(); + QueryEngineMainQuad.unregister(); + } @Override - public void processArgs(CmdArgModule cmdLine) - { + public void processArgs(CmdArgModule cmdLine) { - List engineDecls = cmdLine.getValues(engineDecl) ; + List engineDecls = cmdLine.getValues(engineDecl); -// if ( x.size() > 0 ) -// QueryEngineRegistry.get().factories().clear() ; + // if ( x.size() > 0 ) + // QueryEngineRegistry.get().factories().clear() ; for ( String engineName : engineDecls ) { switch (engineName.toLowerCase()) { @@ -75,7 +76,7 @@ public void processArgs(CmdArgModule cmdLine) } } - List unEngineDecls = cmdLine.getValues(unEngineDecl) ; + List unEngineDecls = cmdLine.getValues(unEngineDecl); for ( String engineName : unEngineDecls ) { switch (engineName.toLowerCase()) { case "reference", "ref" -> QueryEngineRef.unregister(); diff --git a/jena-cmds/src/main/java/arq/query.java b/jena-cmds/src/main/java/arq/query.java index e3e7a6c75e5..f08f250f52f 100644 --- a/jena-cmds/src/main/java/arq/query.java +++ b/jena-cmds/src/main/java/arq/query.java @@ -61,28 +61,27 @@ public class query extends CmdARQ protected ModResultsOut modResults = new ModResultsOut() ; protected ModEngine modEngine = new ModEngine() ; - public static void main (String... argv) - { - new query(argv).mainRun() ; + public static void main(String...argv) { + new query(argv).mainRun(); } - public query(String[] argv) - { - super(argv) ; - modQuery = new ModQueryIn(getDefaultSyntax()) ; - modDataset = setModDataset() ; + public query(String[] argv) { + super(argv); + modQuery = new ModQueryIn(getDefaultSyntax()); + modDataset = setModDataset(); modVersion.addClass(null, Jena.class); - super.addModule(modQuery) ; - super.addModule(modResults) ; - super.addModule(modDataset) ; - super.addModule(modEngine) ; - super.addModule(modTime) ; + super.addModule(modQuery); + super.addModule(modResults); + super.addModule(modDataset); + super.addModule(modEngine); + super.addModule(modTime); - super.getUsage().startCategory("Control") ; - super.add(argExplain, "--explain", "Explain and log query execution") ; - super.add(argRepeat, "--repeat=N or N,M", "Do N times or N warmup and then M times (use for timing to overcome start up costs of Java)"); - super.add(argOptimize, "--optimize=", "Turn the query optimizer on or off (default: on)") ; + super.getUsage().startCategory("Control"); + super.add(argExplain, "--explain", "Explain and log query execution"); + super.add(argRepeat, "--repeat=N or N,M", + "Do N times or N warmup and then M times (use for timing to overcome start up costs of Java)"); + super.add(argOptimize, "--optimize=", "Turn the query optimizer on or off (default: on)"); } /** Default syntax used when the syntax can not be determined from the command name or file extension @@ -163,6 +162,7 @@ protected void exec() String avgStr = modTime.timeStr(avg) ; System.err.println("Total time: "+modTime.timeStr(totalTime)+" sec for repeat count of "+repeatCount+ " : average: "+avgStr) ; } + modEngine.resetRegistrations(); } @Override @@ -211,8 +211,7 @@ protected Dataset dealWithNoDataset(Query query) { } protected long totalTime = 0 ; - protected void queryExec(boolean timed, ResultsFormat fmt, PrintStream resultsDest) - { + protected void queryExec(boolean timed, ResultsFormat fmt, PrintStream resultsDest) { try { Query query = getQuery() ; if ( isVerbose() ) {