diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java index 239a40cfb16..30f81f1748d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java @@ -13,10 +13,13 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; @@ -69,6 +72,8 @@ class LmdbSailStore implements SailStore { private final boolean enableMultiThreading = true; + private Set unusedIds = new HashSet<>(); + /** * A fast non-blocking circular buffer backed by an array. * @@ -134,6 +139,13 @@ class AddQuadOperation implements Operation { @Override public void execute() throws IOException { + if (!unusedIds.isEmpty()) { + // these ids are used again + unusedIds.remove(s); + unusedIds.remove(p); + unusedIds.remove(o); + unusedIds.remove(c); + } boolean wasNew = tripleStore.storeTriple(s, p, o, c, explicit); if (wasNew && context != null) { contextStore.increment(context); @@ -415,6 +427,19 @@ public void prepare() throws SailException { // serializable is not supported at this level } + protected void filterUsedIdsInTripleStore() throws IOException { + if (!unusedIds.isEmpty()) { + tripleStore.filterUsedIds(unusedIds); + } + } + + protected void handleRemovedIdsInValueStore() throws IOException { + if (!unusedIds.isEmpty()) { + valueStore.gcIds(unusedIds); + unusedIds.clear(); + } + } + @Override public void flush() throws SailException { sinkStoreAccessLock.lock(); @@ -446,6 +471,10 @@ public void flush() throws SailException { contextStore.sync(); } finally { if (activeTxn) { + if (!multiThreadingActive) { + filterUsedIdsInTripleStore(); + } + handleRemovedIdsInValueStore(); valueStore.commit(); if (!multiThreadingActive) { tripleStore.commit(); @@ -548,6 +577,8 @@ private void startTransaction(boolean preferThreading) throws SailException { Operation op = opQueue.remove(); if (op != null) { if (op == COMMIT_TRANSACTION) { + filterUsedIdsInTripleStore(); + tripleStore.commit(); nextTransactionAsync = false; asyncTransactionFinished = true; @@ -642,9 +673,17 @@ private long removeStatements(long subj, long pred, long obj, boolean explicit, throws IOException { long removeCount = 0; for (long contextId : contexts) { - Map result = tripleStore.removeTriplesByContext(subj, pred, obj, contextId, explicit); + final Map perContextCounts = new HashMap<>(); + tripleStore.removeTriplesByContext(subj, pred, obj, contextId, explicit, quad -> { + perContextCounts.merge(quad[3], 1L, (c, one) -> c + one); + for (long id : quad) { + if (id != 0L) { + unusedIds.add(id); + } + } + }); - for (Entry entry : result.entrySet()) { + for (Entry entry : perContextCounts.entrySet()) { Long entryContextId = entry.getKey(); if (entryContextId > 0) { Resource modifiedContext = (Resource) valueStore.getValue(entryContextId); @@ -809,5 +848,4 @@ public CloseableIteration getStatements(Reso } } } - -} +} \ No newline at end of file diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnection.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnection.java index 07584e61616..578d81c14a0 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnection.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnection.java @@ -13,13 +13,21 @@ import java.io.IOException; import org.eclipse.rdf4j.common.concurrent.locks.Lock; +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.IterationWrapper; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.SailReadOnlyException; import org.eclipse.rdf4j.sail.base.SailSourceConnection; import org.eclipse.rdf4j.sail.helpers.DefaultSailChangedEvent; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; /** * Connection to an {@link LmdbStore}. @@ -123,6 +131,52 @@ public boolean addInferredStatement(Resource subj, IRI pred, Value obj, Resource return ret; } + @Override + protected CloseableIteration evaluateInternal(TupleExpr tupleExpr, + Dataset dataset, + BindingSet bindings, boolean includeInferred) throws SailException { + // ensure that all elements of the binding set are initialized (lazy values are resolved) + return new IterationWrapper( + super.evaluateInternal(tupleExpr, dataset, bindings, includeInferred)) { + @Override + public BindingSet next() throws QueryEvaluationException { + BindingSet bs = super.next(); + bs.forEach(b -> initValue(b.getValue())); + return bs; + } + }; + } + + @Override + protected CloseableIteration getStatementsInternal(Resource subj, IRI pred, + Value obj, + boolean includeInferred, Resource... contexts) throws SailException { + return new IterationWrapper( + super.getStatementsInternal(subj, pred, obj, includeInferred, contexts)) { + @Override + public Statement next() throws SailException { + // ensure that all elements of the statement are initialized (lazy values are resolved) + Statement stmt = super.next(); + initValue(stmt.getSubject()); + initValue(stmt.getPredicate()); + initValue(stmt.getObject()); + initValue(stmt.getContext()); + return stmt; + } + }; + } + + /** + * Ensures that all components of the value are initialized from the underlying database. + * + * @param value The value that should be initialized + */ + protected void initValue(Value value) { + if (value instanceof LmdbValue) { + ((LmdbValue) value).init(); + } + } + @Override protected void removeStatementsInternal(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TripleStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TripleStore.java index 86c54b5c6b7..51ea9e6b2c8 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TripleStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TripleStore.java @@ -19,6 +19,7 @@ import static org.lwjgl.system.MemoryStack.stackPush; import static org.lwjgl.system.MemoryUtil.NULL; import static org.lwjgl.util.lmdb.LMDB.MDB_CREATE; +import static org.lwjgl.util.lmdb.LMDB.MDB_FIRST; import static org.lwjgl.util.lmdb.LMDB.MDB_LAST; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; import static org.lwjgl.util.lmdb.LMDB.MDB_NOMETASYNC; @@ -39,13 +40,13 @@ import static org.lwjgl.util.lmdb.LMDB.mdb_env_open; import static org.lwjgl.util.lmdb.LMDB.mdb_env_set_mapsize; import static org.lwjgl.util.lmdb.LMDB.mdb_env_set_maxdbs; +import static org.lwjgl.util.lmdb.LMDB.mdb_env_set_maxreaders; import static org.lwjgl.util.lmdb.LMDB.mdb_get; import static org.lwjgl.util.lmdb.LMDB.mdb_put; import static org.lwjgl.util.lmdb.LMDB.mdb_stat; import static org.lwjgl.util.lmdb.LMDB.mdb_txn_abort; import static org.lwjgl.util.lmdb.LMDB.mdb_txn_begin; import static org.lwjgl.util.lmdb.LMDB.mdb_txn_commit; -import static org.lwjgl.util.lmdb.LMDB.nmdb_env_set_maxreaders; import java.io.Closeable; import java.io.File; @@ -57,15 +58,18 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.locks.StampedLock; +import java.util.function.Consumer; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.lmdb.TxnManager.Mode; @@ -126,12 +130,10 @@ class TripleStore implements Closeable { * */ private static final int SCHEME_VERSION = 1; - /*-----------* * Variables * *-----------*/ private static final Logger logger = LoggerFactory.getLogger(TripleStore.class); - /** * The directory that is used to store the index files. */ @@ -191,8 +193,8 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in env = pp.get(0); } - mdb_env_set_maxdbs(env, 12); - nmdb_env_set_maxreaders(env, 256); + E(mdb_env_set_maxdbs(env, 12)); + E(mdb_env_set_maxreaders(env, 256)); // Open environment int flags = MDB_NOTLS; @@ -504,6 +506,111 @@ protected void bucketStart(double fraction, long[] lowerValues, long[] upperValu } } + /** + * Checks if any of ids is used and removes it from the collection. + * + * @param ids Collection with possibly removed IDs + * @throws IOException + */ + protected void filterUsedIds(Collection ids) throws IOException { + try (MemoryStack stack = stackPush()) { + MDBVal maxKey = MDBVal.malloc(stack); + ByteBuffer maxKeyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); + MDBVal keyData = MDBVal.malloc(stack); + ByteBuffer keyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); + + MDBVal valueData = MDBVal.mallocStack(stack); + + PointerBuffer pp = stack.mallocPointer(1); + + // TODO currently this does not test for contexts (component == 3) + // because in most cases context indexes do not exist + for (int component = 0; component <= 2; component++) { + int c = component; + + TripleIndex index = getBestIndex(component == 0 ? 1 : -1, component == 1 ? 1 : -1, + component == 2 ? 1 : -1, component == 3 ? 1 : -1); + + boolean fullScan = index.getPatternScore(component == 0 ? 1 : -1, component == 1 ? 1 : -1, + component == 2 ? 1 : -1, component == 3 ? 1 : -1) == 0; + + for (boolean explicit : new boolean[] { true, false }) { + int dbi = index.getDB(explicit); + + long cursor = 0; + try { + E(mdb_cursor_open(writeTxn, dbi, pp)); + cursor = pp.get(0); + + if (fullScan) { + long[] quad = new long[4]; + int rc = mdb_cursor_get(cursor, keyData, valueData, MDB_FIRST); + while (rc == 0 && !ids.isEmpty()) { + index.keyToQuad(keyData.mv_data(), quad); + ids.remove(quad[0]); + ids.remove(quad[1]); + ids.remove(quad[2]); + ids.remove(quad[3]); + + rc = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + } + } else { + for (Iterator it = ids.iterator(); it.hasNext();) { + long id = it.next(); + if (id < 0) { + it.remove(); + continue; + } + if (component != 2 && (id & 1) == 1) { + // id is a literal and can only appear in object position + continue; + } + + long subj = c == 0 ? id : -1, pred = c == 1 ? id : -1, + obj = c == 2 ? id : -1, context = c == 3 ? id : -1; + + GroupMatcher matcher = index.createMatcher(subj, pred, obj, context); + + maxKeyBuf.clear(); + index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + maxKeyBuf.flip(); + maxKey.mv_data(maxKeyBuf); + + keyBuf.clear(); + index.getMinKey(keyBuf, subj, pred, obj, context); + keyBuf.flip(); + + // set cursor to min key + keyData.mv_data(keyBuf); + int rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + boolean exists = false; + while (!exists && rc == 0) { + if (mdb_cmp(writeTxn, dbi, keyData, maxKey) > 0) { + // id was not found + break; + } else if (!matcher.matches(keyData.mv_data())) { + // value doesn't match search key/mask, fetch next value + rc = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + } else { + exists = true; + } + } + + if (exists) { + it.remove(); + } + } + } + } finally { + if (cursor != 0) { + mdb_cursor_close(cursor); + } + } + } + } + } + } + protected double cardinality(long subj, long pred, long obj, long context) throws IOException { TripleIndex index = getBestIndex(subj, pred, obj, context); @@ -757,24 +864,22 @@ public boolean storeTriple(long subj, long pred, long obj, long context, boolean * @param explicit Flag indicating whether explicit or inferred statements should be removed; true removes * explicit statements that match the pattern, false removes inferred statements that match * the pattern. - * @return A mapping of each modified context to the number of statements removed in that context. + * @param handler Function that gets notified about each deleted quad * @throws IOException */ - public Map removeTriplesByContext(long subj, long pred, long obj, long context, - boolean explicit) throws IOException { + public void removeTriplesByContext(long subj, long pred, long obj, long context, + boolean explicit, Consumer handler) throws IOException { RecordIterator records = getTriples(txnManager.createTxn(writeTxn), subj, pred, obj, context, explicit); - return removeTriples(records, explicit); + removeTriples(records, explicit, handler); } - private Map removeTriples(RecordIterator iter, boolean explicit) throws IOException { - final Map perContextCounts = new HashMap<>(); - - try (iter; MemoryStack stack = MemoryStack.stackPush()) { + public void removeTriples(RecordIterator it, boolean explicit, Consumer handler) throws IOException { + try (MemoryStack stack = MemoryStack.stackPush()) { MDBVal keyValue = MDBVal.callocStack(stack); ByteBuffer keyBuf = stack.malloc(MAX_KEY_LENGTH); long[] quad; - while ((quad = iter.next()) != null) { + while ((quad = it.next()) != null) { if (recordCache == null) { if (requiresResize()) { // map is full, resize required @@ -796,11 +901,11 @@ private Map removeTriples(RecordIterator iter, boolean explicit) thr E(mdb_del(writeTxn, index.getDB(explicit), keyValue, null)); } - perContextCounts.merge(quad[CONTEXT_IDX], 1L, Long::sum); + handler.accept(quad); } + } finally { + it.close(); } - - return perContextCounts; } protected void updateFromCache() throws IOException { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueStore.java index d2157135eb2..9b04b29240c 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/ValueStore.java @@ -15,6 +15,7 @@ import static org.lwjgl.system.MemoryStack.stackPush; import static org.lwjgl.system.MemoryUtil.NULL; import static org.lwjgl.util.lmdb.LMDB.MDB_CREATE; +import static org.lwjgl.util.lmdb.LMDB.MDB_FIRST; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; import static org.lwjgl.util.lmdb.LMDB.MDB_NOMETASYNC; import static org.lwjgl.util.lmdb.LMDB.MDB_NOSYNC; @@ -23,13 +24,17 @@ import static org.lwjgl.util.lmdb.LMDB.MDB_RESERVE; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; +import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_del; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_get; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_open; +import static org.lwjgl.util.lmdb.LMDB.mdb_del; import static org.lwjgl.util.lmdb.LMDB.mdb_env_close; import static org.lwjgl.util.lmdb.LMDB.mdb_env_create; import static org.lwjgl.util.lmdb.LMDB.mdb_env_info; import static org.lwjgl.util.lmdb.LMDB.mdb_env_open; import static org.lwjgl.util.lmdb.LMDB.mdb_env_set_mapsize; +import static org.lwjgl.util.lmdb.LMDB.mdb_env_set_maxdbs; +import static org.lwjgl.util.lmdb.LMDB.mdb_env_set_maxreaders; import static org.lwjgl.util.lmdb.LMDB.mdb_get; import static org.lwjgl.util.lmdb.LMDB.mdb_put; import static org.lwjgl.util.lmdb.LMDB.mdb_stat; @@ -44,6 +49,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collection; import java.util.Optional; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -121,10 +127,14 @@ class ValueStore extends AbstractValueFactory { private long env; private int pageSize; private long mapSize; + // main database private int dbi; + // database with unused IDs + private int unusedDbi; private long writeTxn; private final boolean forceSync; private final boolean autoGrow; + private boolean invalidateRevisionOnCommit = false; /** * This lock is required to block transactions while auto-growing the map size. */ @@ -192,6 +202,9 @@ private void open() throws IOException { env = pp.get(0); } + E(mdb_env_set_maxdbs(env, 6)); + E(mdb_env_set_maxreaders(env, 256)); + // Open environment int flags = MDB_NOTLS; if (!forceSync) { @@ -199,7 +212,7 @@ private void open() throws IOException { } E(mdb_env_open(env, dir.getAbsolutePath(), flags, 0664)); - // Open database + // open main database dbi = openDatabase(env, null, MDB_CREATE, null); // initialize page size and set map size for env @@ -225,6 +238,9 @@ private void open() throws IOException { } return null; }); + + // open unused IDs database + unusedDbi = openDatabase(env, "unused_ids", MDB_CREATE, null); } private long nextId(byte type) throws IOException { @@ -418,7 +434,7 @@ private void resizeMap(long txn, int requiredSize) throws IOException { private long findId(byte[] data, boolean create) throws IOException { Long id = readTransaction(env, (stack, txn) -> { - if (data.length < MAX_KEY_SIZE) { + if (data.length <= MAX_KEY_SIZE) { MDBVal dataVal = MDBVal.calloc(stack); dataVal.mv_data(stack.bytes(data)); MDBVal idVal = MDBVal.calloc(stack); @@ -507,8 +523,7 @@ private long findId(byte[] data, boolean create) throws IOException { ByteBuffer hashIdBb = hashVal.mv_data(); hashIdBb.position(hashLength); idVal.mv_data(hashIdBb); - if (mdb_get(txn, dbi, idVal, dataVal) == 0 && - dataVal.mv_data().compareTo(dataBb) == 0) { + if (mdb_get(txn, dbi, idVal, dataVal) == 0 && dataVal.mv_data().compareTo(dataBb) == 0) { // id was found if stored value is equal to requested value return data2id(hashIdBb); } @@ -661,6 +676,123 @@ public long getId(Value value, boolean create) throws IOException { return LmdbValue.UNKNOWN_ID; } + public void gcIds(Collection ids) throws IOException { + writeTransaction((stack, writeTxn) -> { + MDBVal idVal = MDBVal.calloc(stack); + MDBVal dataVal = MDBVal.calloc(stack); + + resizeMap(writeTxn, 2 * ids.size() * (2 + Long.BYTES)); + + ByteBuffer idBb = idBuffer(stack); + for (Long id : ids) { + idBb.clear(); + + idVal.mv_data(id2data(idBb, id).flip()); + E(mdb_put(writeTxn, unusedDbi, idVal, dataVal, 0)); + } + + deleteIds(stack, writeTxn); + return null; + }); + invalidateRevisionOnCommit = !ids.isEmpty(); + } + + protected void deleteIds(MemoryStack stack, long txn) throws IOException { + int maxHashKeyLength = 2 + 2 * Long.BYTES + 2; + ByteBuffer hashBb = stack.malloc(maxHashKeyLength); + MDBVal idVal = MDBVal.calloc(stack); + MDBVal hashVal = MDBVal.calloc(stack); + MDBVal dataVal = MDBVal.calloc(stack); + + long unusedIdsCursor = 0; + long valuesCursor = 0; + try { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, unusedDbi, pp)); + unusedIdsCursor = pp.get(0); + + // iterate all unused IDs + if (mdb_cursor_get(unusedIdsCursor, idVal, dataVal, MDB_FIRST) == 0) { + do { + if (mdb_get(txn, dbi, idVal, dataVal) == 0) { + ByteBuffer idBuffer = idVal.mv_data(); + ByteBuffer dataBuffer = dataVal.mv_data(); + int dataLength = dataVal.mv_data().remaining(); + if (dataLength > MAX_KEY_SIZE) { + byte[] data = new byte[dataLength]; + dataVal.mv_data().get(data); + long dataHash = hash(data); + + hashBb.clear(); + hashBb.put(HASH_KEY); + Varint.writeUnsigned(hashBb, dataHash); + int hashLength = hashBb.position(); + hashBb.flip(); + + hashVal.mv_data(hashBb); + + // delete HASH -> ID association + if (mdb_del(txn, dbi, hashVal, dataVal) == 0) { + // was first entry, find a possible next entry and make it the first + hashBb.put(0, HASHID_KEY); + hashBb.rewind(); + hashVal.mv_data(hashBb); + + if (valuesCursor == 0) { + // initialize cursor + pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, unusedDbi, pp)); + valuesCursor = pp.get(0); + } + + if (mdb_cursor_get(valuesCursor, hashVal, dataVal, MDB_SET_RANGE) == 0) { + if (compareRegion(hashVal.mv_data(), 0, hashBb, 0, hashLength) == 0) { + ByteBuffer idBuffer2 = hashVal.mv_data(); + idBuffer2.position(hashLength); + idVal.mv_data(idBuffer2); + + hashVal.mv_data(hashBb); + + // HASH -> ID + mdb_put(txn, dbi, hashVal, idVal, 0); + // delete existing mapping + mdb_cursor_del(valuesCursor, 0); + } + } + } else { + // was not the first entry, delete HASH+ID association + hashBb.put(0, HASHID_KEY); + hashBb.limit(hashLength + idVal.mv_data().remaining()); + hashBb.position(hashLength); + hashBb.put(idVal.mv_data()); + hashBb.flip(); + + hashVal.mv_data(hashBb); + // delete HASH+ID -> [] association + mdb_del(txn, dbi, hashVal, null); + } + } else { + // delete value -> ID association + dataVal.mv_data(dataBuffer); + mdb_del(txn, dbi, dataVal, null); + } + + // delete ID -> value association + idVal.mv_data(idBuffer); + mdb_del(txn, dbi, idVal, null); + } + } while (mdb_cursor_get(unusedIdsCursor, idVal, dataVal, MDB_NEXT) == 0); + } + } finally { + if (unusedIdsCursor != 0) { + mdb_cursor_close(unusedIdsCursor); + } + if (valuesCursor != 0) { + mdb_cursor_close(valuesCursor); + } + } + } + public void startTransaction() throws IOException { try (MemoryStack stack = stackPush()) { PointerBuffer pp = stack.mallocPointer(1); @@ -677,10 +809,15 @@ void endTransaction(boolean commit) throws IOException { if (writeTxn != 0) { if (commit) { E(mdb_txn_commit(writeTxn)); + if (invalidateRevisionOnCommit) { + setNewRevision(); + clearCaches(); + } } else { mdb_txn_abort(writeTxn); } writeTxn = 0; + invalidateRevisionOnCommit = false; } } @@ -727,14 +864,16 @@ public void clear() throws IOException { new File(dir, "data.mdb").delete(); new File(dir, "lock.mdb").delete(); + clearCaches(); + open(); + setNewRevision(); + } + + protected void clearCaches() { Arrays.fill(valueCache, null); valueIDCache.clear(); namespaceCache.clear(); namespaceIDCache.clear(); - - open(); - - setNewRevision(); } /** diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbBNode.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbBNode.java index 6d764e94c2c..eeab5e74797 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbBNode.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbBNode.java @@ -83,7 +83,7 @@ public String getID() { return super.getID(); } - protected void init() { + public void init() { if (!initialized) { synchronized (this) { if (!initialized) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbIRI.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbIRI.java index 0c4806be299..8bc261d44d0 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbIRI.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbIRI.java @@ -99,7 +99,7 @@ public String stringValue() { return super.stringValue(); } - protected void init() { + public void init() { if (!initialized) { synchronized (this) { if (!initialized) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbLiteral.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbLiteral.java index 06f3be84c6f..d6efea7435d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbLiteral.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbLiteral.java @@ -192,7 +192,7 @@ public void setLanguage(String language) { this.language = language; } - protected void init() { + public void init() { if (!initialized) { synchronized (this) { if (!initialized) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbValue.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbValue.java index e617e9b66db..27cfe423baa 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbValue.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/model/LmdbValue.java @@ -29,6 +29,11 @@ public interface LmdbValue extends Value { */ long getInternalID(); + /** + * Initializes this value if it was a lazy value (ID-only value) before. + */ + void init(); + /** * Gets the revision of the value store that created this value. The value's internal ID is only valid when it's * value store revision is equal to the value store's current revision.