diff --git a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/DualUnionIteration.java b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/DualUnionIteration.java index 62ec2d38183..249044f146b 100644 --- a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/DualUnionIteration.java +++ b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/DualUnionIteration.java @@ -15,18 +15,17 @@ import java.util.NoSuchElementException; import org.eclipse.rdf4j.common.annotation.Experimental; -import org.eclipse.rdf4j.common.order.StatementOrder; -import org.eclipse.rdf4j.model.Value; /** * Provides a bag union of the two provided iterations. */ public class DualUnionIteration implements CloseableIteration { - private final StatementOrder statementOrder; - private final Comparator cmp; + private final Comparator cmp; private CloseableIteration iteration1; private CloseableIteration iteration2; + private E nextElementIteration1; + private E nextElementIteration2; private E nextElement; /** * Flag indicating whether this iteration has been closed. @@ -37,20 +36,15 @@ private DualUnionIteration(CloseableIteration iteration1, CloseableIteration iteration2) { this.iteration1 = iteration1; this.iteration2 = iteration2; - this.statementOrder = null; this.cmp = null; } @Experimental - public DualUnionIteration(StatementOrder statementOrder, Comparator cmp, + public DualUnionIteration(Comparator cmp, CloseableIteration iteration1, CloseableIteration iteration2) { this.iteration1 = iteration1; this.iteration2 = iteration2; - this.statementOrder = statementOrder; this.cmp = cmp; - - // TODO - throw new UnsupportedOperationException("Not implemented yet"); } public static CloseableIteration getWildcardInstance( @@ -66,25 +60,16 @@ public static CloseableIteration getWildcardInstance( } @Experimental - public static CloseableIteration getWildcardInstance(StatementOrder order, - Comparator cmp, + public static CloseableIteration getWildcardInstance(Comparator cmp, CloseableIteration leftIteration, CloseableIteration rightIteration) { - if (rightIteration instanceof EmptyIteration) { - return leftIteration; - } else if (leftIteration instanceof EmptyIteration) { - return rightIteration; - } else { - if (!rightIteration.hasNext()) { - rightIteration.close(); - return leftIteration; - } - if (!leftIteration.hasNext()) { - leftIteration.close(); - return rightIteration; - } - return new DualUnionIteration<>(order, cmp, leftIteration, rightIteration); - } +// if (rightIteration instanceof EmptyIteration) { +// return leftIteration; +// } else if (leftIteration instanceof EmptyIteration) { +// return rightIteration; +// } else { + return new DualUnionIteration<>(cmp, leftIteration, rightIteration); +// } } public static CloseableIteration getInstance(CloseableIteration leftIteration, @@ -99,32 +84,6 @@ public static CloseableIteration getInstance(CloseableIteration leftIt } } - public E getNextElement() { - if (iteration1 == null && iteration2 != null) { - if (iteration2.hasNext()) { - return iteration2.next(); - } else { - iteration2.close(); - iteration2 = null; - } - } else if (iteration1 != null) { - if (iteration1.hasNext()) { - return iteration1.next(); - } else if (iteration2.hasNext()) { - iteration1.close(); - iteration1 = null; - return iteration2.next(); - } else { - iteration1.close(); - iteration1 = null; - iteration2.close(); - iteration2 = null; - } - } - - return null; - } - @Override public final boolean hasNext() { if (closed) { @@ -156,7 +115,11 @@ public final E next() { */ private E lookAhead() { if (nextElement == null) { - nextElement = getNextElement(); + if (cmp == null) { + lookaheadWithoutOrder(); + } else { + lookaheadWithOrder(); + } if (nextElement == null) { close(); @@ -165,6 +128,73 @@ private E lookAhead() { return nextElement; } + private void lookaheadWithOrder() { + assert cmp != null; + if (nextElementIteration1 == null && iteration1 != null) { + if (iteration1.hasNext()) { + nextElementIteration1 = iteration1.next(); + } else { + iteration1.close(); + iteration1 = null; + } + } + + if (nextElementIteration2 == null && iteration2 != null) { + if (iteration2.hasNext()) { + nextElementIteration2 = iteration2.next(); + } else { + iteration2.close(); + iteration2 = null; + } + } + + if (nextElementIteration1 != null && nextElementIteration2 != null) { + int compare = cmp.compare(nextElementIteration1, nextElementIteration2); + + if (compare <= 0) { + nextElement = nextElementIteration1; + nextElementIteration1 = null; + } else { + nextElement = nextElementIteration2; + nextElementIteration2 = null; + } + } else { + if (nextElementIteration1 != null) { + nextElement = nextElementIteration1; + nextElementIteration1 = null; + } else if (nextElementIteration2 != null) { + nextElement = nextElementIteration2; + nextElementIteration2 = null; + } + } + } + + private void lookaheadWithoutOrder() { + assert cmp == null; + + if (iteration1 == null && iteration2 != null) { + if (iteration2.hasNext()) { + nextElement = iteration2.next(); + } else { + iteration2.close(); + iteration2 = null; + } + } else if (iteration1 != null) { + if (iteration1.hasNext()) { + nextElement = iteration1.next(); + } else if (iteration2.hasNext()) { + iteration1.close(); + iteration1 = null; + nextElement = iteration2.next(); + } else { + iteration1.close(); + iteration1 = null; + iteration2.close(); + iteration2 = null; + } + } + } + /** * Throws an {@link UnsupportedOperationException}. */ diff --git a/core/common/order/src/main/java/org/eclipse/rdf4j/common/order/StatementOrder.java b/core/common/order/src/main/java/org/eclipse/rdf4j/common/order/StatementOrder.java index 97785413f20..cee4702aad7 100644 --- a/core/common/order/src/main/java/org/eclipse/rdf4j/common/order/StatementOrder.java +++ b/core/common/order/src/main/java/org/eclipse/rdf4j/common/order/StatementOrder.java @@ -40,4 +40,5 @@ public Comparator getComparator(Comparator comparator) { throw new IllegalStateException("Unknown StatementOrder: " + this); } + } 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 53d8d6e9f97..acf331272b4 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 @@ -51,7 +51,6 @@ private void calculateNext() { if (bufferIterator.hasNext()) { next = bufferIterator.next(); - assert resetPossible == 1; } else { if (!mark && resetPossible > -1) { resetPossible--; @@ -62,7 +61,7 @@ private void calculateNext() { } if (mark && next != null) { - assert resetPossible == 1; + assert resetPossible > 0; buffer.add(next); } @@ -107,6 +106,10 @@ public E peek() { return next; } + /** + * Mark the current position so that the iterator can be reset to the current state. This will cause elements to be + * stored in memory until one of {@link #reset()}, {@link #unmark()} or {@link #mark()} is called + */ public void mark() { if (closed) { throw new IllegalStateException("The iteration has been closed."); @@ -120,6 +123,10 @@ public void mark() { } + /** + * Reset the iterator to the marked position. Resetting an iterator multiple times will always reset to the same + * position. If the iterator was not marked, this will throw an exception. + */ public void reset() { if (closed) { throw new IllegalStateException("The iteration has been closed."); @@ -127,31 +134,40 @@ public void reset() { if (buffer == null) { throw new IllegalStateException("Mark never set"); } + if (resetPossible < 0) { + throw new IllegalStateException("Reset not possible"); + } + if (mark && bufferIterator.hasNext()) { while (bufferIterator.hasNext()) { buffer.add(bufferIterator.next()); } } - mark = false; - if (resetPossible < 0) { - throw new IllegalStateException("Reset not possible"); - } else if (resetPossible == 0) { + if (resetPossible == 0) { + assert !mark; buffer.add(next); next = null; bufferIterator = buffer.iterator(); - } else if (resetPossible == 1) { + } else if (resetPossible > 0) { next = null; bufferIterator = buffer.iterator(); } + mark = false; resetPossible = 1; } + /** + * @return true if the iterator is marked + */ boolean isMarked() { return !closed && mark; } + /** + * @return true if {@link #reset()} can be called on this iterator + */ boolean isResettable() { return !closed && (mark || resetPossible >= 0); } @@ -160,11 +176,16 @@ boolean isResettable() { public void close() { this.closed = true; iterator.close(); + buffer = null; } - // What will happen if we are iterating over the buffer at this point, then unmark is called followed by mark? + /** + * Unmark the iterator. This will cause the iterator to stop buffering elements. If the iterator was recently reset + * and there are still elements in the buffer, then these elements will still be returned by next(). + */ public void unmark() { mark = false; resetPossible = -1; + buffer = null; } } diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIteratorTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIteratorTest.java index 73fa35a3a38..429a4cfa6dc 100644 --- a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIteratorTest.java +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/iterator/PeekMarkIteratorTest.java @@ -411,4 +411,193 @@ public void testReadToEndMarkReset2() { assertFalse(stringPeekMarkIterator.hasNext()); } + @Test + public void testUnmark() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.reset(); + assertEquals("b", stringPeekMarkIterator.next()); + stringPeekMarkIterator.unmark(); + assertEquals("c", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + assertEquals("d", stringPeekMarkIterator.next()); + assertEquals("e", stringPeekMarkIterator.next()); + stringPeekMarkIterator.reset(); + assertEquals("d", stringPeekMarkIterator.next()); + assertEquals("e", stringPeekMarkIterator.next()); + } + + @Test + public void testUnmark2() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.reset(); + assertEquals("b", stringPeekMarkIterator.next()); + stringPeekMarkIterator.unmark(); + assertEquals("c", stringPeekMarkIterator.next()); + assertEquals("d", stringPeekMarkIterator.next()); + assertEquals("e", stringPeekMarkIterator.next()); + } + + @Test + public void testUnmark3() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.unmark(); + assertFalse(stringPeekMarkIterator.isResettable()); + + Assertions.assertThrows(IllegalStateException.class, stringPeekMarkIterator::reset); + } + + @Test + public void testUnmark4() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.reset(); + assertEquals("b", stringPeekMarkIterator.peek()); + stringPeekMarkIterator.unmark(); + stringPeekMarkIterator.mark(); + assertEquals("b", stringPeekMarkIterator.next()); + stringPeekMarkIterator.reset(); + assertEquals("b", stringPeekMarkIterator.peek()); + + } + + @Test + public void testUnmark5() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.reset(); + stringPeekMarkIterator.unmark(); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.reset(); + assertEquals("b", stringPeekMarkIterator.peek()); + + } + + @Test + public void testUnmark6() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.reset(); + stringPeekMarkIterator.unmark(); + stringPeekMarkIterator.peek(); + stringPeekMarkIterator.mark(); + assertEquals("b", stringPeekMarkIterator.next()); + assertEquals("c", stringPeekMarkIterator.next()); + assertEquals("d", stringPeekMarkIterator.next()); + assertEquals("e", stringPeekMarkIterator.next()); + stringPeekMarkIterator.reset(); + assertEquals("b", stringPeekMarkIterator.next()); + assertEquals("c", stringPeekMarkIterator.next()); + assertEquals("d", stringPeekMarkIterator.next()); + assertEquals("e", stringPeekMarkIterator.next()); + + } + + @Test + public void testUnmark7() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.unmark(); + assertEquals("e", stringPeekMarkIterator.next()); + assertEquals("f", stringPeekMarkIterator.next()); + } + + @Test + public void testUnmark8() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.reset(); + stringPeekMarkIterator.unmark(); + assertEquals("b", stringPeekMarkIterator.next()); + assertEquals("c", stringPeekMarkIterator.next()); + assertEquals("d", stringPeekMarkIterator.next()); + assertEquals("e", stringPeekMarkIterator.next()); + assertEquals("f", stringPeekMarkIterator.next()); + + } + + @Test + public void testUnmark9() { + + PeekMarkIterator stringPeekMarkIterator = new PeekMarkIterator<>( + new CloseableIteratorIteration<>(List.of("a", "b", "c", "d", "e", "f").iterator())); + + assertEquals("a", stringPeekMarkIterator.peek()); + assertEquals("a", stringPeekMarkIterator.next()); + stringPeekMarkIterator.mark(); + stringPeekMarkIterator.next(); // b + stringPeekMarkIterator.next(); // c + stringPeekMarkIterator.next(); // d + stringPeekMarkIterator.unmark(); + assertEquals("e", stringPeekMarkIterator.next()); + assertEquals("f", stringPeekMarkIterator.next()); + + } + } diff --git a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/UnionSailDataset.java b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/UnionSailDataset.java index 4635ce8ca24..b7a9ac90dca 100644 --- a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/UnionSailDataset.java +++ b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/UnionSailDataset.java @@ -73,7 +73,7 @@ public CloseableIteration getNamespaces() throws SailExcept try { iteration1 = dataset1.getNamespaces(); iteration2 = dataset2.getNamespaces(); - return union(iteration1, iteration2); + return DualUnionIteration.getWildcardInstance(iteration1, iteration2); } catch (Throwable t) { try { if (iteration1 != null) { @@ -105,7 +105,7 @@ public CloseableIteration getContextIDs() throws SailExcepti try { iteration1 = dataset1.getContextIDs(); iteration2 = dataset2.getContextIDs(); - return union(iteration1, iteration2); + return DualUnionIteration.getWildcardInstance(iteration1, iteration2); } catch (Throwable t) { try { if (iteration1 != null) { @@ -129,7 +129,7 @@ public CloseableIteration getStatements(Resource subj, IRI try { iteration1 = dataset1.getStatements(subj, pred, obj, contexts); iteration2 = dataset2.getStatements(subj, pred, obj, contexts); - return union(iteration1, iteration2); + return DualUnionIteration.getWildcardInstance(iteration1, iteration2); } catch (Throwable t) { try { if (iteration1 != null) { @@ -154,7 +154,7 @@ public CloseableIteration getTriples(Resource subj, IRI pred, try { iteration1 = dataset1.getTriples(subj, pred, obj); iteration2 = dataset2.getTriples(subj, pred, obj); - return union(iteration1, iteration2); + return DualUnionIteration.getWildcardInstance(iteration1, iteration2); } catch (Throwable t) { try { if (iteration1 != null) { @@ -170,18 +170,6 @@ public CloseableIteration getTriples(Resource subj, IRI pred, } - private CloseableIteration union( - CloseableIteration iteration1, - CloseableIteration iteration2) { - return DualUnionIteration.getWildcardInstance(iteration1, iteration2); - } - - private CloseableIteration union(StatementOrder order, Comparator cmp, - CloseableIteration iteration1, - CloseableIteration iteration2) { - return DualUnionIteration.getWildcardInstance(order, cmp, iteration1, iteration2); - } - @Override public CloseableIteration getStatements(StatementOrder statementOrder, Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException { @@ -191,7 +179,8 @@ public CloseableIteration getStatements(StatementOrder stat try { iteration1 = dataset1.getStatements(statementOrder, subj, pred, obj, contexts); iteration2 = dataset2.getStatements(statementOrder, subj, pred, obj, contexts); - return union(statementOrder, dataset1.getComparator(), iteration1, iteration2); + Comparator cmp = statementOrder.getComparator(dataset1.getComparator()); + return DualUnionIteration.getWildcardInstance(cmp, iteration1, iteration2); } catch (Throwable t) { try { if (iteration1 != null) { diff --git a/core/sail/extensible-store/pom.xml b/core/sail/extensible-store/pom.xml index dc45debd45c..13abf78ab50 100644 --- a/core/sail/extensible-store/pom.xml +++ b/core/sail/extensible-store/pom.xml @@ -75,6 +75,12 @@ ${jmhVersion} test + + ${project.groupId} + rdf4j-sail-inferencer + ${project.version} + test + diff --git a/core/sail/extensible-store/src/test/java/org/eclipse/rdf4j/sail/extensiblestore/ordered/OrderedTest.java b/core/sail/extensible-store/src/test/java/org/eclipse/rdf4j/sail/extensiblestore/ordered/OrderedTest.java index e4ee6695e47..fb4e33d60b9 100644 --- a/core/sail/extensible-store/src/test/java/org/eclipse/rdf4j/sail/extensiblestore/ordered/OrderedTest.java +++ b/core/sail/extensible-store/src/test/java/org/eclipse/rdf4j/sail/extensiblestore/ordered/OrderedTest.java @@ -30,6 +30,8 @@ import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencer; +import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencerConnection; import org.junit.jupiter.api.Test; public class OrderedTest { @@ -69,14 +71,15 @@ public void testSubject() { } @Test - public void testObject() { - ExtensibleStoreOrderedImplForTests store = new ExtensibleStoreOrderedImplForTests(); + public void testObjectWithInferencer() { + SchemaCachingRDFSInferencer schemaCachingRDFSInferencer = new SchemaCachingRDFSInferencer( + new ExtensibleStoreOrderedImplForTests()); - try (NotifyingSailConnection connection = store.getConnection()) { + try (SchemaCachingRDFSInferencerConnection connection = schemaCachingRDFSInferencer.getConnection()) { connection.begin(); connection.addStatement(Values.iri(NAMESPACE, "d"), RDFS.LABEL, Values.literal("b")); - connection.addStatement(Values.iri(NAMESPACE, "e"), RDFS.LABEL, Values.literal("a")); - connection.addStatement(Values.iri(NAMESPACE, "a"), RDFS.LABEL, Values.literal("e")); + connection.addInferredStatement(Values.iri(NAMESPACE, "e"), RDFS.LABEL, Values.literal("a")); + connection.addInferredStatement(Values.iri(NAMESPACE, "a"), RDFS.LABEL, Values.literal("e")); connection.addStatement(Values.iri(NAMESPACE, "c"), RDFS.LABEL, Values.literal("c")); connection.addStatement(Values.iri(NAMESPACE, "b"), RDFS.LABEL, Values.literal("d")); connection.commit(); @@ -89,6 +92,7 @@ public void testObject() { List subjects = collect .stream() .map(Statement::getObject) + .filter(Literal.class::isInstance) .map(i -> (Literal) i) .map(Literal::getLabel) .collect(Collectors.toList());