diff --git a/compliance/store/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreTest.java b/compliance/store/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreTest.java index f88dfe6fe..b328f0b23 100644 --- a/compliance/store/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreTest.java +++ b/compliance/store/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreTest.java @@ -7,11 +7,16 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.nativerdf; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.IOException; +import org.assertj.core.util.Files; import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.query.QueryResults; import org.eclipse.rdf4j.sail.NotifyingSail; import org.eclipse.rdf4j.sail.RDFNotifyingStoreTest; import org.eclipse.rdf4j.sail.SailException; @@ -62,4 +67,22 @@ public void testGetNamespacePersistence() throws Exception { assertEquals(RDF.NAMESPACE, con.getNamespace("rdf")); } + @Test + public void testContextCacheReconstruction() throws Exception { + con.begin(); + con.addStatement(RDF.TYPE, RDF.TYPE, RDF.TYPE, RDF.ALT); + con.commit(); + con.close(); + sail.shutDown(); + + File contextFile = new File(tempDir.getRoot(), "/nativestore/contexts.dat"); + Files.delete(contextFile); + + sail.init(); + con = sail.getConnection(); + + assertTrue(contextFile.exists()); + assertThat(QueryResults.asList(con.getContextIDs()).size()).isEqualTo(1); + } + } diff --git a/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/ContextStore.java b/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/ContextStore.java index 8dc618219..ca7b8c4b1 100644 --- a/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/ContextStore.java +++ b/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/ContextStore.java @@ -24,9 +24,11 @@ import java.util.Objects; import org.eclipse.rdf4j.common.io.IOUtil; +import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.sail.SailException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,12 +86,15 @@ class ContextStore implements Iterable { private final ValueFactory valueFactory; + private final NativeSailStore store; + ContextStore(NativeSailStore store, File dataDir) throws IOException { Objects.requireNonNull(store); Objects.requireNonNull(dataDir); this.file = new File(dataDir, FILE_NAME); this.valueFactory = store.getValueFactory(); + this.store = store; contextInfoMap = new HashMap<>(16); @@ -98,7 +103,7 @@ class ContextStore implements Iterable { } catch (IOException e) { logger.info("could not read context index: " + e.getMessage(), e); logger.debug("attempting reconstruction from store (this may take a while)"); - store.initializeContextCache(); + initializeContextCache(); writeContextsToFile(); logger.info("context index reconstruction complete"); } @@ -163,6 +168,15 @@ private void writeContextsToFile() throws IOException { } } + private void initializeContextCache() throws IOException { + logger.debug("initializing context cache"); + try (CloseableIteration contextIter = store.getContexts()) { + while (contextIter.hasNext()) { + increment(contextIter.next()); + } + } + } + private void readContextsFromFile() throws IOException { synchronized (file) { if (!file.exists()) { diff --git a/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeSailStore.java b/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeSailStore.java index b1c7634a6..e8e94ae6a 100644 --- a/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeSailStore.java +++ b/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeSailStore.java @@ -22,6 +22,7 @@ import org.eclipse.rdf4j.OpenRDFUtil; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.CloseableIteratorIteration; +import org.eclipse.rdf4j.common.iteration.ConvertingIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; import org.eclipse.rdf4j.common.iteration.FilterIteration; import org.eclipse.rdf4j.common.iteration.UnionIteration; @@ -52,13 +53,13 @@ class NativeSailStore implements SailStore { final Logger logger = LoggerFactory.getLogger(NativeSailStore.class); - final TripleStore tripleStore; + private final TripleStore tripleStore; - final ValueStore valueStore; + private final ValueStore valueStore; - final NamespaceStore namespaceStore; + private final NamespaceStore namespaceStore; - final ContextStore contextStore; + private final ContextStore contextStore; /** * A lock to control concurrent access by {@link NativeSailSink} to the TripleStore, ValueStore, and NamespaceStore. @@ -174,8 +175,7 @@ List getContextIDs(Resource... contexts) throws IOException { return contextIDs; } - void initializeContextCache() throws IOException { - logger.debug("initializing context cache"); + CloseableIteration getContexts() throws IOException { RecordIterator btreeIter = tripleStore.getAllTriplesSortedByContext(false); CloseableIteration stIter1; if (btreeIter == null) { @@ -185,20 +185,20 @@ void initializeContextCache() throws IOException { stIter1 = new NativeStatementIterator(btreeIter, valueStore); } - // Filter statements without context resource - try (FilterIteration stIter2 = new FilterIteration( + FilterIteration stIter2 = new FilterIteration( stIter1) { - @Override protected boolean accept(Statement st) { return st.getContext() != null; } - }) { - while (stIter2.hasNext()) { - Statement st = stIter2.next(); - contextStore.increment(st.getContext()); + }; + + return new ConvertingIteration(stIter2) { + @Override + protected Resource convert(Statement sourceObject) throws SailException { + return sourceObject.getContext(); } - } + }; } /** @@ -232,6 +232,7 @@ CloseableIteration createStatementIterator(R int objID = NativeValue.UNKNOWN_ID; if (obj != null) { objID = valueStore.getID(obj); + if (objID == NativeValue.UNKNOWN_ID) { return new EmptyIteration<>(); } diff --git a/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/ContextStoreTest.java b/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/ContextStoreTest.java index 61e87b414..164f76633 100644 --- a/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/ContextStoreTest.java +++ b/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/ContextStoreTest.java @@ -8,15 +8,16 @@ package org.eclipse.rdf4j.sail.nativerdf; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.File; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.sail.SailException; import org.junit.Before; import org.junit.Test; @@ -46,8 +47,9 @@ public class ContextStoreTest { public void setUp() throws Exception { dir = Files.createTempDir(); NativeSailStore sailStore = mock(NativeSailStore.class); - doNothing().when(sailStore).initializeContextCache(); + when(sailStore.getValueFactory()).thenReturn(SimpleValueFactory.getInstance()); + when(sailStore.getContexts()).thenReturn(new EmptyIteration()); subject = new ContextStore(sailStore, dir); }