diff --git a/core/query/src/main/java/org/eclipse/rdf4j/query/AbstractBindingSet.java b/core/query/src/main/java/org/eclipse/rdf4j/query/AbstractBindingSet.java index d3348631276..dd5108c77cf 100644 --- a/core/query/src/main/java/org/eclipse/rdf4j/query/AbstractBindingSet.java +++ b/core/query/src/main/java/org/eclipse/rdf4j/query/AbstractBindingSet.java @@ -78,7 +78,8 @@ public String toString() { Iterator iter = iterator(); while (iter.hasNext()) { - sb.append(iter.next().toString()); + Binding next = iter.next(); + sb.append(next != null ? next.toString() : "null"); if (iter.hasNext()) { sb.append(';'); } diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java index 0878adc310e..728960aab39 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java @@ -23,6 +23,7 @@ import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.util.Values; import org.eclipse.rdf4j.query.AbstractBindingSet; import org.eclipse.rdf4j.query.Binding; import org.eclipse.rdf4j.query.BindingSet; @@ -42,6 +43,8 @@ public class ArrayBindingSet extends AbstractBindingSet implements MutableBindin private static final long serialVersionUID = -1L; private static final Logger logger = LoggerFactory.getLogger(ArrayBindingSet.class); + private static final Value NULL_VALUE = Values + .iri("urn:null:d57c56f3-41a9-468e-8dce-5706ebdef84c_e88d9e52-27cb-4056-a889-1ea353fa6f0c"); private final String[] bindingNames; @@ -49,8 +52,6 @@ public class ArrayBindingSet extends AbstractBindingSet implements MutableBindin private Set bindingNamesSetCache; private boolean empty; - private final boolean[] whichBindingsHaveBeenSet; - private final Value[] values; /** @@ -63,7 +64,6 @@ public class ArrayBindingSet extends AbstractBindingSet implements MutableBindin public ArrayBindingSet(String... names) { this.bindingNames = names; this.values = new Value[names.length]; - this.whichBindingsHaveBeenSet = new boolean[names.length]; this.empty = true; } @@ -71,13 +71,17 @@ public ArrayBindingSet(BindingSet toCopy, Set names, String[] namesArray assert !(toCopy instanceof ArrayBindingSet); this.bindingNames = namesArray; - this.whichBindingsHaveBeenSet = new boolean[this.bindingNames.length]; this.values = new Value[this.bindingNames.length]; for (int i = 0; i < this.bindingNames.length; i++) { Binding binding = toCopy.getBinding(this.bindingNames[i]); + if (binding != null) { this.values[i] = binding.getValue(); - this.whichBindingsHaveBeenSet[i] = true; + if (this.values[i] == null) { + this.values[i] = NULL_VALUE; + } + } else if (hasBinding(this.bindingNames[i])) { + this.values[i] = NULL_VALUE; } } this.empty = toCopy.isEmpty(); @@ -89,8 +93,6 @@ public ArrayBindingSet(ArrayBindingSet toCopy, String... names) { this.bindingNames = names; this.values = Arrays.copyOf(toCopy.values, toCopy.values.length); - this.whichBindingsHaveBeenSet = Arrays.copyOf(toCopy.whichBindingsHaveBeenSet, - toCopy.whichBindingsHaveBeenSet.length); this.empty = toCopy.empty; assert !this.empty || size() == 0; } @@ -111,8 +113,7 @@ public BiConsumer getDirectSetBinding(String bindingName return null; } return (v, a) -> { - a.values[index] = v; - a.whichBindingsHaveBeenSet[index] = true; + a.values[index] = v == null ? NULL_VALUE : v; a.empty = false; a.clearCache(); }; @@ -126,9 +127,8 @@ public BiConsumer getDirectAddBinding(String bindingName return null; } return (v, a) -> { - assert !a.whichBindingsHaveBeenSet[index] : "variable already bound: " + bindingName; - a.values[index] = v; - a.whichBindingsHaveBeenSet[index] = true; + assert a.values[index] == null; + a.values[index] = v == null ? NULL_VALUE : v; a.empty = false; a.clearCache(); }; @@ -143,6 +143,9 @@ public Function getDirectGetBinding(String bindingName } return a -> { Value value = a.values[index]; + if (value == NULL_VALUE) { + value = null; + } if (value != null) { return new SimpleBinding(bindingName, value); } else { @@ -157,7 +160,7 @@ public Function getDirectGetValue(String bindingName) { if (index == -1) { return null; } - return a -> a.values[index]; + return a -> a.values[index] == NULL_VALUE ? null : a.values[index]; } @@ -166,7 +169,7 @@ public Function getDirectHasBinding(String bindingName if (index == -1) { return null; } - return a -> a.whichBindingsHaveBeenSet[index]; + return a -> a.values[index] != null; } private int getIndex(String bindingName) { @@ -195,7 +198,7 @@ public Set getBindingNames() { this.bindingNamesSetCache = Collections.emptySet(); } else if (size == 1) { for (int i = 0; i < this.bindingNames.length; i++) { - if (whichBindingsHaveBeenSet[i]) { + if (values[i] != null) { this.bindingNamesSetCache = Collections.singleton(bindingNames[i]); break; } @@ -204,7 +207,7 @@ public Set getBindingNames() { } else { LinkedHashSet bindingNamesSetCache = new LinkedHashSet<>(size * 2); for (int i = 0; i < this.bindingNames.length; i++) { - if (whichBindingsHaveBeenSet[i]) { + if (values[i] != null) { bindingNamesSetCache.add(bindingNames[i]); } } @@ -222,14 +225,14 @@ public Value getValue(String bindingName) { } for (int i = 0; i < bindingNames.length; i++) { - if (bindingNames[i] == bindingName && whichBindingsHaveBeenSet[i]) { - return values[i]; + if (bindingNames[i] == bindingName && values[i] != null) { + return values[i] == NULL_VALUE ? null : values[i]; } } for (int i = 0; i < bindingNames.length; i++) { - if (bindingNames[i].equals(bindingName) && whichBindingsHaveBeenSet[i]) { - return values[i]; + if (bindingNames[i].equals(bindingName) && values[i] != null) { + return values[i] == NULL_VALUE ? null : values[i]; } } return null; @@ -242,6 +245,9 @@ public Binding getBinding(String bindingName) { } Value value = getValue(bindingName); + if (value == NULL_VALUE) { + value = null; + } if (value != null) { return new SimpleBinding(bindingName, value); @@ -260,7 +266,7 @@ public boolean hasBinding(String bindingName) { if (index == -1) { return false; } - return whichBindingsHaveBeenSet[index]; + return values[index] != null; } @Override @@ -280,8 +286,8 @@ public int size() { int size = 0; - for (boolean value : whichBindingsHaveBeenSet) { - if (value) { + for (Value value : values) { + if (value != null) { size++; } } @@ -298,14 +304,14 @@ public List getSortedBindingNames() { if (size == 1) { for (int i = 0; i < bindingNames.length; i++) { - if (whichBindingsHaveBeenSet[i]) { + if (values[i] != null) { sortedBindingNames = Collections.singletonList(bindingNames[i]); } } } else { ArrayList names = new ArrayList<>(size); for (int i = 0; i < bindingNames.length; i++) { - if (whichBindingsHaveBeenSet[i]) { + if (values[i] != null) { names.add(bindingNames[i]); } } @@ -320,17 +326,17 @@ public List getSortedBindingNames() { @Override public void addBinding(Binding binding) { int index = getIndex(binding.getName()); + Value value = binding.getValue(); if (index == -1) { logger.error( - "We don't actually support adding a binding. " + binding.getName() + " : " + binding.getValue()); + "We don't actually support adding a binding. " + binding.getName() + " : " + value); assert false - : "We don't actually support adding a binding. " + binding.getName() + " : " + binding.getValue(); + : "We don't actually support adding a binding. " + binding.getName() + " : " + value; return; } - assert !this.whichBindingsHaveBeenSet[index]; - this.values[index] = binding.getValue(); - this.whichBindingsHaveBeenSet[index] = true; + assert this.values[index] == null; + this.values[index] = value == null ? NULL_VALUE : value; empty = false; clearCache(); } @@ -341,8 +347,8 @@ public void setBinding(Binding binding) { if (index == -1) { return; } - this.values[index] = binding.getValue(); - this.whichBindingsHaveBeenSet[index] = true; + Value value = binding.getValue(); + this.values[index] = value == null ? NULL_VALUE : value; empty = false; clearCache(); } @@ -355,11 +361,10 @@ public void setBinding(String name, Value value) { } this.values[index] = value; - this.whichBindingsHaveBeenSet[index] = value != null; if (value == null) { this.empty = true; - for (boolean b : whichBindingsHaveBeenSet) { - if (b) { + for (Value value1 : this.values) { + if (value1 != null) { this.empty = false; break; } @@ -382,17 +387,16 @@ private void clearCache() { public void addAll(ArrayBindingSet other) { if (other.bindingNames == bindingNames) { for (int i = 0; i < bindingNames.length; i++) { - if (other.whichBindingsHaveBeenSet[i]) { + if (other.values[i] != null) { this.values[i] = other.values[i]; - this.whichBindingsHaveBeenSet[i] = true; this.empty = false; } } } else { for (int i = 0; i < bindingNames.length; i++) { if (other.hasBinding(bindingNames[i])) { - this.values[i] = other.getValue(bindingNames[i]); - this.whichBindingsHaveBeenSet[i] = true; + Value value = other.getValue(bindingNames[i]); + this.values[i] = value == null ? NULL_VALUE : value; this.empty = false; } } @@ -412,7 +416,7 @@ public ArrayBindingSetIterator() { @Override public boolean hasNext() { while (index < values.length) { - if (whichBindingsHaveBeenSet[index]) { + if (values[index] != null) { return true; } index++; @@ -423,9 +427,12 @@ public boolean hasNext() { @Override public Binding next() { while (index < values.length) { - if (whichBindingsHaveBeenSet[index]) { + if (values[index] != null) { String name = bindingNames[index]; Value value = values[index++]; + if (value == NULL_VALUE) { + value = null; + } if (value != null) { return new SimpleBinding(name, value); } else { diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/ArrayBindingBasedQueryEvaluationContext.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/ArrayBindingBasedQueryEvaluationContext.java index 32b235a613f..a80c6f004bb 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/ArrayBindingBasedQueryEvaluationContext.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/ArrayBindingBasedQueryEvaluationContext.java @@ -22,6 +22,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.Binding; @@ -32,9 +33,11 @@ import org.eclipse.rdf4j.query.algebra.BindingSetAssignment; import org.eclipse.rdf4j.query.algebra.ExtensionElem; import org.eclipse.rdf4j.query.algebra.Group; +import org.eclipse.rdf4j.query.algebra.GroupElem; import org.eclipse.rdf4j.query.algebra.MultiProjection; import org.eclipse.rdf4j.query.algebra.Projection; import org.eclipse.rdf4j.query.algebra.ProjectionElem; +import org.eclipse.rdf4j.query.algebra.QueryModelNode; import org.eclipse.rdf4j.query.algebra.QueryRoot; import org.eclipse.rdf4j.query.algebra.StatementPattern; import org.eclipse.rdf4j.query.algebra.UnaryTupleOperator; @@ -46,6 +49,14 @@ import org.eclipse.rdf4j.query.impl.EmptyBindingSet; public final class ArrayBindingBasedQueryEvaluationContext implements QueryEvaluationContext { + + public static final Predicate HAS_BINDING_FALSE = (bs) -> false; + public static final Function GET_BINDING_NULL = (bs) -> null; + public static final Function GET_VALUE_NULL = (bs) -> null; + public static final BiConsumer SET_BINDING_NO_OP = (val, bs) -> { + }; + public static final BiConsumer ADD_BINDING_NO_OP = SET_BINDING_NO_OP; + private final QueryEvaluationContext context; private final String[] allVariables; private final Set allVariablesSet; @@ -59,7 +70,8 @@ public final class ArrayBindingBasedQueryEvaluationContext implements QueryEvalu private final boolean initialized; - ArrayBindingBasedQueryEvaluationContext(QueryEvaluationContext context, String[] allVariables, + @InternalUseOnly + public ArrayBindingBasedQueryEvaluationContext(QueryEvaluationContext context, String[] allVariables, Comparator comparator) { assert new HashSet<>(Arrays.asList(allVariables)).size() == allVariables.length; this.context = context; @@ -114,16 +126,27 @@ public Predicate hasBinding(String variableName) { return hasBinding[i]; } } + + for (int i = 0; i < allVariables.length; i++) { + if (allVariables[i].equals(variableName)) { + return hasBinding[i]; + } + } + + return HAS_BINDING_FALSE; } assert variableName != null && !variableName.isEmpty(); + Function directHasVariable = defaultArrayBindingSet.getDirectHasBinding(variableName); + if (directHasVariable != null) { return new HasBinding(variableName, directHasVariable); } else { // If the variable is not in the default set, it can never be part of this array binding - return (bs) -> false; + return HAS_BINDING_FALSE; } + } static private class HasBinding implements Predicate { @@ -157,10 +180,19 @@ public Function getBinding(String variableName) { return getBinding[i]; } } + + for (int i = 0; i < allVariables.length; i++) { + if (allVariables[i].equals(variableName)) { + return getBinding[i]; + } + } + + return GET_BINDING_NULL; } Function directAccessForVariable = defaultArrayBindingSet .getDirectGetBinding(variableName); + if (directAccessForVariable != null) { return (bs) -> { if (bs.isEmpty()) { @@ -172,7 +204,7 @@ public Function getBinding(String variableName) { } }; } else { - return (bs) -> null; + return GET_BINDING_NULL; } } @@ -184,16 +216,26 @@ public Function getValue(String variableName) { return getValue[i]; } } + + for (int i = 0; i < allVariables.length; i++) { + if (allVariables[i].equals(variableName)) { + return getValue[i]; + } + } + + return GET_VALUE_NULL; } Function directAccessForVariable = defaultArrayBindingSet .getDirectGetValue(variableName); + if (directAccessForVariable != null) { return new ValueGetter(variableName, directAccessForVariable); } else { // If the variable is not in the default set, it can never be part of this array binding - return (bs) -> null; + return GET_VALUE_NULL; } + } private static class ValueGetter implements Function { @@ -228,6 +270,14 @@ public BiConsumer setBinding(String variableName) { return setBinding[i]; } } + + for (int i = 0; i < allVariables.length; i++) { + if (allVariables[i].equals(variableName)) { + return setBinding[i]; + } + } + + return SET_BINDING_NO_OP; } BiConsumer directAccessForVariable = defaultArrayBindingSet @@ -241,7 +291,7 @@ public BiConsumer setBinding(String variableName) { } }; } else { - return (val, bs) -> bs.setBinding(variableName, val); + return SET_BINDING_NO_OP; } } @@ -253,10 +303,16 @@ public BiConsumer addBinding(String variableName) { return addBinding[i]; } } + for (int i = 0; i < allVariables.length; i++) { + if (allVariables[i].equals(variableName)) { + return addBinding[i]; + } + } + + return ADD_BINDING_NO_OP; } - BiConsumer wrapped = defaultArrayBindingSet - .getDirectAddBinding(variableName); + BiConsumer wrapped = defaultArrayBindingSet.getDirectAddBinding(variableName); if (wrapped != null) { return (val, bs) -> { if (bs instanceof ArrayBindingSet) { @@ -266,7 +322,7 @@ public BiConsumer addBinding(String variableName) { } }; } else { - return (val, bs) -> bs.addBinding(variableName, val); + return ADD_BINDING_NO_OP; } } @@ -286,6 +342,11 @@ public static String[] findAllVariablesUsedInQuery(QueryRoot node) { AbstractSimpleQueryModelVisitor queryModelVisitorBase = new AbstractSimpleQueryModelVisitor<>( true) { + @Override + public void meetOther(QueryModelNode node) throws QueryEvaluationException { + super.meetOther(node); + } + @Override public void meet(Var node) throws QueryEvaluationException { super.meet(node); @@ -338,6 +399,12 @@ public void meet(ExtensionElem node) throws QueryEvaluationException { super.meet(node); } + @Override + public void meet(GroupElem node) throws QueryEvaluationException { + node.setName(varNames.computeIfAbsent(node.getName(), k -> k)); + super.meet(node); + } + @Override public void meet(BindingSetAssignment node) throws QueryEvaluationException { Set bindingNames = node.getBindingNames(); diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinIterator.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinIterator.java index 05f7602216f..63448ba96aa 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinIterator.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinIterator.java @@ -12,11 +12,11 @@ package org.eclipse.rdf4j.query.algebra.evaluation.iterator; import java.util.Comparator; +import java.util.NoSuchElementException; import java.util.function.Function; import org.eclipse.rdf4j.common.annotation.Experimental; import org.eclipse.rdf4j.common.iteration.CloseableIteration; -import org.eclipse.rdf4j.common.iteration.LookAheadIteration; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.Binding; import org.eclipse.rdf4j.query.BindingSet; @@ -30,28 +30,39 @@ * @author HÃ¥vard M. Ottestad */ @Experimental -public class InnerMergeJoinIterator extends LookAheadIteration { +public class InnerMergeJoinIterator implements CloseableIteration { private final PeekMarkIterator leftIterator; private final PeekMarkIterator rightIterator; private final Comparator cmp; - private final Function value; + private final Function valueFunction; private final QueryEvaluationContext context; - private InnerMergeJoinIterator(CloseableIteration leftIterator, - CloseableIteration rightIterator, - Comparator cmp, Function value, QueryEvaluationContext context) + private BindingSet next; + private BindingSet currentLeft; + private Value currentLeftValue; + private Value leftPeekValue; + + // -1 for unset, 0 for equal, 1 for different + int currentLeftValueAndPeekEquals = -1; + + private boolean closed = false; + + InnerMergeJoinIterator(CloseableIteration leftIterator, CloseableIteration rightIterator, + Comparator cmp, Function valueFunction, QueryEvaluationContext context) throws QueryEvaluationException { + this.leftIterator = new PeekMarkIterator<>(leftIterator); this.rightIterator = new PeekMarkIterator<>(rightIterator); this.cmp = cmp; - this.value = value; + this.valueFunction = valueFunction; this.context = context; } public static CloseableIteration getInstance(QueryEvaluationStep leftPrepared, QueryEvaluationStep preparedRight, BindingSet bindings, Comparator cmp, Function value, QueryEvaluationContext context) { + CloseableIteration leftIter = leftPrepared.evaluate(bindings); if (leftIter == QueryEvaluationStep.EMPTY_ITERATION) { return leftIter; @@ -66,10 +77,7 @@ public static CloseableIteration getInstance(QueryEvaluationStep lef return new InnerMergeJoinIterator(leftIter, rightIter, cmp, value, context); } - BindingSet next; - BindingSet currentLeft; - - BindingSet join(BindingSet left, BindingSet right, boolean createNewBindingSet) { + private BindingSet join(BindingSet left, BindingSet right, boolean createNewBindingSet) { MutableBindingSet joined; if (!createNewBindingSet && left instanceof MutableBindingSet) { joined = (MutableBindingSet) left; @@ -89,139 +97,196 @@ BindingSet join(BindingSet left, BindingSet right, boolean createNewBindingSet) return joined; } - BindingSet prevLeft; - - void calculateNext() { + private void calculateNext() { if (next != null) { return; } if (currentLeft == null && leftIterator.hasNext()) { currentLeft = leftIterator.next(); + currentLeftValue = null; + leftPeekValue = null; + currentLeftValueAndPeekEquals = -1; + } if (currentLeft == null) { return; } + loop(); + + } + + private void loop() { while (next == null) { if (rightIterator.hasNext()) { BindingSet peekRight = rightIterator.peek(); - Value left = value.apply(currentLeft); - Value right = value.apply(peekRight); - - /* - * country1: http://www.wikidata.org/entity/Q467864 country2: http://www.wikidata.org/entity/Q222 - * currency: http://www.wikidata.org/entity/Q125999 - */ -// -// if (currentLeft.toString().contains("Q467864") && peekRight.toString().contains("Q222")) { -// System.out.println(); -// } -// if (peekRight.toString().contains("Q467864") && currentLeft.toString().contains("Q222")) { -// System.out.println(); -// } -// -// System.out.println("Left: " + currentLeft); -// System.out.println("Right: " + peekRight); -// System.out.println(); - - int compareTo = cmp.compare(left, right); - - // TODO add an assert block that checks that the left iterator is sequential - -// { -// BindingSet temp = leftIterator.peek(); -// if (temp != null) { -// if (prevLeft != currentLeft) { -// prevLeft = currentLeft; -// int compare = cmp.compare(left, value.apply(temp)); -// if (compare > 0) { -// System.out.println(compare + "\tleft: " + left.toString() + " next left:" -// + value.apply(temp).toString()); -// assert false; -// } -// -// } -// -// } -// } - - if (compareTo == 0) { - if (rightIterator.isResettable()) { - next = join(currentLeft, rightIterator.next(), true); + + if (currentLeftValue == null) { + currentLeftValue = valueFunction.apply(currentLeft); + leftPeekValue = null; + currentLeftValueAndPeekEquals = -1; + } + + int compare = compare(currentLeftValue, valueFunction.apply(peekRight)); + + if (compare == 0) { + equal(); + return; + } else if (compare < 0) { + // leftIterator is behind, or in other words, rightIterator is ahead + if (leftIterator.hasNext()) { + lessThan(); } else { - BindingSet leftPeek = leftIterator.peek(); - if (leftPeek != null && left.equals(value.apply(leftPeek))) { - rightIterator.mark(); - next = join(currentLeft, rightIterator.next(), true); - } else { - BindingSet nextRight = rightIterator.next(); - BindingSet rightPeek = rightIterator.peek(); - if (rightPeek != null && right.equals(value.apply(rightPeek))) { - next = join(currentLeft, nextRight, true); - } else { - next = join(currentLeft, nextRight, false); - } - } + close(); + return; } - break; } else { - if (compareTo < 0) { - // leftIterator is behind, or in other words, rightIterator is ahead - if (leftIterator.hasNext()) { - BindingSet prevLeft = currentLeft; - currentLeft = leftIterator.next(); - if (cmp.compare(value.apply(prevLeft), value.apply(currentLeft)) == 0) { - // we have duplicate keys on the leftIterator and need to reset the rightIterator (if it - // is resettable) - if (rightIterator.isResettable()) { - rightIterator.reset(); - } - } else { - rightIterator.unmark(); - } - } else { - currentLeft = null; - break; - } - } else { - // rightIterator is behind, skip forward - rightIterator.next(); - - } + // rightIterator is behind, skip forward + rightIterator.next(); } } else if (rightIterator.isResettable() && leftIterator.hasNext()) { rightIterator.reset(); currentLeft = leftIterator.next(); + currentLeftValue = null; + leftPeekValue = null; + currentLeftValueAndPeekEquals = -1; + } else { + close(); + return; + } + + } + } + + private int compare(Value left, Value right) { + int compareTo; + + if (left == right) { + compareTo = 0; + } else { + compareTo = cmp.compare(left, right); + } + return compareTo; + } + + private void lessThan() { + Value oldLeftValue = currentLeftValue; + currentLeft = leftIterator.next(); + if (leftPeekValue != null) { + currentLeftValue = leftPeekValue; + } else { + currentLeftValue = valueFunction.apply(currentLeft); + } + + leftPeekValue = null; + currentLeftValueAndPeekEquals = -1; + + if (oldLeftValue.equals(currentLeftValue)) { + // we have duplicate keys on the leftIterator and need to reset the rightIterator (if it + // is resettable) + if (rightIterator.isResettable()) { + rightIterator.reset(); + } + } else { + rightIterator.unmark(); + } + } + + private void equal() { + if (rightIterator.isResettable()) { + next = join(currentLeft, rightIterator.next(), true); + } else { + doLeftPeek(); + + if (currentLeftValueAndPeekEquals == 0) { + rightIterator.mark(); + next = join(currentLeft, rightIterator.next(), true); } else { - currentLeft = null; - break; + next = join(rightIterator.next(), currentLeft, false); } + } + } + private void doLeftPeek() { + if (leftPeekValue == null) { + BindingSet leftPeek = leftIterator.peek(); + leftPeekValue = leftPeek != null ? valueFunction.apply(leftPeek) : null; + currentLeftValueAndPeekEquals = -1; } + if (currentLeftValueAndPeekEquals == -1) { + boolean equals = currentLeftValue.equals(leftPeekValue); + if (equals) { + currentLeftValue = leftPeekValue; + currentLeftValueAndPeekEquals = 0; + } else { + currentLeftValueAndPeekEquals = 1; + } + } } @Override - protected BindingSet getNextElement() throws QueryEvaluationException { + public final boolean hasNext() { + if (isClosed()) { + return false; + } + calculateNext(); - BindingSet temp = next; - next = null; - return temp; + + return next != null; } @Override - protected void handleClose() throws QueryEvaluationException { - try { - leftIterator.close(); - } finally { - if (rightIterator != null) { + public final BindingSet next() { + if (isClosed()) { + throw new NoSuchElementException("The iteration has been closed."); + } + calculateNext(); + + if (next == null) { + close(); + } + + BindingSet result = next; + + if (result != null) { + next = null; + return result; + } else { + throw new NoSuchElementException(); + } + } + + /** + * Throws an {@link UnsupportedOperationException}. + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Checks whether this CloseableIteration has been closed. + * + * @return true if the CloseableIteration has been closed, false otherwise. + */ + public final boolean isClosed() { + return closed; + } + + @Override + public final void close() { + if (!closed) { + closed = true; + try { + leftIterator.close(); + } finally { rightIterator.close(); } } } - } diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIterator.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIterator.java index dc46b2eaccb..55b412a2267 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIterator.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIterator.java @@ -94,7 +94,6 @@ public E next() { } /** - * * @return the next element without consuming it, or null if there are no more elements */ public E peek() { @@ -115,7 +114,14 @@ public void mark() { } mark = true; resetPossible = 1; - buffer = new ArrayList<>(); + + if (buffer != null && !bufferIterator.hasNext()) { + buffer.clear(); + bufferIterator = Collections.emptyIterator(); + } else { + buffer = new ArrayList<>(); + } + if (next != null) { buffer.add(next); } @@ -188,6 +194,12 @@ public void close() { public void unmark() { mark = false; resetPossible = -1; - buffer = null; + if (bufferIterator.hasNext()) { + buffer = null; + } else if (buffer != null) { + + buffer.clear(); + bufferIterator = Collections.emptyIterator(); + } } } diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinTest.java new file mode 100644 index 00000000000..28807258a60 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/InnerMergeJoinTest.java @@ -0,0 +1,340 @@ +/******************************************************************************* + * Copyright (c) 2024 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + ******************************************************************************/ + +package org.eclipse.rdf4j.query.algebra.evaluation.iterator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.CloseableIteratorIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.algebra.evaluation.ArrayBindingSet; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.ArrayBindingBasedQueryEvaluationContext; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.query.impl.ListBindingSet; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class InnerMergeJoinTest { + + private final ArrayBindingBasedQueryEvaluationContext context = new ArrayBindingBasedQueryEvaluationContext( + new QueryEvaluationContext.Minimal(null), new String[] { "a", "b", "c" }, null); + private final Function valueFunction = context.getValue("a"); + private final Comparator cmp = Comparator.comparing(Value::stringValue); + + @Test + public void testInnerMergeJoinEmpty() { + try (InnerMergeJoinIterator innerMergeJoinIterator = new InnerMergeJoinIterator( + new PeekMarkIterator<>(new EmptyIteration<>()), new PeekMarkIterator<>(new EmptyIteration<>()), null, + null, null)) { + boolean b = innerMergeJoinIterator.hasNext(); + assertFalse(b); + } + } + + @Test + public void testInnerMergeJoinRemove() { + try (var innerMergeJoinIterator = new InnerMergeJoinIterator(new PeekMarkIterator<>(new EmptyIteration<>()), + new PeekMarkIterator<>(new EmptyIteration<>()), null, null, null)) { + Assertions.assertThrows(UnsupportedOperationException.class, innerMergeJoinIterator::remove); + } + } + + @Test + public void testInnerMergeJoinClose1() { + + var left = iterator(left("a1", "b1"), left("a2", "b2")); + var right = iterator(right("a1", "b_1"), right("a2", "b_2")); + + try (InnerMergeJoinIterator innerMergeJoinIterator = new InnerMergeJoinIterator(new PeekMarkIterator<>(left), + new PeekMarkIterator<>(right), cmp, valueFunction, context)) { + ArrayList bindingSets = new ArrayList<>(); + while (innerMergeJoinIterator.hasNext()) { + bindingSets.add(innerMergeJoinIterator.next()); + innerMergeJoinIterator.close(); + } + + } + + } + + @Test + public void testInnerMergeJoinClose2() { + + var left = iterator(left("a1", "b1"), left("a2", "b2")); + var right = iterator(right("a1", "b_1"), right("a2", "b_2")); + + try (InnerMergeJoinIterator innerMergeJoinIterator = new InnerMergeJoinIterator(new PeekMarkIterator<>(left), + new PeekMarkIterator<>(right), cmp, valueFunction, context)) { + innerMergeJoinIterator.next(); + innerMergeJoinIterator.close(); + Assertions.assertThrows(NoSuchElementException.class, innerMergeJoinIterator::next); + + } + + } + + @Test + public void testInnerMergeJoinNextNull() { + + var left = iterator(left("a1", "b1"), left("a2", "b2")); + var right = iterator(right("a1", "b_1"), right("a2", "b_2")); + + try (InnerMergeJoinIterator innerMergeJoinIterator = new InnerMergeJoinIterator(new PeekMarkIterator<>(left), + new PeekMarkIterator<>(right), cmp, valueFunction, context)) { + innerMergeJoinIterator.next(); + innerMergeJoinIterator.next(); + Assertions.assertThrows(NoSuchElementException.class, innerMergeJoinIterator::next); + } + + } + + @Test + public void testInnerMergeJoinRightNotResettable() { + + var left = iterator(left("a1", "b1"), left("a2", "b21"), left("a2", "b22"), left("a3", "b3")); + var right = iterator(right("a1", "b_1"), right("a3", "b_3")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList("[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a3\";b=\"b3\";c=\"b_3\"]]"), result); + + } + + @Test + public void testInnerMergeJoin1() { + + var left = iterator(left("a1", "b1"), left("a2", "b2")); + var right = iterator(right("a1", "b_1"), right("a2", "b_2")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList("[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a2\";b=\"b2\";c=\"b_2\"]]"), result); + } + + @Test + public void testInnerMergeJoinLeftRepeat() { + + var left = iterator(left("a1", "b1"), left("a1", "b2"), left("a2", "b3")); + var right = iterator(right("a1", "b_1"), right("a2", "b_2")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals( + toList("[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b2\";c=\"b_1\"], [a=\"a2\";b=\"b3\";c=\"b_2\"]]"), + result); + } + + @Test + public void testInnerMergeJoinLeftRightRepeat() { + + var left = iterator(left("a1", "b1"), left("a1", "b2"), left("a2", "b3")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), right("a2", "b_3")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a1\";b=\"b2\";c=\"b_1\"], [a=\"a1\";b=\"b2\";c=\"b_2\"], [a=\"a2\";b=\"b3\";c=\"b_3\"]]"), + result); + } + + @Test + public void testInnerMergeJoinLeftRightRepeatNotMutable() { + + var left = iterator(left("a1", "b1"), left("a1", "b2"), left("a2", "b3")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), rightImmutable("a2", "b_3")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a1\";b=\"b2\";c=\"b_1\"], [a=\"a1\";b=\"b2\";c=\"b_2\"], [a=\"a2\";b=\"b3\";c=\"b_3\"]]"), + result); + } + + @Test + public void testInnerMergeJoinLeftRightRepeatMutableNotArrayBindingSet() { + + var left = iterator(left("a1", "b1"), left("a1", "b2"), left("a2", "b3")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), rightMutable("a2", "b_3")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a1\";b=\"b2\";c=\"b_1\"], [a=\"a1\";b=\"b2\";c=\"b_2\"], [a=\"a2\";b=\"b3\";c=\"b_3\"]]"), + result); + } + + @Test + public void testInnerMergeJoinLeftHolesRepeat() { + + var left = iterator(left("a1", "b1"), left("a1", "b2"), left("a2", "b3"), left("a3", "b4")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), right("a3", "b_4")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a1\";b=\"b2\";c=\"b_1\"], [a=\"a1\";b=\"b2\";c=\"b_2\"], [a=\"a3\";b=\"b4\";c=\"b_4\"]]"), + result); + } + + @Test + public void testInnerMergeJoinRightRepeat() { + + var left = iterator(left("a1", "b1"), left("a2", "b2"), left("a3", "b3"), left("a4", "b4")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), right("a3", "b_3"), right("a3", "b_32"), + right("a4", "b_4")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a3\";b=\"b3\";c=\"b_3\"], [a=\"a3\";b=\"b3\";c=\"b_32\"], [a=\"a4\";b=\"b4\";c=\"b_4\"]]"), + result); + } + + @Test + public void testInnerMergeJoinRightRepeatLeftEmptyFirst() { + + var left = iterator(left("a1", "b1"), left("a2", "b2"), left("a3", "b3"), left("a4", "b4")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), right("a3", "b_3"), right("a3", "b_32"), + right("a4", "b_4"), right("a5", "b_5"), right("a6", "b_6")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a3\";b=\"b3\";c=\"b_3\"], [a=\"a3\";b=\"b3\";c=\"b_32\"], [a=\"a4\";b=\"b4\";c=\"b_4\"]]"), + result); + } + + @Test + public void testInnerMergeJoinRightRepeatRightEmptyFirst() { + + var left = iterator(left("a1", "b1"), left("a2", "b2"), left("a3", "b3"), left("a4", "b4"), left("a5", "b5"), + left("a5", "b52"), left("a6", "b6")); + var right = iterator(right("a1", "b_1"), right("a1", "b_2"), right("a3", "b_3"), right("a3", "b_32"), + right("a4", "b_4")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_2\"], [a=\"a3\";b=\"b3\";c=\"b_3\"], [a=\"a3\";b=\"b3\";c=\"b_32\"], [a=\"a4\";b=\"b4\";c=\"b_4\"]]"), + result); + } + + @Test + public void testInnerMergeJoinRightBehind() { + + var left = iterator(left("a1", "b1"), left("a3", "b3"), left("a3", "b32"), left("a4", "b4")); + var right = iterator(right("a1", "b_1"), right("a1", "b_12"), right("a2", "b_2"), right("a22", "b_22"), + right("a3", "b_3"), right("a4", "b_4")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_12\"], [a=\"a3\";b=\"b3\";c=\"b_3\"], [a=\"a3\";b=\"b32\";c=\"b_3\"], [a=\"a4\";b=\"b4\";c=\"b_4\"]]"), + result); + } + + @Test + public void testInnerMergeJoinRightResettable() { + + var left = iterator(left("a1", "b1"), left("a3", "b31"), left("a3", "b32"), left("a3", "b33"), + left("a4", "b4")); + var right = iterator(right("a1", "b_1"), right("a1", "b_12"), right("a2", "b_2"), right("a22", "b_22"), + right("a3", "b_31"), right("a3", "b_32"), right("a3", "b_33")); + + List result = toString(innerMergeJoin(left, right)); + + assertEquals(toList( + "[[a=\"a1\";b=\"b1\";c=\"b_1\"], [a=\"a1\";b=\"b1\";c=\"b_12\"], [a=\"a3\";b=\"b31\";c=\"b_31\"], [a=\"a3\";b=\"b31\";c=\"b_32\"], [a=\"a3\";b=\"b31\";c=\"b_33\"], [a=\"a3\";b=\"b32\";c=\"b_31\"], [a=\"a3\";b=\"b32\";c=\"b_32\"], [a=\"a3\";b=\"b32\";c=\"b_33\"], [a=\"a3\";b=\"b33\";c=\"b_31\"], [a=\"a3\";b=\"b33\";c=\"b_32\"], [a=\"a3\";b=\"b33\";c=\"b_33\"]]"), + result); + } + + private List toList(String input) { + input = input.replace("[[", "[").replace("]]", "]"); + return Arrays.asList(input.split(", ")); + } + + private List toString(List result) { + return result.stream().map(BindingSet::toString).collect(Collectors.toList()); + } + + private List innerMergeJoin(CloseableIteration left, CloseableIteration right) { + + try (InnerMergeJoinIterator innerMergeJoinIterator = new InnerMergeJoinIterator(new PeekMarkIterator<>(left), + new PeekMarkIterator<>(right), cmp, valueFunction, context)) { + ArrayList bindingSets = new ArrayList<>(); + while (innerMergeJoinIterator.hasNext()) { + bindingSets.add(innerMergeJoinIterator.next()); + } + + return bindingSets; + } + } + + private BindingSet left(String v1, String v2) { + ArrayBindingSet arrayBindingSet = context.createBindingSet(); + arrayBindingSet.addBinding("a", getLiteral(v1)); + arrayBindingSet.addBinding("b", getLiteral(v2)); + return arrayBindingSet; + } + + Literal a2 = SimpleValueFactory.getInstance().createLiteral("a2"); + + private Literal getLiteral(String v1) { + if (v1 == "a2") { + return a2; + } + + return SimpleValueFactory.getInstance().createLiteral(v1); + } + + private BindingSet right(String v1, String v2) { + ArrayBindingSet arrayBindingSet = context.createBindingSet(); + arrayBindingSet.addBinding("a", getLiteral(v1)); + arrayBindingSet.addBinding("c", getLiteral(v2)); + return arrayBindingSet; + } + + private BindingSet rightMutable(String v1, String v2) { + ArrayBindingSet arrayBindingSet = context.createBindingSet(); + arrayBindingSet.addBinding("a", getLiteral(v1)); + arrayBindingSet.addBinding("c", getLiteral(v2)); + return new QueryBindingSet(arrayBindingSet); + } + + private BindingSet rightImmutable(String v1, String v2) { + return new ListBindingSet(List.of("a", "c"), getLiteral(v1), getLiteral(v2)); + } + + private CloseableIteration iterator(BindingSet... bindingSets) { + + List bindingSets1 = new ArrayList<>(List.of(bindingSets)); + bindingSets1.sort(Comparator.comparing(bindingSet -> bindingSet.getValue("a").stringValue())); + + return new CloseableIteratorIteration<>(bindingSets1.iterator()); + + } + +} diff --git a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/BNodeGenerator.java b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/BNodeGenerator.java index 3b92e1141e1..b26dfcb9de9 100644 --- a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/BNodeGenerator.java +++ b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/BNodeGenerator.java @@ -39,6 +39,7 @@ public ValueExpr getNodeIdExpr() { public void setNodeIdExpr(ValueExpr nodeIdExpr) { this.nodeIdExpr = nodeIdExpr; + this.nodeIdExpr.setParentNode(this); } @Override @@ -48,12 +49,17 @@ public void visit(QueryModelVisitor visitor) throws X { @Override public void visitChildren(QueryModelVisitor visitor) throws X { - // no-op + if (nodeIdExpr != null) { + nodeIdExpr.visit(visitor); + } } @Override public void replaceChildNode(QueryModelNode current, QueryModelNode replacement) { - throw new IllegalArgumentException("Node is not a child node: " + current); + if (current == nodeIdExpr) { + setNodeIdExpr((ValueExpr) replacement); + } + } @Override @@ -68,6 +74,10 @@ public int hashCode() { @Override public BNodeGenerator clone() { - return (BNodeGenerator) super.clone(); + BNodeGenerator clone = (BNodeGenerator) super.clone(); + if (nodeIdExpr != null) { + clone.setNodeIdExpr(nodeIdExpr.clone()); + } + return clone; } } diff --git a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/AbstractSimpleQueryModelVisitor.java b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/AbstractSimpleQueryModelVisitor.java index f2948a6d0a9..44bdd2ccee8 100644 --- a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/AbstractSimpleQueryModelVisitor.java +++ b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/helpers/AbstractSimpleQueryModelVisitor.java @@ -146,7 +146,7 @@ public void meet(BindingSetAssignment node) throws X { @Override public void meet(BNodeGenerator node) throws X { - + node.visitChildren(this); } @Override diff --git a/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/RepositorySPARQLComplianceTestSuite.java b/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/RepositorySPARQLComplianceTestSuite.java index 51830226f6c..cfea6a32b25 100644 --- a/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/RepositorySPARQLComplianceTestSuite.java +++ b/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/RepositorySPARQLComplianceTestSuite.java @@ -68,6 +68,11 @@ Stream arbitraryLengthPath() throws RDF4JException, IOException { return new ArbitraryLengthPathTest(this::getEmptyInitializedRepository).tests(); } + @TestFactory + Stream filterScopeTests() throws RDF4JException, IOException { + return new FilterScopeTest(this::getEmptyInitializedRepository).tests(); + } + @TestFactory Stream basic() throws RDF4JException, IOException { return new BasicTest(this::getEmptyInitializedRepository).tests(); diff --git a/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/FilterScopeTest.java b/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/FilterScopeTest.java index 731ea5e91d0..2d5047905ab 100644 --- a/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/FilterScopeTest.java +++ b/testsuites/sparql/src/main/java/org/eclipse/rdf4j/testsuite/sparql/tests/FilterScopeTest.java @@ -236,7 +236,7 @@ private void loadData(RepositoryConnection conn) { conn.add(bnode4, FOAF.KNOWS, bnode5); } - public Stream tests(RepositoryConnection conn) { + public Stream tests() { return Stream.of( makeTest("testScope1", this::testScope1), makeTest("testScope1WithoutScopingIssue", this::testScope1WithoutScopingIssue),