From eed68e44bfca40027892681b0e1e8447bf967eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Wed, 15 Oct 2025 23:06:24 +0200 Subject: [PATCH 01/79] cache lmdb cursor --- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 53 ++++++++++++++++--- .../org/eclipse/rdf4j/sail/lmdb/Pool.java | 43 +++++++++++++++ .../eclipse/rdf4j/sail/lmdb/TxnManager.java | 12 +++-- .../rdf4j/sail/lmdb/benchmark/README.md | 19 +++++++ pom.xml | 2 +- 5 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index 68c5352a73..957201c4fb 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -109,7 +109,17 @@ class LmdbRecordIterator implements RecordIterator { maxKeyBuf.flip(); this.maxKey.mv_data(maxKeyBuf); } else { - minKeyBuf = null; + // Even when we can't bound with a prefix (no rangeSearch), we can still + // position the cursor closer to the first potentially matching key when + // there are any constraints (matchValues). This avoids scanning from the + // absolute beginning for patterns like ?p ?o constC etc. + if (subj > 0 || pred > 0 || obj > 0 || context >= 0) { + minKeyBuf = pool.getKeyBuffer(); + index.getMinKey(minKeyBuf, subj, pred, obj, context); + minKeyBuf.flip(); + } else { + minKeyBuf = null; + } this.maxKey = null; } @@ -129,10 +139,36 @@ class LmdbRecordIterator implements RecordIterator { this.txnRefVersion = txnRef.version(); this.txn = txnRef.get(); - try (MemoryStack stack = MemoryStack.stackPush()) { - PointerBuffer pp = stack.mallocPointer(1); - E(mdb_cursor_open(txn, dbi, pp)); - cursor = pp.get(0); + // Try to reuse a pooled cursor only for read-only transactions; otherwise open a new one + if (txnRef.isReadOnly()) { + long pooled = pool.getCursor(dbi, index); + if (pooled != 0L) { + long c = pooled; + try { + E(mdb_cursor_renew(txn, c)); + } catch (IOException renewEx) { + // Renewal failed (e.g., incompatible txn). Close pooled cursor and open a fresh one. + mdb_cursor_close(c); + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + c = pp.get(0); + } + } + cursor = c; + } else { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + cursor = pp.get(0); + } + } + } else { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + cursor = pp.get(0); + } } } finally { txnLockManager.unlockRead(readStamp); @@ -199,7 +235,6 @@ public long[] next() { if (maxKey != null && mdb_cmp(txn, dbi, keyData, maxKey) > 0) { lastResult = MDB_NOTFOUND; } else if (matches()) { - // value doesn't match search key/mask, fetch next value lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); } else { // Matching value found @@ -242,7 +277,11 @@ private void closeInternal(boolean maybeCalledAsync) { } try { if (!closed) { - mdb_cursor_close(cursor); + if (txnRef.isReadOnly()) { + pool.freeCursor(dbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } pool.free(keyData); pool.free(valueData); if (minKeyBuf != null) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Pool.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Pool.java index ebb9952582..27c56ee541 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Pool.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Pool.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; + import java.nio.ByteBuffer; import org.lwjgl.system.MemoryUtil; @@ -25,9 +27,14 @@ class Pool { private final MDBVal[] valPool = new MDBVal[1024]; private final ByteBuffer[] keyPool = new ByteBuffer[1024]; private final Statistics[] statisticsPool = new Statistics[512]; + // LMDB cursor pooling (per-thread). We store cursor pointers along with their DBI. + private final long[] cursorPool = new long[1024]; + private final int[] cursorDbiPool = new int[1024]; + private final Object[] cursorOwnerPool = new Object[1024]; private int valPoolIndex = -1; private int keyPoolIndex = -1; private int statisticsPoolIndex = -1; + private int cursorPoolIndex = -1; final MDBVal getVal() { if (valPoolIndex >= 0) { @@ -74,6 +81,39 @@ final void free(Statistics statistics) { } } + /** + * Try to obtain a pooled LMDB cursor for the given {@code dbi}. Returns {@code 0} if none is available. + */ + final long getCursor(int dbi, Object owner) { + for (int i = cursorPoolIndex; i >= 0; i--) { + if (cursorDbiPool[i] == dbi && cursorOwnerPool[i] == owner) { + long cursor = cursorPool[i]; + // compact the stack by moving the top entry to this slot + if (i != cursorPoolIndex) { + cursorPool[i] = cursorPool[cursorPoolIndex]; + cursorDbiPool[i] = cursorDbiPool[cursorPoolIndex]; + cursorOwnerPool[i] = cursorOwnerPool[cursorPoolIndex]; + } + cursorPoolIndex--; + return cursor; + } + } + return 0L; + } + + /** + * Return an LMDB cursor to the pool for potential reuse. If the pool is full, the cursor is closed. + */ + final void freeCursor(int dbi, Object owner, long cursor) { + if (cursorPoolIndex < cursorPool.length - 1) { + cursorPool[++cursorPoolIndex] = cursor; + cursorDbiPool[cursorPoolIndex] = dbi; + cursorOwnerPool[cursorPoolIndex] = owner; + } else { + mdb_cursor_close(cursor); + } + } + final void close() { while (valPoolIndex >= 0) { valPool[valPoolIndex--].close(); @@ -81,6 +121,9 @@ final void close() { while (keyPoolIndex >= 0) { MemoryUtil.memFree(keyPool[keyPoolIndex--]); } + while (cursorPoolIndex >= 0) { + mdb_cursor_close(cursorPool[cursorPoolIndex--]); + } } /** diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnManager.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnManager.java index cf3d486b6f..30dd3dcbd3 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnManager.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnManager.java @@ -64,7 +64,7 @@ private long startReadTxn() throws IOException { * @return the txn reference object */ Txn createTxn(long txn) { - return new Txn(txn) { + return new Txn(txn, false) { @Override public void close() { // do nothing @@ -79,7 +79,7 @@ public void close() { * @throws IOException if the transaction cannot be started for some reason */ Txn createReadTxn() throws IOException { - Txn txnRef = new Txn(createReadTxnInternal()); + Txn txnRef = new Txn(createReadTxnInternal(), true); synchronized (active) { active.put(txnRef, Boolean.TRUE); } @@ -164,9 +164,11 @@ class Txn implements Closeable, AutoCloseable { private long txn; private long version; + private final boolean readOnly; - Txn(long txn) { + Txn(long txn, boolean readOnly) { this.txn = txn; + this.readOnly = readOnly; } long get() { @@ -229,5 +231,9 @@ void setActive(boolean active) throws IOException { long version() { return version; } + + boolean isReadOnly() { + return readOnly; + } } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md new file mode 100644 index 0000000000..592dcdd57f --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md @@ -0,0 +1,19 @@ +Benchmark Mode Cnt Score Error Units +QueryBenchmark.complexQuery avgt 5 3.724 ± 0.083 ms/op +QueryBenchmark.different_datasets_with_similar_distributions avgt 5 2.118 ± 0.089 ms/op +QueryBenchmark.groupByQuery avgt 5 0.916 ± 0.019 ms/op +QueryBenchmark.long_chain avgt 5 641.809 ± 6.881 ms/op + QueryBenchmark.minus avgt 5 10.472 ± 0.556 ms/op +QueryBenchmark.multiple_sub_select avgt 5 55.949 ± 3.581 ms/op +QueryBenchmark.nested_optionals avgt 5 171.423 ± 39.898 ms/op +QueryBenchmark.optional_lhs_filter avgt 5 36.728 ± 2.244 ms/op +QueryBenchmark.optional_rhs_filter avgt 5 51.788 ± 2.241 ms/op +QueryBenchmark.ordered_union_limit avgt 5 73.121 ± 4.403 ms/op +QueryBenchmark.pathExpressionQuery1 avgt 5 20.738 ± 0.385 ms/op +QueryBenchmark.pathExpressionQuery2 avgt 5 4.546 ± 0.338 ms/op +QueryBenchmark.query_distinct_predicates avgt 5 47.712 ± 1.803 ms/op +QueryBenchmark.simple_filter_not avgt 5 5.641 ± 0.151 ms/op +QueryBenchmark.sub_select avgt 5 72.550 ± 10.050 ms/op +QueryBenchmarkFoaf.groupByCount avgt 5 721.870 ± 16.816 ms/op +QueryBenchmarkFoaf.groupByCountSorted avgt 5 651.351 ± 19.324 ms/op +QueryBenchmarkFoaf.personsAndFriends avgt 5 213.945 ± 7.394 ms/op diff --git a/pom.xml b/pom.xml index 1d2f315909..a4d354e26b 100644 --- a/pom.xml +++ b/pom.xml @@ -271,7 +271,7 @@ org.jacoco jacoco-maven-plugin - 0.8.13 + 0.8.14 From e5d2dec5a288380414c3b47556b8c0bbd7e98af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 16 Oct 2025 10:02:49 +0200 Subject: [PATCH 02/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 392 ++++++++++++++++++ .../sail/lmdb/LmdbStatementIterator.java | 6 + .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 261 +++++++++++- .../sail/lmdb/config/LmdbStoreConfig.java | 46 ++ .../sail/lmdb/config/LmdbStoreSchema.java | 12 + .../sail/lmdb/LmdbDupRecordIteratorTest.java | 226 ++++++++++ .../rdf4j/sail/lmdb/LmdbStoreTest.java | 19 + .../sail/lmdb/benchmark/QueryBenchmark.java | 4 +- .../repository/SparqlOrderByTest.java | 2 + 9 files changed, 960 insertions(+), 8 deletions(-) create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java new file mode 100644 index 0000000000..b425f873e2 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -0,0 +1,392 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; +import static org.lwjgl.util.lmdb.LMDB.MDB_GET_MULTIPLE; +import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; +import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT_MULTIPLE; +import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; +import static org.lwjgl.util.lmdb.LMDB.MDB_PREV; +import static org.lwjgl.util.lmdb.LMDB.MDB_SET_KEY; +import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; +import static org.lwjgl.util.lmdb.LMDB.MDB_SUCCESS; +import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; +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_cursor_renew; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.TripleIndex; +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + +/** + * A dupsort/dupfixed-optimized record iterator using MDB_GET_MULTIPLE/NEXT_MULTIPLE to reduce JNI calls. + */ +class LmdbDupRecordIterator implements RecordIterator { + + private final Pool pool; + private final TripleIndex index; + private final int dupDbi; + private final long cursor; + + private final Txn txnRef; + private final long txn; + private long txnRefVersion; + private final StampedLongAdderLockManager txnLockManager; + private final Thread ownerThread = Thread.currentThread(); + + private final MDBVal keyData; + private final MDBVal valueData; + + private final long[] quad; + private final long[] originalQuad; + private final char[] fieldSeq; + + private final boolean matchValues; + private GroupMatcher groupMatcher; + + private ByteBuffer prefixKeyBuf; + private long[] prefixValues; + + private ByteBuffer dupBuf; + private int dupPos; + private int dupLimit; + + private int lastResult; + private boolean closed = false; + + private final RecordIterator fallback; + + LmdbDupRecordIterator(TripleIndex index, long subj, long pred, long obj, long context, + boolean explicit, Txn txnRef) throws IOException { + this.index = index; + this.fieldSeq = index.getFieldSeq(); + this.quad = new long[] { subj, pred, obj, context }; + this.originalQuad = new long[] { subj, pred, obj, context }; + this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; + + this.pool = Pool.get(); + this.keyData = pool.getVal(); + this.valueData = pool.getVal(); + this.dupDbi = index.getDupDB(explicit); + this.txnRef = txnRef; + this.txnLockManager = txnRef.lockManager(); + + RecordIterator fallbackIterator = null; + + long readStamp; + try { + readStamp = txnLockManager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + this.txnRefVersion = txnRef.version(); + this.txn = txnRef.get(); + + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dupDbi, pp)); + cursor = pp.get(0); + } + + prefixKeyBuf = pool.getKeyBuffer(); + prefixValues = new long[2]; + long adjSubj = subj < 0 ? 0 : subj; + long adjPred = pred < 0 ? 0 : pred; + long adjObj = obj < 0 ? 0 : obj; + long adjContext = context < 0 ? 0 : context; + for (int i = 0; i < 2; i++) { + char f = fieldSeq[i]; + long v; + switch (f) { + case 's': + v = adjSubj; + break; + case 'p': + v = adjPred; + break; + case 'o': + v = adjObj; + break; + default: + v = adjContext; + break; + } + prefixValues[i] = v; + } + prefixKeyBuf.clear(); + index.toDupKeyPrefix(prefixKeyBuf, adjSubj, adjPred, adjObj, adjContext); + prefixKeyBuf.flip(); + + boolean positioned = positionOnPrefix(); + if (positioned) { + positioned = primeDuplicateBlock(); + } + if (!positioned) { + closeInternal(false); + fallbackIterator = new LmdbRecordIterator(index, + index.getPatternScore(subj, pred, obj, context) > 0, subj, pred, obj, context, explicit, + txnRef); + } + } finally { + txnLockManager.unlockRead(readStamp); + } + + this.fallback = fallbackIterator; + } + + @Override + public long[] next() { + if (fallback != null) { + return fallback.next(); + } + + long readStamp; + try { + readStamp = txnLockManager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + if (closed) { + return null; + } + + if (txnRefVersion != txnRef.version()) { + E(mdb_cursor_renew(txn, cursor)); + txnRefVersion = txnRef.version(); + if (!positionOnPrefix() || !primeDuplicateBlock()) { + closeInternal(false); + return null; + } + } + + while (true) { + if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { + long v3 = dupBuf.getLong(dupPos); + long v4 = dupBuf.getLong(dupPos + Long.BYTES); + dupPos += Long.BYTES * 2; + fillQuadFromPrefixAndValue(v3, v4); + if (!matchValues || matchesQuad()) { + return quad; + } + continue; + } + + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); + if (lastResult == MDB_SUCCESS) { + resetDuplicateBuffer(valueData.mv_data()); + continue; + } + + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + if (lastResult != MDB_SUCCESS) { + closeInternal(false); + return null; + } + if (!currentKeyHasPrefix()) { + if (!adjustCursorToPrefix()) { + closeInternal(false); + return null; + } + } + if (!primeDuplicateBlock()) { + closeInternal(false); + return null; + } + } + } catch (IOException e) { + throw new SailException(e); + } finally { + txnLockManager.unlockRead(readStamp); + } + } + + private boolean positionOnPrefix() throws IOException { + keyData.mv_data(prefixKeyBuf); + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_KEY); + if (lastResult == MDB_NOTFOUND) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + } + if (lastResult != MDB_SUCCESS) { + return false; + } + if (currentKeyHasPrefix()) { + return true; + } + return adjustCursorToPrefix(); + } + + private boolean adjustCursorToPrefix() throws IOException { + int cmp = comparePrefix(); + while (cmp < 0) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + if (lastResult != MDB_SUCCESS) { + return false; + } + cmp = comparePrefix(); + } + return cmp == 0; + } + + private int comparePrefix() { + ByteBuffer key = keyData.mv_data().duplicate(); + for (int i = 0; i < 2; i++) { + long actual = Varint.readUnsigned(key); + long expected = prefixValues[i]; + if (actual < expected) { + return -1; + } + if (actual > expected) { + return 1; + } + } + return 0; + } + + private boolean primeDuplicateBlock() throws IOException { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_GET_MULTIPLE); + if (lastResult == MDB_SUCCESS) { + resetDuplicateBuffer(valueData.mv_data()); + return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; + } else if (lastResult == MDB_NOTFOUND) { + return false; + } else { + resetDuplicateBuffer(valueData.mv_data()); + return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; + } + } + + private void resetDuplicateBuffer(ByteBuffer buffer) { + if (buffer == null) { + dupBuf = null; + dupPos = dupLimit = 0; + } else { + buffer.order(ByteOrder.nativeOrder()); + dupBuf = buffer; + dupPos = dupBuf.position(); + dupLimit = dupBuf.limit(); + } + } + + private void fillQuadFromPrefixAndValue(long v3, long v4) { + int pi = 0; + for (int i = 0; i < 2; i++) { + char f = fieldSeq[i]; + long v = prefixValues[pi++]; + switch (f) { + case 's': + quad[0] = v; + break; + case 'p': + quad[1] = v; + break; + case 'o': + quad[2] = v; + break; + case 'c': + quad[3] = v; + break; + } + } + char f = fieldSeq[2]; + switch (f) { + case 's': + quad[0] = v3; + break; + case 'p': + quad[1] = v3; + break; + case 'o': + quad[2] = v3; + break; + case 'c': + quad[3] = v3; + break; + } + f = fieldSeq[3]; + switch (f) { + case 's': + quad[0] = v4; + break; + case 'p': + quad[1] = v4; + break; + case 'o': + quad[2] = v4; + break; + case 'c': + quad[3] = v4; + break; + } + } + + private boolean currentKeyHasPrefix() { + return comparePrefix() == 0; + } + + private boolean matchesQuad() { + return (originalQuad[0] < 0 || quad[0] == originalQuad[0]) + && (originalQuad[1] < 0 || quad[1] == originalQuad[1]) + && (originalQuad[2] < 0 || quad[2] == originalQuad[2]) + && (originalQuad[3] < 0 || quad[3] == originalQuad[3]); + } + + private void closeInternal(boolean maybeCalledAsync) { + if (!closed) { + long writeStamp = 0L; + boolean writeLocked = false; + if (maybeCalledAsync && ownerThread != Thread.currentThread()) { + try { + writeStamp = txnLockManager.writeLock(); + writeLocked = true; + } catch (InterruptedException e) { + throw new SailException(e); + } + } + try { + if (!closed) { + mdb_cursor_close(cursor); + pool.free(keyData); + pool.free(valueData); + if (prefixKeyBuf != null) { + pool.free(prefixKeyBuf); + } + } + } finally { + closed = true; + if (writeLocked) { + txnLockManager.unlockWrite(writeStamp); + } + } + } + } + + @Override + public void close() { + if (fallback != null) { + fallback.close(); + } else { + closeInternal(true); + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java index e4b6429afa..eecc692f70 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java @@ -53,6 +53,12 @@ public LmdbStatementIterator(RecordIterator recordIt, ValueStore valueStore) { public Statement getNextElement() throws SailException { try { + + if (Thread.currentThread().isInterrupted()) { + close(); + throw new SailException("The iteration has been interrupted"); + } + long[] quad = recordIt.next(); if (quad == null) { return null; 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 059bb51e66..3d9a8c4d6b 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 @@ -167,6 +167,8 @@ class TripleStore implements Closeable { private int pageSize; private final boolean forceSync; private final boolean autoGrow; + private final boolean dupsortIndices; + private final boolean dupsortRead; private long mapSize; private long writeTxn; private final TxnManager txnManager; @@ -209,8 +211,9 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in env = pp.get(0); } - // 1 for contexts, 12 for triple indexes (2 per index) - E(mdb_env_set_maxdbs(env, 13)); + // Max DBs: 1 for contexts, 12 for triple indexes (2 per index), + // plus up to 12 dupsort companion DBs when enabled. Use a safe upper bound of 25. + E(mdb_env_set_maxdbs(env, 25)); E(mdb_env_set_maxreaders(env, 256)); // Open environment @@ -232,8 +235,11 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in txnManager = new TxnManager(env, Mode.RESET); File propFile = new File(this.dir, PROPERTIES_FILE); + boolean isNewStore = !propFile.exists(); + this.dupsortIndices = isNewStore && config.isDupsortIndices(); + this.dupsortRead = config.isDupsortRead(); String indexSpecStr = config.getTripleIndexes(); - if (!propFile.exists()) { + if (isNewStore) { // newly created lmdb store properties = new Properties(); @@ -257,7 +263,6 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in // Compare the existing indexes with the requested indexes Set reqIndexSpecs = parseIndexSpecList(indexSpecStr); - if (reqIndexSpecs.isEmpty()) { // No indexes specified, use the existing ones indexSpecStr = properties.getProperty(INDEXES_KEY); @@ -344,7 +349,7 @@ private Set parseIndexSpecList(String indexSpecStr) throws SailException private void initIndexes(Set indexSpecs, long tripleDbSize) throws IOException { for (String fieldSeq : indexSpecs) { logger.trace("Initializing index '{}'...", fieldSeq); - indexes.add(new TripleIndex(fieldSeq)); + indexes.add(new TripleIndex(fieldSeq, dupsortIndices)); } // initialize page size and set map size for env @@ -395,7 +400,7 @@ private void reindex(Set currentIndexSpecs, Set newIndexSpecs) t for (String fieldSeq : addedIndexSpecs) { logger.debug("Initializing new index '{}'...", fieldSeq); - TripleIndex addedIndex = new TripleIndex(fieldSeq); + TripleIndex addedIndex = new TripleIndex(fieldSeq, dupsortIndices); RecordIterator[] sourceIter = { null }; try { sourceIter[0] = new LmdbRecordIterator(sourceIndex, false, -1, -1, -1, -1, @@ -527,9 +532,42 @@ boolean hasTriples(boolean explicit) throws IOException { private RecordIterator getTriplesUsingIndex(Txn txn, long subj, long pred, long obj, long context, boolean explicit, TripleIndex index, boolean rangeSearch) throws IOException { + if (dupsortRead && index.isDupsortEnabled() + && leadingBoundCount(index.getFieldSeq(), subj, pred, obj, context) >= 2) { + return new LmdbDupRecordIterator(index, subj, pred, obj, context, explicit, txn); + } return new LmdbRecordIterator(index, rangeSearch, subj, pred, obj, context, explicit, txn); } + private int leadingBoundCount(char[] fieldSeq, long subj, long pred, long obj, long context) { + int count = 0; + for (int i = 0; i < fieldSeq.length; i++) { + boolean bound; + switch (fieldSeq[i]) { + case 's': + bound = subj >= 0; + break; + case 'p': + bound = pred >= 0; + break; + case 'o': + bound = obj >= 0; + break; + case 'c': + bound = context >= 0; + break; + default: + bound = false; + } + if (bound) { + count++; + } else { + break; + } + } + return count; + } + /** * Computes start key for a bucket by linear interpolation between a lower and an upper bound. * @@ -900,6 +938,48 @@ public boolean storeTriple(long subj, long pred, long obj, long context, boolean boolean foundImplicit = false; if (explicit && stAdded) { foundImplicit = mdb_del(writeTxn, mainIndex.getDB(false), keyVal, dataVal) == MDB_SUCCESS; + if (foundImplicit && mainIndex.isDupsortEnabled()) { + // Also remove from inferred dup DB when promoting to explicit + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + mainIndex.toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); + dupKeyBuf.flip(); + dupValBuf.clear(); + mainIndex.toDupValue(dupValBuf, subj, pred, obj, context); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_del(writeTxn, mainIndex.getDupDB(false), dupKeyVal, dupDataVal)); + } finally { + stack.setPointer(frame); + } + } + } + + if (stAdded && mainIndex.isDupsortEnabled()) { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + mainIndex.toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); + dupKeyBuf.flip(); + dupValBuf.clear(); + mainIndex.toDupValue(dupValBuf, subj, pred, obj, context); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_put(writeTxn, mainIndex.getDupDB(explicit), dupKeyVal, dupDataVal, 0)); + } finally { + stack.setPointer(frame); + } } if (stAdded) { @@ -917,6 +997,27 @@ public boolean storeTriple(long subj, long pred, long obj, long context, boolean E(mdb_del(writeTxn, mainIndex.getDB(false), keyVal, dataVal)); } E(mdb_put(writeTxn, index.getDB(explicit), keyVal, dataVal, 0)); + + if (index.isDupsortEnabled()) { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + index.toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); + dupKeyBuf.flip(); + dupValBuf.clear(); + index.toDupValue(dupValBuf, subj, pred, obj, context); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_put(writeTxn, index.getDupDB(explicit), dupKeyVal, dupDataVal, 0)); + } finally { + stack.setPointer(frame); + } + } } if (stAdded) { @@ -1029,6 +1130,29 @@ public void removeTriples(RecordIterator it, boolean explicit, Consumer keyValue.mv_data(keyBuf); E(mdb_del(writeTxn, index.getDB(explicit), keyValue, null)); + + if (index.isDupsortEnabled()) { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + index.toDupKeyPrefix(dupKeyBuf, quad[SUBJ_IDX], quad[PRED_IDX], quad[OBJ_IDX], + quad[CONTEXT_IDX]); + dupKeyBuf.flip(); + dupValBuf.clear(); + index.toDupValue(dupValBuf, quad[SUBJ_IDX], quad[PRED_IDX], quad[OBJ_IDX], + quad[CONTEXT_IDX]); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_del(writeTxn, index.getDupDB(explicit), dupKeyVal, dupDataVal)); + } finally { + stack.setPointer(frame); + } + } } decrementContext(stack, quad[CONTEXT_IDX]); @@ -1070,8 +1194,48 @@ protected void updateFromCache() throws IOException { if (r.add) { E(mdb_put(writeTxn, index.getDB(explicit), keyVal, dataVal, 0)); + if (index.isDupsortEnabled()) { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + index.toDupKeyPrefix(dupKeyBuf, r.quad[0], r.quad[1], r.quad[2], r.quad[3]); + dupKeyBuf.flip(); + dupValBuf.clear(); + index.toDupValue(dupValBuf, r.quad[0], r.quad[1], r.quad[2], r.quad[3]); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_put(writeTxn, index.getDupDB(explicit), dupKeyVal, dupDataVal, 0)); + } finally { + stack.setPointer(frame); + } + } } else { E(mdb_del(writeTxn, index.getDB(explicit), keyVal, null)); + if (index.isDupsortEnabled()) { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + index.toDupKeyPrefix(dupKeyBuf, r.quad[0], r.quad[1], r.quad[2], r.quad[3]); + dupKeyBuf.flip(); + dupValBuf.clear(); + index.toDupValue(dupValBuf, r.quad[0], r.quad[1], r.quad[2], r.quad[3]); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_del(writeTxn, index.getDupDB(explicit), dupKeyVal, dupDataVal)); + } finally { + stack.setPointer(frame); + } + } } } } @@ -1183,9 +1347,12 @@ class TripleIndex { private final IndexKeyWriters.KeyWriter keyWriter; private final IndexKeyWriters.MatcherFactory matcherFactory; private final int dbiExplicit, dbiInferred; + private final boolean dupsortEnabled; + private final int dbiDupExplicit; + private final int dbiDupInferred; private final int[] indexMap; - public TripleIndex(String fieldSeq) throws IOException { + public TripleIndex(String fieldSeq, boolean dupsortEnabled) throws IOException { this.fieldSeq = fieldSeq.toCharArray(); this.keyWriter = IndexKeyWriters.forFieldSeq(fieldSeq); this.matcherFactory = IndexKeyWriters.matcherFactory(fieldSeq); @@ -1193,6 +1360,19 @@ public TripleIndex(String fieldSeq) throws IOException { // open database and use native sort order without comparator dbiExplicit = openDatabase(env, fieldSeq, MDB_CREATE, null); dbiInferred = openDatabase(env, fieldSeq + "-inf", MDB_CREATE, null); + this.dupsortEnabled = dupsortEnabled; + if (dupsortEnabled) { + int flags = MDB_CREATE | org.lwjgl.util.lmdb.LMDB.MDB_DUPSORT | org.lwjgl.util.lmdb.LMDB.MDB_DUPFIXED; + dbiDupExplicit = openDatabase(env, fieldSeq + "-dup", flags, null); + dbiDupInferred = openDatabase(env, fieldSeq + "-dup-inf", flags, null); + } else { + dbiDupExplicit = 0; + dbiDupInferred = 0; + } + } + + public TripleIndex(String fieldSeq) throws IOException { + this(fieldSeq, false); } public char[] getFieldSeq() { @@ -1203,6 +1383,73 @@ public int getDB(boolean explicit) { return explicit ? dbiExplicit : dbiInferred; } + public boolean isDupsortEnabled() { + return dupsortEnabled; + } + + public int getDupDB(boolean explicit) { + return explicit ? dbiDupExplicit : dbiDupInferred; + } + + void toDupKeyPrefix(ByteBuffer bb, long subj, long pred, long obj, long context) { + long s = subj, p = pred, o = obj, c = context; + for (int i = 0; i < 2; i++) { + char f = fieldSeq[i]; + switch (f) { + case 's': + Varint.writeUnsigned(bb, s); + break; + case 'p': + Varint.writeUnsigned(bb, p); + break; + case 'o': + Varint.writeUnsigned(bb, o); + break; + case 'c': + Varint.writeUnsigned(bb, c); + break; + } + } + } + + void toDupValue(ByteBuffer bb, long subj, long pred, long obj, long context) { + // write last two fields as two longs + char f3 = fieldSeq[2]; + char f4 = fieldSeq[3]; + long v3; + switch (f3) { + case 's': + v3 = subj; + break; + case 'p': + v3 = pred; + break; + case 'o': + v3 = obj; + break; + default: + v3 = context; + break; + } + long v4; + switch (f4) { + case 's': + v4 = subj; + break; + case 'p': + v4 = pred; + break; + case 'o': + v4 = obj; + break; + default: + v4 = context; + break; + } + bb.putLong(v3); + bb.putLong(v4); + } + protected int[] getIndexes(char[] fieldSeq) { int[] indexes = new int[fieldSeq.length]; for (int i = 0; i < fieldSeq.length; i++) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java index 4c072e9131..235badd7a2 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java @@ -73,6 +73,9 @@ public class LmdbStoreConfig extends BaseSailConfig { private boolean autoGrow = true; + private boolean dupsortIndices = true; + private boolean dupsortRead = true; + private long valueEvictionInterval = Duration.ofSeconds(60).toMillis(); /*--------------* @@ -190,6 +193,24 @@ public LmdbStoreConfig setValueEvictionInterval(long valueEvictionInterval) { return this; } + public boolean isDupsortIndices() { + return dupsortIndices; + } + + public LmdbStoreConfig setDupsortIndices(boolean dupsortIndices) { + this.dupsortIndices = dupsortIndices; + return this; + } + + public boolean isDupsortRead() { + return dupsortRead; + } + + public LmdbStoreConfig setDupsortRead(boolean dupsortRead) { + this.dupsortRead = dupsortRead; + return this; + } + @Override public Resource export(Model m) { Resource implNode = super.export(m); @@ -226,6 +247,12 @@ public Resource export(Model m) { if (valueEvictionInterval != Duration.ofSeconds(60).toMillis()) { m.add(implNode, LmdbStoreSchema.VALUE_EVICTION_INTERVAL, vf.createLiteral(valueEvictionInterval)); } + if (dupsortIndices) { + m.add(implNode, LmdbStoreSchema.DUPSORT_INDICES, vf.createLiteral(true)); + } + if (dupsortRead) { + m.add(implNode, LmdbStoreSchema.DUPSORT_READ, vf.createLiteral(true)); + } return implNode; } @@ -330,6 +357,25 @@ public void parse(Model m, Resource implNode) throws SailConfigException { + " property, found " + lit); } }); + + Models.objectLiteral(m.getStatements(implNode, LmdbStoreSchema.DUPSORT_INDICES, null)).ifPresent(lit -> { + try { + setDupsortIndices(lit.booleanValue()); + } catch (IllegalArgumentException e) { + throw new SailConfigException( + "Boolean value required for " + LmdbStoreSchema.DUPSORT_INDICES + " property, found " + + lit); + } + }); + Models.objectLiteral(m.getStatements(implNode, LmdbStoreSchema.DUPSORT_READ, null)).ifPresent(lit -> { + try { + setDupsortRead(lit.booleanValue()); + } catch (IllegalArgumentException e) { + throw new SailConfigException( + "Boolean value required for " + LmdbStoreSchema.DUPSORT_READ + " property, found " + + lit); + } + }); } catch (ModelException e) { throw new SailConfigException(e.getMessage(), e); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreSchema.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreSchema.java index 8a9c5acca8..6ad43f6518 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreSchema.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreSchema.java @@ -76,6 +76,16 @@ public class LmdbStoreSchema { */ public final static IRI VALUE_EVICTION_INTERVAL; + /** + * http://rdf4j.org/config/sail/lmdb#dupsortIndices + */ + public final static IRI DUPSORT_INDICES; + + /** + * http://rdf4j.org/config/sail/lmdb#dupsortRead + */ + public final static IRI DUPSORT_READ; + static { ValueFactory factory = SimpleValueFactory.getInstance(); TRIPLE_INDEXES = factory.createIRI(NAMESPACE, "tripleIndexes"); @@ -88,5 +98,7 @@ public class LmdbStoreSchema { NAMESPACE_ID_CACHE_SIZE = factory.createIRI(NAMESPACE, "namespaceIDCacheSize"); AUTO_GROW = factory.createIRI(NAMESPACE, "autoGrow"); VALUE_EVICTION_INTERVAL = factory.createIRI(NAMESPACE, "valueEvictionInterval"); + DUPSORT_INDICES = factory.createIRI(NAMESPACE, "dupsortIndices"); + DUPSORT_READ = factory.createIRI(NAMESPACE, "dupsortRead"); } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorTest.java new file mode 100644 index 0000000000..a2a3b92c1a --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorTest.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.util.Values; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; + +/** + * Focused integration tests that exercise {@link LmdbDupRecordIterator} via the public Sail API. The scenarios reflect + * the assumptions made in the iterator about prefix binding and duplicate handling. + */ +class LmdbDupRecordIteratorTest { + + @TempDir + File dataDir; + + private LmdbStore store; + private NotifyingSailConnection con; + private final ValueFactory vf = SimpleValueFactory.getInstance(); + + @BeforeEach + void setUp() throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,posc"); + config.setDupsortIndices(true); + config.setDupsortRead(true); + store = new LmdbStore(dataDir, config); + store.init(); + con = store.getConnection(); + } + + @AfterEach + void tearDown() throws Exception { + if (con != null) { + con.close(); + con = null; + } + if (store != null) { + store.shutDown(); + store = null; + } + } + + @Test + void predicateAndObjectBoundReturnsAllSubjects() throws Exception { + Resource ctx = vf.createIRI("urn:ctx"); + loadTypeStatements(ctx); + + int count = countStatements(null, RDF.TYPE, RDFS.CLASS, true); + assertEquals(3, count, "Expected duplicate iterator to surface all matching subjects"); + } + + @Test + void predicateObjectAndContextBoundRestrictsToContext() throws Exception { + Resource ctx1 = vf.createIRI("urn:ctx:1"); + Resource ctx2 = vf.createIRI("urn:ctx:2"); + loadTypeStatements(ctx1); + con.begin(); + con.addStatement(vf.createIRI("urn:other"), RDF.TYPE, RDFS.CLASS, ctx2); + con.commit(); + + int countCtx1 = countStatements(null, RDF.TYPE, RDFS.CLASS, true, ctx1); + int countCtx2 = countStatements(null, RDF.TYPE, RDFS.CLASS, true, ctx2); + + assertEquals(2, countCtx1, "Context-specific lookup should include only ctx1 statements"); + assertEquals(1, countCtx2, "Context-specific lookup should include only ctx2 statements"); + } + + @Test + void singleLeadingBindingFallsBackGracefully() throws Exception { + loadTypeStatements(vf.createIRI("urn:ctx:fallback")); + int count = countStatements(null, RDF.TYPE, null, true); + assertEquals(3, count, "Fallback path should still return all type statements"); + } + + @Test + void duplicateIterationDisabledFallsBackToStandardIterator() throws Exception { + tearDown(); + + LmdbStoreConfig config = new LmdbStoreConfig("spoc,posc"); + config.setDupsortIndices(true); + config.setDupsortRead(false); + store = new LmdbStore(dataDir, config); + store.init(); + con = store.getConnection(); + + loadTypeStatements(vf.createIRI("urn:ctx:disabled")); + + int count = countStatements(null, RDF.TYPE, RDFS.CLASS, true); + assertEquals(3, count, "Store should still return complete results when dupsort is disabled"); + } + + @Test + void includeInferredStatementsWhenRequested() throws Exception { + Resource ctx = vf.createIRI("urn:ctx:inf"); + loadTypeStatements(ctx); + + con.begin(); + ((LmdbStoreConnection) con).addInferredStatement(vf.createIRI("urn:dan"), RDF.TYPE, RDFS.CLASS, ctx); + con.commit(); + + int explicitOnly = countStatements(null, RDF.TYPE, RDFS.CLASS, false); + int withInferred = countStatements(null, RDF.TYPE, RDFS.CLASS, true); + + assertEquals(3, explicitOnly, "Explicit view should ignore inferred additions"); + assertEquals(4, withInferred, "Requested inferred statements should be surfaced"); + } + + @Test + void rangePositioningSkipsOverSmallerPrefixes() throws Exception { + Resource ctx = vf.createIRI("urn:ctx:range"); + IRI otherType = vf.createIRI("urn:type:other"); + IRI targetType = vf.createIRI("urn:type:target"); + + con.begin(); + con.addStatement(vf.createIRI("urn:noise"), RDF.TYPE, otherType, ctx); + con.addStatement(vf.createIRI("urn:alpha"), RDF.TYPE, targetType, ctx); + con.addStatement(vf.createIRI("urn:beta"), RDF.TYPE, targetType, ctx); + con.addStatement(vf.createIRI("urn:gamma"), RDF.TYPE, targetType, ctx); + con.commit(); + + int count = countStatements(null, RDF.TYPE, targetType, true, ctx); + assertEquals(3, count, "Iterator should position on the first matching predicate/object prefix"); + } + + private void loadTypeStatements(Resource ctx) throws Exception { + con.begin(); + con.addStatement(vf.createIRI("urn:alice"), RDF.TYPE, RDFS.CLASS); + con.addStatement(vf.createIRI("urn:bob"), RDF.TYPE, RDFS.CLASS, ctx); + con.addStatement(vf.createIRI("urn:carol"), RDF.TYPE, RDFS.CLASS, ctx); + con.commit(); + } + + private int countStatements(Resource subj, IRI pred, org.eclipse.rdf4j.model.Value obj, boolean includeInferred, + Resource... ctx) throws Exception { + int count = 0; + try (var iter = con.getStatements(subj, pred, obj, includeInferred, ctx)) { + while (iter.hasNext()) { + iter.next(); + count++; + } + } + return count; + } + + @Test + @Timeout(5) + public void test() { + + // Add some data to the repository + con.begin(); + con.addStatement(Values.bnode(), RDF.TYPE, RDFS.CLASS); + con.addStatement(Values.bnode(), RDF.TYPE, RDFS.CLASS); + con.addStatement(Values.bnode(), RDF.TYPE, Values.bnode(), Values.bnode()); + con.addStatement(Values.bnode(), RDF.TYPE, Values.bnode(), Values.bnode()); + con.commit(); + + try (CloseableIteration statements = con.getStatements(null, RDF.TYPE, RDFS.CLASS, true)) { + int count = 0; + int max = 100; + while (max-- > 0 && statements.hasNext()) { + count++; + Statement next = statements.next(); + System.out.println(next); + } + assertThat(count).isEqualTo(2); + } + + } + + @Test + void clearRemovesAllStatements() throws Exception { + con.begin(); + for (int i = 0; i < 5; i++) { + con.addStatement(vf.createIRI("urn:s" + i), RDF.TYPE, RDFS.CLASS, vf.createIRI("urn:ctx" + (i % 2))); + } + con.commit(); + + con.begin(); + con.clear(); + con.commit(); + + assertEquals(0, countStatements(null, null, null, true)); + } + + @Test + void clearAfterSnapshotScenario() throws Exception { + // mimic SailIsolationLevelTest.setUp sequence + con.begin(); + con.addStatement(RDF.NIL, RDF.TYPE, RDF.LIST); + con.commit(); + + con.begin(); + con.clear(); + con.commit(); + + assertEquals(0, countStatements(null, null, null, true)); + } + +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreTest.java index ba1551bdb8..258029a875 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreTest.java @@ -15,6 +15,7 @@ import java.io.File; import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.sail.NotifyingSail; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; @@ -60,4 +61,22 @@ public void testGetNamespacePersistence() { assertEquals(RDF.NAMESPACE, con.getNamespace("rdf")); } + + @Test + public void testPredicateObjectDupIteration() throws Exception { + con.begin(); + con.addStatement(painter, RDF.TYPE, RDFS.CLASS); + con.addStatement(painting, RDF.TYPE, RDFS.CLASS); + con.commit(); + + int count = 0; + try (var statements = con.getStatements(null, RDF.TYPE, RDFS.CLASS, true)) { + while (statements.hasNext()) { + statements.next(); + count++; + } + } + + assertEquals(2, count); + } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java index 3a9c88ad84..4a561a5fe1 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java @@ -126,7 +126,9 @@ public class QueryBenchmark { public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() - .include("QueryBenchmark.ordered_union_limit") // adapt to run other benchmark tests + .include("QueryBenchmark.complexQuery") // adapt to run other benchmark tests + .warmupIterations(0) + .measurementIterations(10) .forks(0) .build(); diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/SparqlOrderByTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/SparqlOrderByTest.java index 43a8e88fd6..86e58850b0 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/SparqlOrderByTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/SparqlOrderByTest.java @@ -33,7 +33,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +@Timeout(value = 10) public abstract class SparqlOrderByTest { @BeforeAll From 617deff7497903f134698fd9ae491d695e63b745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 16 Oct 2025 10:15:47 +0200 Subject: [PATCH 03/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index b425f873e2..ce8b901170 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -102,11 +102,7 @@ class LmdbDupRecordIterator implements RecordIterator { this.txnRefVersion = txnRef.version(); this.txn = txnRef.get(); - try (MemoryStack stack = MemoryStack.stackPush()) { - PointerBuffer pp = stack.mallocPointer(1); - E(mdb_cursor_open(txn, dupDbi, pp)); - cursor = pp.get(0); - } + cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); prefixKeyBuf = pool.getKeyBuffer(); prefixValues = new long[2]; @@ -365,7 +361,11 @@ private void closeInternal(boolean maybeCalledAsync) { } try { if (!closed) { - mdb_cursor_close(cursor); + if (txnRef.isReadOnly()) { + pool.freeCursor(dupDbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } pool.free(keyData); pool.free(valueData); if (prefixKeyBuf != null) { @@ -389,4 +389,23 @@ public void close() { closeInternal(true); } } + + private long openCursor(long txn, int dbi, boolean tryReuse) throws IOException { + if (tryReuse) { + long pooled = pool.getCursor(dbi, index); + if (pooled != 0L) { + try { + E(mdb_cursor_renew(txn, pooled)); + return pooled; + } catch (IOException renewEx) { + mdb_cursor_close(pooled); + } + } + } + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + return pp.get(0); + } + } } From 4dc188b34eb8587235b2757c94d611c22724a5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 16 Oct 2025 13:15:11 +0200 Subject: [PATCH 04/79] wip --- .../eclipse/rdf4j/model/base/AbstractIRI.java | 16 ----- .../eclipse/rdf4j/model/impl/SimpleIRI.java | 16 ----- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 2 + .../rdf4j/sail/lmdb/model/LmdbIRI.java | 68 +++++++++---------- .../rdf4j/sail/lmdb/benchmark/README.md | 55 ++++++++++++++- 5 files changed, 88 insertions(+), 69 deletions(-) diff --git a/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java b/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java index b068fcff57..8cf6dfb525 100644 --- a/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java +++ b/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java @@ -124,22 +124,6 @@ private int split() { } } - @Override - public int hashCode() { - return iri.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof IRI)) { - return false; - } - return iri.equals(o.toString()); - } - } } diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java b/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java index 08729a6c5a..e40b15e714 100644 --- a/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java @@ -123,20 +123,4 @@ public String getLocalName() { return iriString.substring(localNameIdx); } - @Override - public int hashCode() { - return iriString.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof IRI)) { - return false; - } - - return iriString.equals(o.toString()); - } } 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 3d9a8c4d6b..23c4e9373b 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 @@ -541,6 +541,8 @@ && leadingBoundCount(index.getFieldSeq(), subj, pred, obj, context) >= 2) { private int leadingBoundCount(char[] fieldSeq, long subj, long pred, long obj, long context) { int count = 0; + + for (int i = 0; i < fieldSeq.length; i++) { boolean bound; switch (fieldSeq[i]) { 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 f6c77a0c93..4bb124a2b4 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 @@ -10,15 +10,15 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.model; -import java.io.ObjectStreamException; -import java.util.Objects; - import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.util.URIUtil; import org.eclipse.rdf4j.sail.lmdb.ValueStoreRevision; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ObjectStreamException; +import java.util.Objects; + public class LmdbIRI implements LmdbResource, IRI { private static final long serialVersionUID = -5888138591826143179L; @@ -151,53 +151,49 @@ public void init() { public boolean equals(Object o) { if (this == o) { return true; - } - - if (o == null) { + } else if (o instanceof LmdbIRI) { + return equalsLmdbIRI(((LmdbIRI) o)); + } else if (o instanceof IRI) { + IRI other = (IRI) o; + return stringValue().equals(other.stringValue()); + } else { return false; } - if (o.getClass() == LmdbIRI.class) { - if (internalID == UNKNOWN_ID) { - boolean equals = stringValue().equals(((IRI) o).stringValue()); - if (equals && revision.equals(((LmdbIRI) o).revision)) { - internalID = ((LmdbIRI) o).internalID; - } - return equals; - } - - LmdbIRI otherLmdbURI = (LmdbIRI) o; + } - if (revision.equals(otherLmdbURI.revision)) { - if (otherLmdbURI.internalID == UNKNOWN_ID) { - boolean equals = stringValue().equals(((IRI) o).stringValue()); - if (equals) { - otherLmdbURI.internalID = this.internalID; - } - return equals; + private boolean equalsLmdbIRI(LmdbIRI o) { + if (internalID == UNKNOWN_ID) { + boolean equals = stringValue().equals(o.stringValue()); + if (equals && revision.equals(o.revision)) { + internalID = o.internalID; + } + return equals; + } else if (revision.equals(o.revision)) { + if (o.internalID == UNKNOWN_ID) { + boolean equals = stringValue().equals(o.stringValue()); + if (equals) { + o.internalID = this.internalID; } - + return equals; + } else { // LmdbURI's from the same revision of the same lmdb store, with // both ID's set - boolean equal = internalID == otherLmdbURI.internalID; + boolean equal = internalID == o.internalID; if (equal) { if (iriString == null) { - iriString = otherLmdbURI.iriString; - localNameIdx = otherLmdbURI.localNameIdx; - } else if (otherLmdbURI.iriString == null) { - otherLmdbURI.iriString = iriString; - otherLmdbURI.localNameIdx = localNameIdx; + iriString = o.iriString; + localNameIdx = o.localNameIdx; + } else if (o.iriString == null) { + o.iriString = iriString; + o.localNameIdx = localNameIdx; } } return equal; } + } else { + return stringValue().equals(o.stringValue()); } - - if (!(o instanceof IRI)) { - return false; - } - - return stringValue().equals(((IRI) o).stringValue()); } @Override diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md index 592dcdd57f..07926efcc7 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/README.md @@ -1,9 +1,11 @@ +## pool cursors +``` Benchmark Mode Cnt Score Error Units QueryBenchmark.complexQuery avgt 5 3.724 ± 0.083 ms/op QueryBenchmark.different_datasets_with_similar_distributions avgt 5 2.118 ± 0.089 ms/op QueryBenchmark.groupByQuery avgt 5 0.916 ± 0.019 ms/op QueryBenchmark.long_chain avgt 5 641.809 ± 6.881 ms/op - QueryBenchmark.minus avgt 5 10.472 ± 0.556 ms/op +QueryBenchmark.minus avgt 5 10.472 ± 0.556 ms/op QueryBenchmark.multiple_sub_select avgt 5 55.949 ± 3.581 ms/op QueryBenchmark.nested_optionals avgt 5 171.423 ± 39.898 ms/op QueryBenchmark.optional_lhs_filter avgt 5 36.728 ± 2.244 ms/op @@ -17,3 +19,54 @@ QueryBenchmark.sub_select avgt 5 72.550 QueryBenchmarkFoaf.groupByCount avgt 5 721.870 ± 16.816 ms/op QueryBenchmarkFoaf.groupByCountSorted avgt 5 651.351 ± 19.324 ms/op QueryBenchmarkFoaf.personsAndFriends avgt 5 213.945 ± 7.394 ms/op +``` + +### dup key +``` +Benchmark Mode Cnt Score Error Units +QueryBenchmark.complexQuery avgt 5 3.451 ± 0.026 ms/op +QueryBenchmark.different_datasets_with_similar_distributions avgt 5 2.523 ± 0.018 ms/op +QueryBenchmark.groupByQuery avgt 5 0.875 ± 0.011 ms/op +QueryBenchmark.long_chain avgt 5 674.397 ± 6.014 ms/op +QueryBenchmark.lots_of_optional avgt 5 295.217 ± 2.850 ms/op +QueryBenchmark.minus avgt 5 10.704 ± 0.640 ms/op +QueryBenchmark.multiple_sub_select avgt 5 57.411 ± 1.345 ms/op +QueryBenchmark.nested_optionals avgt 5 218.365 ± 0.449 ms/op +QueryBenchmark.optional_lhs_filter avgt 5 34.577 ± 0.341 ms/op +QueryBenchmark.optional_rhs_filter avgt 5 65.118 ± 0.355 ms/op +QueryBenchmark.ordered_union_limit avgt 5 68.742 ± 1.475 ms/op +QueryBenchmark.pathExpressionQuery1 avgt 5 20.378 ± 0.232 ms/op +QueryBenchmark.pathExpressionQuery2 avgt 5 5.127 ± 0.031 ms/op +QueryBenchmark.query_distinct_predicates avgt 5 44.293 ± 0.293 ms/op +QueryBenchmark.simple_filter_not avgt 5 5.509 ± 0.061 ms/op +QueryBenchmark.sub_select avgt 5 76.451 ± 0.970 ms/op +QueryBenchmarkFoaf.groupByCount avgt 5 742.100 ± 11.235 ms/op +QueryBenchmarkFoaf.groupByCountSorted avgt 5 602.247 ± 31.896 ms/op +QueryBenchmarkFoaf.personsAndFriends avgt 5 218.583 ± 1.921 ms/op + +``` + + +### Instance of LmdbIRI +```text +Benchmark Mode Cnt Score Error Units +QueryBenchmark.complexQuery avgt 5 3.431 ± 0.027 ms/op +QueryBenchmark.different_datasets_with_similar_distributions avgt 5 2.504 ± 0.008 ms/op +QueryBenchmark.groupByQuery avgt 5 0.865 ± 0.006 ms/op +QueryBenchmark.long_chain avgt 5 670.361 ± 5.933 ms/op +QueryBenchmark.lots_of_optional avgt 5 294.593 ± 3.254 ms/op +QueryBenchmark.minus avgt 5 10.788 ± 0.241 ms/op +QueryBenchmark.multiple_sub_select avgt 5 56.797 ± 0.608 ms/op +QueryBenchmark.nested_optionals avgt 5 217.519 ± 2.129 ms/op +QueryBenchmark.optional_lhs_filter avgt 5 34.818 ± 0.603 ms/op +QueryBenchmark.optional_rhs_filter avgt 5 64.181 ± 0.366 ms/op +QueryBenchmark.ordered_union_limit avgt 5 68.970 ± 0.575 ms/op +QueryBenchmark.pathExpressionQuery1 avgt 5 20.395 ± 0.229 ms/op +QueryBenchmark.pathExpressionQuery2 avgt 5 4.756 ± 0.041 ms/op +QueryBenchmark.query_distinct_predicates avgt 5 49.811 ± 1.072 ms/op +QueryBenchmark.simple_filter_not avgt 5 5.559 ± 0.049 ms/op +QueryBenchmark.sub_select avgt 5 74.768 ± 1.344 ms/op +QueryBenchmarkFoaf.groupByCount avgt 5 707.383 ± 13.236 ms/op +QueryBenchmarkFoaf.groupByCountSorted avgt 5 633.781 ± 159.621 ms/op +QueryBenchmarkFoaf.personsAndFriends avgt 5 214.109 ± 3.490 ms/op +``` From 450793c3275fc416b78b699fb10a48b93940cbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 16 Oct 2025 20:20:40 +0200 Subject: [PATCH 05/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 109 +++++++++++++----- 1 file changed, 78 insertions(+), 31 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index ce8b901170..112bd7b30a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -110,24 +110,43 @@ class LmdbDupRecordIterator implements RecordIterator { long adjPred = pred < 0 ? 0 : pred; long adjObj = obj < 0 ? 0 : obj; long adjContext = context < 0 ? 0 : context; - for (int i = 0; i < 2; i++) { - char f = fieldSeq[i]; + { + char f = fieldSeq[0]; long v; switch (f) { - case 's': - v = adjSubj; - break; - case 'p': - v = adjPred; - break; - case 'o': - v = adjObj; - break; - default: - v = adjContext; - break; + case 's': + v = adjSubj; + break; + case 'p': + v = adjPred; + break; + case 'o': + v = adjObj; + break; + default: + v = adjContext; + break; } - prefixValues[i] = v; + prefixValues[0] = v; + } + { + char f = fieldSeq[1]; + long v; + switch (f) { + case 's': + v = adjSubj; + break; + case 'p': + v = adjPred; + break; + case 'o': + v = adjObj; + break; + default: + v = adjContext; + break; + } + prefixValues[1] = v; } prefixKeyBuf.clear(); index.toDupKeyPrefix(prefixKeyBuf, adjSubj, adjPred, adjObj, adjContext); @@ -246,9 +265,19 @@ private boolean adjustCursorToPrefix() throws IOException { private int comparePrefix() { ByteBuffer key = keyData.mv_data().duplicate(); - for (int i = 0; i < 2; i++) { + { + long actual = Varint.readUnsigned(key); + long expected = prefixValues[0]; + if (actual < expected) { + return -1; + } + if (actual > expected) { + return 1; + } + } + { long actual = Varint.readUnsigned(key); - long expected = prefixValues[i]; + long expected = prefixValues[1]; if (actual < expected) { return -1; } @@ -286,22 +315,40 @@ private void resetDuplicateBuffer(ByteBuffer buffer) { private void fillQuadFromPrefixAndValue(long v3, long v4) { int pi = 0; - for (int i = 0; i < 2; i++) { - char f = fieldSeq[i]; + { + char f = fieldSeq[0]; long v = prefixValues[pi++]; switch (f) { - case 's': - quad[0] = v; - break; - case 'p': - quad[1] = v; - break; - case 'o': - quad[2] = v; - break; - case 'c': - quad[3] = v; - break; + case 's': + quad[0] = v; + break; + case 'p': + quad[1] = v; + break; + case 'o': + quad[2] = v; + break; + case 'c': + quad[3] = v; + break; + } + } + { + char f = fieldSeq[1]; + long v = prefixValues[pi++]; + switch (f) { + case 's': + quad[0] = v; + break; + case 'p': + quad[1] = v; + break; + case 'o': + quad[2] = v; + break; + case 'c': + quad[3] = v; + break; } } char f = fieldSeq[2]; From fcebff5aa92ef74afc51a533d101dda5c1c2d8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 16 Oct 2025 23:00:37 +0200 Subject: [PATCH 06/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 266 +++++++----------- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 194 ++++++++++++- .../rdf4j/sail/lmdb/model/LmdbIRI.java | 6 +- .../sail/lmdb/SubjectPredicateIndexTest.java | 79 ++++++ 4 files changed, 359 insertions(+), 186 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 112bd7b30a..f4295255c4 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -10,12 +10,22 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.DupIndex; +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; import static org.lwjgl.util.lmdb.LMDB.MDB_GET_MULTIPLE; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; -import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT_MULTIPLE; import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; -import static org.lwjgl.util.lmdb.LMDB.MDB_PREV; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_KEY; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; import static org.lwjgl.util.lmdb.LMDB.MDB_SUCCESS; @@ -24,26 +34,18 @@ import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_open; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_renew; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; -import org.eclipse.rdf4j.sail.SailException; -import org.eclipse.rdf4j.sail.lmdb.TripleStore.TripleIndex; -import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; -import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; -import org.lwjgl.PointerBuffer; -import org.lwjgl.system.MemoryStack; -import org.lwjgl.util.lmdb.MDBVal; - /** * A dupsort/dupfixed-optimized record iterator using MDB_GET_MULTIPLE/NEXT_MULTIPLE to reduce JNI calls. */ class LmdbDupRecordIterator implements RecordIterator { + @FunctionalInterface + interface FallbackSupplier { + RecordIterator get() throws IOException; + } + private final Pool pool; - private final TripleIndex index; + private final DupIndex index; private final int dupDbi; private final long cursor; @@ -58,10 +60,8 @@ class LmdbDupRecordIterator implements RecordIterator { private final long[] quad; private final long[] originalQuad; - private final char[] fieldSeq; private final boolean matchValues; - private GroupMatcher groupMatcher; private ByteBuffer prefixKeyBuf; private long[] prefixValues; @@ -74,14 +74,17 @@ class LmdbDupRecordIterator implements RecordIterator { private boolean closed = false; private final RecordIterator fallback; + private final FallbackSupplier fallbackSupplier; + - LmdbDupRecordIterator(TripleIndex index, long subj, long pred, long obj, long context, - boolean explicit, Txn txnRef) throws IOException { + LmdbDupRecordIterator(DupIndex index, long subj, long pred, + boolean explicit, Txn txnRef, FallbackSupplier fallbackSupplier) throws IOException { this.index = index; - this.fieldSeq = index.getFieldSeq(); - this.quad = new long[] { subj, pred, obj, context }; - this.originalQuad = new long[] { subj, pred, obj, context }; - this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; + + this.quad = new long[]{subj, pred, -1, -1}; + this.originalQuad = new long[]{subj, pred, -1, -1}; + this.matchValues = subj > 0 || pred > 0; + this.fallbackSupplier = fallbackSupplier; this.pool = Pool.get(); this.keyData = pool.getVal(); @@ -105,51 +108,12 @@ class LmdbDupRecordIterator implements RecordIterator { cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); prefixKeyBuf = pool.getKeyBuffer(); - prefixValues = new long[2]; - long adjSubj = subj < 0 ? 0 : subj; - long adjPred = pred < 0 ? 0 : pred; - long adjObj = obj < 0 ? 0 : obj; - long adjContext = context < 0 ? 0 : context; - { - char f = fieldSeq[0]; - long v; - switch (f) { - case 's': - v = adjSubj; - break; - case 'p': - v = adjPred; - break; - case 'o': - v = adjObj; - break; - default: - v = adjContext; - break; - } - prefixValues[0] = v; - } - { - char f = fieldSeq[1]; - long v; - switch (f) { - case 's': - v = adjSubj; - break; - case 'p': - v = adjPred; - break; - case 'o': - v = adjObj; - break; - default: - v = adjContext; - break; - } - prefixValues[1] = v; - } + prefixValues = new long[]{subj, pred}; prefixKeyBuf.clear(); - index.toDupKeyPrefix(prefixKeyBuf, adjSubj, adjPred, adjObj, adjContext); + Varint.writeUnsigned(prefixKeyBuf, subj); + Varint.writeUnsigned(prefixKeyBuf, pred); + +// index.toDupKeyPrefix(prefixKeyBuf, subj, pred, 0, 0); prefixKeyBuf.flip(); boolean positioned = positionOnPrefix(); @@ -158,9 +122,7 @@ class LmdbDupRecordIterator implements RecordIterator { } if (!positioned) { closeInternal(false); - fallbackIterator = new LmdbRecordIterator(index, - index.getPatternScore(subj, pred, obj, context) > 0, subj, pred, obj, context, explicit, - txnRef); + fallbackIterator = createFallback(); } } finally { txnLockManager.unlockRead(readStamp); @@ -197,37 +159,45 @@ public long[] next() { while (true) { if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { - long v3 = dupBuf.getLong(dupPos); - long v4 = dupBuf.getLong(dupPos + Long.BYTES); - dupPos += Long.BYTES * 2; - fillQuadFromPrefixAndValue(v3, v4); - if (!matchValues || matchesQuad()) { - return quad; + long v3; + long v4; + if (true) { + v3 = readLittleEndianLong(dupBuf, dupPos); + v4 = readLittleEndianLong(dupBuf, dupPos + Long.BYTES); + dupPos += Long.BYTES * 2; + } else { + v3 = dupBuf.getLong(dupPos); + v4 = dupBuf.getLong(dupPos + Long.BYTES); + dupPos += Long.BYTES * 2; } - continue; + fillQuadFromPrefixAndValue(v3, v4); + return quad; } - lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); - if (lastResult == MDB_SUCCESS) { - resetDuplicateBuffer(valueData.mv_data()); - continue; - } + closeInternal(false); + return null; - lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); - if (lastResult != MDB_SUCCESS) { - closeInternal(false); - return null; - } - if (!currentKeyHasPrefix()) { - if (!adjustCursorToPrefix()) { - closeInternal(false); - return null; - } - } - if (!primeDuplicateBlock()) { - closeInternal(false); - return null; - } +// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); +// if (lastResult == MDB_SUCCESS) { +// resetDuplicateBuffer(valueData.mv_data()); +// continue; +// } +// +// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); +// if (lastResult != MDB_SUCCESS) { +// closeInternal(false); +// return null; +// } +// if (!currentKeyHasPrefix()) { +// if (!adjustCursorToPrefix()) { +// closeInternal(false); +// return null; +// } +// } +// if (!primeDuplicateBlock()) { +// closeInternal(false); +// return null; +// } } } catch (IOException e) { throw new SailException(e); @@ -306,81 +276,24 @@ private void resetDuplicateBuffer(ByteBuffer buffer) { dupBuf = null; dupPos = dupLimit = 0; } else { - buffer.order(ByteOrder.nativeOrder()); - dupBuf = buffer; + ByteBuffer source = buffer.duplicate(); + source.position(buffer.position()); + source.limit(buffer.limit()); + ByteBuffer copy = ByteBuffer.allocate(source.remaining()); + copy.put(source); + copy.flip(); + copy.order(ByteOrder.nativeOrder()); + dupBuf = copy; dupPos = dupBuf.position(); dupLimit = dupBuf.limit(); } } private void fillQuadFromPrefixAndValue(long v3, long v4) { - int pi = 0; - { - char f = fieldSeq[0]; - long v = prefixValues[pi++]; - switch (f) { - case 's': - quad[0] = v; - break; - case 'p': - quad[1] = v; - break; - case 'o': - quad[2] = v; - break; - case 'c': - quad[3] = v; - break; - } - } - { - char f = fieldSeq[1]; - long v = prefixValues[pi++]; - switch (f) { - case 's': - quad[0] = v; - break; - case 'p': - quad[1] = v; - break; - case 'o': - quad[2] = v; - break; - case 'c': - quad[3] = v; - break; - } - } - char f = fieldSeq[2]; - switch (f) { - case 's': - quad[0] = v3; - break; - case 'p': - quad[1] = v3; - break; - case 'o': - quad[2] = v3; - break; - case 'c': - quad[3] = v3; - break; - } - f = fieldSeq[3]; - switch (f) { - case 's': - quad[0] = v4; - break; - case 'p': - quad[1] = v4; - break; - case 'o': - quad[2] = v4; - break; - case 'c': - quad[3] = v4; - break; - } + quad[0] = prefixValues[0]; + quad[1] = prefixValues[1]; + quad[2] = v3; + quad[3] = v4; } private boolean currentKeyHasPrefix() { @@ -389,9 +302,7 @@ private boolean currentKeyHasPrefix() { private boolean matchesQuad() { return (originalQuad[0] < 0 || quad[0] == originalQuad[0]) - && (originalQuad[1] < 0 || quad[1] == originalQuad[1]) - && (originalQuad[2] < 0 || quad[2] == originalQuad[2]) - && (originalQuad[3] < 0 || quad[3] == originalQuad[3]); + && (originalQuad[1] < 0 || quad[1] == originalQuad[1]); } private void closeInternal(boolean maybeCalledAsync) { @@ -455,4 +366,19 @@ private long openCursor(long txn, int dbi, boolean tryReuse) throws IOException return pp.get(0); } } + + private RecordIterator createFallback() throws IOException { + if (fallbackSupplier == null) { + return null; + } + return fallbackSupplier.get(); + } + + private long readLittleEndianLong(ByteBuffer buffer, int offset) { + long value = 0L; + for (int i = 0; i < Long.BYTES; i++) { + value |= (buffer.get(offset + i) & 0xFFL) << (i * 8); + } + return value; + } } 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 23c4e9373b..d0511b2776 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 @@ -61,6 +61,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -160,6 +161,7 @@ class TripleStore implements Closeable { * The list of triple indexes that are used to store and retrieve triples. */ private final List indexes = new ArrayList<>(); + private final SubjectPredicateIndex subjectPredicateIndex; private final ValueStore valueStore; private long env; @@ -212,8 +214,9 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in } // Max DBs: 1 for contexts, 12 for triple indexes (2 per index), - // plus up to 12 dupsort companion DBs when enabled. Use a safe upper bound of 25. - E(mdb_env_set_maxdbs(env, 25)); + // plus up to 12 dupsort companion DBs when enabled and an additional subject-predicate index. + // Use a safe upper bound of 27. + E(mdb_env_set_maxdbs(env, 27)); E(mdb_env_set_maxreaders(env, 256)); // Open environment @@ -272,6 +275,8 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in } } + subjectPredicateIndex = dupsortIndices ? new SubjectPredicateIndex() : null; + if (!String.valueOf(SCHEME_VERSION).equals(properties.getProperty(VERSION_KEY)) || !indexSpecStr.equals(properties.getProperty(INDEXES_KEY))) { // Store up-to-date properties @@ -477,6 +482,15 @@ public void close() throws IOException { } } + if (subjectPredicateIndex != null) { + try { + subjectPredicateIndex.close(); + } catch (Throwable e) { + logger.warn("Failed to close subject-predicate dup index", e); + caughtExceptions.add(e); + } + } + mdb_env_close(env); env = 0; @@ -507,7 +521,9 @@ public RecordIterator getAllTriplesSortedByContext(Txn txn) throws IOException { for (TripleIndex index : indexes) { if (index.getFieldSeq()[0] == 'c') { // found a context-first index - return getTriplesUsingIndex(txn, -1, -1, -1, -1, true, index, false); + LmdbDupRecordIterator.FallbackSupplier fallback = () -> new LmdbRecordIterator(index, false, -1, -1, -1, + -1, true, txn); + return getTriplesUsingIndex(txn, -1, -1, -1, -1, true, index, false, fallback); } } return null; @@ -518,7 +534,13 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c TripleIndex index = getBestIndex(subj, pred, obj, context); // System.out.println("get triples: " + Arrays.asList(subj, pred, obj,context)); boolean doRangeSearch = index.getPatternScore(subj, pred, obj, context) > 0; - return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch); + LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = () -> new LmdbRecordIterator(index, doRangeSearch, + subj, pred, obj, context, explicit, txn); + if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj < 0 && context < 0) { + return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, + fallbackSupplier); + } + return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch, fallbackSupplier); } boolean hasTriples(boolean explicit) throws IOException { @@ -531,18 +553,14 @@ boolean hasTriples(boolean explicit) throws IOException { } private RecordIterator getTriplesUsingIndex(Txn txn, long subj, long pred, long obj, long context, - boolean explicit, TripleIndex index, boolean rangeSearch) throws IOException { - if (dupsortRead && index.isDupsortEnabled() - && leadingBoundCount(index.getFieldSeq(), subj, pred, obj, context) >= 2) { - return new LmdbDupRecordIterator(index, subj, pred, obj, context, explicit, txn); - } - return new LmdbRecordIterator(index, rangeSearch, subj, pred, obj, context, explicit, txn); + boolean explicit, TripleIndex index, boolean rangeSearch, + LmdbDupRecordIterator.FallbackSupplier fallbackSupplier) throws IOException { + return fallbackSupplier.get(); } private int leadingBoundCount(char[] fieldSeq, long subj, long pred, long obj, long context) { int count = 0; - for (int i = 0; i < fieldSeq.length; i++) { boolean bound; switch (fieldSeq[i]) { @@ -984,6 +1002,10 @@ public boolean storeTriple(long subj, long pred, long obj, long context, boolean } } + if (stAdded && subjectPredicateIndex != null) { + subjectPredicateIndex.put(writeTxn, subj, pred, obj, context, explicit, stack); + } + if (stAdded) { for (int i = 1; i < indexes.size(); i++) { @@ -1026,6 +1048,9 @@ public boolean storeTriple(long subj, long pred, long obj, long context, boolean incrementContext(stack, context); } } + if (foundImplicit && subjectPredicateIndex != null) { + subjectPredicateIndex.delete(writeTxn, subj, pred, obj, context, false, stack); + } } return stAdded; @@ -1155,6 +1180,12 @@ public void removeTriples(RecordIterator it, boolean explicit, Consumer stack.setPointer(frame); } } + + } + + if (subjectPredicateIndex != null) { + subjectPredicateIndex.delete(writeTxn, quad[SUBJ_IDX], quad[PRED_IDX], quad[OBJ_IDX], + quad[CONTEXT_IDX], explicit, stack); } decrementContext(stack, quad[CONTEXT_IDX]); @@ -1241,6 +1272,16 @@ protected void updateFromCache() throws IOException { } } } + + if (subjectPredicateIndex != null && r != null) { + if (r.add) { + subjectPredicateIndex.put(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, + stack); + } else { + subjectPredicateIndex.delete(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, + stack); + } + } } } recordCache.close(); @@ -1343,7 +1384,17 @@ private void storeProperties(File propFile) throws IOException { } } - class TripleIndex { + interface DupIndex { + char[] getFieldSeq(); + + int getDupDB(boolean explicit); + + void toDupKeyPrefix(ByteBuffer bb, long subj, long pred, long obj, long context); + + int getPatternScore(long subj, long pred, long obj, long context); + } + + class TripleIndex implements DupIndex { private final char[] fieldSeq; private final IndexKeyWriters.KeyWriter keyWriter; @@ -1393,7 +1444,8 @@ public int getDupDB(boolean explicit) { return explicit ? dbiDupExplicit : dbiDupInferred; } - void toDupKeyPrefix(ByteBuffer bb, long subj, long pred, long obj, long context) { + @Override + public void toDupKeyPrefix(ByteBuffer bb, long subj, long pred, long obj, long context) { long s = subj, p = pred, o = obj, c = context; for (int i = 0; i < 2; i++) { char f = fieldSeq[i]; @@ -1691,6 +1743,122 @@ void destroy(long txn) { } } + class SubjectPredicateIndex implements DupIndex { + + private final char[] fieldSeq = new char[] { 's', 'p', 'o', 'c' }; + private final int dbiDupExplicit; + private final int dbiDupInferred; + + SubjectPredicateIndex() throws IOException { + int flags = MDB_CREATE | org.lwjgl.util.lmdb.LMDB.MDB_DUPSORT + | org.lwjgl.util.lmdb.LMDB.MDB_DUPFIXED; + dbiDupExplicit = openDatabase(env, "sp-dup", flags, null); + dbiDupInferred = openDatabase(env, "sp-dup-inf", flags, null); + } + + @Override + public char[] getFieldSeq() { + return fieldSeq; + } + + @Override + public int getDupDB(boolean explicit) { + return explicit ? dbiDupExplicit : dbiDupInferred; + } + + @Override + public void toDupKeyPrefix(ByteBuffer bb, long subj, long pred, long obj, long context) { + Varint.writeUnsigned(bb, subj); + Varint.writeUnsigned(bb, pred); + } + + void toDupValue(ByteBuffer bb, long subj, long pred, long obj, long context) { + bb.putLong(obj); + bb.putLong(context); + } + + @Override + public int getPatternScore(long subj, long pred, long obj, long context) { + int score = 0; + if (subj >= 0) { + score++; + } else { + return score; + } + if (pred >= 0) { + score++; + } else { + return score; + } + if (obj >= 0) { + score++; + } else { + return score; + } + if (context >= 0) { + score++; + } + return score; + } + + void put(long txn, long subj, long pred, long obj, long context, boolean explicit, MemoryStack stack) + throws IOException { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); + dupKeyBuf.flip(); + dupValBuf.clear(); + writeLongLittleEndian(dupValBuf, obj); + writeLongLittleEndian(dupValBuf, context); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_put(txn, getDupDB(explicit), dupKeyVal, dupDataVal, 0)); + } finally { + stack.setPointer(frame); + } + } + + void delete(long txn, long subj, long pred, long obj, long context, boolean explicit, MemoryStack stack) + throws IOException { + int frame = stack.getPointer(); + try { + ByteBuffer dupKeyBuf = stack.malloc(MAX_KEY_LENGTH); + ByteBuffer dupValBuf = stack.malloc(Long.BYTES * 2); + dupKeyBuf.clear(); + toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); + dupKeyBuf.flip(); + dupValBuf.clear(); + writeLongLittleEndian(dupValBuf, obj); + writeLongLittleEndian(dupValBuf, context); + dupValBuf.flip(); + MDBVal dupKeyVal = MDBVal.malloc(stack); + MDBVal dupDataVal = MDBVal.malloc(stack); + dupKeyVal.mv_data(dupKeyBuf); + dupDataVal.mv_data(dupValBuf); + E(mdb_del(txn, getDupDB(explicit), dupKeyVal, dupDataVal)); + } finally { + stack.setPointer(frame); + } + } + + void close() { + mdb_dbi_close(env, dbiDupExplicit); + mdb_dbi_close(env, dbiDupInferred); + } + + private void writeLongLittleEndian(ByteBuffer buffer, long value) { + for (int i = 0; i < Long.BYTES; i++) { + buffer.put((byte) ((value >> (i * 8)) & 0xFF)); + } + } + } + static boolean threeOfFourAreZeroOrMax(long subj, long pred, long obj, long context) { // Precompute the 8 equalities once (cheapest operations here) boolean zS = subj == 0L, zP = pred == 0L, zO = obj == 0L, zC = context == 0L; 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 4bb124a2b4..3e551762d6 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 @@ -10,15 +10,15 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.model; +import java.io.ObjectStreamException; +import java.util.Objects; + import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.util.URIUtil; import org.eclipse.rdf4j.sail.lmdb.ValueStoreRevision; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ObjectStreamException; -import java.util.Objects; - public class LmdbIRI implements LmdbResource, IRI { private static final long serialVersionUID = -5888138591826143179L; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java new file mode 100644 index 0000000000..689081ce66 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; + +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class SubjectPredicateIndexTest { + + @TempDir + File dataDir; + + private TripleStore tripleStore; + + @BeforeEach + void setUp() throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("posc"); + config.setDupsortIndices(true); + config.setDupsortRead(true); + tripleStore = new TripleStore(dataDir, config, null); + + tripleStore.startTransaction(); + tripleStore.storeTriple(1, 2, 3, 0, true); + tripleStore.storeTriple(1, 2, 4, 0, true); + tripleStore.commit(); + } + + @AfterEach + void tearDown() throws Exception { + if (tripleStore != null) { + tripleStore.close(); + } + } + + @Test + void subjectPredicatePatternUsesDupIterator() throws Exception { + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + RecordIterator iter = tripleStore.getTriples(txn, 1, 2, -1, -1, true)) { + assertThat(iter).isInstanceOf(LmdbDupRecordIterator.class); + + int count = 0; + while (iter.next() != null) { + count++; + } + assertEquals(2, count); + } + } + + @Test + void predicateObjectPatternFallsBackToStandardIterator() throws Exception { + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + RecordIterator iter = tripleStore.getTriples(txn, -1, 2, 3, -1, true)) { + assertThat(iter).isNotInstanceOf(LmdbDupRecordIterator.class); + + int count = 0; + while (iter.next() != null) { + count++; + } + assertEquals(1, count); + } + } +} From 9bcc7f4a49efe7ec3d8dfd92904ab6170d30fcb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 17 Oct 2025 09:41:53 +0200 Subject: [PATCH 07/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 127 ++++++------------ .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 32 ++++- .../rdf4j/sail/lmdb/model/LmdbIRI.java | 2 +- 3 files changed, 72 insertions(+), 89 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index f4295255c4..a7f697dd49 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -10,21 +10,10 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; -import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; -import org.eclipse.rdf4j.sail.SailException; -import org.eclipse.rdf4j.sail.lmdb.TripleStore.DupIndex; -import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; -import org.lwjgl.PointerBuffer; -import org.lwjgl.system.MemoryStack; -import org.lwjgl.util.lmdb.MDBVal; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; -import static org.lwjgl.util.lmdb.LMDB.MDB_GET_MULTIPLE; +import static org.lwjgl.util.lmdb.LMDB.MDB_GET_CURRENT; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; +import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT_DUP; import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_KEY; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; @@ -34,6 +23,17 @@ import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_open; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_renew; +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.DupIndex; +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + /** * A dupsort/dupfixed-optimized record iterator using MDB_GET_MULTIPLE/NEXT_MULTIPLE to reduce JNI calls. */ @@ -66,9 +66,9 @@ interface FallbackSupplier { private ByteBuffer prefixKeyBuf; private long[] prefixValues; - private ByteBuffer dupBuf; - private int dupPos; - private int dupLimit; + private boolean hasCurrentDuplicate = false; + private long currentObj; + private long currentContext; private int lastResult; private boolean closed = false; @@ -76,13 +76,12 @@ interface FallbackSupplier { private final RecordIterator fallback; private final FallbackSupplier fallbackSupplier; - LmdbDupRecordIterator(DupIndex index, long subj, long pred, - boolean explicit, Txn txnRef, FallbackSupplier fallbackSupplier) throws IOException { + boolean explicit, Txn txnRef, FallbackSupplier fallbackSupplier) throws IOException { this.index = index; - this.quad = new long[]{subj, pred, -1, -1}; - this.originalQuad = new long[]{subj, pred, -1, -1}; + this.quad = new long[] { subj, pred, -1, -1 }; + this.originalQuad = new long[] { subj, pred, -1, -1 }; this.matchValues = subj > 0 || pred > 0; this.fallbackSupplier = fallbackSupplier; @@ -108,7 +107,7 @@ interface FallbackSupplier { cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); prefixKeyBuf = pool.getKeyBuffer(); - prefixValues = new long[]{subj, pred}; + prefixValues = new long[] { subj, pred }; prefixKeyBuf.clear(); Varint.writeUnsigned(prefixKeyBuf, subj); Varint.writeUnsigned(prefixKeyBuf, pred); @@ -118,7 +117,7 @@ interface FallbackSupplier { boolean positioned = positionOnPrefix(); if (positioned) { - positioned = primeDuplicateBlock(); + positioned = loadDuplicate(MDB_GET_CURRENT); } if (!positioned) { closeInternal(false); @@ -151,53 +150,25 @@ public long[] next() { if (txnRefVersion != txnRef.version()) { E(mdb_cursor_renew(txn, cursor)); txnRefVersion = txnRef.version(); - if (!positionOnPrefix() || !primeDuplicateBlock()) { + if (!positionOnPrefix() || !loadDuplicate(MDB_GET_CURRENT)) { closeInternal(false); return null; } } while (true) { - if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { - long v3; - long v4; - if (true) { - v3 = readLittleEndianLong(dupBuf, dupPos); - v4 = readLittleEndianLong(dupBuf, dupPos + Long.BYTES); - dupPos += Long.BYTES * 2; - } else { - v3 = dupBuf.getLong(dupPos); - v4 = dupBuf.getLong(dupPos + Long.BYTES); - dupPos += Long.BYTES * 2; - } - fillQuadFromPrefixAndValue(v3, v4); - return quad; + if (!hasCurrentDuplicate) { + closeInternal(false); + return null; } - closeInternal(false); - return null; - -// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); -// if (lastResult == MDB_SUCCESS) { -// resetDuplicateBuffer(valueData.mv_data()); -// continue; -// } -// -// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); -// if (lastResult != MDB_SUCCESS) { -// closeInternal(false); -// return null; -// } -// if (!currentKeyHasPrefix()) { -// if (!adjustCursorToPrefix()) { -// closeInternal(false); -// return null; -// } -// } -// if (!primeDuplicateBlock()) { -// closeInternal(false); -// return null; -// } + long v3 = currentObj; + long v4 = currentContext; + if (!loadDuplicate(MDB_NEXT_DUP)) { + hasCurrentDuplicate = false; + } + fillQuadFromPrefixAndValue(v3, v4); + return quad; } } catch (IOException e) { throw new SailException(e); @@ -258,35 +229,25 @@ private int comparePrefix() { return 0; } - private boolean primeDuplicateBlock() throws IOException { - lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_GET_MULTIPLE); + private boolean loadDuplicate(int op) throws IOException { + lastResult = mdb_cursor_get(cursor, keyData, valueData, op); if (lastResult == MDB_SUCCESS) { - resetDuplicateBuffer(valueData.mv_data()); - return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; + readCurrentDuplicate(valueData.mv_data()); + hasCurrentDuplicate = true; + return true; } else if (lastResult == MDB_NOTFOUND) { + hasCurrentDuplicate = false; return false; } else { - resetDuplicateBuffer(valueData.mv_data()); - return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; + throw new IOException("Failed to load duplicate, lmdb error code: " + lastResult); } } - private void resetDuplicateBuffer(ByteBuffer buffer) { - if (buffer == null) { - dupBuf = null; - dupPos = dupLimit = 0; - } else { - ByteBuffer source = buffer.duplicate(); - source.position(buffer.position()); - source.limit(buffer.limit()); - ByteBuffer copy = ByteBuffer.allocate(source.remaining()); - copy.put(source); - copy.flip(); - copy.order(ByteOrder.nativeOrder()); - dupBuf = copy; - dupPos = dupBuf.position(); - dupLimit = dupBuf.limit(); - } + private void readCurrentDuplicate(ByteBuffer buffer) { + ByteBuffer duplicate = buffer.duplicate(); + int offset = duplicate.position(); + currentObj = readLittleEndianLong(duplicate, offset); + currentContext = readLittleEndianLong(duplicate, offset + Long.BYTES); } private void fillQuadFromPrefixAndValue(long v3, long v4) { 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 d0511b2776..c40a826381 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 @@ -1447,8 +1447,25 @@ public int getDupDB(boolean explicit) { @Override public void toDupKeyPrefix(ByteBuffer bb, long subj, long pred, long obj, long context) { long s = subj, p = pred, o = obj, c = context; - for (int i = 0; i < 2; i++) { - char f = fieldSeq[i]; + { + char f = fieldSeq[0]; + switch (f) { + case 's': + Varint.writeUnsigned(bb, s); + break; + case 'p': + Varint.writeUnsigned(bb, p); + break; + case 'o': + Varint.writeUnsigned(bb, o); + break; + case 'c': + Varint.writeUnsigned(bb, c); + break; + } + } + { + char f = fieldSeq[1]; switch (f) { case 's': Varint.writeUnsigned(bb, s); @@ -1853,9 +1870,14 @@ void close() { } private void writeLongLittleEndian(ByteBuffer buffer, long value) { - for (int i = 0; i < Long.BYTES; i++) { - buffer.put((byte) ((value >> (i * 8)) & 0xFF)); - } + buffer.put((byte) ((value >> (0)) & 0xFF)); + buffer.put((byte) ((value >> (8)) & 0xFF)); + buffer.put((byte) ((value >> (2 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (3 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (4 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (5 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (6 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (7 * 8)) & 0xFF)); } } 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 3e551762d6..a9e9cf2e50 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 @@ -197,7 +197,7 @@ private boolean equalsLmdbIRI(LmdbIRI o) { } @Override - public int hashCode() { + public final int hashCode() { if (this.iriString != null) { return this.iriString.hashCode(); } From 05cff455da0b4aa4cea24c526bea5e7d9f65766f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 17 Oct 2025 21:16:26 +0200 Subject: [PATCH 08/79] wip --- .../java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 6d87d4bc33..421cb98dc5 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 @@ -382,10 +382,12 @@ CloseableIteration createStatementIterator( } } - List contextIDList = new ArrayList<>(contexts.length); + List contextIDList; if (contexts.length == 0) { - contextIDList.add(LmdbValue.UNKNOWN_ID); + RecordIterator records = tripleStore.getTriples(txn, subjID, predID, objID, LmdbValue.UNKNOWN_ID, explicit); + return new LmdbStatementIterator(records, valueStore); } else { + contextIDList = new ArrayList<>(contexts.length); for (Resource context : contexts) { if (context == null) { contextIDList.add(0L); From 02ea306ff3350f56b9d2f321238c71d68c7ff58a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 17 Oct 2025 22:16:03 +0200 Subject: [PATCH 09/79] wip --- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) 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 c40a826381..7a45ac5586 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 @@ -161,6 +161,7 @@ class TripleStore implements Closeable { * The list of triple indexes that are used to store and retrieve triples. */ private final List indexes = new ArrayList<>(); + private volatile TripleIndex[] bestIndexLookup = new TripleIndex[IndexPattern.lookupSize()]; private final SubjectPredicateIndex subjectPredicateIndex; private final ValueStore valueStore; @@ -381,6 +382,8 @@ private void initIndexes(Set indexSpecs, long tripleDbSize) throws IOExc } return null; }); + + rebuildBestIndexLookup(); } private void reindex(Set currentIndexSpecs, Set newIndexSpecs) throws IOException, SailException { @@ -465,6 +468,8 @@ private void reindex(Set currentIndexSpecs, Set newIndexSpecs) t for (String fieldSeq : newIndexSpecs) { indexes.add(currentIndexes.remove(fieldSeq)); } + + rebuildBestIndexLookup(); } @Override @@ -898,6 +903,21 @@ protected double cardinality(long subj, long pred, long obj, long context) throw } protected TripleIndex getBestIndex(long subj, long pred, long obj, long context) { + TripleIndex[] lookup = bestIndexLookup; + int mask = IndexPattern.toMask(subj, pred, obj, context); + TripleIndex bestIndex = lookup[mask]; + if (bestIndex != null) { + return bestIndex; + } + + bestIndex = selectBestIndex(subj, pred, obj, context); + if (bestIndex != null) { + lookup[mask] = bestIndex; + } + return bestIndex; + } + + private TripleIndex selectBestIndex(long subj, long pred, long obj, long context) { int bestScore = -1; TripleIndex bestIndex = null; @@ -912,6 +932,88 @@ protected TripleIndex getBestIndex(long subj, long pred, long obj, long context) return bestIndex; } + private void rebuildBestIndexLookup() { + TripleIndex[] newLookup = new TripleIndex[IndexPattern.lookupSize()]; + if (!indexes.isEmpty()) { + for (IndexPattern pattern : IndexPattern.values()) { + TripleIndex bestIndex = selectBestIndex(pattern.subjValue, pattern.predValue, pattern.objValue, + pattern.contextValue); + newLookup[pattern.mask] = bestIndex; + } + } + bestIndexLookup = newLookup; + } + + private enum IndexPattern { + NONE(-1, -1, -1, -1), + S(0, -1, -1, -1), + P(-1, 0, -1, -1), + SP(0, 0, -1, -1), + O(-1, -1, 0, -1), + SO(0, -1, 0, -1), + PO(-1, 0, 0, -1), + SPO(0, 0, 0, -1), + C(-1, -1, -1, 0), + SC(0, -1, -1, 0), + PC(-1, 0, -1, 0), + SPC(0, 0, -1, 0), + OC(-1, -1, 0, 0), + SOC(0, -1, 0, 0), + POC(-1, 0, 0, 0), + SPOC(0, 0, 0, 0); + + private final long subjValue; + private final long predValue; + private final long objValue; + private final long contextValue; + private final int mask; + + IndexPattern(long subjValue, long predValue, long objValue, long contextValue) { + this.subjValue = subjValue; + this.predValue = predValue; + this.objValue = objValue; + this.contextValue = contextValue; + int mask = 0; + if (subjValue >= 0) { + mask |= 1; + } + if (predValue >= 0) { + mask |= 1 << 1; + } + if (objValue >= 0) { + mask |= 1 << 2; + } + if (contextValue >= 0) { + mask |= 1 << 3; + } + this.mask = mask; + } + + private static final IndexPattern[] LOOKUP = buildLookup(); + + private static IndexPattern[] buildLookup() { + IndexPattern[] lookup = new IndexPattern[1 << 4]; + for (IndexPattern pattern : values()) { + lookup[pattern.mask] = pattern; + } + return lookup; + } + + static int toMask(long s, long p, long o, long c) { + // For a signed long, (x >>> 63) == 0 when x >= 0, == 1 when x < 0. + // XOR with 1 flips that so we get 1 when x >= 0, 0 when x < 0. + long b0 = (s >>> 63) ^ 1L; + long b1 = (p >>> 63) ^ 1L; + long b2 = (o >>> 63) ^ 1L; + long b3 = (c >>> 63) ^ 1L; + return (int) (b0 | (b1 << 1) | (b2 << 2) | (b3 << 3)); + } + + static int lookupSize() { + return LOOKUP.length; + } + } + private boolean requiresResize() { if (autoGrow) { return LmdbUtil.requiresResize(mapSize, pageSize, writeTxn, 0); From a86554c3b158b4cff8bbfdcf3b99332d28fc289e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 17 Oct 2025 22:54:42 +0200 Subject: [PATCH 10/79] wip --- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 492 ++++++++++++++++-- 1 file changed, 453 insertions(+), 39 deletions(-) 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 7a45ac5586..8082788383 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 @@ -1496,6 +1496,11 @@ interface DupIndex { int getPatternScore(long subj, long pred, long obj, long context); } + @FunctionalInterface + private interface PatternScoreFunction { + int score(long subj, long pred, long obj, long context); + } + class TripleIndex implements DupIndex { private final char[] fieldSeq; @@ -1506,12 +1511,14 @@ class TripleIndex implements DupIndex { private final int dbiDupExplicit; private final int dbiDupInferred; private final int[] indexMap; + private final PatternScoreFunction patternScoreFunction; public TripleIndex(String fieldSeq, boolean dupsortEnabled) throws IOException { this.fieldSeq = fieldSeq.toCharArray(); this.keyWriter = IndexKeyWriters.forFieldSeq(fieldSeq); this.matcherFactory = IndexKeyWriters.matcherFactory(fieldSeq); this.indexMap = getIndexes(this.fieldSeq); + this.patternScoreFunction = PatternScoreFunctions.forFieldSeq(fieldSeq); // open database and use native sort order without comparator dbiExplicit = openDatabase(env, fieldSeq, MDB_CREATE, null); dbiInferred = openDatabase(env, fieldSeq + "-inf", MDB_CREATE, null); @@ -1656,45 +1663,7 @@ protected int[] getIndexes(char[] fieldSeq) { * that the index will perform a sequential scan. */ public int getPatternScore(long subj, long pred, long obj, long context) { - int score = 0; - - for (char field : fieldSeq) { - switch (field) { - case 's': - if (subj >= 0) { - score++; - } else { - return score; - } - break; - case 'p': - if (pred >= 0) { - score++; - } else { - return score; - } - break; - case 'o': - if (obj >= 0) { - score++; - } else { - return score; - } - break; - case 'c': - if (context >= 0) { - score++; - } else { - return score; - } - break; - default: - throw new RuntimeException("invalid character '" + field + "' in field sequence: " - + new String(fieldSeq)); - } - } - - return score; + return patternScoreFunction.score(subj, pred, obj, context); } void getMinKey(ByteBuffer bb, long subj, long pred, long obj, long context) { @@ -1862,6 +1831,451 @@ void destroy(long txn) { } } + private static final class PatternScoreFunctions { + + private PatternScoreFunctions() { + } + + private static PatternScoreFunction forFieldSeq(String fieldSeq) { + switch (fieldSeq) { + case "spoc": + return PatternScoreFunctions::score_spoc; + case "spco": + return PatternScoreFunctions::score_spco; + case "sopc": + return PatternScoreFunctions::score_sopc; + case "socp": + return PatternScoreFunctions::score_socp; + case "scpo": + return PatternScoreFunctions::score_scpo; + case "scop": + return PatternScoreFunctions::score_scop; + case "psoc": + return PatternScoreFunctions::score_psoc; + case "psco": + return PatternScoreFunctions::score_psco; + case "posc": + return PatternScoreFunctions::score_posc; + case "pocs": + return PatternScoreFunctions::score_pocs; + case "pcso": + return PatternScoreFunctions::score_pcso; + case "pcos": + return PatternScoreFunctions::score_pcos; + case "ospc": + return PatternScoreFunctions::score_ospc; + case "oscp": + return PatternScoreFunctions::score_oscp; + case "opsc": + return PatternScoreFunctions::score_opsc; + case "opcs": + return PatternScoreFunctions::score_opcs; + case "ocsp": + return PatternScoreFunctions::score_ocsp; + case "ocps": + return PatternScoreFunctions::score_ocps; + case "cspo": + return PatternScoreFunctions::score_cspo; + case "csop": + return PatternScoreFunctions::score_csop; + case "cpso": + return PatternScoreFunctions::score_cpso; + case "cpos": + return PatternScoreFunctions::score_cpos; + case "cosp": + return PatternScoreFunctions::score_cosp; + case "cops": + return PatternScoreFunctions::score_cops; + default: + throw new IllegalArgumentException("Unsupported field sequence: " + fieldSeq); + } + } + + private static int score_spoc(long subj, long pred, long obj, long context) { + if (subj < 0) { + return 0; + } + if (pred < 0) { + return 1; + } + if (obj < 0) { + return 2; + } + if (context < 0) { + return 3; + } + return 4; + } + + private static int score_spco(long subj, long pred, long obj, long context) { + if (subj < 0) { + return 0; + } + if (pred < 0) { + return 1; + } + if (context < 0) { + return 2; + } + if (obj < 0) { + return 3; + } + return 4; + } + + private static int score_sopc(long subj, long pred, long obj, long context) { + if (subj < 0) { + return 0; + } + if (obj < 0) { + return 1; + } + if (pred < 0) { + return 2; + } + if (context < 0) { + return 3; + } + return 4; + } + + private static int score_socp(long subj, long pred, long obj, long context) { + if (subj < 0) { + return 0; + } + if (obj < 0) { + return 1; + } + if (context < 0) { + return 2; + } + if (pred < 0) { + return 3; + } + return 4; + } + + private static int score_scpo(long subj, long pred, long obj, long context) { + if (subj < 0) { + return 0; + } + if (context < 0) { + return 1; + } + if (pred < 0) { + return 2; + } + if (obj < 0) { + return 3; + } + return 4; + } + + private static int score_scop(long subj, long pred, long obj, long context) { + if (subj < 0) { + return 0; + } + if (context < 0) { + return 1; + } + if (obj < 0) { + return 2; + } + if (pred < 0) { + return 3; + } + return 4; + } + + private static int score_psoc(long subj, long pred, long obj, long context) { + if (pred < 0) { + return 0; + } + if (subj < 0) { + return 1; + } + if (obj < 0) { + return 2; + } + if (context < 0) { + return 3; + } + return 4; + } + + private static int score_psco(long subj, long pred, long obj, long context) { + if (pred < 0) { + return 0; + } + if (subj < 0) { + return 1; + } + if (context < 0) { + return 2; + } + if (obj < 0) { + return 3; + } + return 4; + } + + private static int score_posc(long subj, long pred, long obj, long context) { + if (pred < 0) { + return 0; + } + if (obj < 0) { + return 1; + } + if (subj < 0) { + return 2; + } + if (context < 0) { + return 3; + } + return 4; + } + + private static int score_pocs(long subj, long pred, long obj, long context) { + if (pred < 0) { + return 0; + } + if (obj < 0) { + return 1; + } + if (context < 0) { + return 2; + } + if (subj < 0) { + return 3; + } + return 4; + } + + private static int score_pcso(long subj, long pred, long obj, long context) { + if (pred < 0) { + return 0; + } + if (context < 0) { + return 1; + } + if (subj < 0) { + return 2; + } + if (obj < 0) { + return 3; + } + return 4; + } + + private static int score_pcos(long subj, long pred, long obj, long context) { + if (pred < 0) { + return 0; + } + if (context < 0) { + return 1; + } + if (obj < 0) { + return 2; + } + if (subj < 0) { + return 3; + } + return 4; + } + + private static int score_ospc(long subj, long pred, long obj, long context) { + if (obj < 0) { + return 0; + } + if (subj < 0) { + return 1; + } + if (pred < 0) { + return 2; + } + if (context < 0) { + return 3; + } + return 4; + } + + private static int score_oscp(long subj, long pred, long obj, long context) { + if (obj < 0) { + return 0; + } + if (subj < 0) { + return 1; + } + if (context < 0) { + return 2; + } + if (pred < 0) { + return 3; + } + return 4; + } + + private static int score_opsc(long subj, long pred, long obj, long context) { + if (obj < 0) { + return 0; + } + if (pred < 0) { + return 1; + } + if (subj < 0) { + return 2; + } + if (context < 0) { + return 3; + } + return 4; + } + + private static int score_opcs(long subj, long pred, long obj, long context) { + if (obj < 0) { + return 0; + } + if (pred < 0) { + return 1; + } + if (context < 0) { + return 2; + } + if (subj < 0) { + return 3; + } + return 4; + } + + private static int score_ocsp(long subj, long pred, long obj, long context) { + if (obj < 0) { + return 0; + } + if (context < 0) { + return 1; + } + if (subj < 0) { + return 2; + } + if (pred < 0) { + return 3; + } + return 4; + } + + private static int score_ocps(long subj, long pred, long obj, long context) { + if (obj < 0) { + return 0; + } + if (context < 0) { + return 1; + } + if (pred < 0) { + return 2; + } + if (subj < 0) { + return 3; + } + return 4; + } + + private static int score_cspo(long subj, long pred, long obj, long context) { + if (context < 0) { + return 0; + } + if (subj < 0) { + return 1; + } + if (pred < 0) { + return 2; + } + if (obj < 0) { + return 3; + } + return 4; + } + + private static int score_csop(long subj, long pred, long obj, long context) { + if (context < 0) { + return 0; + } + if (subj < 0) { + return 1; + } + if (obj < 0) { + return 2; + } + if (pred < 0) { + return 3; + } + return 4; + } + + private static int score_cpso(long subj, long pred, long obj, long context) { + if (context < 0) { + return 0; + } + if (pred < 0) { + return 1; + } + if (subj < 0) { + return 2; + } + if (obj < 0) { + return 3; + } + return 4; + } + + private static int score_cpos(long subj, long pred, long obj, long context) { + if (context < 0) { + return 0; + } + if (pred < 0) { + return 1; + } + if (obj < 0) { + return 2; + } + if (subj < 0) { + return 3; + } + return 4; + } + + private static int score_cosp(long subj, long pred, long obj, long context) { + if (context < 0) { + return 0; + } + if (obj < 0) { + return 1; + } + if (subj < 0) { + return 2; + } + if (pred < 0) { + return 3; + } + return 4; + } + + private static int score_cops(long subj, long pred, long obj, long context) { + if (context < 0) { + return 0; + } + if (obj < 0) { + return 1; + } + if (pred < 0) { + return 2; + } + if (subj < 0) { + return 3; + } + return 4; + } + } + class SubjectPredicateIndex implements DupIndex { private final char[] fieldSeq = new char[] { 's', 'p', 'o', 'c' }; From 9651025e293959a6f2f7635657cee916289fb477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 17 Oct 2025 23:31:44 +0200 Subject: [PATCH 11/79] wip --- .../sail/lmdb/LmdbStatementIterator.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java index eecc692f70..d9df2f8dbb 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java @@ -13,7 +13,7 @@ import java.io.IOException; import java.util.NoSuchElementException; -import org.eclipse.rdf4j.common.iteration.AbstractCloseableIteration; +import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; @@ -24,7 +24,7 @@ * A statement iterator that wraps a RecordIterator containing statement records and translates these records to * {@link Statement} objects. */ -class LmdbStatementIterator extends AbstractCloseableIteration { +class LmdbStatementIterator implements CloseableIteration { /*-----------* * Variables * @@ -34,6 +34,10 @@ class LmdbStatementIterator extends AbstractCloseableIteration { private final ValueStore valueStore; private Statement nextElement; + /** + * Flag indicating whether this iteration has been closed. + */ + private boolean closed = false; /*--------------* * Constructors * @@ -85,7 +89,6 @@ public Statement getNextElement() throws SailException { } } - @Override protected void handleClose() throws SailException { recordIt.close(); } @@ -141,4 +144,24 @@ private Statement lookAhead() { 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; + } + + /** + * Calls {@link #handleClose()} upon first call and makes sure the resource closures are only executed once. + */ + @Override + public final void close() { + if (!closed) { + closed = true; + handleClose(); + } + } } From a04b1f2b2c8902e387f16e645846a85bc5bc2f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 05:28:10 +0200 Subject: [PATCH 12/79] wip --- .../java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index a7f697dd49..ce44243757 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -36,6 +36,8 @@ /** * A dupsort/dupfixed-optimized record iterator using MDB_GET_MULTIPLE/NEXT_MULTIPLE to reduce JNI calls. + * + * This iterator is hard coded to only work with the SP index and assumes that the object and context keys are free (-1), */ class LmdbDupRecordIterator implements RecordIterator { From fc9a68e3a017db22a94939682004c3a85bcbeb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 05:32:04 +0200 Subject: [PATCH 13/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 3 +- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 4 +- .../lmdb/MultipleSubselectRegressionTest.java | 106 ++++++++++++++++++ 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index ce44243757..f4ea5da13c 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -37,7 +37,8 @@ /** * A dupsort/dupfixed-optimized record iterator using MDB_GET_MULTIPLE/NEXT_MULTIPLE to reduce JNI calls. * - * This iterator is hard coded to only work with the SP index and assumes that the object and context keys are free (-1), + * This iterator is hard coded to only work with the SP index and assumes that the object and context keys are free + * (-1), */ class LmdbDupRecordIterator implements RecordIterator { 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 8082788383..34a4e52134 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 @@ -61,7 +61,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -541,7 +540,8 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c boolean doRangeSearch = index.getPatternScore(subj, pred, obj, context) > 0; LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = () -> new LmdbRecordIterator(index, doRangeSearch, subj, pred, obj, context, explicit, txn); - if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj < 0 && context < 0) { + if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj == -1 && context == -1) { + assert context == -1 && obj == -1 : "subject-predicate index can only be used for (s,p,?,?) patterns"; return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, fallbackSupplier); } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java new file mode 100644 index 0000000000..9954cfb1fc --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class MultipleSubselectRegressionTest { + + private static final String DATASET_RESOURCE = "benchmarkFiles/datagovbe-valid.ttl"; + private static final String DATASET_IRI = "http://data.gov.be/dataset/brussels/3fded591-0cda-485f-97e7-3b982c7ff34b"; + private static final String MULTIPLE_SUB_SELECT_QUERY; + + static { + try (InputStream query = getResource(MultipleSubselectRegressionTest.class, + "benchmarkFiles/multiple-sub-select.qr")) { + MULTIPLE_SUB_SELECT_QUERY = IOUtils.toString(query, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Test + void lmdbMatchesMemoryForMultipleSubSelect(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + assertEquals(evaluateMultipleSubselectCount(memoryConn), evaluateMultipleSubselectCount(lmdbConn)); + assertEquals(evaluateDatasetLanguages(memoryConn), evaluateDatasetLanguages(lmdbConn)); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + private static void loadDataset(SailRepositoryConnection connection) throws IOException { + connection.begin(IsolationLevels.NONE); + connection.add(getResource(MultipleSubselectRegressionTest.class, DATASET_RESOURCE), "", RDFFormat.TURTLE); + connection.commit(); + } + + private static long evaluateMultipleSubselectCount(SailRepositoryConnection connection) { + return connection + .prepareTupleQuery(MULTIPLE_SUB_SELECT_QUERY) + .evaluate() + .stream() + .count(); + } + + private static List evaluateDatasetLanguages(SailRepositoryConnection connection) { + String query = String.join("\n", + "PREFIX dct: ", + "SELECT ?lang WHERE {", + " <" + DATASET_IRI + "> dct:language ?lang .", + "}"); + + try (var result = connection.prepareTupleQuery(query).evaluate()) { + return result + .stream() + .map(bs -> bs.getValue("lang").stringValue()) + .sorted() + .collect(Collectors.toList()); + } + } + + private static InputStream getResource(Class anchor, String resourceName) { + InputStream stream = anchor.getClassLoader().getResourceAsStream(resourceName); + if (stream == null) { + throw new IllegalStateException("Missing resource: " + resourceName); + } + return stream; + } +} From db32dd3da8749c8af5738f631a82b184aa684a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 07:44:10 +0200 Subject: [PATCH 14/79] wip --- .../rdf4j/common/iteration/Iterations.java | 11 + core/sail/lmdb/pom.xml | 6 + ...dicateIndexDistributionRegressionTest.java | 367 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java diff --git a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/Iterations.java b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/Iterations.java index 85184886f6..4d06940de8 100644 --- a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/Iterations.java +++ b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/Iterations.java @@ -43,6 +43,17 @@ public static List asList(CloseableIteration iteration) { } } + public static long count(CloseableIteration iteration) { + try (iteration) { + long count = 0; + while (iteration.hasNext()) { + iteration.next(); + count++; + } + return count; + } + } + /** * Get a Set containing all elements obtained from the specified iteration. * diff --git a/core/sail/lmdb/pom.xml b/core/sail/lmdb/pom.xml index d818020171..e73e88fdc4 100644 --- a/core/sail/lmdb/pom.xml +++ b/core/sail/lmdb/pom.xml @@ -175,6 +175,12 @@ ${project.version} test + + ${project.groupId} + rdf4j-sail-memory + ${project.version} + test + ${project.groupId} rdf4j-rio-nquads diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java new file mode 100644 index 0000000000..5c6a3b5772 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java @@ -0,0 +1,367 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.DCAT; +import org.eclipse.rdf4j.model.vocabulary.DCTERMS; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryResults; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class SubjectPredicateIndexDistributionRegressionTest { + + private static final String DATASET_RESOURCE = "benchmarkFiles/datagovbe-valid.ttl"; + private static final IRI DATASET_IRI = SimpleValueFactory.getInstance() + .createIRI("http://data.gov.be/dataset/brussels/3fded591-0cda-485f-97e7-3b982c7ff34b"); + private static final String FILTERED_MULTIPLE_SUBSELECT_QUERY = String.join("\n", + "PREFIX ex: ", + "PREFIX owl: ", + "PREFIX rdf: ", + "PREFIX rdfs: ", + "PREFIX sh: ", + "PREFIX xsd: ", + "PREFIX dcat: ", + "PREFIX dc: ", + "PREFIX skos: ", + "PREFIX foaf: ", + "PREFIX dct: ", + "", + "SELECT ?type1 ?type2 ?language2 ?mbox ?count ?identifier2 WHERE {", + " VALUES ?a { <" + DATASET_IRI + "> }", + " {", + " SELECT * WHERE {", + " ?a a ?type2.", + " ?b a ?type1.", + "", + " ?b dcat:dataset ?a.", + "", + " ?a dcat:distribution ?mbox.", + " ?a dct:language ?language.", + " FILTER (?type1 != ?type2)", + " ?a dct:identifier ?identifier.", + " }", + " }", + "", + " {", + " SELECT DISTINCT ?a (COUNT(?dist) AS ?count) ?language2 WHERE {", + " ?a a ?type2.", + " ?a dcat:distribution ?dist.", + " ?a dct:language ?language2.", + " } GROUP BY ?a ?language2 HAVING (?count > 2)", + " }", + "}"); + + @Test + void countLanguage(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + + assertEquals(countl, countm); + + var eng = SimpleValueFactory.getInstance() + .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); + assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "LMDB store should contain the ENG language statement"); + assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "Memory store should contain the ENG language statement"); + + List memoryRows = evaluateMultipleSubselect(memoryConn); + List lmdbRows = evaluateMultipleSubselect(lmdbConn); + + Collections.sort(memoryRows); + Collections.sort(lmdbRows); + + assertEquals(memoryRows, lmdbRows); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void countLanguageDifferentIndexes1(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + + assertEquals(countl, countm); + + var eng = SimpleValueFactory.getInstance() + .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); + assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "LMDB store should contain the ENG language statement"); + assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "Memory store should contain the ENG language statement"); + + List memoryRows = evaluateMultipleSubselect(memoryConn); + List lmdbRows = evaluateMultipleSubselect(lmdbConn); + + Collections.sort(memoryRows); + Collections.sort(lmdbRows); + + assertEquals(memoryRows, lmdbRows); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void countLanguageDifferentIndexes2(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + + assertEquals(countl, countm); + + var eng = SimpleValueFactory.getInstance() + .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); + assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "LMDB store should contain the ENG language statement"); + assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "Memory store should contain the ENG language statement"); + + List memoryRows = evaluateMultipleSubselect(memoryConn); + List lmdbRows = evaluateMultipleSubselect(lmdbConn); + + Collections.sort(memoryRows); + Collections.sort(lmdbRows); + + assertEquals(memoryRows, lmdbRows); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void countLanguageDifferentIndexes3(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + + assertEquals(countl, countm); + + var eng = SimpleValueFactory.getInstance() + .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); + assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "LMDB store should contain the ENG language statement"); + assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "Memory store should contain the ENG language statement"); + + List memoryRows = evaluateMultipleSubselect(memoryConn); + List lmdbRows = evaluateMultipleSubselect(lmdbConn); + + Collections.sort(memoryRows); + Collections.sort(lmdbRows); + + assertEquals(memoryRows, lmdbRows); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void countLanguageDifferentIndexes4(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("ospc,psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); + + assertEquals(countl, countm); + + var eng = SimpleValueFactory.getInstance() + .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); + assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "LMDB store should contain the ENG language statement"); + assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), + "Memory store should contain the ENG language statement"); + + List memoryRows = evaluateMultipleSubselect(memoryConn); + List lmdbRows = evaluateMultipleSubselect(lmdbConn); + + Collections.sort(memoryRows); + Collections.sort(lmdbRows); + + assertEquals(memoryRows, lmdbRows); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void lmdbExposesAllLanguagesForSimpleDataset(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + SailRepository lmdbRepository = new SailRepository( + new LmdbStore(tempDir.resolve("lmdb-simple").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadSimpleDataset(lmdbConn); + loadSimpleDataset(memoryConn); + + List lmdbLanguages = evaluateLanguages(lmdbConn); + List memoryLanguages = evaluateLanguages(memoryConn); + + Collections.sort(lmdbLanguages); + Collections.sort(memoryLanguages); + + assertEquals(memoryLanguages, lmdbLanguages); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + private static void loadDataset(SailRepositoryConnection connection) throws IOException { + connection.begin(IsolationLevels.NONE); + try (InputStream data = getResource(DATASET_RESOURCE)) { + connection.add(data, "", RDFFormat.TURTLE); + } + connection.commit(); + } + + private static InputStream getResource(String name) { + InputStream stream = SubjectPredicateIndexDistributionRegressionTest.class.getClassLoader() + .getResourceAsStream(name); + if (stream == null) { + throw new IllegalStateException("Missing resource: " + name); + } + return stream; + } + + private static List evaluateMultipleSubselect(SailRepositoryConnection connection) { + try (var result = connection.prepareTupleQuery(FILTERED_MULTIPLE_SUBSELECT_QUERY).evaluate()) { + return QueryResults.asList(result) + .stream() + .map(SubjectPredicateIndexDistributionRegressionTest::formatBindingSet) + .collect(Collectors.toList()); + } + } + + private static String formatBindingSet(BindingSet bindingSet) { + return bindingSet.getBindingNames() + .stream() + .sorted() + .map(name -> name + "=" + bindingSet.getValue(name)) + .collect(Collectors.joining(", ")); + } + + private static void loadSimpleDataset(SailRepositoryConnection connection) throws IOException { + var vf = SimpleValueFactory.getInstance(); + var dataset = vf.createIRI("urn:dataset:simple"); + var catalog = vf.createIRI("urn:catalog:simple"); + connection.begin(IsolationLevels.NONE); + connection.add(dataset, org.eclipse.rdf4j.model.vocabulary.RDF.TYPE, DCAT.DATASET); + connection.add(catalog, org.eclipse.rdf4j.model.vocabulary.RDF.TYPE, DCAT.CATALOG); + connection.add(catalog, DCAT.DATASET, dataset); + connection.add(dataset, DCTERMS.LANGUAGE, vf.createIRI("urn:lang:ENG")); + connection.add(dataset, DCTERMS.LANGUAGE, vf.createIRI("urn:lang:FRA")); + connection.add(dataset, DCTERMS.LANGUAGE, vf.createIRI("urn:lang:NLD")); + connection.add(dataset, DCAT.DISTRIBUTION, vf.createIRI("urn:dist:one")); + connection.add(dataset, DCAT.DISTRIBUTION, vf.createIRI("urn:dist:two")); + connection.add(dataset, DCAT.DISTRIBUTION, vf.createIRI("urn:dist:three")); + connection.commit(); + } + + private static List evaluateLanguages(SailRepositoryConnection connection) { + try (var iter = connection.getStatements(SimpleValueFactory.getInstance().createIRI("urn:dataset:simple"), + DCTERMS.LANGUAGE, null, true)) { + return Iterations.stream(iter) + .map(Statement::getObject) + .map(Value::stringValue) + .collect(Collectors.toList()); + } + } +} From c4da941d36dbc5aad48383200e2c623b3b788d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 08:33:18 +0200 Subject: [PATCH 15/79] wip --- ...dicateIndexDistributionRegressionTest.java | 299 +- core/sail/lmdb/src/test/resources/temp.nquad | 28636 ++++++++++++++++ 2 files changed, 28848 insertions(+), 87 deletions(-) create mode 100644 core/sail/lmdb/src/test/resources/temp.nquad diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java index 5c6a3b5772..8e92b0caf2 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java @@ -10,16 +10,6 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - import org.eclipse.rdf4j.common.iteration.Iterations; import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; @@ -30,17 +20,31 @@ import org.eclipse.rdf4j.model.vocabulary.DCTERMS; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryResults; +import org.eclipse.rdf4j.repository.RepositoryResult; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.rio.RDFWriter; +import org.eclipse.rdf4j.rio.Rio; import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; import org.eclipse.rdf4j.sail.memory.MemoryStore; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + class SubjectPredicateIndexDistributionRegressionTest { - private static final String DATASET_RESOURCE = "benchmarkFiles/datagovbe-valid.ttl"; + private static final String DATASET_RESOURCE = "temp.nquad"; private static final IRI DATASET_IRI = SimpleValueFactory.getInstance() .createIRI("http://data.gov.be/dataset/brussels/3fded591-0cda-485f-97e7-3b982c7ff34b"); private static final String FILTERED_MULTIPLE_SUBSELECT_QUERY = String.join("\n", @@ -91,7 +95,7 @@ void countLanguage(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -100,26 +104,203 @@ void countLanguage(@TempDir Path tempDir) throws Exception { assertEquals(countl, countm); - var eng = SimpleValueFactory.getInstance() - .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); - assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "LMDB store should contain the ENG language statement"); - assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "Memory store should contain the ENG language statement"); - - List memoryRows = evaluateMultipleSubselect(memoryConn); - List lmdbRows = evaluateMultipleSubselect(lmdbConn); - - Collections.sort(memoryRows); - Collections.sort(lmdbRows); - - assertEquals(memoryRows, lmdbRows); } finally { lmdbRepository.shutDown(); memoryRepository.shutDown(); } } +// @Test +// void countLanguage2(@TempDir Path tempDir) throws Exception { +// LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); +// SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); +// SailRepository referenceRepository = new SailRepository(new MemoryStore()); +// SailRepository memoryRepository = new SailRepository(new MemoryStore()); +// +// lmdbRepository.init(); +// referenceRepository.init(); +// memoryRepository.init(); +// +// try (SailRepositoryConnection memoryConn = referenceRepository.getConnection()) { +// loadDataset(memoryConn); +// } +// +// try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); +// SailRepositoryConnection memoryConn = memoryRepository.getConnection(); +// SailRepositoryConnection referenceConn = referenceRepository.getConnection()) { +// +// try (RepositoryResult statements = referenceConn.getStatements(null, null, null, true)) { +// int count = 0; +// for (Statement stmt : statements) { +// lmdbConn.begin(IsolationLevels.NONE); +// memoryConn.begin(IsolationLevels.NONE); +// try { +// lmdbConn.add(stmt); +// memoryConn.add(stmt); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// +// lmdbConn.commit(); +// memoryConn.commit(); +// +// count++; +// +// long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// if (countm != countl) { +// System.out.println("Discrepancy at count " + count); +// assertEquals(countl, countm); +// } +// +// } +// } +// +// +// } finally { +// lmdbRepository.shutDown(); +// memoryRepository.shutDown(); +// } +// } +// +// @Test +// void countLanguage3(@TempDir Path tempDir) throws Exception { +// LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); +// SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); +// SailRepository referenceRepository = new SailRepository(new MemoryStore()); +// SailRepository memoryRepository = new SailRepository(new MemoryStore()); +// +// lmdbRepository.init(); +// referenceRepository.init(); +// memoryRepository.init(); +// +// try (SailRepositoryConnection memoryConn = referenceRepository.getConnection()) { +// loadDataset(memoryConn); +// } +// +// try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); +// SailRepositoryConnection memoryConn = memoryRepository.getConnection(); +// SailRepositoryConnection referenceConn = referenceRepository.getConnection()) { +// +// try (RepositoryResult statements = referenceConn.getStatements(null, null, null, true)) { +// lmdbConn.begin(IsolationLevels.NONE); +// memoryConn.begin(IsolationLevels.NONE); +// +// int count = 0; +// +// for (Statement stmt : statements) { +// +// count++; +// if (count < 18000) { +// continue; +// } +// if (count >= 40000) { +// break; +// } +// +// try { +// if (count % 10000 == 0) { +// lmdbConn.commit(); +// memoryConn.commit(); +// long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// assertEquals(countl, countm, "Discrepancy at count " + count); +// lmdbConn.begin(IsolationLevels.NONE); +// memoryConn.begin(IsolationLevels.NONE); +// } +// lmdbConn.add(stmt); +// memoryConn.add(stmt); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } +// lmdbConn.commit(); +// memoryConn.commit(); +// +// +// long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// assertEquals(countl, countm); +// +// +// } +// +// +// } finally { +// lmdbRepository.shutDown(); +// memoryRepository.shutDown(); +// } +// } +// +// @Test +// void countLanguage4(@TempDir Path tempDir) throws Exception { +// +// +// LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); +// SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb" + UUID.randomUUID()).toFile(), config)); +// SailRepository referenceRepository = new SailRepository(new MemoryStore()); +// SailRepository memoryRepository = new SailRepository(new MemoryStore()); +// +// lmdbRepository.init(); +// referenceRepository.init(); +// memoryRepository.init(); +// +// try (SailRepositoryConnection memoryConn = referenceRepository.getConnection()) { +// loadDataset(memoryConn); +// } +// +// try (FileOutputStream fileOutputStream = new FileOutputStream("/Users/havardottestad/Documents/Programming/rdf4j/temp.nquad")) { +// +// RDFWriter writer = Rio.createWriter(RDFFormat.NQUADS, fileOutputStream); +// writer.startRDF(); +// +// try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); +// SailRepositoryConnection memoryConn = memoryRepository.getConnection(); +// SailRepositoryConnection referenceConn = referenceRepository.getConnection()) { +// +// try (RepositoryResult statements = referenceConn.getStatements(null, null, null, true)) { +// lmdbConn.begin(IsolationLevels.NONE); +// memoryConn.begin(IsolationLevels.NONE); +// +// int count = 0; +// +// for (Statement stmt : statements) { +// count++; +// if (count < 9349) { +// continue; +// } +// +// +// try { +// writer.handleStatement(stmt); +// lmdbConn.add(stmt); +// memoryConn.add(stmt); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// +// if (count > 37983) { +// break; +// } +// } +// +// writer.endRDF(); +// lmdbConn.commit(); +// memoryConn.commit(); +// +// +// long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); +// assertEquals(countl, countm, "Discrepancy at count " + count); +// +// } finally { +// lmdbRepository.shutDown(); +// memoryRepository.shutDown(); +// } +// } +// } +// } + @Test void countLanguageDifferentIndexes1(@TempDir Path tempDir) throws Exception { LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc"); @@ -130,7 +311,7 @@ void countLanguageDifferentIndexes1(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -139,20 +320,6 @@ void countLanguageDifferentIndexes1(@TempDir Path tempDir) throws Exception { assertEquals(countl, countm); - var eng = SimpleValueFactory.getInstance() - .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); - assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "LMDB store should contain the ENG language statement"); - assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "Memory store should contain the ENG language statement"); - - List memoryRows = evaluateMultipleSubselect(memoryConn); - List lmdbRows = evaluateMultipleSubselect(lmdbConn); - - Collections.sort(memoryRows); - Collections.sort(lmdbRows); - - assertEquals(memoryRows, lmdbRows); } finally { lmdbRepository.shutDown(); memoryRepository.shutDown(); @@ -169,7 +336,7 @@ void countLanguageDifferentIndexes2(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -178,20 +345,6 @@ void countLanguageDifferentIndexes2(@TempDir Path tempDir) throws Exception { assertEquals(countl, countm); - var eng = SimpleValueFactory.getInstance() - .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); - assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "LMDB store should contain the ENG language statement"); - assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "Memory store should contain the ENG language statement"); - - List memoryRows = evaluateMultipleSubselect(memoryConn); - List lmdbRows = evaluateMultipleSubselect(lmdbConn); - - Collections.sort(memoryRows); - Collections.sort(lmdbRows); - - assertEquals(memoryRows, lmdbRows); } finally { lmdbRepository.shutDown(); memoryRepository.shutDown(); @@ -208,7 +361,7 @@ void countLanguageDifferentIndexes3(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -217,20 +370,6 @@ void countLanguageDifferentIndexes3(@TempDir Path tempDir) throws Exception { assertEquals(countl, countm); - var eng = SimpleValueFactory.getInstance() - .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); - assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "LMDB store should contain the ENG language statement"); - assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "Memory store should contain the ENG language statement"); - - List memoryRows = evaluateMultipleSubselect(memoryConn); - List lmdbRows = evaluateMultipleSubselect(lmdbConn); - - Collections.sort(memoryRows); - Collections.sort(lmdbRows); - - assertEquals(memoryRows, lmdbRows); } finally { lmdbRepository.shutDown(); memoryRepository.shutDown(); @@ -247,7 +386,7 @@ void countLanguageDifferentIndexes4(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -256,20 +395,6 @@ void countLanguageDifferentIndexes4(@TempDir Path tempDir) throws Exception { assertEquals(countl, countm); - var eng = SimpleValueFactory.getInstance() - .createIRI("http://publications.europa.eu/resource/authority/language/ENG"); - assertTrue(lmdbConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "LMDB store should contain the ENG language statement"); - assertTrue(memoryConn.hasStatement(DATASET_IRI, DCTERMS.LANGUAGE, eng, true), - "Memory store should contain the ENG language statement"); - - List memoryRows = evaluateMultipleSubselect(memoryConn); - List lmdbRows = evaluateMultipleSubselect(lmdbConn); - - Collections.sort(memoryRows); - Collections.sort(lmdbRows); - - assertEquals(memoryRows, lmdbRows); } finally { lmdbRepository.shutDown(); memoryRepository.shutDown(); @@ -287,7 +412,7 @@ void lmdbExposesAllLanguagesForSimpleDataset(@TempDir Path tempDir) throws Excep memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadSimpleDataset(lmdbConn); loadSimpleDataset(memoryConn); diff --git a/core/sail/lmdb/src/test/resources/temp.nquad b/core/sail/lmdb/src/test/resources/temp.nquad new file mode 100644 index 0000000000..8bf1c2505f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/temp.nquad @@ -0,0 +1,28636 @@ + . + "application/zip" . + . + "Développement Durable" . + . + "Development durable" . + . + "Duurzame ontkwikkeling" . + . + "Economie, Business, SME, Economische groei, tewerkstelling" . + . + "Economy, Business, SME, Economic development, Employment " . + . + "Education, Training, Research" . + . + "Hébergement, Restauration" . + . + "Justice, Sécurité, Police, Crime" . + . + "Justice, Safety, Police, Crime" . + . + "Justitie, Veiligheid, Politie, Criminaliteit" . + . + "Mobilité, Transport" . + . + "Sport, Leisure" . + . + "Toerism" . + . + "Toerisme" . + . + "Tourisme" . + . + "Environment, Cleanliness " . + . + "Milieu, Netheid " . + . + "Tourism" . + . + "Vervoer, Verplaatsingen" . + . + "Développement durable" . + . + "Economie, Business, KMO, Economische ontwikkeling, Tewerkstelling" . + . + "Sports, Leisure" . + . + "Duurzame ontwikkeling" . + . + "Economy, Business, SME, Economic development, Employment" . + . + "Sport, Loisirs" . + . + "Diensten, Sociaal" . + . + "Economie, Business, PME, Développement économique, Emploi" . + . + "Gezondheid" . + . + "Health" . + . + "Santé" . + . + "Spatial planning, Town planning, Buildings, Equipment, Housing" . + . + "Sport, Vrije tijd" . + . + "Onderwijs, Opleiding, Onderzoek" . + . + "Education, Formation, Recherche, Enseignement" . + . + "Milieu, Netheid" . + . + "Environment, Cleanliness" . + . + "Services, Social" . + . + "Mobiliteit, Vervoer" . + . + "Culture, Heritage" . + . + "Ruimtelijke ordening, Stedenbouw, Gebouwen, Uitrusting, Huisvesting" . + . + "Cultuur, Erfgoed" . + . + "Environnement, Propreté" . + . + "Mobility, Transport" . + . + "Aménagement du territoire, Urbanisme, Bâtiments, Equipements, Logement" . + . + "Mobilité, Transports" . + . + "Culture, Patrimoine, Folklore" . + . + "Internet, TIC" . + . + "Themes" . + . + "Internet, ICT" . + . + "Administration, Gouvernement, Finances publiques, Citoyenneté" . + . + "Administratie, Overheid, Openbare financiën, Burgerschap" . + . + "Administration, Government, Public finances, Citizenship" . + . + "Stad Brussel"@nl . + . + "SHP" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "5107cc21da806deb87e7a1634beeef762a1e26e0" . + . + "0fcad777d5be99046fbac97c7bc4fdc3db750fcc" . + . + "fcc241e9ed9828d7413d54db2a7f1225a56e8781" . + . + "11f1c197033749b3a2340eae2eb914c85b121592" . + . + "4b8538199e8dd383005571c00828d251712766c6" . + . + . + "47dbea015352b5e176b9b60e47e02d29461cdd0b" . + . + "c41892aa79702b840540edfaa0eec3088c6343b8" . + . + "a3359296c5404e20bcef17a7759e78f1102b9854" . + . + "5a3a5c99de0505bc20d288724c5ce2065b5f2168" . + . + "b3d102531bb2721ed66895df6d1731bd4b48211e" . + . + "8e3f529944b25923e0c180a8afcb5f0a439755d6" . + . + "f14215623595ad5bd62961bdd6530ecc835c0d1d" . + . + "2757b731d79af1071d7cac6f1a5016ceadbdbd0e" . + . + "ad0d4a982e14731478d88bc1ae77ed7ef98cf182" . + . + . + "562678540037fc0f258afdc0bff365526c2e3706" . + . + "fa92c6bcf5cd3e1551f9d42662b0e075bf10a44f" . + . + "47dbab99715f3b0069359dc77e12ab5eb851612f" . + . + "3469063eca7d8a521a8de5361eccd8483efd046a" . + . + "6bc1579e81e0aa614644b082f8fdd43d3a2a5424" . + . + "c9878d1c45c1035425175305a1686dd788c78c23" . + . + "84add22436b2016752c85cd17a4669a432af3a72" . + . + "33e833c655f7c0326340a5b501e2f9b422573505" . + . + "db126c4b126e067a6721e5afd37a17694a3edc0d" . + . + . + "590203faaef7788a0644ec137980e60de38ca96b" . + . + "8b7592dac816195979cd45d9769278a5a957069a" . + . + "b425a91983145c8142db88bae4f1c1e2d748601c" . + . + "4c240b2723a55b98fb119ae3df4db42d6da7a5b6" . + . + "246a864a1531247c45424f994b5982b2424c62fe" . + . + "07c2aa7ec52df6cb15d995f93e1a2f7080152b3b" . + . + "68a3442d0225f45fcbce82e9eac3ef829f1acb49" . + . + . + "ec5d272181dc23c93648fc1a6ba672ffc4dd37c7" . + . + "62295d03c0f6e44e103fd0d6b6fe9a73bdd7b82c" . + . + "60f4046f87286fd89a860df9e0f4d04cc67bd8a6" . + . + "57073149c983f7c7bccfed6e030541566bf4ca83" . + . + "105a8da5c7d5db560abb2d58c734f4796c661a6f" . + . + "e08eadc380b77b01c3c120325a4ab6926dcf3228" . + . + "1dd47506cc0e8eff267007422022ae660ca16a7b" . + . + "c1f74fb867eeb9e1c199f3952e5a15953f0a1127" . + . + . + "b467975db52df8f29e8756845260eb0a53cb50a2" . + . + "7ecb5a5637747cf274ac4250341e2c7f2c4b1608" . + . + "459ebf80110601eb958d8107f21dfe46e7b2d0f7" . + . + "a1f0297b512a2181896c57dcae2a0a780e0e8962" . + . + "b5d1e0697ea0e6c054310ceafa5ebac57975fc4a" . + . + "d64bd3395cd83b7e96b7ab75e30e1a5039d1614b" . + . + "49aa4cbd76398d3a64661ef109b1873539def12a" . + . + "e4e9fb483e8ff10e555ebec15749abd2cbce72a1" . + . + "5c006b6bdd47284f450a7035bb914c152df8112c" . + . + . + "ef842ad37ac75e44e4690936a4398e2b29b5c213" . + . + "d488bdaeeac3bf552402989bc9d690b992abf9a1" . + . + "58852b6e8eb757549c8b379e498dd203f4ebf78d" . + . + "93b6aa9aec0172cb22baf4ebaf3f32407c466e51" . + . + "19f96a0574b8016f3f24c21122b65306c74e0615" . + . + "c81a271869507bd9e16c262e07032c3cce511688" . + . + "ac636023d8f90365a130d426d82fa19cb61c26a2" . + . + "2c96b2b53cb477532ea6797ae1571e5fec4868ef" . + . + "50bafe3b4edae230ecb9c0619a4e0e5bd9995e41" . + . + "a08d7c6edd11e9507a3a94349e12ebc7ab0acba6" . + . + "c9415c3f1080e2127c5e2f1c6a6837fbe23cd7d7" . + . + "c6ea9791d8d510c22a3ab3823bf9e220e8a7499c" . + . + "b04bc88419a357b16c8d3a7db4849b2cca671459" . + . + "23e6c79e50f01bba65b15b3c4919c2c4757aba0a" . + . + "56250a7e4a1943dacbcaf0a586138675839c647e" . + . + "028555b32f3b7901a4cb58bb8c960d5d34b03d9d" . + . + "9a0d6438769d3b1bff21ff65dfc24a8ed487eb2e" . + . + "1aa069a89344dbe8ef899784af92dffa66e52e6b" . + . + "fa97a2f4a87b49b37f7b8189848eb97af6adc8b1" . + . + "582d264a6adf2535eab01462239e693570e4ad6b" . + . + . + "ff684f7492aa41cdab321b87ae722881818cc27c" . + . + "7d032787e3b74304eededb2b35cebf1f88ca8e63" . + . + "57a9ccf1964d4d5c3f7fbd0682c5a6b6ada73cca" . + . + "7b495cc0fc9e9901662d29d4d007cb6a677ac40e" . + . + "5f56f929bb78368eb475422a99bc8818f91d9028" . + . + "a379880556761392e25dba924eabeaa51f127f27" . + . + "927ca1f7bca1c8fd6fd82454209b395926456e7f" . + . + . + "ef21c4d1a2edaa787d38652e78564e808f5f414e" . + . + "84c2f0f9378dd19a4103c9ba16b2c02030731dee" . + . + "790a36d29b872814ef7d4a771053a4a36d02adc2" . + . + "a2f3682f74e2da9a94a8bc3b9cd41dbe79368b7f" . + . + "fe0ab087922d9bdb1124eda1614d56418232e29f" . + . + "674f7a32ab0e01ceb6bd423c5aef2e73dbe9bf1b" . + . + "50babd0263147d65e1565ad289734028883a8d16" . + . + "4e1b860972463a2b865da80d9590d5b1767a0528" . + . + . + "00d36cc16336f5df22b11655b2335147fc33e3e2" . + . + "525d30ddfc8abaa2cd9c0fa0d6cdd31223299e3f" . + . + "3120b4ecfa3bc0e59b2e0d451daa59a9bb7238f4" . + . + "d7f3340c82fd876031caec032b3bb6518807fad3" . + . + "c06612bcd3f568f2f8da63c1067fbada7cd23ebd" . + . + . + . + "97b4470cd72c4741f9559e7ce67a6356d8519c4a" . + . + . + "66d5b7b2f809201513e5727e64cffe758032f015" . + "4e28d482db0f90d4188aaa80af0a14e985f30eb6" . + . + . + . + . + . + "8aed86a7b78e8e8121a7d419fff35acf8c486135" . + "cdf47b64ba898467eefa343e7bbbcbeb4509e02b" . + "f0a32fc1115a85fc3a15b56c552b1c6713555cc7" . + "0ec69c15538cee6731881f349a9df598b034c5db" . + . + "cee4fa5c5b8e9764a7e554329c9e552859d1dec9" . + "420567f99ce15a3ee52b98e095588982ba14e741" . + "322bb2a486b32adf9072f774b068f03ee01c7422" . + "dba34d59b0ac5693b58aef3f20f34775b4017e4d" . + "b08ba239ee9185b6073ae32a13ba752496f55c9f" . + "0a3666fba0c9e0c2a59ba5e43c2b4579eefcbec8" . + "13a29c688eebea55fdd41d14eef6c6ac6fcf3e12" . + "e65c2afc7963073eb3b8fa5b0e78413fdb69283e" . + "14610116aba15b1e99c5aac06c23e9811e344d6a" . + "b48d2b920a6fe081b9b5c11b7b972243cbb036f6" . + "76f851e0782b1824f3732f7284c24d179181b072" . + . + "7756f3722a9b867309d3a8fa1d1f751db5c98f3d" . + "08f9f2a16cc8f99b7c14fc8244fbf1547d00be22" . + "80f23aeb288bb1a32b1461e6af02b2324f865cdd" . + "07c94f8c7c9c7b73ff7aa8ac46aacf8abc03b285" . + "8a459909848ef8ff879cf992c90029e4c2c6ee67" . + "499bf96929870a48e06d85cebee19252f309eff2" . + . + "657b6972aeb90375119c4aae58beeacd3c4de1bb" . + "1c5deb753852d87715889e3a6eae947515403c47" . + "b18e2a1e7833bd7695a729547aa6a3e9a7179bc6" . + "c573db354ae03a83867d1281ac9505abcc991da0" . + "104d38ae087356e67ea75713772f7035b4096787" . + "116dfe892190f8caacbeb6374feb6e3f82e63dad" . + . + . + "4330576b94282b686dbd12968bb871028d998ded" . + "6adaa5e4f2890323ad32f5752d5482c9a873a75a" . + "13f0b6c1a9062fcc625a1d96a6a29119e6b863e7" . + "b66e2e8790c8b4017efb8da8cc0d9dd2ce1f2030" . + "f7cb794053c41edbad192738459c9cc392b6fa73" . + "0cfbf0d9798fcf2d26214193c8846464b0d3fbd7" . + "69be928bcb14da0c75700c4666b6199d44ea54ca" . + "5f121485c6096617a2c19f5269d296967747c617" . + "0f19689e0b8edddfeafb11478f8a698bd7cffc2c" . + "1b57fc0dfcf98d83d8d9aced8f7ee776635c6931" . + "733f0740477a7329d034a648ebaf2627ba69c652" . + "352e73a340970b0fdf9c8d00081dd8a880654918" . + "8ec94654eb51b1d115284f9bab2d2450d7f6a4bb" . + "39308e935b8c2daf32ff4eaad8435b2d8ec8685a" . + "40a6a9f70bbd222b7a6d44a7585e0945bcd3a54c" . + . + . + "aa8784fdf20ffd7bd021532deb9d14d352b9d688" . + "246e4b8ece8da17f45ce2e265a5564aadbb6475b" . + "f96d982b89d21761f02258aaaf047798646ff07f" . + "691fb879826bc97b0f94803a844c4726ed951922" . + "53f4f6923240996669d0a9d914550bbb8cb44385" . + "30ea99f34b8986d6c08a99198be90b96ba8504ea" . + "709d276a3514abb692135279059268ccfb189a79" . + "c87df5123933a3e2a48f2abf7a5207b7db6e46d3" . + "b50b4e4d29d9c208992e9e17e3bcf3cf0584e1c0" . + "361ab7137795fe0284e5766e2b91b3df473eb07b" . + "8a144f6d4d61746b8da1f7589c7ee69757904359" . + "094b8e3d6a0b4384266830ca1a89c8c891983fa8" . + "e5e79d15c67c4696626fb221fa143ce5e9bc91e9" . + "ae32f132eb983398f9934a13d29ae250e89a33c1" . + "f3576ac35a0091ee444258dc41b1382755428113" . + . + "f65fb26a351aab003edbf6f72159ef1532328bf0" . + "a823c87c48097228ad392ebd5912e51da35fcaa9" . + "bac9447d8c7c7e333a0e08e5515786593ae4864a" . + "b9cb566a906670075cbb326f4799eebd3f3ade7b" . + "23308e20d104492b19b3308209a91d3273e19c7f" . + "0dc21a7d3e51d2df1036483b577925392b878031" . + "acd9b0a7621c6ada0ea8d0b514491d380a2eaaa9" . + "77d71092d0da527307a2731b034ed0794d84b16a" . + "d423c509ce85507c3ad4023a83940373f73a337e" . + "9bd5ae0c8c031f615ea784dc9617deab941e866e" . + . + "d5a9f747cdcd108352aa780364e2fbe83794335c" . + "cc3a164236d0ff5f763b8b88d5147b6b55172fe1" . + "bcfedb366e1f93346603b6e5ec909210d536d322" . + "e41d0abf3779ac80df1fc61f34f5d2ef8abd8ef8" . + "468f7dd21c405c48825100036cf2d0f574cc04d4" . + "2e1cb8ead963a588033b1a41adff633b785bd5c2" . + "e206d8a27aa0a33a171103d6929913fd8ecc8de3" . + "65a84dd1296925c87cfe7a7643968100f76a3401" . + . + "3321419e8dbf526edc7e81d741852c31a62098be" . + "ccd20b9703db67143b2259f5ba9e2b0e9d70897a" . + "8e4b7d8e848e54b82f8cbd8ed723ced6ebaeaa7f" . + "aacf4842385bd45ce85d66d6e0aef83279c4642e" . + "7fa7c870c1b6820b0f699a3e5de73c9518f87223" . + "0a9a180729b70d2cbed8a64f79279d91a6d247de" . + "ca57bfab3eb2541d262f3fd1a87bbc5c3d1dd87f" . + . + "362f815db3603cb7cb9cd87d9f32a1a61c846a4f" . + "ef62bf947eeabb03a54e408e87f6da6171961d62" . + "81941fd777cd784249fd67be55515a8df078489c" . + "a1d6d1b775ceef010c14dc279e850325f46d6f34" . + "bfb84655111b6085236c6b3f0795fe2fcbb59291" . + "10eb4d576fc867d2abd7532f9998500dcf8c2610" . + "620fed53ca08b29e01c50bbd80047fe33f160dc1" . + "b68d432a5fe0919cb5c6fc47783eeb74bf51c437" . + . + . + "6cca77dac09a8bac9fbca2fe98102a99ff43d272" . + "74fff7d3fedbf4654082d620913ae4eca6bfc09b" . + "bf598398d2b87c18d05ae8ca5c07fe0bb592ed47" . + "bca9f5bc92012d7f5fec7b19bb725823a46b935b" . + "ea8e26690db85ae2086077498a2982d4e80ec02c" . + "78cdeea36b6487066381e5201bfc65a9f0cb2543" . + "6d8732d60607eecfcca240eb006725f4560637bb" . + "7b597813f9b1f562f39ebc4069760aedd480afb8" . + "e7a0684df7eeed7c81400df5b6bca14fa818eb95" . + "123d9908c0aa35132d4c7ffa232f4175c9037910" . + "f8175b5ab4d784f09202df3452667940e509c67d" . + "a1eabfdc5dfd2c3d44d84725947ae5f672a70397" . + "aed310348e0c9f00214e4d92cc3433778216b299" . + "71f224b9152d42b8b7c9158030d6ce2affff30f4" . + "9994a82319e5c75080da92988d0673a04d4d94c4" . + "21bcc21a8c2d00e7c4e408cfdc7049833016ee68" . + "fe10a9c4f036922db711c89c11eef63b239729c4" . + "9b5622289aac57a4b526649ba90017858a7f485d" . + "a6dff66857ddc03c78cf996fe0854435f5b84741" . + "a44ea0ba689a95334feeaa0a997a3168f46cb9d0" . + "b325f1dc85b86a4b9b45fba80565d9bc1fc41135" . + "5ec5facef814f3a7680993cd1af7b95820d37985" . + "0de350e324b5023d83e565330622b64de99219a8" . + "340ac6d571d9e64cbc0b7a4dd4b89c7dcb64ab59" . + "075a0d96dedec8da065acedf5e0e047dc8383d8c" . + . + "f6bf6e4febe8de6b91bb14a49de5d6487f1f296c" . + "d702eb6614b449653fac43b753c31aa9da9a2f95" . + "2cf02c4831449436fe2e4fffe0940bb491d480e8" . + "8f53ac2bedd22651d4754f25fe22620428b0ebe2" . + "20cdd600af3d26d80e148ee9025a7cd25bd0f8b9" . + "f7b83271a8db3662b0d0cf67729317acecfcbb3c" . + "7bf4938931836fb9ca758bfcbe208db67915d100" . + "72909a59969a32eaa7ca938c0553e2c06fb22fa6" . + . + "5cb1b3340da05b8e2d4346dc474726f01d051dc1" . + "189499b1a2969a5485f97cafcab599488c42c648" . + "e3c00486d1bd4146f1c26b3bc58528fd343a6fa9" . + "ec0f23e6d7e4559955b3945d9852b2164450153b" . + "fe805acd9232063ff7c89cf432e2b352328c0a09" . + "70f222924a6b0c63e15537bee54577e4937ddeda" . + "c9fb60acd68ef5f27f89c5d2b5ac585bc3012300" . + "0d467ab52b87ec3764984cfc0a3243640f9125a1" . + "26eb726bf82b044e30535d68710341c8c9dba7fb" . + "170eea91f88297fa6b695320b894a3cda6e4ab7a" . + . + "c845631b2f21f55a932d9b2442984212eda8a409" . + "0867f58c385f21726a56f2c3488a87640d46de88" . + "313f60008de21573ae2cc294ba55472c180d96f5" . + "3440a7c9ef9b588af61fb026acd1d6241f7f7c83" . + "c5808431742c2bb36c97b8e0a557be39bcefc512" . + "9d1bb0592c95ce79d1210d59304f81da5a5c3f9d" . + "f91d11c93e8e6a46fa3cd53c1b981a78fd116784" . + "4930c832896544c1ec4c4fbbe834b41c602e98b5" . + "f90c3718697e6321c56a1bd389e06e13880ad07b" . + "a28b00e9e49a4123c386711c773baa021f5aa48c" . + . + "d1980c57e82d08e5e29c9c2ffce1a12366892674" . + "1372e313e4da6dd8042660dc168fdd34e338a2d8" . + "4080126fda642130e1181fd5803e3122a25749cd" . + "46f0d96ca847e58a7dcc16aa17717cd78d864a34" . + "6ef81def1470ab9d002471f4462e4158f096a68d" . + "d1b26c16b08bb35409c5815fd408f86cbeb4dfe2" . + "9f58a33f05cd0bf4eb5356a2f33f7acd6c6754dd" . + "386c130a5684b23d60a79031b3885aa66e8d411a" . + "220a891c88423eb2b2a67e48cc6bdb9b43f522d0" . + "b76a980ffaeb316c059d43f625931b3761aa86e4" . + "c74bc3db955caa649ff7df4f369c22bda80ac067" . + "4feeee64ffa4c9e2e1b72f9837fe100022cbc732" . + "3322cea76966dfad99d02729c6307ba404c495ac" . + . + "f4dc9fff0b1819b86165da3e3c3ee8a10b850dcd" . + "42c0aeb873b4a7a27a350a1740de24e41eaaf038" . + "a7ff2e48c0f01bcb3db6e858b2660cdc4a735db7" . + "42900bc0754b1b7ad45820b0cc0703e01a65b5f4" . + "ac69aa67661b27280d5c576fdab7896936fe3d24" . + "bb10b4a5af962324ae3825eb6bc13d78004edee2" . + "9dc99faca5d4a5985d416e75cce799659554e5db" . + "a0dbfb06652e3b7b93cb4680e456f08a5445f5e6" . + "f66512dd7437697bfea97174e616fbfbf6ba8f98" . + "7a5c1c02bddb164dcd65b4f9ea29e6dc10182862" . + "2227e6e645b431b8338cb6007b46f68b24a0dd75" . + "497d6b3da396bd2f978fc8fa8b012b75144b06cb" . + . + "c4461bd1cb57b97ce84ff79600038323da4d0d2b" . + "53ff4275a7933aac04737007d85d03ffad63acb6" . + "f9bc8c203087ed7bf0d423608e180277758fa266" . + "f54f484f541a0725e40c727c66de8450b602c015" . + "9a5dfdf896078b39dab92334b6a8d4fc81c1f1f5" . + "4b6b743f21129eeb9d43b9a70bc1faddf1631dcb" . + . + "497d3391f2dd851212743bcc7846719e36e64a6f" . + "9e0f5b6488bcd33c248f998e9556fd9f207bc576" . + "d5aca6e9671e5abdaea8b5692cdd9541874d7cf7" . + "0d746fc607e42b95cf0dca4184428fefb80587b5" . + "bbe00b0008216c491ed694f7c90faf74a3395771" . + "5907f9b5c06564b810eb77e5c3032e22a2e35e70" . + "06331d8bd32ea1cec6d94cf4e6755c3b3d4caf31" . + . + "0731094ee46c633281e4cab42dd032c2234a107d" . + "6e4429f9fc921fd113c2d620a9c6ee171225f791" . + "d48f42943932f72d6915c8f891d7fd9f6cea2cc5" . + "4bbe0a0532d450222e5c9ffc7762ab86721e3693" . + "3cc5886d8ad2a05e2fd5475c3af26645f38662c7" . + "7d98d52f551a3c53b48976810ba20e92842fc272" . + "b3fd4247cdf0b7e1309d42d1f85bc82abec89a2f" . + "538a349e5b26134d3273be29002c04edfbcaea5f" . + "1e9725bb0cab3e73325a169022383ddddc571b6a" . + "eb8d5e4c2ab2078ae80d76faf8f6ec02cd2febd9" . + . + "950774e6103d456a528fbb2bfa86acd285414218" . + "34d82c212ee412a1a693e56889d5c3facfb43750" . + "b9c87a26ef840ae99ccd105ed8e6adc4c0cf5718" . + "0894089d12d3fe6e17d7a8a50399c35b876b8280" . + "d10b636a822fe9e7f160483138256e1b9903a454" . + "cde4bef2eac73b81f3e6f1d45ccfd617732ec04f" . + "78052a875d4b03c72dcd21420e0d3d47fc67ee8b" . + "2131db4ae23bcd0805541ec4d57ef4a768fca33d" . + "1b4dd9bf2b6f5b173447da7a4812e6606fda8009" . + "a37039c7e81db540690ab28c82672f933861f415" . + . + "fb99c80b26b751131ae27a2e4a44d1e5a88fb9f3" . + "08c3af99b0950a6eb4363bb12e0d2a645f886360" . + "d4a28fe8ebe5236fe79622c70583a2ef55cfe78b" . + "4114cf32926ec41c624839b2203c85978da09803" . + "327be0af98491c8f046a72bf9d3eb58952eab9f4" . + "26f4e8682ebd6cd9c66d00e0d6128de2f3540634" . + "4793b70282acce6be0a2457f5256f438b2ef7fa1" . + "f75722d6b9bc6be0da96d924c530aa5977d0e586" . + "8391b9d0206e6ecf631e87eeb3f26641253d2910" . + "9f0b75a4bccfaadece3f7b05bcf3705f2df07482" . + . + "bb19b2e3b926fc1fbc076ab130d3acbadaabd4e7" . + "da56949b9ade7bdd658e7a73f4975a667cb4412b" . + "2a4cd219a480c5a6f65c2696f33fb6810772c5e0" . + "02a21e1a4dda8ae883b18c4da2a04fefb0e58e4e" . + "ecc4d7583fb642d22114b3799f06ea9172182263" . + "6b4cc4966156197a098cebaa8bd5a7a4d9474007" . + "1c505ae6edca936ff2c22b9c3187c5d6dfe2c1da" . + "e7754f767ce6ba0f39599d551f5d0783806fa7a6" . + "61a893abd0750f74a5d6d01cf8202ae88dbb199b" . + . + "971d4d1b1de0ec60a020dca263b57d361310c8f1" . + "59cdcbf70fb7b6eaa00e34b5d228005c1236cc1c" . + "60df11e6d4d986a62651f4698f9fa3bbd80c3eae" . + "c1ad24ffa6b454ed2c076bfa679405886799f6bb" . + "b7ce971451ac41ba89fc01c583f2bc9536897e07" . + "f7b2a22b9072c9a45a9d27615ea615df6f62a8e5" . + "d305f64ffe89cda3a0b518740e582e3786cc959e" . + "83e5dde4934a3c64db38c1e3cf7ed4a02b7a86bb" . + "f2fdea759e80e9ac41f18f4f9ba8399201fdc7fa" . + . + "2a7f7b2e69421b3d030ca307b0d6624b8694394d" . + "9ceedbc6993469709f53288920e0ad68a3a97157" . + "a971bf2dd473fd12d1d2be37bf4cf85a75b63fa8" . + "8d177ef06687d4221f767bb5a1f297770fd79c1d" . + "d74be068a3b6d9ba8f01c208a06ab19cffdd3c0a" . + "7dbca29e0d1b7c747a07659257862db5c352c242" . + "2df9a9d8eb089947e9f40a0b443fa90c21fc0263" . + "ddede1695d8ef08b6b1fdcf5681e4437aa63d70a" . + . + "1ab512952ab0ffb30ae736f9f607356042f8d249" . + "fc50658d123b0deef503ee4f204b5c3937c82712" . + "2623cc8e11adb757875323785677a1acab2a6fd1" . + "17f5d18e7270e17734607d2fe6ff12933751bbae" . + "ff87c3d34c299d3f31ac7d22f26fa4528e8d4083" . + "935e0c916189a1676bd5e782d682c9592bd576d2" . + "a028cde1968cf45730c69ec88b89ff61decb27de" . + "556faac71d694b4fd7eeb78604cc546dbf74d70e" . + . + "0c65bee52a0f323a7c6bb97367d303418967d5e6" . + "289d1f9a233f73c8ee806f63edb056290f6e9ba7" . + "659ed3a5e33380dbcf8f41d812b178d654564e04" . + "7f5d9568db769dbf719e0d824d1ce8ea69159bbb" . + "0075c3e8efd8e501c61b15b73b9fdb37cefd7c86" . + "b0d648f8dec2eae7097422876d2d6ef713d501fd" . + "3e2815db52f3e3823281710f6da731f923efebc3" . + "99f68b904142a6dc43cc1ecd2282ca92638c657e" . + "b47f952183273481d3a0f37bca288e09b50b5632" . + . + "7c49b9eaf36ffa7c549ac3e62b4b44d35199610d" . + "52fd66fb111ed782852fde8898d8796379e17091" . + "5857413ef77fd73201e59b24e9dbac37114900db" . + "ef86c0bb8239cc4f724e83d75520f6788e32bbda" . + "fe591a1a69a26582045037da37c1eaefadfd6e4a" . + "7b5b2f66a842add6af40feab6e9f180a7aeb05de" . + "d3be7dc9bc08a51aeb9f36a2db10180807417423" . + "d60cc0b97f113b6b25334bcdec3a39a1039e5118" . + "fcffca136faf1787ef1ebdae7652d998fc46c01e" . + "7ea34c85ee3938b8a56cbd674ea69291f04e7feb" . + "524dac0689ba6bec232d5e0feac0d9f802aebe78" . + "d38d25f59ba3beb9946756865ad4f10aacd11345" . + "54177001c2be72e0b2161beff073d6ffad7aab63" . + . + "91b92f6270aa64618d5ced1214226c57eb2219ed" . + "9bef49d85bf79842fa67bac904121c706636de47" . + "ec5531edc3a6c2f12c08f8b8c5ad67dcdd6019a8" . + "afaf8a3d1d4cdb769d2b649ea81b68df368eec83" . + "4712fc758a8b5412ae7bd1f9fa7593b869b8afc4" . + "849da5a1222f9c64d996341d8df1261e9045ba04" . + "7151f168884deca2bf6d6654efd281710c0b4e32" . + "0a03e8dc628f39c9ecc00eb2e190e5de56a3cd4c" . + "7d6c9eedfc84238e6c36ee10d5659bebfc0b8b14" . + "39fbfe4a1359c0562740013e33773b39379ba9b5" . + . + "04c61e7393dabb68f4ca915cc95812b8dab039f7" . + "647d0a4f2538590b2d4a9c91a9df700d4bb46d20" . + "0bec2d98f600a6b7566d419a54a4354887603570" . + "4e1f75dda05e33a3636e0415c6ee6229056f3d45" . + "a0c1d9c0e9d2f4da7c608445d385bd41a65e9920" . + "44e146d9e1017572d98775de5095b3674c7a26a3" . + "77ef032a75e2e842996bf96d0fdeab3d8addcec1" . + "e7fbd98e5888160a4c8dee5ab611cc4248ef2826" . + "a6c8448916df36cddb4fb8202fb35e6dc8995344" . + "a6d4ae16784ed69d1e9d2638ceac9e32ab5103b2" . + . + "40f9e448b33623fa693066e017fcda155cecf59c" . + "464608f09beb022785e60b4f50283ac55d2e1df0" . + "52381f484f89a4a974fe21ab0508bfdc43131d31" . + "68acc5ca026b5d235411f8fa8157c934468cc359" . + "b19492fd4bcdb9ec4dc3eda7e1ec99fe37e5d67c" . + "b6e28c40e59c6a3b7164d20c278fa3604f83dc1d" . + "470f7fdb1a1a05221bf77a64135587dd01137370" . + "36c19caed531ff2078e344f84fe30285ac0cde40" . + "8bbd543c71f1bc9b43e059276238fe1b17565262" . + "7f74bb2057947f2dd0257d2434e95159fd615a82" . + . + "aa298dae303a8a08fcb7c8d222757b8811ca320f" . + "cbf8bdb7d611d70d462ef58b32cb1e8223427529" . + "52099237df3bf57a2512df040e62e6ba137affc0" . + "eeea96b306730a75d14e76f21f2d3804cca2e03f" . + "1a6de0ca23f2c0b2a8d6e1ee218928fc16f51f63" . + "3a666652904a545ec912520637cd316f7d8bfba3" . + "e382f28b3cf4fef9ac401f295408b7e71d13731a" . + "e3c667e032c84e1ec00f0feefd99555e7f0f9de6" . + "ee0d0851067f1509db2d87b3e5c790b0d707af1a" . + "7cdf54f1fd9fad6bacad619d5d7c07cf11abdb6b" . + "7ee1a6203b039056a27b0f83f9ce9c922b3bcd52" . + "705aee776a4b1a4f65b876493cc894cedd98dca2" . + "81aeb999adb3ab179d6b2cc9b16ae65260eb71de" . + "8a94d5a59de3258fa2ebf3c45798bd6ff71ff90f" . + "9b0a84a2b086ba31a337fcb75ac36ad88d00b566" . + . + "186d0ef9d8d1ea65577e3308442e2570b9459753" . + "e17f0c1e517574a3a865965d170e48fe6ac498d8" . + "df5f3c352cddede86ed0bda8023cff91b3492d2c" . + "567c83032c6be8fbb0f1ec06869d8604094719c9" . + "ba010c830a1899b2b9b00f60e64046c140dda557" . + "51d95bf50bf064dfc1900412c3edd90bc9ece2e6" . + "8fa1609bb66114c3bbb3dab47bf8771be9af6dbe" . + "0c11492cf79ca49ba49558631961e7fdacec1e59" . + . + "9cbedcdb5d10828e27862833c010b8be95b9adc9" . + "50bba3c61976351b4c857f18b32615d69d38cb85" . + "14c654bd05b0b7b915c95d855299c7a8a8a1bfdf" . + "ce8e798a8eb4c8951e88172209e849250c0344e3" . + "7411b4bf4013eeb5ef192033fcaa8a7f234d3670" . + "76a16a4a6630e47e28a682d7184f5e4fd67d6a0c" . + "5bc1848e656070b805aa299cdeb06e4daac6194c" . + "487154fbc9e3c3fdb864563f98b5ddf72ec59ad8" . + . + "5eb369d21aeb32623519f104e8551cafdf952ce9" . + "f932898ac44bbb78b3e6adf7e6417dbafcbbf970" . + "c3f425b8075c4c0363c56eb1b6f035dee97129ca" . + "1d7119f0ff60cb2b8df170e9171274c499ad7055" . + "f4ffac172642af2bf026e909ae611424ed6bd38a" . + "f52ad2a1fc792d18df7193d184a3eb51dbbee110" . + "64d65ae359bb19c3a4494ee975f65f08e89497b9" . + "b6ac603576f2e9c41c173303b429076c71f5384c" . + "a331d005cb258e60521bd8a79961aaa3be80855d" . + . + "29a98ef55521baaab9d7c6e0594684e4db5077b3" . + "4d5ea872e41da732508a30b9b1df562a19f485f1" . + "80fa050e7a9ced568ce957aacf9bc8fb11af11f5" . + "a115859fc0671d4001130dab1f3aad1813d34dfe" . + "ca5fb4639bbaf6b18ad3258629b3e7cc85c95c51" . + "4a2a7f5edd1b7c471d093d086f7a6e9950f49a70" . + "bd778e3ff3d937ea943dab3818bb6ae524821ceb" . + "91a2f3a32b13455548b7c5b08c783ed119078a25" . + "12fc4af5ac48c7b9081a9d65414ab4fb5dd4ae9c" . + . + "f1f33c93b6b7ad344fccd7deafd6d4ee6713c215" . + "b45cd8c0109bc9839cf956db90d82b56d2c520c9" . + "2abbc2884df2679f0b30aaeead4a237afe9afbce" . + "ad2d691b9b3b5a6590509b9354fa7ffeb8fdd05f" . + "bc67886007786b782dbe43d54543b43bf25c679c" . + "40d0201566c222bd3af5e72effd124c994952fa2" . + "0fc59415fd5fe2d9e4d56ca2686c3968ceed65ce" . + "92d19c98d36d298d3242e4f1b4634c08790f1682" . + . + "c46f644a8f75197a7506ef0010b7b0576de807ed" . + "f2d638528cbef258146124b52b7455047f9c6ff9" . + "5dd371b54079ba7cc56d927b1596dff666c08ba0" . + "c01d03cfa90e82a4c239792d96ff0d4453e59a54" . + "710c457a6dca727701371a359caf88784e58ef4e" . + "eb260d1c1f710207fbaf2fda0d8b40bc56e0a518" . + . + "b40f683da0f156d6d23c7365b07df382baf4557c" . + "adf7ec853eae4499b266a893d8596f4349e21659" . + "7c99bf3a3f827c12c4ea2fef4123c5528b69d5d5" . + "74ff6aeb5ad53a2cb6b20da48c34564ff52d720e" . + "5f9983dbdb75fd7448cfb4eea96c1a10452275f9" . + "b79225464a299d2928fdccf09706a7d6858b6d97" . + "8fae743370338a16b53b8897a418016d2b57dbd9" . + "930858780449bc523a7fc644f9479f5df4111fab" . + . + "6b206db2682f5e7c6f8a7e731af9a445e81ebeb7" . + "f4e6329387191ebb12fe9e93e9f37fd2d5ac4782" . + "b94f633cc3c2f2408e2e70daf053166841e48d79" . + "495e859336ebd35d38fad447334a4474c755a702" . + "49199140fca61cb8af966a1b9c63dfca0c9ccf57" . + "9cc096604edf11d0a3dd0fa131b1fffd276de564" . + "c5e1f57bf6731dd443a4c858f95b8d99b127a4d0" . + "1edf2232e4a0e06eff9ec1f4f364c4a63c40cd1e" . + "01216d16e490070ac70d64464864fea17da4843b" . + . + "c22dcc21307f96fba3faede090756b32a9a6c380" . + "39cc9869b1fa63781b03610d4102a1c776c78b4f" . + "ade229aa4946d06d69c6a407c133ac49611ebf27" . + "127d4178090de356e2fd82b64044841d3067377f" . + . + "2456c3f299535694202ef4b61b92ba05b2024213" . + . + "62147979701bf798616009732b000547abc77704" . + . + "9f6dad47c2d211e34113ea0f40750300301da649" . + . + "a4ee33ed313689d446d8bdf1b236b2af8d553ea1" . + . + "c81d1646870da9f635bb090bff19c8d750647a04" . + . + "1dc164c4b87b5a8acf8c1aff192604a5ecaccee7" . + . + "53643131c0130d808637a4202712d1c122cfa503" . + . + "3d62fc7129b0ccf2ddbadca122172e981fbfe3c9" . + . + "8913459415cc4e18a8a74e87f7a58cd82119bdac" . + . + "66146d0f13b25f09f3f6ec5faf5c8af2d410001a" . + . + "97ce01d9b8f7d8d774a7c384da25bfbd2cf2e05e" . + . + "f21fff69fc9f779e80578657f1c6da8a13a192a0" . + . + "0a9c1cef036ee5e77fe9b6198b90399eb349583e" . + . + "44fda6e64b25629cb11c77eaec655f5dbd51dd43" . + . + "18b5f43c8a3320b1acd1e62bb2c6d365c7b9718a" . + "ce1f52d554ed305647579423d72c586189dd74e0" . + "90cd1c6e35355e253764afecd5a233b98569dd80" . + "2dd6b41a93576594b4a1bbb1bc6ce24f2a58d521" . + "8339d64726ec54d7210325e861532492540a19dd" . + "54ee3246b54ab9509d1193af66d623becbf24227" . + "c5bc3ed7af370982f2b533e9feae3472aa4e8b99" . + "ad9ba3d264b3549b4643fee31b38bf78b0e517c4" . + "cca20ae1b54758ea953e01bfebd950e76740d333" . + "0e6bc1a2b6df5886f259efcee444d351656788d6" . + "94d9e0666c5f0228b907a251caff8374ec0a3c0d" . + "2575cb95bb1da012122b48584cc099d365baf204" . + "c1ac6df6b539e6d02c412fadd818df23279b6d7c" . + "13b251ea66947303bab62274c3c1e03571c2c524" . + "38919331d52c5d76cd59d8ce6620b19e98d97162" . + "1c87ea0b7dc7fa9a9da1107506e6196eb30d11ce" . + "4d494a22e2873f6161eea03cad0673680ad83f7c" . + "ce713c2ffd8168ffe02ab6fa3bef0b446545d34b" . + "77f9a122d872189b5e7c502f6632ad4c35392ef4" . + "31a31ae055777d8ba6dca65e5dff56263c05580c" . + "1a637d83c0b5e4624feff54edf27d7898e7e919d" . + "9cfab05459f7f9e291505621057fb9766f1bc53e" . + "739ac65df7ed2791df429a5023c8e524ed278d53" . + "8f652489c9f07c67d961312f1db0b9c0eace3c2c" . + "87ca5409e835323557aa2cf793e9cdb9d0a0a8ae" . + "e44f8761f67eb2b0ee51c25c9f2e8e9c208b4881" . + "3d85a79dfa88c3e385a229767d493888c558910e" . + "064c903608cb7bff2d49ee93faa25c28ee1cf8d0" . + "5469dcfe126132705aef6a4b523949668a570469" . + "6904a3530aa7679f372bc96c0014cd5fe294a305" . + "57ac8a74ef81346ee268d71451d706d3045180e7" . + "4ec29e42f26e5e4c3aec489bb330201cfc5671db" . + "342bb5ab482757ce5ce2c6f02291ee144daf42ee" . + "eed60886fb9025d9ec37b211b7f1d7e5ace3678a" . + "df67c44a003fa87ea4fa174450ad53b1d48219e8" . + "a46a00437815608c1255cf376f52f71c3913f715" . + "003b30cb5757c87651ab8bd5ff69069304c1fbfc" . + "6874391a5214fbda5e06bf6e717d65d8e3b7b96e" . + "72282f5b75188716bd30af82eaac801e639a217e" . + "1deec599fd7674c0aec5222582b769719a8b9e71" . + "046bd5d7032fde032562cdf08cbf311de6af3a01" . + "1e841bcac4d381f07bc232483e6ece7dfcca5c32" . + "72a83f224f22b1e9a9a5deb464fb6e8421af5f1c" . + "7d95f54fb8473f3c275ceded6a761ae950940af1" . + "3d8cd39765a201f84b200012064b8f51fad3e3e2" . + "f8d98b8247d93f371d1cf5e5c0c07c956b37a032" . + "1ec3f6915f744d5ec274e3345bb6a36018c4b162" . + "ee59551dfc5084d1088d43dff0352568159fb0fc" . + "b6edce9f8b40d2231324a52bdf0923faa9b29f88" . + "8ce9c1beca50fba9a23854e5b28ccecc88caa188" . + "e682c8283dd99dcb3a595519a2ed38903e39d0b3" . + "c06dfe1c1d6bd485ea10488fc2d02cfa052074af" . + "d1f99739fdc64b8a0721a64dc63de7df3ac93987" . + "6a0b28f1686fff4c3ea1e073a35ee5ae76f40092" . + "6f424906a211410964a21f1d89c2862661a26b1e" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Aankledingen van Manneken-Pis"@nl . + "Aanplakborden"@nl . + "Aanplakzuilen"@nl . + "Bezoeken & bezoekers website Stad Brussel"@nl . + "Aanvragen voor een bouwvergunning"@nl . + "Aanvragen voor geleverde milieuvergunningen"@nl . + "Administratief centrum en verbindingsbureaus"@nl . + "Afval"@nl . + "Agenda van de dag"@nl . + "Android-applicatie"@nl . + "Apotheken"@nl . + "Apotheken"@nl . + "Begraafplaatsen"@nl . + "Bezochte webpagina's van de Stad (per URL) in 2013"@nl . + "Bezochte webpagina's van de Stad (per titel) in 2013"@nl . + "Bezochte webpagina's van de Stad (per titel) in 2014"@nl . + "Bezochte webpagina's van de Stad (per titel) in 2015"@nl . + "Bezochte webpagina's van de Stad (per URL) in 2014"@nl . + "Bezochte webpagina's van de Stad (per URL) in 2015"@nl . + "Bioscopen"@nl . + "Burgemeesters"@nl . + "Buurthuizen"@nl . + "Consultaties voor kinderen bij K&G"@nl . + "Consultaties voor kinderen bij ONE"@nl . + "Covers van de Brusseleir"@nl . + "Culturele plaatsen"@nl . + "Dataset van de datasets"@nl . + "Demografische statistieken"@nl . + "Initiatieven voor duurzame ontwikkeling : Elkaar helpen"@nl . + "Energieverbruik"@nl . + "Ereburgers"@nl . + "Initiatieven voor duurzame ontwikkeling : Eten en drinken"@nl . + "Europese instellingen"@nl . + "Europese mannelijke bevolking in Brussel"@nl . + "Europese vrouwelijke bevolking in Brussel"@nl . + "Facebookpagina's Stad Brussel"@nl . + "Fietstrommels"@nl . + "Fonteinen met drinkbaar water"@nl . + "Food trucks"@nl . + "Franstalige bibliotheken"@nl . + "Franstalige scholen"@nl . + "​Gebruikers van de Nederlandstalige bibliotheken"@nl . + "Geldautomaten"@nl . + "Gemeentelijke actualiteit"@nl . + "Gemeentelijke basisscholen"@nl . + "Gemeentelijke kleuterscholen"@nl . + "Gemeentelijke lagere scholen"@nl . + "Gemeentelijke secundaire scholen"@nl . + "Gemeenteraadsverkiezingen (aantal stemmen)"@nl . + "Gemeenteraadsverkiezingen (procenten)"@nl . + "Gemeenteraadsverkiezingen (zetels)"@nl . + "Gemotoriseerde tweewielers"@nl . + "Geografische oorsprong (Belgische gemeenten) van de bezoeken aan de website van de Stad (2015)"@nl . + "Geografische oorsprong (Belgische gemeenten) van de bezoeken aan de website van de Stad (2014)"@nl . + "Geografische oorsprong (Belgische gemeenten) van de bezoeken aan de website van de Stad (2013)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2009)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2010)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2011)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2012)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2013)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2014)"@nl . + "Geografische oorsprong van de bezoeken aan de website van de Stad (2015)"@nl . + "Gewestwegen"@nl . + "Glasbollen"@nl . + "Haltes MIVB"@nl . + "Initiatieven voor duurzame ontwikkeling : Zich verplaatsen"@nl . + "Initiatieven voor duurzame ontwikkeling : Herstellen, hergebruiken, recycleren, composteren"@nl . + "Hondentoiletten"@nl . + "Huizen voor het Kind"@nl . + "​Informatiedragers in de Nederlandstalige bibliotheken"@nl . + "Ingediende aanvragen voor milieuvergunningen"@nl . + "Invasieve exotische planten"@nl . + "Jeugdhotels"@nl . + "Kinderdagverblijven & peutertuinen"@nl . + "Louise - Bois de la Cambre quarter"@nl . + "Mannelijke voornamen 2000"@nl . + "Mannelijke voornamen 2001"@nl . + "Mannelijke voornamen 2002"@nl . + "Mannelijke voornamen 2003"@nl . + "Mannelijke voornamen 2004"@nl . + "Mannelijke voornamen 2005"@nl . + "Mannelijke voornamen 2006"@nl . + "Mannelijke voornamen 2007"@nl . + "Mannelijke voornamen 2008"@nl . + "Mannelijke voornamen 2009"@nl . + "Mannelijke voornamen 2010"@nl . + "Mannelijke voornamen 2011"@nl . + "Mannelijke voornamen 2012"@nl . + "Mannelijke voornamen (2013)"@nl . + "Mannelijke voornamen 2013"@nl . + "Mannelijke voornamen 2014"@nl . + "​Mannelijke voornamen 2015"@nl . + "Mannelijke voornamen 2015"@nl . + "Markten"@nl . + "Middenklassewoningen (met inkomensvoorwaarden) van de Grondregie (2014)"@nl . + "Middenklassewoningen van de Grondregie (2014)"@nl . + "Monumenten 1914-1918"@nl . + "Musea in Brussel"@nl . + "Museums"@nl . + "Nederlandstalige bibliotheken"@nl . + "Nederlandstalige scholen"@nl . + "Nestkasten"@nl . + "Netheidscomités"@nl . + "NMBS-stations"@nl . + "Noordoostwijk"@nl . + "Noordwijk"@nl . + "Openbare aanbestedingen"@nl . + "Openbare computerruimtes (OCR)"@nl . + "Openbare ziekenhuizen"@nl . + "Opmerkelijke bomen"@nl . + "Outdoor multisportvelden"@nl . + "Parkeerplaatsen voor gehandicapten"@nl . + "Parken"@nl . + "Parkings"@nl . + "Parkings voor reisbussen"@nl . + "Petanquebanen"@nl . + "Plaatsen met openbare internettoegang (POIT)"@nl . + "Politiekantoren"@nl . + "Pollumeter"@nl . + "Bevolking van Brussel"@nl . + "Rusthuizen voor ouderen"@nl . + "Sectoren bewonerskaart"@nl . + "Seniorenpaviljoenen"@nl . + "Slimme vuilnisbakken"@nl . + "Sociale netwerken"@nl . + "Speeltuinen"@nl . + "Sportzalen en stadions"@nl . + "Stations Cambio"@nl . + "Stations Zen Car"@nl . + "Stembureaus"@nl . + "Street Art"@nl . + "Striproute"@nl . + "Tellingen op het kruispunt Van der Weyden - Stalingrad"@nl . + "Standplaatsen taxi's"@nl . + "Theaters"@nl . + "Toerismekantoren"@nl . + "Toiletten"@nl . + "​Uitleningen en raadplegingen in de Nederlandstalige bibliotheken"@nl . + "Updates van de datasets"@nl . + "Urinoirs"@nl . + "Vacatures"@nl . + "Vastgoed (woningen) van de Grondregie (2014)"@nl . + "Verkeer bij evenementen & werken"@nl . + "Verkeersintensiteit"@nl . + "Verwachte evolutie van de bevolking van Brussel (met aanwas)"@nl . + "Verwachte evolutie van de Brusselse bevolking naar leeftijdsgroepen"@nl . + "Verwachte evolutie van de Brusselse seniorenbevolking"@nl . + "Villo!-stations & beschikbaarheid in real time"@nl . + "Volgers van de Twitter account"@nl . + "Vrouwelijke voornamen 2000"@nl . + "Vrouwelijke voornamen 2001"@nl . + "Vrouwelijke voornamen 2002"@nl . + "Vrouwelijke voornamen 2003"@nl . + "Vrouwelijke voornamen 2004"@nl . + "Vrouwelijke voornamen 2005"@nl . + "Vrouwelijke voornamen 2006"@nl . + "Vrouwelijke voornamen 2007"@nl . + "Vrouwelijke voornamen 2008"@nl . + "Vrouwelijke voornamen 2009"@nl . + "Vrouwelijke voornamen 2010"@nl . + "Vrouwelijke voornamen 2011"@nl . + "Vrouwelijke voornamen 2012"@nl . + "Vrouwelijke voornamen (2013)"@nl . + "Vrouwelijke voornamen 2013"@nl . + "Vrouwelijke voornamen 2014"@nl . + "Vrouwelijke voornamen 2015"@nl . + "Vrouweljke voornamen 2015"@nl . + "Webcams"@nl . + "Wedstrijden"@nl . + "Wifi"@nl . + "Wijken"@nl . + "Wijken"@nl . + "Woningen binnen wijkcontracten van de Grondregie (2014)"@nl . + "Woningen van de grondregie"@nl . + "Initiatieven voor duurzame ontwikkeling : Zich ontspannen"@nl . + "Zwembaden"@nl . + "Zwembaden"@nl . + "Manneken-Pis"@nl . + "folklore"@nl . + "aanplak"@nl . + "afiche"@nl . + "poster"@nl . + "aanplak"@nl . + "affiche"@nl . + "poster"@nl . + "Internet"@nl . + "web"@nl . + "website"@nl . + "stedenbouw"@nl . + "vergunning"@nl . + "aanvraag"@nl . + "milieu"@nl . + "milieuvergunning"@nl . + "administratie"@nl . + "gemeente"@nl . + "afval"@nl . + "netheid"@nl . + "Android"@nl . + "app"@nl . + "applicatie"@nl . + "smartphone"@nl . + "apotheek"@nl . + "gezondheid"@nl . + "aptheek"@nl . + "gezondheid"@nl . + "pharmacie"@nl . + "santé"@nl . + "begraafplaats"@nl . + "kerkhof"@nl . + "bioscoop"@nl . + "cinema"@nl . + "buurthuis"@nl . + "K&G"@nl . + "Kind & Gezin"@nl . + "consultatie"@nl . + "gezondheid"@nl . + "kind"@nl . + "ONE"@nl . + "consultatie"@nl . + "gezondheid"@nl . + "kind"@nl . + "magazine"@nl . + "Cultuur"@nl . + "ur"@nl . + "dataset"@nl . + "echtscheiding"@nl . + "geboorte"@nl . + "huwelijk"@nl . + "overlijden"@nl . + "scheiding"@nl . + " ruil"@nl . + " uitwisseling "@nl . + "Burger"@nl . + "Burgerinitiatieven"@nl . + "Buurtcomité"@nl . + "Diensten"@nl . + "Lokaal ruilsysteem"@nl . + "Netwerk voor kennisuitwisseling"@nl . + "Netwerken "@nl . + "Netwerken voor uitwisseling en ruil"@nl . + "Verenigingsleven"@nl . + "Vzw"@nl . + "initiatieve"@nl . + "elektriciteit"@nl . + "energie"@nl . + "gas"@nl . + "water"@nl . + "BUURTWINKELS"@nl . + "Biowinkel"@nl . + "HORECA"@nl . + "Kruidenwinkel"@nl . + "Markt"@nl . + "Moestuin"@nl . + "SAGAL"@nl . + "STEDELIJKE LANDBOUW"@nl . + "Stadsboerderij"@nl . + "café"@nl . + "drinken"@nl . + "eten"@nl . + "restaurant"@nl . + "slowfood"@nl . + "Europa"@nl . + "internationaal"@nl . + "Europa"@nl . + "bevolking"@nl . + "man"@nl . + "Europa"@nl . + "bevolking"@nl . + "vrouw"@nl . + "Facebook"@nl . + "sociale netwerken"@nl . + "fontein"@nl . + "water"@nl . + "bib"@nl . + "bibliotheek"@nl . + "boek"@nl . + "franstalig"@nl . + "school"@nl . + "ATM"@nl . + "actualiteit"@nl . + "nieuws"@nl . + "basischool"@nl . + "onderwijs"@nl . + "school"@nl . + "kleuterschool"@nl . + "onderwijs"@nl . + "school"@nl . + "lager"@nl . + "onderwijs"@nl . + "school"@nl . + "middelbaar"@nl . + "onderwijs"@nl . + "school"@nl . + "secundair"@nl . + "gemeenteraad"@nl . + "stem"@nl . + "verkiezing"@nl . + "gemeenteraad"@nl . + "verkiezing"@nl . + "gemeenteraad"@nl . + "verkiezing"@nl . + "moto"@nl . + "parkeerplaats"@nl . + "parkeren"@nl . + "Internet"@nl . + "site web"@nl . + "website"@nl . + "weg"@nl . + "wegenis"@nl . + "container"@nl . + "glas"@nl . + "MIVB"@nl . + "bus"@nl . + "halte"@nl . + "metro"@nl . + "station"@nl . + "tram"@nl . + "Collecto"@nl . + "Herstelling "@nl . + "Taxi"@nl . + "Waterbus"@nl . + "fiets"@nl . + "Afvalinzameling"@nl . + "Compost"@nl . + "Container"@nl . + "Herstelling "@nl . + "Kleding"@nl . + "Kledingcontainer"@nl . + "Kringloopwinkel"@nl . + "Kurken"@nl . + "Plastic potten "@nl . + "Proxy chimik"@nl . + "Schoenmaker"@nl . + "Tweedehands"@nl . + "Weggeefmarkt"@nl . + "canisite"@nl . + "chien"@nl . + "propreté"@nl . + "enfance"@nl . + "enfant"@nl . + "milieu"@nl . + "milieuv"@nl . + "herberg"@nl . + "hotel"@nl . + "jeugd"@nl . + "toerisme"@nl . + "Cambre"@nl . + "Louise"@nl . + "district"@nl . + "quarter"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "jongen"@nl . + "voornaam"@nl . + "mannelijk"@nl . + "voornaam"@nl . + "handel"@nl . + "markt"@nl . + "Grondregie"@nl . + "woning"@nl . + "Gronregie"@nl . + "woning"@nl . + "14-18"@nl . + "1914-1918"@nl . + "monument"@nl . + "cultuur"@nl . + "musea"@nl . + "museum"@nl . + "tentoonstelling"@nl . + "bibliotheek"@nl . + "boek"@nl . + "school"@nl . + "nest"@nl . + "nestkast"@nl . + "vogel"@nl . + "vogelhuis"@nl . + "comité"@nl . + "NMBS"@nl . + "station"@nl . + "trein"@nl . + "ICT"@nl . + "Internet"@nl . + "OCR"@nl . + "hospitaal"@nl . + "ziekenhuis"@nl . + "boom"@nl . + "sport"@nl . + "terrein"@nl . + "auto"@nl . + "handicap"@nl . + "parkeren"@nl . + "natuur"@nl . + "park"@nl . + "auto"@nl . + "parkeerplaats"@nl . + "parking"@nl . + "bus"@nl . + "reisbus"@nl . + "toerisme"@nl . + "ICT"@nl . + "Internet"@nl . + "POIT"@nl . + "politie"@nl . + "veiligheid"@nl . + "Brussels Hoofdstedelijk Gewest"@nl . + "luchtkwaliteit"@nl . + "pollumeter"@nl . + "administratie"@nl . + "bevolking"@nl . + "demografie"@nl . + "OCMW"@nl . + "rusthuis"@nl . + "senior"@nl . + "verzorgingstehuis"@nl . + "auto"@nl . + "bewonerskaart"@nl . + "mobiliteit"@nl . + "parkeerkaart"@nl . + "parkeerplaats"@nl . + "smart city"@nl . + "Facebook"@nl . + "Flickr"@nl . + "Google+"@nl . + "LinkedIn"@nl . + "Pinterest"@nl . + "Twitter"@nl . + "social network"@nl . + "speeltuin"@nl . + "sport"@nl . + "sportzaal"@nl . + "stadion"@nl . + "Cambio"@nl . + "auto"@nl . + "auto"@nl . + "stembureau"@nl . + "verkiezing"@nl . + "Street Art"@nl . + "art"@nl . + "kunst"@nl . + "street art"@nl . + "Cultuur"@nl . + "fresco"@nl . + "muurtekening"@nl . + "personage"@nl . + "strip"@nl . + "stripmuren"@nl . + "stripmuur"@nl . + "telling"@nl . + "voertuig"@nl . + "taxi"@nl . + "schouwburg"@nl . + "theater"@nl . + "information"@nl . + "tourisme"@nl . + "touriste"@nl . + "bibliotheek"@nl . + "Open Data"@nl . + "RSS"@nl . + "job"@nl . + "vacature"@nl . + "werk"@nl . + "Grondregie"@nl . + "woning"@nl . + "mobiliteit"@nl . + "verkeer"@nl . + "verkeer"@nl . + "bevolking"@nl . + "demografie"@nl . + "emigratie"@nl . + "geboorte"@nl . + "immigratie"@nl . + "sterfte"@nl . + "bevolking"@nl . + "demografie"@nl . + "leeftijd"@nl . + "bevolking"@nl . + "senior"@nl . + "Villo!"@nl . + "fiets"@nl . + "Twitter"@nl . + "follower"@nl . + "volger"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "meisje"@nl . + "voornaam"@nl . + "voornaam"@nl . + "voornaam"@nl . + "webcam"@nl . + "RSS"@nl . + "wedstrijd"@nl . + "Internet"@nl . + "hotspot"@nl . + "wi-fi"@nl . + "wifi"@nl . + "quartier"@nl . + "quartier"@nl . + "Grondregie"@nl . + "woning"@nl . + "Informatiecentrum "@nl . + "Jeugdcentra"@nl . + "Jeugdhuizen "@nl . + "jongeren "@nl . + "zwembad"@nl . + "sport"@nl . + "zwembad"@nl . + "Actualités communales"@fr . + "Agenda du jour"@fr . + "Aires de jeux"@fr . + "Aires de stationnement taxi"@fr . + "Appels d'offres"@fr . + "Application Android"@fr . + "Arrêts STIB"@fr . + "Box à vélos"@fr . + "Affichage public"@fr . + "Arbres remarquables"@fr . + "Auberges de jeunesse et hôtels pour jeunes"@fr . + "Bibliothèques francophones"@fr . + "Bulles à verre"@fr . + "Bureaux de police"@fr . + "Bureaux de tourisme"@fr . + "Canisites"@fr . + "Centre administratif et bureaux de liaison"@fr . + "Cinémas"@fr . + "Colonnes d'affichage communal"@fr . + "Concours communaux"@fr . + "Distributeurs bancaires"@fr . + "Ecoles francophones"@fr . + "Bibliothèques néerlandophones"@fr . + "Elèves dans les écoles communales francophones"@fr . + "Plantes exotiques invasives"@fr . + "Installations sportives de la Ville de Bruxelles"@fr . + "Points d'accès publics à l'Internet"@fr . + "Institutions européennes"@fr . + "Lieux culturels"@fr . + "Maisons de quartier"@fr . + "Musées"@fr . + "Offres d'emploi"@fr . + "Parcours BD"@fr . + "Parcs et jardins publics"@fr . + "Parkings pour personnes handicapées"@fr . + "Parkings publics"@fr . + "Rues par secteur pour les cartes de riverain"@fr . + "Pages Facebook"@fr . + "Théâtres"@fr . + "Toilettes publiques"@fr . + "Top 100 des livres empruntés par bibliothèque (2013)"@fr . + "Urinoirs publics"@fr . + "Wifi"@fr . + "Bureaux de vote"@fr . + "Bus scolaires"@fr . + "Bourgmestres"@fr . + "Consommation énergétique"@fr . + "Projection de l'évolution de la population bruxelloise par tranches d'âge"@fr . + "Statistiques démographiques"@fr . + "Fontaines d'eau potable"@fr . + "Food trucks"@fr . + "Prénoms masculins (2013)"@fr . + "Logement moyen à la Régie foncière en 2014"@fr . + "Visites & visiteurs du site web"@fr . + "Pharmacies"@fr . + "Centres de jeunes de Bravvo"@fr . + "Cimetières"@fr . + "​Citoyens d'honneur"@fr . + "Collections des bibliothèques francophones en 2013"@fr . + "Collections des bibliothèques francophones en 2014"@fr . + "Collections des bibliothèques francophones en 2015"@fr . + "Comités Propreté"@fr . + "Comptages au carrefour van der Weyden - Stalingrad"@fr . + "Consultations pour enfants de K&G"@fr . + "Consultations pour enfants de l'ONE"@fr . + "Costumes de Manneken-Pis"@fr . + "Couvertures du Brusseleir"@fr . + "Crèches & prégardiennats"@fr . + "Déchets"@fr . + "Demandes de permis d'environnement délivrés"@fr . + "Demandes de permis d'environnement introduits"@fr . + "Demandes de permis d'urbanisme"@fr . + "Deux-roues motorisés"@fr . + "Ecoles artistiques francophones communales"@fr . + "Ecoles de promotion sociale francophones communales"@fr . + "Ecoles maternelles francophones communales"@fr . + "Ecoles néerlandophones"@fr . + "Ecoles primaires francophones communales"@fr . + "Ecoles secondaires francophones communales"@fr . + "Ecoles spécialisées francophones communales"@fr . + "Ecoles supérieures francophones communales"@fr . + "Elections communales (nombre de votes)"@fr . + "Elections communales (pourcentages)"@fr . + "Elections communales (sièges)"@fr . + "Espaces publics numériques (EPN)"@fr . + "Evénements trafic & travaux"@fr . + "Fiets/Vélo: fietszone in park / zones cyclables dans les parcs"@fr . + "Fiets/Vélo: gemarkeerde fietspaden – pistes cyclables marquées"@fr . + "Fiets/Vélo: verhoogde fietspaden - pistes cyclables surélevées"@fr . + "Fiets/Vélo : voetgangerszone-fietszone / piétonnier - zone cyclable"@fr . + "Fontaines d'eau potable"@fr . + "Gares SNCB"@fr . + "Guérites et bulles des Petits Riens"@fr . + "Habillages de Manneken-Pis"@fr . + "Hôpitaux publics"@fr . + "Logements contrats de quartier à la Régie foncière (2014)"@fr . + "Logements de la Régie foncière"@fr . + "Logements moyens (avec conditions de revenus) de la Régie foncière (2014)"@fr . + "Maisons de repos pour personnes âgées"@fr . + "Maisons des Enfants"@fr . + "Carte des ressources durables : Manger et boire"@fr . + "Manneken-Pis en Photos"@fr . + "Marchés de plein air"@fr . + "​Mise à jour des jeux de données"@fr . + "Monuments commémoratifs de la guerre de 1914-1918"@fr . + "Monuments funéraires du cimetière de Laeken"@fr . + "Musées à Bruxelles"@fr . + "Nichoirs"@fr . + "Niveaux de service de trafic"@fr . + "Origine géographique (communes belges) des visites du site web de la Ville (2014)"@fr . + "Origine géographique (communes belges) des visites du site web de la Ville (2015)"@fr . + "Origine géographique (communes belges) des visites du site web de la Ville (2013)"@fr . + "Origine géographique des visites du site web de la Ville (2009)"@fr . + "Origine géographique des visites du site web de la Ville (2010)"@fr . + "Origine géographique des visites du site web de la Ville (2011)"@fr . + "Origine géographique des visites du site web de la Ville (2012)"@fr . + "Origine géographique (pays) des visites du site web de la Ville (2013)"@fr . + "Origine géographique (pays) des visites du site web de la Ville (2014)"@fr . + "Pages du site web de la Ville (par titre) visitées en 2014"@fr . + "Pages du site web de la Ville (par titre) visitées en 2013"@fr . + "Pages du site web de la Ville (par titre) visitées en 2015"@fr . + "Pages du site web (par URL) de la Ville visitées en 2013"@fr . + "Pages du site web (par URL) de la Ville visitées en 2014"@fr . + "Pages du site web (par URL) de la Ville visitées en 2015"@fr . + "Parkings pour autocars touristiques"@fr . + "Patrimoine (logements) de la Régie foncière (2014)"@fr . + "Pavillons Seniors"@fr . + "Pharmacies"@fr . + "Piscines"@fr . + "Pollumètre"@fr . + "Population"@fr . + "Population bruxelloise"@fr . + "Population européenne masculine à Bruxelles"@fr . + "Population européenne féminine à Bruxelles"@fr . + "Poubelles intelligentes"@fr . + "PPAS"@fr . + "Prénoms féminins 2000"@fr . + "Prénoms féminins 2001"@fr . + "Prénoms féminins 2002"@fr . + "Prénoms féminins 2003"@fr . + "Prénoms féminins 2004"@fr . + "Prénoms féminins 2005"@fr . + "Prénoms féminins 2006"@fr . + "Prénoms féminins 2007"@fr . + "Prénoms féminins 2008"@fr . + "Prénoms féminins 2009"@fr . + "Prénoms féminins 2010"@fr . + "Prénoms féminins 2011"@fr . + "Prénoms féminins 2012"@fr . + "Prénoms féminins (2013)"@fr . + "Prénoms féminins 2013"@fr . + "Prénoms féminins 2014"@fr . + "Prénoms féminins 2015"@fr . + "Prénoms féminins 2015"@fr . + "Prénoms masculins 2000"@fr . + "Prénoms masculins 2001"@fr . + "Prénoms masculins 2002"@fr . + "Prénoms masculins 2003"@fr . + "Prénoms masculins 2004"@fr . + "Prénoms masculins 2005"@fr . + "Prénoms masculins 2006"@fr . + "Prénoms masculins 2007"@fr . + "Prénoms masculins 2008"@fr . + "Prénoms masculins 2009"@fr . + "Prénoms masculins 2010"@fr . + "Prénoms masculins 2011"@fr . + "Prénoms masculins 2012"@fr . + "Prénoms masculins 2013"@fr . + "Prénoms masculins 2014"@fr . + "Prénoms masculins 2015"@fr . + "Prenoms masculins 2015"@fr . + "Prêts dans les bibliothèques francophones en 2013"@fr . + "Prêts dans les bibliothèques francophones en 2014"@fr . + "Prêts dans les bibliothèques francophones en 2015"@fr . + "Projection de l'évolution de la population bruxelloise (avec soldes)"@fr . + "Projection de l'évolution de la population des seniors bruxellois"@fr . + "Quartiers"@fr . + "Références - Hôpitaux"@fr . + "Carte des ressources durables: Réparer, réutiliser, recycler, composter"@fr . + "Réseaux sociaux"@fr . + "Réservations dans les bibliothèques francophones en 2013"@fr . + "Réservations dans les bibliothèques francophones en 2014"@fr . + "Réservations dans les bibliothèques francophones en 2015"@fr . + "Jeu de données des jeux de données"@fr . + "Carte des ressources durables : Se déplacer (réparation de vélos, collecto & waterbus)"@fr . + "Carte des ressources durables: Centres de jeunes et centres d'informations des jeunes"@fr . + "Carte des ressources durables : S'entraider"@fr . + "None"@fr . + "Stationnement des poids lourds interdit"@fr . + "Stations Cambio"@fr . + "Stations Villo! & disponibilités en temps réel"@fr . + "Stations Zen Car"@fr . + "Street Art"@fr . + "Suiveurs des comptes Twitter"@fr . + "Terrains de pétanque"@fr . + "​Terrains multisports extérieurs"@fr . + "Fiets/Vélo: busbanen/fietsstroken - bandes bus/bandes cyclables"@fr . + "Top 100 de la Bibliothèque principale de Bruxelles 1 en 2013"@fr . + "Top 100 des livres empruntés par bibliothèque (2014)"@fr . + "Top 100 des livres empruntés par bibliothèque (2015)"@fr . + "None"@fr . + "Voetgangerszone"@fr . + "Voiries régionales"@fr . + "Webcams"@fr . + "None"@fr . + "None"@fr . + "actualité"@fr . + "actualités"@fr . + "information"@fr . + "agenda"@fr . + "annonce"@fr . + "information"@fr . + "enfant"@fr . + "jeu"@fr . + "taxi"@fr . + "appel d'offre"@fr . + "marché public"@fr . + "Android"@fr . + "app"@fr . + "application"@fr . + "smartphone"@fr . + "arrêt"@fr . + "bus"@fr . + "métro"@fr . + "station"@fr . + "tram"@fr . + "affichage"@fr . + "affiche"@fr . + "publicité"@fr . + " arbre"@fr . + " environnement"@fr . + " vert"@fr . + "nature"@fr . + "auberge de jeunesse"@fr . + "hôtel"@fr . + " lecture publique"@fr . + " livre"@fr . + "bibliothèque"@fr . + "bulle à verre"@fr . + "container"@fr . + "conteneur"@fr . + "déchet"@fr . + "commissariat"@fr . + "police"@fr . + "information"@fr . + "tourisme"@fr . + "touriste"@fr . + "canisite"@fr . + "chien"@fr . + "propreté"@fr . + "Centre administratif"@fr . + "administratif"@fr . + "bureau de liaison"@fr . + "cinéma"@fr . + "affichage"@fr . + "affiche"@fr . + "publicité"@fr . + "concours"@fr . + "ATM"@fr . + "argent"@fr . + "distributeur bancaire"@fr . + "finances"@fr . + "guichet automatique"@fr . + "enseignement"@fr . + "francophone"@fr . + "école"@fr . + "enseignement"@fr . + "néerlandophone"@fr . + "école"@fr . + "école"@fr . + "élève"@fr . + "étudiant"@fr . + "plante"@fr . + "Internet"@fr . + "PAPI"@fr . + "Europe"@fr . + "centre culturel"@fr . + "culture"@fr . + "musée"@fr . + "théâtre"@fr . + "maison de quartier"@fr . + "art"@fr . + "histoire"@fr . + "musée"@fr . + "emploi"@fr . + "job"@fr . + "travail"@fr . + " BD"@fr . + " bande dessinée"@fr . + " fresque"@fr . + " parcours BD"@fr . + "mur BD"@fr . + "espace vert"@fr . + "jardin public"@fr . + "nature"@fr . + "parc"@fr . + "PMR"@fr . + "handicap"@fr . + "handicapé"@fr . + "stationnement"@fr . + "voiture"@fr . + "parking"@fr . + "voiture"@fr . + "riverain"@fr . + "rue"@fr . + "stationnement"@fr . + "voiture"@fr . + "Facebook"@fr . + "réseaux sociaux"@fr . + "culture"@fr . + "théâtre"@fr . + "propreté"@fr . + "toilette"@fr . + "bibliothèque"@fr . + "lecture"@fr . + "livre"@fr . + "propreté"@fr . + "toilette"@fr . + "urinoir"@fr . + "Internet"@fr . + "wi-fi"@fr . + "wifi"@fr . + "vote"@fr . + "élection"@fr . + "autobus"@fr . + "bus"@fr . + "mobilité"@fr . + "parking"@fr . + "scolaire"@fr . + "école"@fr . + "bourgmestre"@fr . + "mayorat"@fr . + "eau"@fr . + "gaz"@fr . + "électricité"@fr . + "énergie"@fr . + "démographie"@fr . + "population"@fr . + "divorce"@fr . + "décès"@fr . + "mariage"@fr . + "naissance"@fr . + "séparation"@fr . + "eau"@fr . + "fontaine"@fr . + "homme"@fr . + "prénom"@fr . + "Régie foncière"@fr . + "logement"@fr . + "Google Analytics"@fr . + "Internet"@fr . + "TIC"@fr . + "site Internet"@fr . + "site web"@fr . + "pharmacie"@fr . + "santé"@fr . + "Bravvo"@fr . + "centre de jeunes"@fr . + "jeune"@fr . + "cimetière"@fr . + "citoyen d'honneur"@fr . + "bibliothèque"@fr . + "lecture publique"@fr . + "livre"@fr . + "bibliothèque"@fr . + "lecture publique"@fr . + "livre"@fr . + "bibliothèque"@fr . + "lecture publique"@fr . + "livre"@fr . + "citoyen"@fr . + "comité"@fr . + "propreté"@fr . + "comptage"@fr . + "mobilité"@fr . + "véhicule"@fr . + "enfant"@fr . + "santé"@fr . + "enfant"@fr . + "santé"@fr . + "Manneken-Mis"@fr . + "folklore"@fr . + "musée"@fr . + "magazine"@fr . + "revue"@fr . + "crèche"@fr . + "milieu d'accueil"@fr . + "petite enfance"@fr . + "prégardiennat"@fr . + "déchet"@fr . + "propreté"@fr . + "permis"@fr . + "urbanisme"@fr . + "permis"@fr . + "urbanisme"@fr . + "permis"@fr . + "urbanisme"@fr . + "moto"@fr . + "parking"@fr . + "stationnement"@fr . + "art"@fr . + "artistique"@fr . + "enseignement"@fr . + "école"@fr . + "enseignement"@fr . + "promotion sociale"@fr . + "école"@fr . + "enseignement"@fr . + "maternelle"@fr . + "école"@fr . + "enseignement"@fr . + "néerlandophone"@fr . + "école"@fr . + "enseignement"@fr . + "primaire"@fr . + "école"@fr . + "enseignement"@fr . + "secondaire"@fr . + "école"@fr . + "enseignement"@fr . + "maternel"@fr . + "primaire"@fr . + "spécial"@fr . + "spécialisé"@fr . + "école"@fr . + "enseignement"@fr . + "supérieure"@fr . + "école"@fr . + "conseil communal"@fr . + "élection"@fr . + "conseil communal"@fr . + "élection"@fr . + "conseil communal"@fr . + "élection"@fr . + "EPN"@fr . + "Internet"@fr . + "chantier"@fr . + "circulation"@fr . + "trafic"@fr . + "travaux"@fr . + "cycliste"@fr . + "fiets"@fr . + "vélo"@fr . + "cycliste"@fr . + "fiets"@fr . + "vélo"@fr . + "cycliste"@fr . + "fiets"@fr . + "vélo"@fr . + "WC"@fr . + "eau"@fr . + "fontaine"@fr . + "urinoir"@fr . + "SNCB"@fr . + "gare"@fr . + "train"@fr . + "Manneken-Pis"@fr . + "folklore"@fr . + "hôpital"@fr . + "santé"@fr . + "Régie foncière"@fr . + "logement"@fr . + "Régie foncière"@fr . + "logement"@fr . + "Régie foncière"@fr . + "logement"@fr . + "CPAS"@fr . + "maison de repos"@fr . + "maison de repos et de soins"@fr . + "senior"@fr . + "enfance"@fr . + "enfant"@fr . + "DEEE"@fr . + "Donnerie"@fr . + "bouchon"@fr . + "collecte"@fr . + "composter"@fr . + "don"@fr . + "donner"@fr . + "déchet"@fr . + "encombrant"@fr . + "informatique"@fr . + "offrir"@fr . + "oxfam"@fr . + "pile"@fr . + "propreté"@fr . + "proxy chimique"@fr . + "recycler"@fr . + "repair café"@fr . + "ressourcerie"@fr . + "retouche"@fr . + "réparer"@fr . + "réutiliser"@fr . + "vélo"@fr . + "vêtement"@fr . + "commerce"@fr . + "marché"@fr . + "Open Data"@fr . + "RSS"@fr . + "14-18"@fr . + "1914-1918"@fr . + "Première Guerre mondiale"@fr . + "buste"@fr . + "histoire"@fr . + "monument"@fr . + "patrimoine"@fr . + "statue"@fr . + "cimetière"@fr . + "funéraire"@fr . + "monument"@fr . + "tombe"@fr . + "tombeau"@fr . + "nichoir"@fr . + "nid"@fr . + "oiseau"@fr . + "circulation"@fr . + "route"@fr . + "trafic"@fr . + "site web"@fr . + "visite"@fr . + "site Internet"@fr . + "site web"@fr . + "site Internet"@fr . + "site web"@fr . + "site Internet"@fr . + "site web"@fr . + "site Internet"@fr . + "site web"@fr . + "site Internet"@fr . + "site web"@fr . + "site Internet"@fr . + "site web"@fr . + "autocar"@fr . + "tourisme"@fr . + "Régie foncière"@fr . + "logement"@fr . + "pharmacie"@fr . + "santé"@fr . + "natation"@fr . + "piscine"@fr . + "Région de Bruxelles-Capitale"@fr . + "pollumètre"@fr . + "pollution"@fr . + "habitant"@fr . + "population"@fr . + "administration"@fr . + "population"@fr . + "Europe"@fr . + "habitant"@fr . + "population"@fr . + "Europe"@fr . + "habitant"@fr . + "population"@fr . + "poubelle"@fr . + "propreté"@fr . + "smart city"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "femme"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "prénom"@fr . + "fille"@fr . + "féminin"@fr . + "naissance"@fr . + "prénom"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "prénom"@fr . + "garçon"@fr . + "masculin"@fr . + "prénom"@fr . + "masculin"@fr . + "prénom"@fr . + "bibliothèque"@fr . + "livre"@fr . + "prêt"@fr . + " lecture publique"@fr . + "bibliothèque"@fr . + "livre"@fr . + "prêt"@fr . + " lecture publique"@fr . + "bibliothèque"@fr . + "livre"@fr . + "prêt"@fr . + "décès"@fr . + "immigration"@fr . + "naissance"@fr . + "population"@fr . + "émigration"@fr . + "démographie"@fr . + "population"@fr . + "senior"@fr . + "quartier"@fr . + "GIAL"@fr . + "achat"@fr . + "marché"@fr . + "Cordonnerie"@fr . + "DEEE"@fr . + "bouchon"@fr . + "collecte"@fr . + "composter"@fr . + "don"@fr . + "donner"@fr . + "donnerie"@fr . + "déchet"@fr . + "encombrant"@fr . + "informatique"@fr . + "offrir"@fr . + "oxfam"@fr . + "pile"@fr . + "propreté"@fr . + "proxy chimique"@fr . + "recycler"@fr . + "repair café"@fr . + "ressourcerie"@fr . + "retouche"@fr . + "réparer"@fr . + "réutiliser"@fr . + "seconde main"@fr . + "vélo"@fr . + "vêtement"@fr . + "Facebook"@fr . + "Flickr"@fr . + "Google+"@fr . + "LinkedIn"@fr . + "Pinterest"@fr . + "Twitter"@fr . + "YouTube"@fr . + "réseau social"@fr . + "réseaux sociaux"@fr . + "bibliothèque"@fr . + "livre"@fr . + " lecture publique"@fr . + "bibliothèque"@fr . + "livre"@fr . + " lecture publique"@fr . + "bibliothèque"@fr . + "livre"@fr . + "jeu de données"@fr . + "bouger"@fr . + "collecto"@fr . + "déplacer"@fr . + "réparation"@fr . + "taxi"@fr . + "vélo"@fr . + "water bus"@fr . + "waterbus"@fr . + "aide"@fr . + "centre d'information des jeunes"@fr . + "centre de jeunes"@fr . + "information"@fr . + "jeune"@fr . + "maison de jeunes"@fr . + "musée"@fr . + "théâtre"@fr . + "ASBL"@fr . + "RES"@fr . + "SEL"@fr . + "accompagnement"@fr . + "accueil"@fr . + "aide"@fr . + "association"@fr . + "associative"@fr . + "auberge"@fr . + "comité de quatier "@fr . + "entraide"@fr . + "hebergement"@fr . + "initiative"@fr . + "initiative citoyenne"@fr . + "réseaux d'échange de savoir"@fr . + "service"@fr . + "soutien"@fr . + "système d'échange local"@fr . + "camion"@fr . + "mobilité"@fr . + "poids lourds"@fr . + "transport"@fr . + "Cambio"@fr . + "auto"@fr . + "voiture"@fr . + "Villo!"@fr . + "vélo"@fr . + "voiture"@fr . + "art"@fr . + "street art"@fr . + "Twitter"@fr . + "follower"@fr . + "sport"@fr . + "terrain de sport"@fr . + "cycliste"@fr . + "fiets"@fr . + "vélo"@fr . + "bibliothèque"@fr . + "lecture"@fr . + "livre"@fr . + "prêt"@fr . + " lecture publique"@fr . + "bibliothèque"@fr . + "lecture"@fr . + "livre"@fr . + " lecture publique"@fr . + "bibliothèque"@fr . + "lecture"@fr . + "livre"@fr . + "caméra"@fr . + "webcam"@fr . + "Administrative centre and liaison offices"@en . + "Advertising columns"@en . + "Agenda of the day"@en . + "Android application"@en . + "Applications for a planning permit"@en . + "Applications for a supplied environmental permit"@en . + "ATMs"@en . + "Bicycle boxes"@en . + "Billboards"@en . + "Geographical origin of the visits to the website of the City of Brussels (2009)"@en . + "Urinals"@en . + "Cambio stations"@en . + "Cemeteries"@en . + "Children's Homes"@en . + "Cinemas"@en . + "Cleanliness Committees"@en . + "Comic book route"@en . + "Community centres"@en . + "Consultations for children at K&G"@en . + "Consultations for children at ONE"@en . + "Counts at the crossroad Van der Weyden - Stalingrad"@en . + "Covers of the Brusseleir"@en . + "Cultural places"@en . + "Data set of the data sets"@en . + "Demographic statistics"@en . + "Dog toilets"@en . + "Dressings of Manneken-Pis"@en . + "Dutch-language libraries"@en . + "Energy consumption"@en . + "European female population in Brussels"@en . + "European institutions"@en . + "European male population in Brussels"@en . + "Expected evolution of the Brussels population by age"@en . + "Expected evolution of the Brussels seniors population"@en . + "Expected evolution of the population of Brussels (with growth)"@en . + "Facebook pages"@en . + "Female first names 2000"@en . + "Female first names 2001"@en . + "Female first names 2002"@en . + "Female first names 2003"@en . + "Female first names 2004"@en . + "Female first names 2005"@en . + "Female first names 2006"@en . + "Female first names 2007"@en . + "Female first names 2008"@en . + "Female first names 2009"@en . + "Female first names 2010"@en . + "Female first names 2011"@en . + "Female first names 2012"@en . + "Female first names (2013)"@en . + "Female first names 2013"@en . + "Female first names 2014"@en . + "Female first names 2015"@en . + "Female first names 2015"@en . + "Food trucks"@en . + "French-language libraries"@en . + "French-language schools"@en . + "Geographical origin (belgian municipalities) of the visits to the website of the City (2015)"@en . + "Geographical origin (belgian municipalities) of the visits to the website of the City (2014)"@en . + "Geographical origin (belgian municipalities) of the visits to the website of the City (2013)"@en . + "Geographical origin of the visits to the website of the City of Brussels (2011)"@en . + "Geographical origin of the visits to the website of the City of Brussels (2012)"@en . + "Geographical origin of the visits to the website of the City of Brussels (2010)"@en . + "Geographical origin of the visits to the website of the City of Brussels (2013)"@en . + "Geographical origin of the visits to the website of the City of Brussels (2014)"@en . + "Geographical origin of the visits to the website of the City of Brussels (2015)"@en . + "Glass containers"@en . + "Haren quarter"@en . + "Homes of the Property Management Agency"@en . + "Honorary citizens"@en . + "Invasive exotic plants"@en . + "Job offers"@en . + "Laeken quarter"@en . + "Male first names 2000"@en . + "Male first names 2001"@en . + "Male first names 2002"@en . + "Male first names 2003"@en . + "Male first names 2004"@en . + "Male first names 2005"@en . + "Male first names 2006"@en . + "Male first names 2007"@en . + "Male first names 2008"@en . + "Male first names 2009"@en . + "Male first names 2010"@en . + "Male first names 2011"@en . + "Male first names 2012"@en . + "Male first names (2013)"@en . + "Male first names 2013"@en . + "Male first names 2014"@en . + "Male first names 2015"@en . + "Male first names 2015"@en . + "Markets"@en . + "Mayors"@en . + "Motorized two-wheelers"@en . + "Municipal contests"@en . + "Municipal elections (number of votes)"@en . + "Municipal elections (percentages)"@en . + "Municipal elections (seats)"@en . + "Municipal News"@en . + "Municipal news"@en . + "Museums in Brussels"@en . + "Museums"@en . + "Neder-over-Heembeek quarter"@en . + "Nest boxes"@en . + "North quarter"@en . + "Northeast quarter"@en . + "Nurseries & kindergartens"@en . + "Nursing homes for elderly"@en . + "Outdoor multisports grounds"@en . + "Parking spaces for disabled"@en . + "Parkings for coaches"@en . + "Parks"@en . + "Pentagon quarter"@en . + "Petanque courts"@en . + "Pharmacies"@en . + "Pharmacies"@en . + "Playgrounds"@en . + "Police stations"@en . + "Polling stations"@en . + "Pollumeter"@en . + "Population of Brussels"@en . + "​Public computer rooms"@en . + "Public hospitals"@en . + "Public Internet access points"@en . + "Public parkings"@en . + "Public toilets"@en . + "Regional roads"@en . + "Remarkable trees"@en . + "Right bank of the canal quarter"@en . + "Government contracts"@en . + "Sectors of the local resident card"@en . + "Seniors Pavilions"@en . + "Smart waste bins"@en . + "SNCB stations"@en . + "Social networks"@en . + "Sports halls and stadiums"@en . + "STIB stops"@en . + "Street Art"@en . + "Submitted applications for an environmental permit"@en . + "Swimming pools"@en . + "Taxi stands"@en . + "Theatres"@en . + "Tourist offices"@en . + "Traffic during events & works"@en . + "Traffic volume"@en . + "Twitter followers"@en . + "Updates of the data sets"@en . + "Villo! stations & availability in real time"@en . + "Visited web pages of the City (by title) in 2013"@en . + "Visited web pages of the City (by title) in 2014"@en . + "Visited web pages of the City (by title) in 2015"@en . + "Visited web pages of the City (by URL) in 2014"@en . + "Visited web pages of the City (by URL) in 2015"@en . + "Visited web pages of the City (by URL) in 2013"@en . + "Visitors & visits City of Brussels website"@en . + "Waste"@en . + "Webcams"@en . + "Wifi"@en . + "World War I memorials"@en . + "Youth hostels"@en . + "Zen Car stations"@en . + "administration"@en . + "municipality"@en . + "office"@en . + "advertisement"@en . + "advertising"@en . + "billboard"@en . + "culture"@en . + "poster"@en . + "Android"@en . + "app"@en . + "application"@en . + "smartphone"@en . + "permit"@en . + "planning"@en . + "environment"@en . + "permit"@en . + "billboard"@en . + "culture"@en . + "information"@en . + "poster"@en . + "Internet"@en . + "website"@en . + "Cambio"@en . + "car"@en . + "cemetery"@en . + "church yard"@en . + "churchyard"@en . + "graveyard"@en . + "enfance"@en . + "enfant"@en . + "cinema"@en . + "film"@en . + "leisure"@en . + "movie"@en . + "theatre"@en . + "citizen"@en . + "cleanliness"@en . + "committee"@en . + "comic"@en . + "culture"@en . + "strip"@en . + "tourisme"@en . + "Community centre"@en . + "K&G"@en . + "child"@en . + "health"@en . + "child"@en . + "health"@en . + "mobility"@en . + "vehicle"@en . + "magazine"@en . + "revue"@en . + "culture"@en . + "museum"@en . + "theatre"@en . + "dataset"@en . + "cleanliness"@en . + "dog"@en . + "toilet"@en . + "Manneken-Pis"@en . + "folklore"@en . + "book"@en . + "library"@en . + "electricity"@en . + "energy"@en . + "gas"@en . + "water"@en . + "Europe"@en . + "population"@en . + "woman"@en . + "Europe"@en . + "commission"@en . + "institution"@en . + "parliament"@en . + "Europe"@en . + "man"@en . + "population"@en . + "age"@en . + "demography"@en . + "population"@en . + "demography"@en . + "population"@en . + "senior"@en . + "birth"@en . + "death"@en . + "emigration"@en . + "immigration"@en . + "migration"@en . + "population"@en . + "Facebook"@en . + "social networks"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "first name"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "girl"@en . + "female"@en . + "first name"@en . + "voornaam"@en . + "female"@en . + "first name"@en . + "book"@en . + "culture"@en . + "library"@en . + "reading"@en . + "school"@en . + "container"@en . + "glass"@en . + "Haren"@en . + "district"@en . + "quarter"@en . + "town"@en . + "Régie foncière"@en . + "environment"@en . + "nature"@en . + "plant"@en . + "employment"@en . + "job"@en . + "Laeken"@en . + "district"@en . + "quarter"@en . + "town"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "first name"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "birth"@en . + "first name"@en . + "male"@en . + "first name"@en . + "male"@en . + "commerce"@en . + "market"@en . + "moto"@en . + "parking"@en . + "contest"@en . + "election"@en . + "conseil communal"@en . + "élection"@en . + "election"@en . + "information"@en . + "news"@en . + "news"@en . + "culture"@en . + "heritage"@en . + "museum"@en . + "Neder-Over-Heembeek"@en . + "district"@en . + "quarter"@en . + "town"@en . + "bird"@en . + "birdhouse"@en . + "nest"@en . + "nestbox"@en . + "district"@en . + "quarter"@en . + "district"@en . + "quarter"@en . + "kindergarten"@en . + "nursery"@en . + "CPAS"@en . + "rest home"@en . + "retirement"@en . + "senior"@en . + "ground"@en . + "multisport"@en . + "sport"@en . + "accessibility"@en . + "disabled"@en . + "handicap"@en . + "autocar"@en . + "bus"@en . + "parking"@en . + "tourisme"@en . + "green"@en . + "nature"@en . + "park"@en . + "public garden"@en . + "Pentagon"@en . + "district"@en . + "quarter"@en . + "health"@en . + "pharmacy"@en . + "health"@en . + "pharmacy"@en . + "child"@en . + "playground"@en . + "police"@en . + "safety"@en . + "security"@en . + "election"@en . + "poll"@en . + "vote"@en . + "Région de Bruxelles-Capitale"@en . + "pollumètre"@en . + "pollution"@en . + "administration"@en . + "demography"@en . + "population"@en . + "ICT"@en . + "computer"@en . + "health"@en . + "hospital"@en . + "ICT"@en . + "Internet"@en . + "PIAP"@en . + "car"@en . + "park"@en . + "parking"@en . + "tree"@en . + "canal"@en . + "district"@en . + "quarter"@en . + "contract"@en . + . + "car"@en . + "mobility"@en . + "parking"@en . + "senior"@en . + . + "smart city"@en . + "waste"@en . + "waste bin"@en . + "SNCB"@en . + "station"@en . + "train"@en . + "Facebook"@en . + "Flickr"@en . + "Google+"@en . + "LinkedIn"@en . + "Pinterest"@en . + "Twitter"@en . + "YouTube"@en . + "social network"@en . + "STIB"@en . + "bus"@en . + "metro"@en . + "station"@en . + "stop"@en . + "tram"@en . + . + "Street Art"@en . + "art"@en . + "street art"@en . + "environment"@en . + "permit"@en . + "swimming pool"@en . + "taxi"@en . + . + "theater"@en . + "theatre"@en . + "information"@en . + "touriste"@en . + "traffic"@en . + "road"@en . + "traffic"@en . + "Twitter"@en . + "follower"@en . + "Open Data"@en . + "RSS"@en . + "Villo!"@en . + "bike"@en . + "Internet"@en . + "visit"@en . + "visitor"@en . + "web"@en . + "website"@en . + . + "cleanliness"@en . + "waste"@en . + "webcam"@en . + "Internet"@en . + "wi-fi"@en . + "wifi"@en . + . + "hostel"@en . + "tourism"@en . + "tourist"@en . + "youth"@en . + "auto"@en . + "car"@en . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Bourgmestres de la Ville de Bruxelles avec la mention des dates de naissance et de décès, les dates du début de mandat et d''entrée en fonction de bourgmestre (ff), les dates des arrêtés royaux de nomination, des installations, des fins de fonction ou démissions.\n"@fr . + "Consommation énergétique de la Ville de Bruxelles depuis 2008.\n"@fr . + "Sur la base de la population bruxelloise en 2012, une projection de l'évolution de la population de la Ville de Bruxelles jusqu'en 2024 par tranches d'âge.\n"@fr . + "Statistiques relatives aux naissances, mariages, divorces et séparations, décès à la Ville de Bruxelles\n"@fr . + "Fontaines d'eau potable sur le territoire de la Ville de Bruxelles recensées par Infirmiers de Rue ASBL.\n"@fr . + "Localisation des emplacements pour les food trucks sélectionnés par la Ville de Bruxelles.\n"@fr . + "Prénoms des hommes en Région de Bruxelles-Capitale en 2013.\n"@fr . + "Situation du logement moyen de la Régie foncière au 31 décembre 2014.\n"@fr . + "Visites et visiteurs du site web de la Ville de Bruxelles (www.bruxelles.be).\n"@fr . + "Localisation des pharmacies sur le territoire de la Ville de Bruxelles à partir d'OpenStreetMap (OSM).\n"@fr . + "Localisation des centres de jeunes du réseau géré par Bravvo et s'adressant aux 12-18 ans.\n"@fr . + "Cimetières de la Ville de Bruxelles avec la localisation de l'entrée, les jours et heures d'ouverture et les coordonnées téléphoniques.\n"@fr . + "Personnalités auxquelles la Ville de Bruxelles a décerné le titre de citoyen d'honneur (avec mention de la date de la remise de la distinction).\n"@fr . + "Etat des collections (par types de documents) des bibliothèques francophones de la Ville de Bruxelles au 31 décembre 2013.\n"@fr . + "Etat des collections (par types de documents) des bibliothèques francophones de la Ville de Bruxelles au 31 décembre 2014.\n"@fr . + "Etat des collections (par types de documents) des bibliothèques francophones de la Ville de Bruxelles au 31 décembre 2015.\n"@fr . + "Informations sur les Comités Propreté de la Ville de Bruxelles (nom du comité, de la structure d'accueil, adresse).\n"@fr . + "Comptages manuels des mouvements (de véhicules légers, de poids lourds, de bus ou autocars, de motos, de vélos ou en équivalents véhicules personnels) au carrefour de la rue Roger van der Weyden et de l'avenue de Stalingrad.\n"@fr . + "Localisation des consultations pour enfants (0-3 ans) de K&G (Kind & Gezin) sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des consultations pour enfants (0-6 ans) de l'ONE (Office de la Naissance et de l'Enfance - francophone) sur le territoire de la Ville de Bruxelles.\n"@fr . + "L'inventaire des costumes de Manneken-Pis conservés au Musée de la Ville de Bruxelles avec date ou période de remise du costume.\n"@fr . + "Couvertures du magazine de la Ville de Bruxelles, \"Le Brusseleir\".\n"@fr . + "Localisation des crèches & prégardiennats de la Ville de Bruxelles avec mention de l'organisme de reconnaissance (ONE, Kind & Gezin), du nombre de place, des heures d'ouverture.\n"@fr . + "Nombres de tonnes de déchets évacués par le service Propreté publique de la Ville de Bruxelles pour être versés en décharge publique, recyclés ou incinérés.\n"@fr . + "Les demandes de permis d'environnement délivrés par le département Urbanisme de la Ville de Bruxelles depuis 2010.\nIl est à noter que les permis délivrés concernent souvent des demandes introduites au cours des années précédentes. \nLes chiffres retenus ici concernent les permis délivrés résultant de demandes introduites au cours des 3 années précédentes.\n"@fr . + "Les demandes de permis d'environnement introduits auprès du département Urbanisme de la Ville de Bruxelles depuis 2010.\nLes permis délivrés concernent souvent des demandes introduites au cours des années précédentes. Les chiffres retenus ici concernent les permis délivrés résultant de demandes introduites au cours des 3 années précédentes.\n"@fr . + "Les demandes de permis d'urbanisme à la Ville de Bruxelles par destination du bien depuis 2003.\n"@fr . + "Emplacements pour le stationnement des deux-roues motorisés sur le territoire de la Ville de Bruxelles.\n"@fr . + "Etablissement d'enseignement artistique francophone du réseau scolaire de la Ville de Bruxelles.\n"@fr . + "Ecoles de promotion sociale francophones du réseau scolaire de la Ville de Bruxelles.\n"@fr . + "Ecoles maternelles francophones du réseau scolaire de la Ville de Bruxelles.\n"@fr . + "Localisation des écoles néerlandophones de la Ville de Bruxelles.\n"@fr . + "Enseignement primaire francophone de la Ville de Bruxelles.\n"@fr . + "Ecoles secondaires francophones du réseau scolaire de la Ville de Bruxelles.\n"@fr . + "Enseignement spécialisé maternel et primaire francophone de la Ville de Bruxelles.\n"@fr . + "Ecoles supérieures francophones du réseau scolaire de la Ville de Bruxelles.\n"@fr . + "Résultat des élections communales à Bruxelles depuis 2000 (nombre de votes par liste).\n"@fr . + "Résultat des élections communales à Bruxelles depuis 2000 (pourcentages des votes par liste).\n"@fr . + "Résultat des élections communales à Bruxelles depuis 2000 (nombre de sièges par liste)\n"@fr . + "Localisation des espaces publics numériques (EPN) de la Ville de Bruxelles.\n"@fr . + "Evénements trafic (incidents, files...), ainsi que les chantiers perturbants en Région de Bruxelles-Capitale.\n"@fr . + "Fietsgemarkeerde fietspadenVélo: pistes cyclables marquées\n\n\n"@fr . + "Fiets: verhoogde fietspaden - Vélo : pistes cyclables surélevées\n\n\n"@fr . + "Fiets: voetgangerszone-fietszoneVélo : piétonnier - zone cyclable\n\n"@fr . + "Localisation des fontaines d'eau potable, WC et urinoirs gratuits.\n"@fr . + "Gares de chemins de fer (SNCB) avec les gares de départ et de destination.\n"@fr . + "Guérites et bulles mises en place par Les Petits Riens pour la collecte de textiles usagés (vêtements en bon état).\n"@fr . + "Jours et heures d'habillage de Manneken-Pis (avec le nom du costume, sa référence, le contexte de cet habillage).\n"@fr . + "Hôpitaux publics (membres du réseau IRIS) sur le territoire de la Ville de Bruxelles.\n"@fr . + "Situation du logement contrats de quartier de la Régie foncière au 31 décembre 2014.\n"@fr . + "Localisation et répartition par quartier des logements de la Régie foncière avec le nombre d'unités par logement.\n"@fr . + "Situation du logement moyen (avec conditions de revenus) de la Régie foncière au 31 décembre 2014\n"@fr . + "Localisations et informations sur les maisons de repos (MR) et les maisons de repos et de soins (MRS) du Centre Public d'Action Sociale (CPAS de Bruxelles.\n"@fr . + "Localisation des Maisons des Enfants à destination des jeunes Bruxellois (6-12 ans).\n"@fr . + "Localisation des différents lieux qui promeuvent une alimentation durable  (restaurants, marchés, potagers, fermes, magasins, Gasap, etc.) sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des marchés de plein air sur le territoire de la Ville de Bruxelles.\n"@fr . + "Flux RSS avec la mise à jour des jeux de données de la\nplateforme opendata.bruxelles.be : opendata.bruxelles.be/api/datasets/1.0/search?format=rss&sort;=modified\n"@fr . + "Monuments de la Ville de Bruxelles commémorant la Première Guerre mondiale.\n"@fr . + "Quelques monuments et tombeaux du cimetière de Laeken à Bruxelles.\n"@fr . + "Localisation des musées présents sur le territoire de la Ville de Bruxelles (y compris les musées de la Ville de Bruxelles).\n"@fr . + "Localisation des nichoirs installés par la Ville de Bruxelles.\n"@fr . + "Niveaux de service de trafic sur certaines parties du réseau routier de la Région de Bruxelles-Capitale.\n"@fr . + "Villes (communes) d'origine des visiteurs du site web de la Ville de Bruxelles en provenance de Belgique, déterminées par leur adresse iP. Source: Google Analytics.\n"@fr . + "Villes (communes) d'origine des visiteurs du site web de la Ville de Bruxelles en provenance de Belgique, déterminées par leur adresse iP. Source: Google Analytics.\n"@fr . + "Villes (communes) d'origine des visiteurs du site web de la Ville de Bruxelles en provenance de Belgique, déterminées par leur adresse iP. Source: Google Analytics.\n"@fr . + "Pays et territoires des visiteurs du site web de la Ville de Bruxelles (2009) déterminés par la zone géographique associée à leur adresse IP. Source: Google Analytics.\n"@fr . + "Pays et territoires des visiteurs du site web de la Ville de Bruxelles (2010) déterminés par la zone géographique associée à leur adresse IP. Source: Google Analytics.\n"@fr . + "Pays et territoires des visiteurs du site web de la Ville de Bruxelles (2011) déterminés par la zone géographique associée à leur adresse IP. Source: Google Analytics.\n"@fr . + "Pays et territoires des visiteurs du site web de la Ville de Bruxelles déterminés par la zone géographique associée à leur adresse IP (2012). Source: Google Analytics.\n"@fr . + "Pays et territoires des visiteurs du site web de la Ville de Bruxelles (2013) déterminés par la zone géographique associée à leur adresse IP. Source: Google Analytics.\n"@fr . + "Pays et territoires des visiteurs du site web de la Ville de Bruxelles (2014) déterminés par la zone géographique associée à leur adresse IP. Source: Google Analytics.\n"@fr . + "Statistiques de fréquentation des pages du site web de la Ville de Bruxelles (en 2014) avec le titre de la page, le nombre de pages vues, le nombre de consultations uniques, le temps moyen passé sur la page. Source: Google Analytics.\nAttention: certaines pages du site web de la Ville ont pu être rendues invisibles.\n"@fr . + "Statistiques de fréquentation des pages du site web de la Ville de Bruxelles (en 2013) avec le titre de la page, le nombre de pages vues, le nombre de consultations uniques, le temps moyen passé sur la page. Source: Google Analytics.\nAttention: certaines pages du site web de la Ville ont pu être rendues invisibles.\n"@fr . + "Statistiques de fréquentation des pages du site web de la Ville de Bruxelles (en 2015) avec le titre de la page, le nombre de pages vues, le nombre de consultations uniques, le temps moyen passé sur la page. Source: Google Analytics.\nAttention: certaines pages du site web de la Ville ont pu être rendues invisibles.\n"@fr . + "Statistiques de fréquentation des pages du site web de la Ville de Bruxelles avec l'adresse de la page (suivant www.bruxelles.be, le nombre de pages vues, le nombre de consultations uniques, le temps moyen passé sur la page. Source: Google Analytics.\nChaque page du site web de la Ville de Bruxelles est identifiée par 4 chiffres figurant dans son adresse. Une recherche sur ces 4 chiffres permet d'obtenir les statistiques relatives à la page considérée.\n"@fr . + "Statistiques de fréquentation des pages du site web de la Ville de Bruxelles (en 2014) avec l'adresse de la page (suivant www.bruxelles.be, le nombre de pages vues, le nombre de consultations uniques, le temps moyen passé sur la page. Source: Google Analytics.\nChaque page du site web de la Ville de Bruxelles est identifiée par 4 chiffres figurant dans son adresse. Une recherche sur ces 4 chiffres permet d'obtenir les statistiques relatives à la page considérée.\n"@fr . + "Statistiques de fréquentation des pages du site web de la Ville de Bruxelles (en 2015) avec l'adresse de la page (suivant www.bruxelles.be, le nombre de pages vues, le nombre de consultations uniques). Source: Google Analytics.\nChaque page du site web de la Ville de Bruxelles est identifiée par 4 chiffres figurant dans son adresse. Une recherche sur ces 4 chiffres permet d'obtenir les statistiques relatives à la page considérée.\n"@fr . + "Localisation des emplacements de stationnement pour les autocars de tourisme sur le territoire de la Ville de Bruxelles.\n"@fr . + "Patrimoine (logements) de la Régie foncière au 31 décembre 2014\n"@fr . + "Localisation et horaires des Pavillons Seniors de la Ville de Bruxelles.\n"@fr . + "Pharmacies sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des piscines de la Ville de Bruxelles.\n"@fr . + "Pollumètre de la Région de Bruxelles-Capitale permettant de suivre en permanence l'évolution de la qualité de l'air avec un indice global calculé en rassemblant les données mesurées par toutes les stations pour l'ozone (O3), le dioxyde d'azote (NO2), le dioxyde de soufre (SO2) et les poussières (PM10 ): www.ibgebim.be/Pollumetre/dynamic_index.txt. \nBruxelles Environnement demande d'effectuer la récupération des données en rapatriant l'information périodiquement (à l'heure 35) et non en venant la chercher à chaque ouverture de la page du site contenant le pollumètre).\n"@fr . + "Population résidente de droit à Bruxelles au 1er janvier, par année.\n"@fr . + "Chiffres totaux (depuis 1921) de la population de la Ville de Bruxelles tels qu'enregistrés par le département Démographie.\n"@fr . + "Evolution de la population originaire des différents pays d'Europe (hommes) à Bruxelles (Ville de Bruxelles) pour la période 2009-2014. \n"@fr . + "Evolution de la population originaire des différents pays d'Europe (femmes à Bruxelles (Ville de Bruxelles) pour la période 2009-2014. \n"@fr . + "Poubelles intelligentes de la Ville de Bruxelles.\n"@fr . + "Plans particuliers d'affectation du sol (PPAS) de la Ville de Bruxelles\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2000.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2001.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2002.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2003.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2004.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2005.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2006.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2007.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2008.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2009.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2010.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2011.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2012.\n"@fr . + "Prénoms des femmes en Région de Bruxelles-Capitale en 2013.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2013.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2014.\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2015 (jusqu'au 27 octobre).\n"@fr . + "Prénoms féminins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2015.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2000.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2001.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2002.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2003.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2004.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2005.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2006.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2007.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2008.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2009.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2010.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2011.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2012.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2013.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2014.\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2015 (jusqu'au 27 octobre).\n"@fr . + "Prénoms masculins enregistrés auprès de la Ville de Bruxelles au cours de l'année 2015.\n"@fr . + "Statistiques des prêts dans les bibliothèques francophones de la Ville de Bruxelles en 2013.\n"@fr . + "Statistiques des prêts dans les bibliothèques francophones de la Ville de Bruxelles en 2014.\n"@fr . + "Statistiques des prêts dans les bibliothèques francophones de la Ville de Bruxelles en 2015.\n"@fr . + "Sur la base de la population bruxelloise en 2012, une projection de l'évolution de la population de la Ville de Bruxelles jusqu'en 2024, tenant compte du solde naturel (naissances - décès) et du solde migratoire (immigration - émigration).\n"@fr . + "Sur la base de la population bruxelloise en 2012, une\nprojection de l'évolution de la population de la Ville de Bruxelles jusqu'en\n2024 par tranches d'âge.\n"@fr . + "Carte des quartiers de Bruxelles (Pentagone, Haren, Laeken, Louise, Neder-Over-Heembeek, Nord, Nord-Est, Rive droite du canal).\n"@fr . + "Références de la centrale d'achat et de marchés informatiques de GIAL, centre informatique de la Ville de Bruxelles.\n"@fr . + "Localisation des différents lieux de collecte d'objets réutilisables (vêtements, informatique, etc.); de déchets ( d'encombrants, DEE,  produits chimiques, etc.); de composts; de ventes de seconde main et de réparation de toutes sortes sur le territoire de la Ville de Bruxelles.\n"@fr . + "Cadastre (non officiel) des comptes et pages de la Ville de Bruxelles (Ville, départements, services, bibliothèques, écoles, autres institutions, ASBL, actions, évènements) sur les réseaux sociaux (Facebook, Twitter, Google+, LinkedIn, Pinterest, Flickr, YouTube).\n"@fr . + "Le classement des réservations de documents dans les bibliothèques francophones de la Ville de Bruxelles en 2013.\n"@fr . + "Le classement des réservations de documents dans les bibliothèques francophones de la Ville de Bruxelles en 2014.\n"@fr . + "Le classement des réservations de documents dans les bibliothèques francophones de la Ville de Bruxelles en 2015.\n"@fr . + "Jeu de données des jeux de données de la plateforme Open Data de la Ville de Bruxelles.\n"@fr . + "Localisation des différents arrêts collecto, waterbus ainsi que les lieux de réparation de vélos sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des maisons de jeunes, centres de jeunes et centre d'informations des jeunes présents sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des différents comités de quatier, ASBL, réseaux d'échange de savoir, systémes d'échange locaux, initiatives citoyennes et accompagnements durables sur le territoire de la Ville de Bruxelles. \n"@fr . + "Localisation des stations de voitures partagées Cambio.\n"@fr . + "Localisation des stations Villo! (vélo partagé) en Région de Bruxelles-Capitale avec l'indication des disponibilités (vélos, emplacements) en temps réel.\n"@fr . + "Localisation des stations de voitures partagées électriques Zen Car sur le territoire de la Ville de Bruxelles.\n"@fr . + "Initiatives Street Art soutenues par la Ville de Bruxelles.\n"@fr . + "\n Suiveurs (followers) des comptes Twitter Ville de Bruxelles en français, néerlandais et anglais.\n"@fr . + "Terrains de pétanque de la Ville de Bruxelles.\n"@fr . + "Terrains multisports extérieurs de la Ville de Bruxelles (avec adresse et description de l'équipement).\n"@fr . + "Fiets: busbanen/fietsstrokenVélo : bandes bus/bandes cyclables\n\n\n"@fr . + "Classement des prêts de la Bibliothèque principale de Bruxelles 1 (Riches Claires) en 2013.\n"@fr . + "Classement par bibliothèque des livres les plus empruntés dans les bibliothèques de la Ville de Bruxelles en 2014.\n"@fr . + "Classement par bibliothèque des livres les plus empruntés dans les bibliothèques de la Ville de Bruxelles en 2015.\n"@fr . + "Voiries gérées par la Région de Bruxelles-Capitale. Certaines voiries (chemins, rues, chaussées, avenues, boulevards, places…) sont situées sur le territoire de la Ville de Bruxelles (BXL); d'autres peuvent le jouxter.\n"@fr . + "Localisation des webcams permanentes accessibles depuis le site web de la Ville de Bruxelles.\n"@fr . + "Location of the Administrative centre and liaison offices of the City of Brussels.\n"@en . + "Location of advertising columns of the City of Brussels.\n"@en . + "Announcement of the activities of the day published on the website of the City of Brussels.\n"@en . + "Statistics of the number of downloads of the Android\napplication of the City of Brussels. A new version of the Android application of the City of Brussels was presented in March 2015.\n"@en . + "Applications for a planning permit by the City of\nBrussels by destination of the property and since 2003. \n"@en . + "Applications for a supplied environmental permit provided\nby the City of Brussels by destination of the property and since 2003. \n"@en . + "Location of ATMS on the territory of the City of Brussels\n"@en . + "Location of bicycle boxes on the territory of the City of Brussels.\n"@en . + "Location of billboards of the City of Brussels\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2011), determined by the geographical area of their IP address.. Source: Google Analytics."@en . + "Location of public urinals (toilets for men) on the territory of the City of Brussels.\n"@en . + "Location of the Cambio carsharing stations. \n"@en . + "Cemeteries of the City of Brussels with the location of the entrance, opening days and hours and telephone numbers.\n"@en . + "Location of the Children's Homes (Maisons des Enfants) intended for young Brussels (6 to 12 years).\n"@en . + "Location of cinemas on the territory of the City of Brussels.\n"@en . + "Information on the Cleanliness committees of the City of Brussels (name of the Cleanliness committee, name of the meeting point, address).\n"@en . + "Location of comic book walls of the City of Brussels (with characters and authors).\n"@en . + "Localisation of the community centres of the CPAS of Brussels\n"@en . + "Location of consultations for children (0-3 years) at the Dutch-language K&G (Kind & Gezin) on the territory of the City of Brussels.\n"@en . + "Location of consultations for children (0-6 years) at the French-language ONE (Office de la Naissance et de l'Enfance) on the territory of the City of Brussels.\n"@en . + "Manual counts of movements (light vehicles, trucks, buses or coaches, motorcycles, bicycles or equivalent personal vehicles) at the intersection of the Rue Roger van der Weyden and the Avenue de Stalingrad.\n"@en . + "Covers of the magazine of the City of Brussels, the ‘Brusseleir’.\n"@en . + "Location of the cultural places of the City of Brussels.\n"@en . + "Data set of the data sets of the Open Data platform of\nthe City of Brussels.\n"@en . + "Statistics on births, marriages, divorces and separations, deaths in the City of Brussels.\n"@en . + "Location of dog toilets on the territory of the City of Brussels.\n"@en . + "Days and hours of the dressings of Manneken-Pis (with the name of the costume, the reference and the context of the dressing). \n"@en . + "Location of Dutch-language libraries of the City of Brussels.\n"@en . + "Energy consumption of the City of Brussels since 2008. \n"@en . + "Evolution of the population from the different countries of Europe (women) for the 2009-2014 period.\n"@en . + "Location of the European institutions on the territory of the City of Brussels.\n"@en . + "Evolution of the population from the different countries of Europe (men) for the 2009-2014 period.\n"@en . + "On the basis of the population of Brussels in 2012, a\nprojection of the evolution of the population of the City until 2024 by age\ngroup.\n"@en . + "On the basis of the population of Brussels in 2012, a\nprojection of the evolution of the senior population in Brussels until 2024.\n"@en . + "On the basis of the population of Brussels in 2012, a\nprojection of the evolution of the population of the City until 2024, taking\ninto account the natural increase (births - deaths) and net migration (immigration\n- emigration).\n"@en . + "\n Statistics of the Facebook pages City of Brussels (number of 'likes') in French, Dutch and English.\n"@en . + "Female first names registered at the City of Brussels in 2000.\n"@en . + "Female first names registered at the City of Brussels in 2001.\n"@en . + "Female first names registered at the City of Brussels in 2002.\n"@en . + "Female first names registered at the City of Brussels in 2003.\n"@en . + "Female first names registered at the City of Brussels in 2004.\n"@en . + "Female first names registered at the City of Brussels in 2005.\n"@en . + "Female first names registered at the City of Brussels in 2006.\n"@en . + "Female first names registered at the City of Brussels in 2007.\n"@en . + "Female first names registered at the City of Brussels in 2008.\n"@en . + "Female first names registered at the City of Brussels in 2009.\n"@en . + "Female first names registered at the City of Brussels in 2010.\n"@en . + "Female first names registered at the City of Brussels in 2011.\n"@en . + "Female first names registered at the City of Brussels in 2012.\n"@en . + "Names of women in the Brussels Capital Region in 2013.\n"@en . + "Female first names registered at the City of Brussels in 2013.\n"@en . + "Female first names registered at the City of Brussels in 2014.\n"@en . + "Female first names registered at the City of Brussels in 2015 (until 27 October).\n"@en . + "Female first names registered at the City of Brussels in 2015.\n"@en . + "Location of the food trucks selected by the City of Brussels.\n"@en . + "Location of French-language libraries of the City of Brussels and social networks.\n"@en . + "Location of French-language schools of the City of Brussels.\n"@en . + "Cities (municipalities) of origin of visitors to the website of the City of Brussels from Belgium, determined by their IP address. Source: Google Analytics.\n"@en . + "Cities (municipalities) of origin of visitors to the website of the City of Brussels from Belgium, determined by their IP address. Source: Google Analytics.\n"@en . + "Cities (municipalities) of origin of visitors to the website of the City of Brussels from Belgium, determined by their IP address. Source: Google Analytics.\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2011), determined by the geographical area of their IP address. Source: Google Analytics.\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2012), determined by the geographical area of their IP address. Source: Google Analytics.\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2010), determined by the geographical area of their IP address. Source: Google Analytics.\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2013), determined by the geographical area of their IP address. Source: Google Analytics.\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2014), determined by the geographical area of their IP address. Source: Google Analytics.\n"@en . + "Countries and territories of the visitors of the website of the City of Brussels (2015), determined by the geographical area of their IP address. Source: Google Analytics.\n"@en . + "Location of glass containers on the territory of the City of Brussels.\n"@en . + "(Old town of) Haren quarter of Brussels.\n"@en . + "Location and distribution by quarter of the homes of the Property Management Agency with the number of units per home.\n"@en . + "Eminent people to whom the City of Brussels has awarded the title of honorary citizen (indicating the name or pseudonym, the function, the date of the granting of the title).\n"@en . + "Location of invasive exotic plants on the territory of the City of Brussels.\n"@en . + "RSS feeds of the job offers on the website of the City of Brussels: http://www.brussels.be/artdet.cfm?function=rss&id=6308\n"@en . + "(Old town of) Laeken quarter of Brussels.\n"@en . + "Male first names registered at the City of Brussels in 2000.\n"@en . + "Male first names registered at the City of Brussels in 2001.\n"@en . + "Male first names registered at the City of Brussels in 2002.\n"@en . + "Male first names registered at the City of Brussels in 2003.\n"@en . + "Male first names registered at the City of Brussels in 2004.\n"@en . + "Male first names registered at the City of Brussels in 2005.\n"@en . + "Male first names registered at the City of Brussels in 2006.\n"@en . + "Male first names registered at the City of Brussels in 2007.\n"@en . + "Male first names registered at the City of Brussels in 2008.\n"@en . + "Male first names registered at the City of Brussels in 2009.\n"@en . + "Male first names registered at the City of Brussels in 2010.\n"@en . + "Male first names registered at the City of Brussels in 2011.\n"@en . + "Male first names registered at the City of Brussels in 2012.\n"@en . + "Names of men in the Brussels Capital Region in 2013.\n"@en . + "Male first names registered at the City of Brussels in 2013.\n"@en . + "Male first names registered at the City of Brussels in 2014.\n"@en . + "Male first names registered at the City of Brussels in 2015 (until 27 October).\n"@en . + "Male first names registered at the City of Brussels in 2015.\n"@en . + "Location of the markets on the territory of the City of Brussels.\n"@en . + "Mayors of the City of Brussels, with indication of the date of birth and date of death, the date of the start of term of the Mayor, the dates of the royal decrees for the appointment, the installation, the end of the term or the dismissal.\n"@en . + "Parking spaces for motorized two-wheelers in the City of Brussels\n"@en . + "RSS feeds of the municipal contests on the website of the City of Brussels: http://www.brussel.be/artdet.cfm?function=rss&id=6314\n"@en . + "Results of the local elections in Brussels since 2000\n(number of votes per list). \n"@en . + "Results of the local elections in Brussels since 2000\n(number of votes per list). \n"@en . + "Results of the local elections in Brussels since 2000\n(number of seats per list).\n"@en . + "RSS feeds of the municipal news on the website of the City of Brussels: http://www.brussels.be/artdet.cfm?function=rss&id=4694\n"@en . + "News published on the website of the City of Brussels.\n"@en . + "Location of the museums on the territory of the City of Brussels (including the museums of the City of Brussels).\n"@en . + "Location of the museums of the City of Brussels\n"@en . + "(Old town of) Neder-Over-Heembeek (NOH) quarter of Brussels.\n"@en . + "Location of the nest boxes installed at the City of Brussels.\n"@en . + "North quarter of Brussels.\n"@en . + "Northeast quarter of Brussels.\n"@en . + "Location of the nurseries and kindergartens of the\nCity specifying the accrediting institution (ONE, Kind & Gezin), the number\nof places, opening hours.\n"@en . + "Location and information of the rest homes (MR) and retirement and nursing homes (MRS)of the Public Welfare Centre (CPAS) of Brussels.\n"@en . + "Outdoor multisports grounds of the City of Brussels (with address and description of the equipment).\n"@en . + "\n Location of parking spaces for disabled on the territory of the City of Brussels.\n\n"@en . + "Location of the parking zones for coaches (tourist buses) on the territory of the City of Brussels.\n"@en . + "Location of the parks and gardens on the territory of the City of Brussels.\n"@en . + "Pentagon quarter (city centre) of Brussels.\n"@en . + "Petanque courts of the City of Brussels.\n"@en . + "Location of the pharmacies on the territory of the City of Brussels from OpenStreetMap (OSM).\n"@en . + "Pharmacies on the territory of the City of Brussels.\n"@en . + "Playgrounds of the City of Brussels indicating the age category and description of the playsets.\n"@en . + "Location of the police offices on the territory of the City of Brussels.\n"@en . + "Location of the polling stations on the territory of the\nCity of Brussels.\n"@en . + "The Pollumeter of the Brussels Capital Region follows\nthe evolution of the air quality. The overall index is calculated on the basis\nof measurements at all places for oxygen (O3), nitrogen dioxide (NO2), sulfur\ndioxide (SO2) and particulate matter (PM10): www.ibgebim.be/Pollumetre/dynamic_index.txt.\nBruxelles Environnement - Leefmilieu Brussel requests\nto periodically retrieve the data (at 35 past the hour), and not to search each\ntime at a web page with the Pollumeter.\n"@en . + "Total number (since 1921) of the Brussels population\nas recorded by the Department of Demography. \n"@en . + "Public computer rooms (PCR) of the City of Brussels.\n"@en . + "Public hospitals (members of the IRIS network) on the territory of the City of Brussels.\n"@en . + "Location of the Public Internet access points of the City of Brussels.\n"@en . + "Location of public paying parking lots on the territory of the City of Brussels. (source: http://opendatastore.brussels/en )\n"@en . + "Location of the public toilets on the territory of the City of Brussels.\n"@en . + "Roads managed by the Brussels Capital Region. Some roads (streets, highways, avenues, boulevards, squares,...) are located on the territory of the City; other roads may be adjacent.\n"@en . + "Location of the remarkable trees on the territory of the City of Brussels.\n"@en . + "Right bank of the canal quarter of Brussels.\n"@en . + "RSS feeds of the government contracts on the website of the City of Brussels: www.brussels.be/artdet.cfm?function=rss&id=5477\n"@en . + "List of street names of each sector of the local resident (parking) card on the territory of the City of Brussels.\n"@en . + "Location of the Seniors Pavilions of the City.\n"@en . + "Smart waste bins on the territory of the City of Brussels.\n"@en . + "Train stations (SNCB - NMBS) with departure and arrival stations.\n"@en . + "Register (unofficial) of accounts and pages of the City of Brussels (City, departments, services, libraries, schools, other institutions, non-profit associations, actions, events) on social networks (Facebook, Twitter, Google+, LinkedIn, Pinterest, Flickr, YouTube).\n"@en . + "Location of sports halls and stadiums of the City of Brussels.\n"@en . + "Location of the STIB stops (stations).\n"@en . + "Street Art supported by the City of Brussels.\n"@en . + "Submitted applications for environmental permits\nby the City of Brussels by destination of the property and since 2003.\n"@en . + "Location of the swimming pools of the City of Brussels.\n"@en . + "Location of the Taxi stands on the terrotory of the Region of Brussels Capital. (Source:  http://opendatastore.brussels/en/)\n"@en . + "Location of theatres on the territory of the City of Brussels\n"@en . + "Location of the tourist offices on the territory of the City of Brussels.\n"@en . + "Traffic (incidents,\ncongestion,...) , as well as disturbing works in the Brussels Capital Region.\n"@en . + "Traffic intensity at different parts of the regional road network.\n"@en . + "\n Followers for Twitter accounts City of Brussels in French, Dutch or English.\n"@en . + "RSS feed with updated data sets of the\nopendata.brussels.be platform :opendata.brussels.be/api/datasets/1.0/search?format=rss&sort;=modified\n"@en . + "Location of the Villo! stations in the Brussels Capital Region with indication of the availability (bikes, bike stands) in real time.\n"@en . + "Visitor statistics of the pages of the website of the City of Brussels with the title of the page, the number of pages viewed, the number of unique visits, average time spent on the page. Source: Google Analytics.\n"@en . + "Visitor statistics of the pages of the website of the City of Brussels (2014) with the title of the page, the number of pages viewed, the number of unique visits, average time spent on the page. Source: Google Analytics.\n"@en . + "Visitor statistics of the pages of the website of the City of Brussels (2015) by title, the number of pages viewed, the number of unique visits. Source: Google Analytics.\n"@en . + "Visitor statistics of the pages of the website of the City of Brussels with the address of the page (after www.brussels.be), the number of pages viewed, the number of unique visits. Source: Google Analytics.\n"@en . + "Visitor statistics of the pages of the website of the City of Brussels with the address of the page (after www.brussels.be), the number of pages viewed, the number of unique visits, average time spent on the page. Source: Google Analytics.\n"@en . + "Visitor statistics of the pages of the website of the City of Brussels with the address of the page (after www.brussels.be), the number of pages viewed, the number of unique visits, average time spent on the page. Source: Google Analytics.\n"@en . + "Number of visitors ands visits City of Brussels website.\n"@en . + "Tons of waste removed by the Cleanliness Service of the City of Brussels\nto be burned, landfilled or recycled."@en . + "Location of the permanent webcams that are accessible on the website of the City of Brussels.\n"@en . + "Location of free wifi on the territory of the City of Brussels\n"@en . + "Monuments of the City of Brussels commemorating World War I.\n"@en . + "Location of the youth hotels on the territory of the City of Brussels.\n"@en . + "Location of the stations of the Zen Car electric carsharing system on the territory of the City of Brussels.\n"@en . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aankledingen-van-manneken-pis" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aankledingen-van-manneken-pis" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aanplakborden" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aanplakborden" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aanplakzuilen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aanplakzuilen" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aantal-bezoekers-website-stad-brussel" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aantal-bezoekers-website-stad-brussel" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aanvragen-voor-een-bouwvergunning" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aanvragen-voor-een-bouwvergunning" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aanvragen-voor-geleverde-milieuvergunningen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aanvragen-voor-geleverde-milieuvergunningen" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/actualites-ville-de-bruxelles" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/actualites-ville-de-bruxelles" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/administratief-centrum-en-verbindingsbureaus" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/administratief-centrum-en-verbindingsbureaus" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/administratief-centrum-en-verbindingsbureaus" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/administratief-centrum-en-verbindingsbureaus" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/administrative-centre-and-liaison-offices" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/administrative-centre-and-liaison-offices" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/administrative-centre-and-liaison-offices" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/administrative-centre-and-liaison-offices" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/advertising-columns" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/advertising-columns" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/afval" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/afval" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/agenda" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/agenda" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/agenda-of-the-day" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/agenda-of-the-day" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/agenda-van-de-dag" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/agenda-van-de-dag" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aires-de-jeux" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aires-de-jeux" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/aires-de-stationnements-taxi" . + . + "Brussels OpenData License" . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/aires-de-stationnements-taxi" . + . + "Brussels OpenData License" . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/aires-de-stationnements-taxi" . + . + "Brussels OpenData License" . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/aires-de-stationnements-taxi" . + . + "Brussels OpenData License" . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/android-applicatie" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/android-applicatie" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/android-application" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/android-application" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken0" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken0" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken0" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/apotheken0" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/appels-doffres" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/appels-doffres" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/application-android" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/application-android" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/applications-for-a-planning-permit" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/applications-for-a-planning-permit" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/applications-for-a-supplied-environmental-permit" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/applications-for-a-supplied-environmental-permit" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/arrets-stib" . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/arrets-stib" . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/arrets-stib" . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/arrets-stib" . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/atms" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/atms" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/atms" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/atms" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/begraafplaatsen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/begraafplaatsen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/begraafplaatsen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/begraafplaatsen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-in-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-in-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-titel-in-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-titel-in-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-titel-in-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-titel-in-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-titel-in-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-titel-in-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-url-in-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-url-in-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-url-in-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bezochte-webpaginas-van-de-stad-per-url-in-2015" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bicycle-boxes" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bicycle-boxes" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bicycle-boxes" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bicycle-boxes" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/billboards" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/billboards" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bioscopen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bioscopen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bioscopen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bioscopen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/box-a-velos" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/box-a-velos" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/box-a-velos" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/box-a-velos" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_affichage_public" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_affichage_public" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_arbres_remarquables" . + . + "Open Data Ville de Bruxelles" . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_arbres_remarquables" . + . + "Open Data Ville de Bruxelles" . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_auberges_de_jeunesse_et_hotels_pour_jeunes" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_auberges_de_jeunesse_et_hotels_pour_jeunes" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_auberges_de_jeunesse_et_hotels_pour_jeunes" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_auberges_de_jeunesse_et_hotels_pour_jeunes" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bibliotheques_francophones" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bibliotheques_francophones" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bibliotheques_francophones" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bibliotheques_francophones" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bulles_a_verre" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bulles_a_verre" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bulles_a_verre" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bulles_a_verre" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_police" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_police" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_police" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_police" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_tourisme" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_tourisme" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_tourisme" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_bureaux_de_tourisme" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_canisite" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_canisite" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_canisite" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_canisite" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_centre_administratif_et_bureaux_de_liaison" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_centre_administratif_et_bureaux_de_liaison" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_centre_administratif_et_bureaux_de_liaison" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_centre_administratif_et_bureaux_de_liaison" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_cinemas" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_cinemas" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_cinemas" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_cinemas" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_colonnes_affichage_communal" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_colonnes_affichage_communal" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_concours_communaux" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_concours_communaux" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_distributeurs_bancaires" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_distributeurs_bancaires" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_distributeurs_bancaires" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_distributeurs_bancaires" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_francophones" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_francophones" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_francophones" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_francophones" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_neerlandophones" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_neerlandophones" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_neerlandophones" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_ecoles_neerlandophones" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_eleves_ecoles_francophones" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_eleves_ecoles_francophones" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_especes_de_plantes_exotiques_invasives" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_especes_de_plantes_exotiques_invasives" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_especes_de_plantes_exotiques_invasives" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_especes_de_plantes_exotiques_invasives" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_installations_sportives_de_la_ville_de_bruxelles" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_installations_sportives_de_la_ville_de_bruxelles" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_installations_sportives_de_la_ville_de_bruxelles" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_installations_sportives_de_la_ville_de_bruxelles" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_internet_public" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_internet_public" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_internet_public" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_internet_public" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_l_europe_a_bruxelles" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_l_europe_a_bruxelles" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_l_europe_a_bruxelles" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_l_europe_a_bruxelles" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_lieux_culturels" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_lieux_culturels" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_lieux_culturels" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_lieux_culturels" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_maisons_de_quartier" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_maisons_de_quartier" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_maisons_de_quartier" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_maisons_de_quartier" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_musees" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_musees" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_musees" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_musees" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_offres_d_emploi" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_offres_d_emploi" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcours_bd" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcours_bd" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcours_bd" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcours_bd" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcs_et_jardins" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcs_et_jardins" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcs_et_jardins" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parcs_et_jardins" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_pour_personnes_handicapees" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_pour_personnes_handicapees" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_pour_personnes_handicapees" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_pour_personnes_handicapees" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_publics" . + . + "Brussels OpenData License" . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_publics" . + . + "Brussels OpenData License" . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_publics" . + . + "Brussels OpenData License" . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_parkings_publics" . + . + "Brussels OpenData License" . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_rues_par_secteur_pour_les_cartes_de_riverain" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_rues_par_secteur_pour_les_cartes_de_riverain" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_statistiques_facebook_francais" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_statistiques_facebook_francais" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_theatres" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_theatres" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_theatres" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_theatres" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_toilettes_publiques" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_toilettes_publiques" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_toilettes_publiques" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_toilettes_publiques" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_top_100_livres_empruntes_par_bibilotheque" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_top_100_livres_empruntes_par_bibilotheque" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_urinoirs_publics" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_urinoirs_publics" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_urinoirs_publics" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_urinoirs_publics" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_wifi" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_wifi" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_wifi" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bruxelles_wifi" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bureaux-de-vote0" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bureaux-de-vote0" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bureaux-de-vote0" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bureaux-de-vote0" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/burgemeesters" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/burgemeesters" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/busscolaires" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/busscolaires" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/busscolaires" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/busscolaires" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/buurthuizen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/buurthuizen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/buurthuizen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/buurthuizen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_bourgmestres" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_bourgmestres" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_consommation_energetique" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_consommation_energetique" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_demo_evolutionpop_tranchesage" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_demo_evolutionpop_tranchesage" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_demographie_140306_annee" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_demographie_140306_annee" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_fontaines" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_fontaines" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_fontaines" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_fontaines" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_food_trucks" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_food_trucks" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_food_trucks" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_food_trucks" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_prenoms_masculins_2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_prenoms_masculins_2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_regiefonciere_logementsmoyens2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_regiefonciere_logementsmoyens2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_siteweb_origines_visiteurs2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_siteweb_origines_visiteurs2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_siteweb_visites_visiteursfr" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_siteweb_visites_visiteursfr" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_urinals" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_urinals" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_urinals" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/bxl_urinals" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cambio-stations" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cambio-stations" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cambio-stations" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cambio-stations" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cartographie-openstreetmap-des-pharmacies-a-bruxelles" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cartographie-openstreetmap-des-pharmacies-a-bruxelles" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cartographie-openstreetmap-des-pharmacies-a-bruxelles" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cartographie-openstreetmap-des-pharmacies-a-bruxelles" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cemeteries" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cemeteries" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cemeteries" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cemeteries" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/centres-de-jeunes-de-bravvo" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/centres-de-jeunes-de-bravvo" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/centres-de-jeunes-de-bravvo" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/centres-de-jeunes-de-bravvo" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/childrens-homes" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/childrens-homes" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/childrens-homes" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/childrens-homes" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cimetieres" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cimetieres" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cimetieres" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cimetieres" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cinemas" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cinemas" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cinemas" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cinemas" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/citoyens-dhonneur" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/citoyens-dhonneur" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cleanliness-committees" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cleanliness-committees" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cleanliness-committees" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cleanliness-committees" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/collections-des-bibliotheques-francophones" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/collections-des-bibliotheques-francophones" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/collections-des-bibliotheques-francophones-en-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/collections-des-bibliotheques-francophones-en-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/collections-des-bibliotheques-francophones-en-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/collections-des-bibliotheques-francophones-en-2015" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/comic-book-route" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/comic-book-route" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/comic-book-route" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/comic-book-route" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/comites-proprete" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/comites-proprete" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/comites-proprete" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/comites-proprete" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/community-centres" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/community-centres" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/community-centres" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/community-centres" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/comptages-au-carrefour-van-der-weyden-stalingrad" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/comptages-au-carrefour-van-der-weyden-stalingrad" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-kg" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-kg" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-kg" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-kg" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-one" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-one" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-one" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/consultaties-voor-kinderen-bij-one" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-kg" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-kg" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-kg" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-kg" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-one" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-one" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-one" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-for-children-at-one" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-kg" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-kg" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-kg" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-kg" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-lone" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-lone" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-lone" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/consultations-pour-enfants-de-lone" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/costumes-de-manneken-pis" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/costumes-de-manneken-pis" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/counts-at-the-crossroad-van-der-weyden-stalingrad" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/counts-at-the-crossroad-van-der-weyden-stalingrad" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/couvertures-du-brusseleir" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/couvertures-du-brusseleir" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/covers-of-the-brusseleir" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/covers-of-the-brusseleir" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/covers-van-de-brusseleir" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/covers-van-de-brusseleir" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/creches-pregardiennats" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/creches-pregardiennats" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/creches-pregardiennats" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/creches-pregardiennats" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/cultural-places" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/cultural-places" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/cultural-places" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/cultural-places" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/culturele-plaatsen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/culturele-plaatsen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/culturele-plaatsen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/culturele-plaatsen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/data-set-of-the-data-sets" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/data-set-of-the-data-sets" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/dataset-van-de-datasets" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/dataset-van-de-datasets" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/dechets" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/dechets" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/demandes-de-permis-denvironnement-delivres" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/demandes-de-permis-denvironnement-delivres" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/demandes-de-permis-denvironnement-introduits" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/demandes-de-permis-denvironnement-introduits" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/demandes-de-permis-durbanisme" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/demandes-de-permis-durbanisme" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/demografische-statistieken" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/demografische-statistieken" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/demographic-statistics" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/demographic-statistics" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/deux-roues-motories" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/deux-roues-motories" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/deux-roues-motories" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/deux-roues-motories" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/dog-toilets" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/dog-toilets" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/dog-toilets" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/dog-toilets" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/dressings-of-manneken-pis" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/dressings-of-manneken-pis" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/dutch-language-libraries" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/dutch-language-libraries" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/dutch-language-libraries" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/dutch-language-libraries" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-artistiques-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-artistiques-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-artistiques-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-artistiques-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-de-promotion-sociale-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-de-promotion-sociale-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-de-promotion-sociale-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-de-promotion-sociale-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-maternelles-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-maternelles-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-maternelles-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-maternelles-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-neerlandophones" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-neerlandophones" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-neerlandophones" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-neerlandophones" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-primaires-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-primaires-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-primaires-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-primaires-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-secondaires-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-secondaires-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-secondaires-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-secondaires-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-specialisees-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-specialisees-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-specialisees-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-specialisees-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-superieures-francophones-communales" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-superieures-francophones-communales" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-superieures-francophones-communales" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ecoles-superieures-francophones-communales" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/elections-communales" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/elections-communales" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/elections-communales-pourcentages" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/elections-communales-pourcentages" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/elections-communales-sieges" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/elections-communales-sieges" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/elkaar-helpen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/elkaar-helpen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/elkaar-helpen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/elkaar-helpen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/energieverbruik" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/energieverbruik" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/energy-consumption" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/energy-consumption" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ereburgers1" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ereburgers1" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/espaces-publics-numeriques-epn" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/espaces-publics-numeriques-epn" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/espaces-publics-numeriques-epn" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/espaces-publics-numeriques-epn" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/eten-en-drinken" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/eten-en-drinken" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/eten-en-drinken" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/eten-en-drinken" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/european-female-population-in-brussels" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/european-female-population-in-brussels" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/european-institutions" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/european-institutions" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/european-institutions" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/european-institutions" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/european-male-population-in-brussels" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/european-male-population-in-brussels" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-instelligen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-instelligen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-instelligen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-instelligen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-mannelijke-bevolking-in-brussel" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-mannelijke-bevolking-in-brussel" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-vrouwelijke-bevolking-in-brussel" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/europese-vrouwelijke-bevolking-in-brussel" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/evenements-trafic-travaux" . + . + "contacter Bruxelles Mobilité" . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/evenements-trafic-travaux" . + . + "contacter Bruxelles Mobilité" . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/evenements-trafic-travaux" . + . + "contacter Bruxelles Mobilité" . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/evenements-trafic-travaux" . + . + "contacter Bruxelles Mobilité" . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/expected-evolution-of-the-brussels-population-by-age" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/expected-evolution-of-the-brussels-population-by-age" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/expected-evolution-of-the-brussels-seniors-population" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/expected-evolution-of-the-brussels-seniors-population" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/expected-evolution-of-the-population-of-brussels-with-growth" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/expected-evolution-of-the-population-of-brussels-with-growth" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/facebook-pages" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/facebook-pages" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/facebookpagina-stad-brussel" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/facebookpagina-stad-brussel" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2000" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2000" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2001" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2001" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2002" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2002" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2003" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2003" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2004" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2004" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2005" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2005" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2006" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2006" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2007" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2007" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2008" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2008" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2010" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2010" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-20130" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-20130" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-20150" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/female-first-names-20150" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fietstrommels" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fietstrommels" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fietstrommels" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fietstrommels" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-fietspaden-in-park-bandes-cyclables-dans-les-parcs" . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-fietspaden-in-park-bandes-cyclables-dans-les-parcs" . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-fietspaden-in-park-bandes-cyclables-dans-les-parcs" . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-fietspaden-in-park-bandes-cyclables-dans-les-parcs" . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-gemarkeerde-fietspaden-pistes-cyclables-marquees" . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-gemarkeerde-fietspaden-pistes-cyclables-marquees" . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-gemarkeerde-fietspaden-pistes-cyclables-marquees" . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-gemarkeerde-fietspaden-pistes-cyclables-marquees" . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-verhoogde-fietspaden-pistes-cyclables-surelevees" . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-verhoogde-fietspaden-pistes-cyclables-surelevees" . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-verhoogde-fietspaden-pistes-cyclables-surelevees" . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-verhoogde-fietspaden-pistes-cyclables-surelevees" . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-voetgangerszone-fietszone-pietonnier-zone-cyclable" . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-voetgangerszone-fietszone-pietonnier-zone-cyclable" . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-voetgangerszone-fietszone-pietonnier-zone-cyclable" . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fietsvelo-voetgangerszone-fietszone-pietonnier-zone-cyclable" . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fontaines-deau-potable" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fontaines-deau-potable" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fontaines-deau-potable" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fontaines-deau-potable" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/fonteinen-met-drinkbaar-water" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/fonteinen-met-drinkbaar-water" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/fonteinen-met-drinkbaar-water" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/fonteinen-met-drinkbaar-water" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks-copie" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks-copie" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks-copie" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/food-trucks-copie" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-bibliotheken" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-bibliotheken" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-bibliotheken" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-bibliotheken" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-scholen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-scholen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-scholen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/franstalige-scholen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-libraries" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-libraries" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-libraries" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-libraries" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-schools" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-schools" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-schools" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/french-language-schools" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gares-sncb" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/gares-sncb" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gares-sncb" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/gares-sncb" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gebruikers-van-de-nederlandstalige-bibliotheken" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gebruikers-van-de-nederlandstalige-bibliotheken" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geldautomaten" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/geldautomaten" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geldautomaten" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/geldautomaten" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijk-actualiteit" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijk-actualiteit" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-basisscholen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-basisscholen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-basisscholen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-basisscholen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-kleuterscholen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-kleuterscholen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-kleuterscholen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-kleuterscholen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-lagere-scholen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-lagere-scholen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-lagere-scholen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-lagere-scholen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-secundaire-scholen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-secundaire-scholen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-secundaire-scholen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeentelijke-secundaire-scholen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeenteraadsverkiezingen-aantal-stemmen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeenteraadsverkiezingen-aantal-stemmen" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeenteraadsverkiezingen-procenten" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeenteraadsverkiezingen-procenten" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeenteraadsverkiezingen-zetels" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemeenteraadsverkiezingen-zetels" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gemotoriseerde-tweewielers" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/gemotoriseerde-tweewielers" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gemotoriseerde-tweewielers" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/gemotoriseerde-tweewielers" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-belgische-gemeenten-van-de-bezoeken-aan-de-website-van-d0" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-belgische-gemeenten-van-de-bezoeken-aan-de-website-van-d0" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-belgische-gemeenten-van-de-bezoeken-aan-de-website-van-de" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-belgische-gemeenten-van-de-bezoeken-aan-de-website-van-de" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-gemeenten-van-de-bezoeken-aan-de-website-van-de-stad-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-gemeenten-van-de-bezoeken-aan-de-website-van-de-stad-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2010" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2010" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geografische-oorsprong-van-de-bezoeken-aan-de-website-van-de-stad-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-belgian-municipalities-of-the-visits-to-the-website-of-the-0" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-belgian-municipalities-of-the-visits-to-the-website-of-the-0" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-belgian-municipalities-of-the-visits-to-the-website-of-the-c" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-belgian-municipalities-of-the-visits-to-the-website-of-the-c" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-municipalities-of-the-visits-to-the-website-of-the-city-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-municipalities-of-the-visits-to-the-website-of-the-city-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-20130" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-20130" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/geographical-origin-of-the-visits-to-the-website-of-the-city-of-brussels-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/gewestelijke-wegen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/gewestelijke-wegen" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/glasbollen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/glasbollen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/glasbollen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/glasbollen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/glass-containers" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/glass-containers" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/glass-containers" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/glass-containers" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/guerites-et-bulles-des-petits-riens" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/guerites-et-bulles-des-petits-riens" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/guerites-et-bulles-des-petits-riens" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/guerites-et-bulles-des-petits-riens" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/habillages-de-manneken-pis" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/habillages-de-manneken-pis" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/haltes-mivb" . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/haltes-mivb" . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/haltes-mivb" . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/haltes-mivb" . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/haren-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/haren-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/haren-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/haren-quarter" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren0" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren0" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren0" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/herstellen-hergebruiken-recycleren-composteren0" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/homes-of-the-property-management-agency" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/homes-of-the-property-management-agency" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/homes-of-the-property-management-agency" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/homes-of-the-property-management-agency" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/hondentoiletten" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/hondentoiletten" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/hondentoiletten" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/hondentoiletten" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/honorary-citizens1" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/honorary-citizens1" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/hopitaux-publics" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/hopitaux-publics" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/hopitaux-publics" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/hopitaux-publics" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/huizen-voor-het-kind" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/huizen-voor-het-kind" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/huizen-voor-het-kind" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/huizen-voor-het-kind" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/informatiedragers-in-de-nederlandstalige-bibliotheken" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/informatiedragers-in-de-nederlandstalige-bibliotheken" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ingediende-aanvragen-voor-milieuvergunningen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ingediende-aanvragen-voor-milieuvergunningen" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/invasieve-exotische-planten" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/invasieve-exotische-planten" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/invasieve-exotische-planten" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/invasieve-exotische-planten" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/invasive-exotic-plants" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/invasive-exotic-plants" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/invasive-exotic-plants" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/invasive-exotic-plants" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/jeugdhotels" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/jeugdhotels" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/jeugdhotels" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/jeugdhotels" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/job-offers" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/job-offers" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/kinderdagverblijven-peutertuinen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/kinderdagverblijven-peutertuinen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/kinderdagverblijven-peutertuinen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/kinderdagverblijven-peutertuinen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/laeken-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/laeken-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/laeken-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/laeken-quarter" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-contrats-de-quartier-a-la-regie-fonciere-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-contrats-de-quartier-a-la-regie-fonciere-2014" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-de-la-regie-fonciere" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-de-la-regie-fonciere" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-de-la-regie-fonciere" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-de-la-regie-fonciere" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-moyens-avec-conditions-de-revenus-de-la-regie-fonciere-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/logements-moyens-avec-conditions-de-revenus-de-la-regie-fonciere-2014" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/louise-bois-de-la-cambre-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/louise-bois-de-la-cambre-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/louise-bois-de-la-cambre-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/louise-bois-de-la-cambre-quarter" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-de-repos-pour-personnes-agees" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-de-repos-pour-personnes-agees" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-de-repos-pour-personnes-agees" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-de-repos-pour-personnes-agees" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-des-enfants" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-des-enfants" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-des-enfants" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/maisons-des-enfants" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2000" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2000" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2002" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2002" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20020" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20020" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2003" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2003" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2004" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2004" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2005" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2005" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2006" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2006" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2007" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2007" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2008" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2008" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2010" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2010" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20130" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20130" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20140" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20140" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20150" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/male-first-names-20150" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/manger-et-boire-durablement" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/manger-et-boire-durablement" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/manger-et-boire-durablement" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/manger-et-boire-durablement" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/manneken-pis" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/manneken-pis" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2000" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2000" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2001" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2001" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2002" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2002" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2003" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2003" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2004" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2004" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2005" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2005" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2006" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2006" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2007" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2007" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2008" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2008" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2010" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2010" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-20130" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-20130" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-20150" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mannelijke-voornamen-20150" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/marches" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/marches" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/marches" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/marches" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/markets" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/markets" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/markets" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/markets" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/markten" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/markten" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/markten" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/markten" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mayors" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mayors" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/middenklassewoningen-met-inkomensvoorwaarden-van-de-grondregie-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/middenklassewoningen-met-inkomensvoorwaarden-van-de-grondregie-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/middenklassewoningen-van-de-grondregie-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/middenklassewoningen-van-de-grondregie-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/mise-a-jour-des-jeux-de-donnees" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/mise-a-jour-des-jeux-de-donnees" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/monumenten-1914-1918" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/monumenten-1914-1918" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/monumenten-1914-1918" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/monumenten-1914-1918" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-commemoratifs-de-la-guerre-de-1914-1918" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-commemoratifs-de-la-guerre-de-1914-1918" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-commemoratifs-de-la-guerre-de-1914-1918" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-commemoratifs-de-la-guerre-de-1914-1918" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-funeraires-du-cimetiere-de-laeken" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-funeraires-du-cimetiere-de-laeken" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-funeraires-du-cimetiere-de-laeken" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/monuments-funeraires-du-cimetiere-de-laeken" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/motos" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/motos" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/motos" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/motos" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-contests" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-contests" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-elections-number-of-votes" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-elections-number-of-votes" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-elections-percentages" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-elections-percentages" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-elections-seats" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-elections-seats" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-news" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-news" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-news0" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/municipal-news0" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/musea-in-brussel" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/musea-in-brussel" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/musea-in-brussel" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/musea-in-brussel" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/musees-a-bruxelles" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/musees-a-bruxelles" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/musees-a-bruxelles" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/musees-a-bruxelles" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/museums" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/museums" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/museums" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/museums" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/museums-in-brussels" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/museums-in-brussels" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/museums-in-brussels" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/museums-in-brussels" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/museums0" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/museums0" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/museums0" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/museums0" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/neder-over-heembeek-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/neder-over-heembeek-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/neder-over-heembeek-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/neder-over-heembeek-quarter" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlandstalige-bibliotheken" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlandstalige-bibliotheken" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlandstalige-bibliotheken" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlandstalige-bibliotheken" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlanstalige-scholen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlanstalige-scholen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlanstalige-scholen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nederlanstalige-scholen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nest-boxes" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nest-boxes" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nest-boxes" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nest-boxes" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nestkasten" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nestkasten" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nestkasten" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nestkasten" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/netheidscomites" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/netheidscomites" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/netheidscomites" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/netheidscomites" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nichoirs" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nichoirs" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nichoirs" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nichoirs" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/niveaux-de-service-de-trafic" . + . + "Voir producteur" . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/niveaux-de-service-de-trafic" . + . + "Voir producteur" . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/niveaux-de-service-de-trafic" . + . + "Voir producteur" . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/niveaux-de-service-de-trafic" . + . + "Voir producteur" . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nmbs-stations" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nmbs-stations" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nmbs-stations" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nmbs-stations" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/noordoostwijk" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/noordoostwijk" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/noordoostwijk" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/noordoostwijk" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/noordwijk" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/noordwijk" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/noordwijk" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/noordwijk" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/north-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/north-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/north-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/north-quarter" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/northeast-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/northeast-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/northeast-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/northeast-quarter" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nurseries-kindergartens" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nurseries-kindergartens" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nurseries-kindergartens" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nurseries-kindergartens" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/nursing-homes-for-elderly" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/nursing-homes-for-elderly" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/nursing-homes-for-elderly" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/nursing-homes-for-elderly" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-aanbestedingen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-aanbestedingen" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-computerruimtes-ocr" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-computerruimtes-ocr" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-computerruimtes-ocr" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-computerruimtes-ocr" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-ziekenhuizen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-ziekenhuizen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-ziekenhuizen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/openbare-ziekenhuizen" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/opmerkelijke-bomen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/opmerkelijke-bomen" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-communes-belges-des-visites-du-site-web-de-la-ville-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-communes-belges-des-visites-du-site-web-de-la-ville-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-communes-belges-des-visites-du-site-web-de-la-ville-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-communes-belges-des-visites-du-site-web-de-la-ville-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-communes-des-visites-du-site-web-de-la-ville-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-communes-des-visites-du-site-web-de-la-ville-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2010" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2010" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-des-visites-du-site-web-de-la-ville-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-pays-des-visites-du-site-web-de-la-ville-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/origine-geographique-pays-des-visites-du-site-web-de-la-ville-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/outdoor-multisports-grounds" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/outdoor-multisports-grounds" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/outdoor-multisportvelden" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/outdoor-multisportvelden" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-par-titre-visitees-en-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-par-titre-visitees-en-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-par-titre-visitees-en-20130" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-par-titre-visitees-en-20130" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-par-titre-visitees-en-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-par-titre-visitees-en-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-visitees-en-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-de-la-ville-visitees-en-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-par-url-de-la-ville-visitees-en-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-par-url-de-la-ville-visitees-en-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-par-url-de-la-ville-visitees-en-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pages-du-site-web-par-url-de-la-ville-visitees-en-2015" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parkeerplaatsen-voor-gehandicapten" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parkeerplaatsen-voor-gehandicapten" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parkeerplaatsen-voor-gehandicapten" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parkeerplaatsen-voor-gehandicapten" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parken" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parken" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parken" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parken" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parking-spaces-for-disabled" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parking-spaces-for-disabled" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parking-spaces-for-disabled" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parking-spaces-for-disabled" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings" . + . + "Licentie Brussels OpenData License" . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings" . + . + "Licentie Brussels OpenData License" . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings" . + . + "Licentie Brussels OpenData License" . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings" . + . + "Licentie Brussels OpenData License" . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-for-coaches" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-for-coaches" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-for-coaches" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-for-coaches" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-pour-autocars-touristiques" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-pour-autocars-touristiques" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-pour-autocars-touristiques" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-pour-autocars-touristiques" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-voor-reisbussen" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-voor-reisbussen" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-voor-reisbussen" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parkings-voor-reisbussen" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/parks" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/parks" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/parks" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/parks" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/patrimoine-logements-de-la-regie-fonciere-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/patrimoine-logements-de-la-regie-fonciere-2014" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pavillons-seniors" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/pavillons-seniors" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pavillons-seniors" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/pavillons-seniors" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pentagon-quarter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/pentagon-quarter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pentagon-quarter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/pentagon-quarter" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/petanque-courts" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/petanque-courts" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/petanquebanen" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/petanquebanen" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies0" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies0" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies0" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies0" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies1" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies1" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies1" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/pharmacies1" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/piscines" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/piscines" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/piscines" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/piscines" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/plaatsen-met-openbare-internettoegang-poit" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/plaatsen-met-openbare-internettoegang-poit" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/plaatsen-met-openbare-internettoegang-poit" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/plaatsen-met-openbare-internettoegang-poit" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/playgrounds" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/playgrounds" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/police-stations" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/police-stations" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/police-stations" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/police-stations" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/politiekantoren" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/politiekantoren" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/politiekantoren" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/politiekantoren" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/polling-stations" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/polling-stations" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/polling-stations" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/polling-stations" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pollumeter" . + . + "contacteer Leefmilieu Brussel " . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pollumeter" . + . + "contacteer Leefmilieu Brussel " . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pollumeter0" . + . + "see producer" . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pollumeter0" . + . + "see producer" . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/pollumetre" . + . + "contactez Bruxelles Environnement" . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/pollumetre" . + . + "contactez Bruxelles Environnement" . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/population" . + . + "Voir SPF Economie" . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/population" . + . + "Voir SPF Economie" . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/population-bruxelloise" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/population-bruxelloise" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/population-bruxelloise-copie" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/population-bruxelloise-copie" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/population-bruxelloise-copie0" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/population-bruxelloise-copie0" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/population-europeenne-a-bruxelles" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/population-europeenne-a-bruxelles" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/population-europeenne-feminine-a-bruxelles" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/population-europeenne-feminine-a-bruxelles" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/poubellesintelligentes" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/poubellesintelligentes" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/poubellesintelligentes" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/poubellesintelligentes" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/ppas" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/ppas" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/ppas" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/ppas" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2000" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2000" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2001" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2001" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2002" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2002" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2003" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2003" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2004" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2004" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2005" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2005" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2006" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2006" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2007" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2007" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2008" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2008" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2009" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2009" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2010" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2010" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2011" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2011" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2012" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2012" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-20130" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-20130" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-20131" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-20131" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-20150" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-feminins-20150" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2000" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2000" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2001" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2001" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2002" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2002" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2003" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2003" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2004" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2004" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2005" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2005" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2006" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2006" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2007" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2007" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2008" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2008" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2009" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2009" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2010" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2010" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2011" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2011" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2012" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2012" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2013" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2013" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2014" . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2014" . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-20150" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prenoms-masculins-20150" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prets-dans-les-bibliotheques-francophones-en-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prets-dans-les-bibliotheques-francophones-en-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prets-dans-les-bibliotheques-francophones-en-2014" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prets-dans-les-bibliotheques-francophones-en-2014" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/prets-dans-les-bibliotheques-francophones-en-2015" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/prets-dans-les-bibliotheques-francophones-en-2015" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/projection-de-levolution-de-la-population-bruxelloise-avec-soldes" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/projection-de-levolution-de-la-population-bruxelloise-avec-soldes" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/projection-de-levolution-de-la-population-des-seniors-bruxellois" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/projection-de-levolution-de-la-population-des-seniors-bruxellois" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/public-computer-rooms" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/public-computer-rooms" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/public-computer-rooms" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/public-computer-rooms" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/public-hospitals" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/public-hospitals" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/public-hospitals" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/public-hospitals" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/public-internet-access-points" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/public-internet-access-points" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/public-internet-access-points" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/public-internet-access-points" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/public-parkings" . + . + "Brussels OpenData License" . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/public-parkings" . + . + "Brussels OpenData License" . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/public-parkings" . + . + "Brussels OpenData License" . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/public-parkings" . + . + "Brussels OpenData License" . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/public-toilets" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/public-toilets" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/public-toilets" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/public-toilets" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/quartiers" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/quartiers" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/quartiers" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/quartiers" . + . + . + . + . + "application/zip" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/references-hopitaux" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/references-hopitaux" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/references-hopitaux" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/references-hopitaux" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/regional-roads" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/regional-roads" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/remarkable-trees" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/remarkable-trees" . + . + . + . + . + "application/json" . + . + . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/reparer-reutiliser-recycler-composter" . + . + . + . + . + "text/csv" . + . + "geojson export of https://opendata.brussels.be/api/v2/catalog/datasets/reparer-reutiliser-recycler-composter" . + . + . + . + . + "application/json" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/reparer-reutiliser-recycler-composter" . + . + . + . + . + "application/json" . + . + "shp export of https://opendata.brussels.be/api/v2/catalog/datasets/reparer-reutiliser-recycler-composter" . + . + . + . + . + "application/zip" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/reseaux-sociaux" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/reseaux-sociaux" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/reservations-dans-les-bibliotheques-francophones-en-2013" . + . + . + . + . + "text/csv" . + . + "json export of https://opendata.brussels.be/api/v2/catalog/datasets/reservations-dans-les-bibliotheques-francophones-en-2013" . + . + . + . + . + "application/json" . + . + . + . + "csv export of https://opendata.brussels.be/api/v2/catalog/datasets/reservations-dans-les-bibliotheques-francophones-en-2014" . + . + . + . + . + "text/csv" . + "Dagen en uren van de aankledingen van Manneken-Pis (met de naam van het kostuum, de referentie en de context van de aankleding).\n"@nl . + "Locatie van de aanplakborden van de Stad Brussel\n"@nl . + "Locatie van de aanplakzuilen van de Stad Brussel.\n"@nl . + "Statistieken van de bezoekersaantallen website Stad Brussel.\n"@nl . + "Aanvragen voor een stedenbouwkundige vergunning bij de\nStad Brussel per bestemming van het goed en sinds 2003. \n"@nl . + "Aanvragen voor milieuvergunningen die geleverd werden door de Stad Brussel per\nbestemming van het goed en sinds 2003.\n"@nl . + "Locatie van het Administratief centrum en de verbindingsbureaus van de Stad Brussel.\n"@nl . + "Aantal ton afval verwijderd door de dienst Netheid van de Stad Brussel om te worden verbrand, gestort op een vuilnisbelt of gerecycleerd.\n"@nl . + "Aankondiging van de activiteiten van de dag gepubliceerd op de website van de Stad Brussel.\n"@nl . + "Statistieken van het aantal downloads van de Android-applicatie van de Stad Brussel. Een nieuwe versie van de Android-applicatie van de Stad Brussel werd aangeboden in maart 2015.\n"@nl . + "Locatie van de apotheken op het grondgebied van de Stad Brussel vanuit OpenStreetMap (OSM).\n"@nl . + "Apotheken op het grondgebied van de Stad Brussel.\n"@nl . + "Kerkhoven van de Stad Brussel met de locatie van de ingang, openingsdagen en -uren en telefoonnummers.\n"@nl . + "Bezoekersstatistieken van de pagina's van de website van de Stad Brussel met het adres van de pagina (volgend op www.brussel.be), het aantal bekeken pagina's, het aantal unieke bezoeken, de gemiddelde tijd besteed aan de pagina. Bron: Google Analytics.\n"@nl . + "Bezoekersstatistieken van de pagina's van de website van de Stad Brussel met de titel van de pagina, het aantal bekeken pagina's, het aantal unieke bezoeken, de gemiddelde tijd besteed aan de pagina. Bron: Google Analytics.\n"@nl . + "Bezoekersstatistieken van de pagina's van de website van de Stad Brussel (2014) met de titel van de pagina, het aantal bekeken pagina's, het aantal unieke bezoeken, de gemiddelde tijd besteed aan de pagina. Bron: Google Analytics.\n"@nl . + "Bezoekersstatistieken van de pagina's van de website van de Stad Brussel (2015) met de titel van de pagina, het aantal bekeken pagina's, het aantal unieke bezoeken, de gemiddelde tijd besteed aan de pagina. Bron: Google Analytics.\n"@nl . + "Bezoekersstatistieken van de pagina's van de website van de Stad Brussel (2014) met het adres van de pagina (volgend op www.brussel.be), het aantal bekeken pagina's, het aantal unieke bezoeken, de gemiddelde tijd besteed aan de pagina. Bron: Google Analytics.\n"@nl . + "Bezoekersstatistieken van de pagina's van de website van de Stad Brussel (2014) met het adres van de pagina (volgend op www.brussel.be), het aantal bekeken pagina's, het aantal unieke bezoeken. Bron: Google Analytics.\n"@nl . + "Locaties van de bioscopen op het grondgebied van de Stad Brussel.\n"@nl . + "Burgemeesters van de Stad Brussel met de vermelding van de geboortedatum en overlijdensdatum, de datum van aanvang van mandat envan de ambtstermijn van de burgemeester (WN), de data van de koninklijke besluiten tot benoeming, de installatie, het einde van de functie of het ontslag.\n"@nl . + "Localisatie van de Buurthuizen van het OCMW Brussel\n"@nl . + "Locatie van de consultaties voor kinderen (0 tot 3 jaar) bij K&G (Kind & Gezin) op het grondgebied van de Stad Brussel."@nl . + "Locatie van de consultaties voor kinderen (0-6 jaar) bij het Franstalige ONE (Office de la Naissance et de l'Enfance) op het grondgebied van de Stad Brussel.\n"@nl . + "Covers van het magazine van de Stad Brussel, ‘De Brusseleir’.\n"@nl . + "Locatie van de culturele plaatsen van de Stad Brussel.\n"@nl . + "Dataset van de datasets van het Open Data platform van de\nStad Brussel.\n"@nl . + "Statistieken over geboorten, huwelijken, echtscheidingen\nen scheidingen, overlijdens in de Stad Brussel.\n"@nl . + "De locatie van de verschillende buurtcomités, vzw’s, netwerken voor kennisuitwisseling en ruilsystemen, burgerinitiatieven en organisaties die begeleiding bieden op het vlak van duurzaamheid op het grondgebied van de Stad Brussel.\n"@nl . + "Energieverbruik van de Stad Brussel sinds 2008. \n"@nl . + "Vooraanstaande personen waaraan de Stad Brussel de titel van ereburger heeft toegekend (met vermelding van de naam of pseudoniem, de functie, de datum van de toekenning van de onderscheiding)."@nl . + "De locatie van de verschillende organisaties die duurzame voeding promoten (restaurants, markten, moestuinen, boerderijen, winkels, SAGAL’s, enz.) op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de Europese instellingen op het grondgebied van de Stad Brussel.\n"@nl . + "Evolutie van de bevolking afkomstig van de verschillende landen van Europa (mannen) voor de periode 2009-2014.\n"@nl . + "Evolutie van de bevolking afkomstig van de verschillende landen van Europa (vrouwen) voor de periode 2009-2014.\n"@nl . + "\n Statistieken van de Facebookpagina's Stad Brussel (aantal 'likes') in het Frans, het Nederlands en het Engels.\n"@nl . + "Locatie van fietstrommels op het grondgebied van de Stad Brussel.\n"@nl . + "Fonteinen met drinkbaar water van Brussel (Straatverplegers VZW).\n"@nl . + "Locatie van de food trucks geselecteerd door de StaD Brussel.\n"@nl . + "Locatie van de Franstalige bibliotheken van de Stad Brussel.\n"@nl . + "Locatie van de Franstalige scholen van de Stad Brussel.\n"@nl . + "Het aantal gebruikers (actieve leden) van de Nederlandstalige bibliotheken van de Stad Brussel (Laken, Neder-Over-Heembeek en Haren).\n"@nl . + "Locatie van geldautomaten op het grondgebied van de Stad Brussel.\n"@nl . + "Actualiteit gepubliceerd op de website van de Stad Brussel.\n"@nl . + "Basisonderwijs van de Stad Brussel.\n"@nl . + "Kleuteronderwijs van de Stad Brussel.\n"@nl . + "Lager onderwijs van de Stad Brussel.\n"@nl . + "Secundair onderwijs van de Stad Brussel.\n"@nl . + "Resultaten van de lokale verkiezingen in Brussel sinds\n2000 (aantal stemmen per lijst). \n"@nl . + "Resultaten van de lokale verkiezingen in Brussel sinds\n2000 (aantal stemmen per lijst). \n"@nl . + "Resultaten van de lokale verkiezingen in Brussel sinds\n2000 (aantal zetels per lijst).\n"@nl . + "Parkeerplaatsen voor gemotoriseerde tweewielers op het grondgebiend van de Stad Brussel.\n"@nl . + "Steden (gemeenten) van herkomst van de bezoekers van de website van de Stad Brussel uit België, bepaald door hun IP-adres. Bron: Google Analytics.\n"@nl . + "Steden (gemeenten) van herkomst van de bezoekers van de website van de Stad Brussel uit België, bepaald door hun IP-adres. Bron: Google Analytics.\n"@nl . + "Steden (gemeenten) van herkomst van de bezoekers van de website van de Stad Brussel uit België, bepaald door hun IP-adres. Bron: Google Analytics.\n"@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2009) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics."@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2010) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics.\n"@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2011) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics.\n"@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2012) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics.\n"@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2013) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics.\n"@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2014) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics.\n"@nl . + "Landen en grondgebieden van de bezoekers van de website van de Stad Brussel (2015) bepaald door de geografische zone van hun IP-adres. Bron: Google Analytics.\n"@nl . + "Wegen beheerd door het Brussels Hoofdstedelijk Gewest. Sommige wegen (straten, steenwegen, lanen, pleinen,...) bevinden zich op het grondgebied van de Stad Brussel; andere kunnen eraan grenzen.\n"@nl . + "Locatie van de glascontainers op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de haltes (stations) van de MIVB.\n"@nl . + "De locatie van de haltes van Collecto en Waterbus en fietsherstelplaatsen op het grondgebied van de Stad Brussel.\n"@nl . + "De locatie van inzamelpunten (kleding, IT-materiaal, enz.); afvalverwerking (grofvuil, kga, enz.); compostlocaties, tweedehandswinkels en plaatsen voor herstellingen die zich op het grondgebied van de Stad Brussel bevinden.\n"@nl . + "Localisatie van hondentoiletten op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de Huizen voor het Kind die bestemd zijn voor jonge Brusselaars (6 tot 12 jaar).\n"@nl . + "Het aantal informatiedragers (boeken, tijdschriften, speelgoed, audiovisuele materialen,...) in de Nederlandstalige bibliotheken van de Stad Brussel (Laken, Neder-Over-Heembeek en Haren).\n"@nl . + "Ingediende\naanvragen voor milieuvergunningen bij de Stad Brussel per bestemming van het\ngoed en sinds 2003.\n"@nl . + "Locatie van invasieve exotische plantensoorten op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de jeugdherbergen en jeugdhotels op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de kinderdagverblijven (crèches) & peutertuinen van de Stad Brussel met vermelding van de erkennende instelling (ONE, Kind & Gezin), het aantal plaatsen, openingstijden.\n"@nl . + "Louise - Bois de la Cambre quarter of Brussels.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2000.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2001.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2002.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2003.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2004.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2005.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2006.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2007.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2008.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2009.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2010.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2011.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2012.\n"@nl . + "Voormannen van mannen in het Brussels Hoofdstedelijk Gewest in 2013\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2013.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2014.\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2015 (tot 27 oktober).\n"@nl . + "Mannelijke voornamen geregistreerd bij de Stad Brussel in 2015.\n"@nl . + "Locatie van de markten op het grondgebied van de Stad Brussel.\n"@nl . + "Situatie van de middenklassewoningen (met inkomensvoorwaarden) van de Grondregie op 31 december 2014\n"@nl . + "Situatie van de middenklassewoningen van de Grondregie op 31 december 2014\n"@nl . + "Monumenten van de Stad Brussel die de Eerste Wereldoorlog herdenken\n"@nl . + "Locatie van de musea op het grondgebied van de Stad Brussel (waaronder de musea van de Stad Brussel).\n"@nl . + "Locatie van de Musea van de Stad Brussel\n"@nl . + "Locatie van de Nederlandstalige bibliotheken van de Stad Brussel\n"@nl . + "Locatie van de Nederlandstalige scholen van de Stad Brussel\n"@nl . + "Locatie van de nestkastjes geplaatst in de Stad Brussel.\n"@nl . + "Informatie over de Netheidscomités van de Stad Brussel (naam Netheidscomité, naam ontvangststructuur, adres).\n"@nl . + "Treinstations (NMBS) met vertrek- en eindstations.\n"@nl . + "Noordoostwijk van Brussel.\n"@nl . + "Noordwijk van Brussel.\n"@nl . + "RSS-feeds van de openbare aanbestedingen op de website van de Stad Brussel: http://www.brussel.be/artdet.cfm?function=rss&id=5477\n"@nl . + "Locatie van de openbare computerruimtes (OCR) van de Stad Brussel\n"@nl . + "Openbare ziekenhuizen (leden van het IRIS-netwerk) op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de opmerkelijke bomen op het grondgebied van de Stad Brussel\n"@nl . + "Outdoor multisportvelden van de Stad Brussel (met adres en de beschrijving van de uitrusting).\n"@nl . + "\n Locatie van de parkeerplaatsen voor gehandicapten op het grondgebied van de Stad Brussel.\n\n"@nl . + "Locatie van de parken op het grondgebied van de Stad Brussel.\n"@nl . + "\n Locatie van de openbare, betalende parkings op het grondgebied van de Stad Brussel. (bron: http://opendatastore.brussels/nl )\n"@nl . + "Locatie van de parkeerplaatsen voor reisbussen (autocars) op het grondgebied van de Stad Brussel.\n"@nl . + "Petanquebanen van de Stad Brussel.\n"@nl . + "Locatie van de Plaatsen met openbare internettoegang (POIT) van de Stad Brussel.\n"@nl . + "Locatie van de politiekantoren op het grondgebied van de Stad Brussel.\n"@nl . + "De Pollumeter van het Brussels Hoofdstedelijk Gewest\nvolgt de evolutie van de luchtkwaliteit. De globale index wordt berekend op\nbasis van metingen in alle meetpunten voor zuurstof (O3), stikstofdioxide\n(NO2), zwaveldioxide (SO2) en fijne stofdeeltjes (PM10): www.ibgebim.be/Pollumetre/dynamic_index.txt. \nLeefmilieu Brussel verzoekt om de data periodiek op te halen (om 35 na\nhet uur) en niet door elke keer te zoeken op een webpagina met de Pollumeter.\n"@nl . + "Totale aantal (sinds 1921) van de Brusselse bevolking\nzoals geregistreerd door het departement Demografie. \n"@nl . + "Locatie en informatie van de rusthuizen en rust- en verzorgingstehuizen van het Openbaar Centrum voor Maatschappelijk Welzijn (OCMW) van Brussel.\n"@nl . + "Lijst met straten per sector van de bewonerskaarten op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie en openingsuren van de Seniorenpaviljoenen van de Stad Brussel.\n"@nl . + "Slimme vuilnisbakken op het grondgebied van de Stad Brussel.\n"@nl . + "Register (officieus) van accounts en pagina's van de Stad Brussel (Stad, departementen, diensten, bibliotheken, scholen, andere instellingen, vzw's, acties, evenementen) op sociale netwerken (Facebook, Twitter, Google+, LinkedIn, Pinterest, Flickr, YouTube).\n"@nl . + "Speeltuinen van de Stad Brussel met vermelding van de leeftijdscategorie en beschrijving van de speeltuigen.\n"@nl . + "Locatie van de sportzalen en stadions van de Stad Brussel.\n"@nl . + "Locatie van de stations van Cambio voor autodelen.\n"@nl . + "Locatie van de stations van het elektrische autodeelsysteem Zen Car op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de stembureaus op het grondgebied van de Stad\nBrussel.\n"@nl . + "Street Art ondersteund door de Stad Brussel.\n"@nl . + "Locatie van de stripmuren van de Stad Brussel (personages & auteurs).\n"@nl . + "Manuele tellingen van bewegingen (lichte voertuigen, vrachtwagens, bussen of autocars, motorfietsen, fietsen of gelijkwaardige persoonlijke voertuigen) op het kruispunt van de Rogier van der Weydenstraat en de Stalingradlaan.\n"@nl . + "Locatie van de standplaatsen\nvoor taxi's op het grondgebied van het Brussels Hoofdstedelijk Gewest. (Bron:  http://opendatastore.brussels/nl/ )\n"@nl . + "Locaties van de theaters op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de toerismekantoren van VisitBrussels op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de openbare toiletten op het grondgebied van de Stad Brussel.\n"@nl . + "Het aantal uitleningen en raadplegingen in de Nederlandstalige bibliotheken van de Stad Brussel (Laken, Neder-Over-Heembeek en Haren).\n"@nl . + "RSS-feed met bijgewerkte datasets van het platform\nopendata.brussel.be. :opendata.brussel.be/api/datasets/1.0/search?format=rss&sort;=modified\n"@nl . + "Locatie van de openbare urinoirs (toiletten voor mannen) op het grondgebied van de Stad Brussel.\n"@nl . + "Dataset van de RSS-feeds van de vacatures op de website van de Stad Brussel (van het departement Personeel, Openbaar onderwijs, paragemeentelijke verenigingen,...): http://www.brussel.be/artdet.cfm?function=rss&id=4108\n"@nl . + "Vastgoed (woningen) van de Grondregie op 31 december 2014.\n"@nl . + "Verkeer (incidenten,\nfiles... ), evenals storende werken in het Brussels Hoofdstedelijk Gewest.\n"@nl . + "Verkeersintensiteit op verschillende delen van het gewestelijk wegennetwerk.\n"@nl . + "Op basis van de bevolking van Brussel in 2012, een\nprojectie van de evolutie van de bevolking van de Stad Brussel tot 2024,\nrekening houdend met de natuurlijke aanwas (geboorte - sterfte) en het\nmigratiesaldo (immigratie - emigratie).\n"@nl . + "Op basis van de bevolking van Brussel in 2012, een projectie van de\nevolutie van de bevolking van de Stad Brussel tot 2024 per leeftijdsgroep.\n"@nl . + "Op basis van de bevolking van Brussel in 2012, een\nprojectie van de evolutie van de seniorenbevolking in Brussel tot 2024.\n"@nl . + "Locatie van de Villo!-stations in het Brussels Hoofdstedelijk Gewest met indicatie van de beschikbaarheid (fietsen, fietspalen) in real time.\n"@nl . + "\n Volgers (followers) van de Twitter account in het Frans, Nederlands en Engels.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2000.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2001.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2002.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2003.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2004.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2005.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2006.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2007.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2008.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2009.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2010.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2011.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2012.\n"@nl . + "Voornamen van vorouwen in het Brussels Hoofdstedelijk Gewest in 2013\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2013.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2014.\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2015 (tot 27 oktober).\n"@nl . + "Vrouwelijke voornamen geregistreerd bij de Stad Brussel in 2015.\n"@nl . + "Locatie van de permanente webcams die toegankelijk zijn op de website van de Stad Brussel.\n"@nl . + "Dataset van de RSS-feeds van de wedstrijden op de website van de Stad Brussel: http://www.brussel.be/artdet.cfm?function=rss&id=6314\n"@nl . + "Locatie van wifi op het grondgebied van de Stad Brussel.\n"@nl . + "Caart van de wijcken van Brussel (Vijfhoek, Haren, Laken, Louiza - Ter Kamerenbos, Neder-Over-Heembeek, Noord, Noord-Oost, Rechterover Kanaal).\n"@nl . + "Kaart van de Brusselse wijken (Vijfhoek, Haren, Laken, Louiza, Neder-Over-Heembeek, Noord, Noord-Oost, Rechteroever van het kanaal).\n"@nl . + "Situatie van de woningen binnen wijkcontracten van de Grondregie op 31 december 2014\n"@nl . + "Locatie en verdeling per wijk van de woningen van de Grondregie met het aantal wooneenheden per woning.\n"@nl . + "De locatie van de jeugdcentra, jeugdhuizen en het informatiecentrum voor jongeren op het grondgebied van de Stad Brussel.\n"@nl . + "Locatie van de zwembaden van de Stad Brussel.\n"@nl . + "Locatie van de zwembaden van de Stad Brussel.\n"@nl . + "Actualités publiées sur le site web de la Ville de Bruxelles.\n"@fr . + "Annonces des activités du jour à l'agenda du site web de la Ville de Bruxelles.\n"@fr . + "Aires de jeux de la Ville de Bruxelles avec mention de la catégorie d'âge visée et descriptif des engins.\n"@fr . + "Localisation des emplacements de taxis sur le territoire de la Région de Bruxelles Capitale. (Source:  http://opendatastore.brussels/fr/)\n"@fr . + "Fil RSS des marchés publics (appels d'offres) proposés sur le site web de la Ville de Bruxelles : http://www.bruxelles.be/artdet.cfm?function=rss&id=5477. Les candidats soumissionnaires peuvent aussi s'enregistrer via l'adresse soumissionnaires.bruxelles.be\n"@fr . + "Nombre de téléchargements de l'application Android de la Ville de Bruxelles. A noter: une nouvelle version de l'application Android de la Ville de Bruxelles a été proposée en mars 2015.\n"@fr . + "Localisation des arrêts (stations) de la STIB.\n"@fr . + "Localisation des box à vélos installés sur le territoire de la Ville de Bruxelles\n"@fr . + "Localisation des panneaux d'affichage public gérés par la Ville de Bruxelles.\n"@fr . + "Localisation des arbres remarquables sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des auberges de jeunesse et des hôtels pour jeunes sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des bibliothèques francophones de la Ville de Bruxelles et présence sur les réseaux sociaux.\n"@fr . + "Localisation des bulles (conteneurs) à verre installées sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des commissariats de police ou services de garde situés sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des bureaux de tourisme de VisitBrussels (organisme régional chargé du tourisme) sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des canisites installés sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation du Centre administratif et des bureaux de liaison de l'administration de la Ville de Bruxelles.\n"@fr . + "Salles de cinéma sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des colonnes d'affichage communal de la Ville de Bruxelles où des manifestations culturelles, sociales ou sportives, peuvent être annoncées, via un affichage par la cellule communale Affichage et Publicité.\n"@fr . + "\n\tFil RSS des concours communaux du site internet de la Ville de Bruxelles: http://www.brussels.be/artdet.cfm?function=rss&id=4694.\n"@fr . + "Localisation des distributeurs (guichets automatiques) bancaires (distributeurs automatiques de billets ou ATM) installés sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des écoles francophones de la Ville de Bruxelles.\n"@fr . + "Localisation des bibliothèques néerlandophones de la Ville de Bruxelles.\n"@fr . + "Evolution du nombre d'élèves ou d'inscriptions dans les écoles francophones de la Ville de Bruxelles.\n"@fr . + "Localisation des espèces de plantes exotiques invasives sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des installations sportives (stades, salles, complexes et terrains de sport) de la Ville de Bruxelles.\n"@fr . + "Localisation des points d'accès publics à l'Internet (PAPI) sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des institutions européennes sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des lieux culturels (organisés ou soutenus par la Ville) sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des maisons de quartier de la Ville de Bruxelles\n"@fr . + "Localisation des musées de la Ville de Bruxelles.\n"@fr . + "Fil RSS des offres d'emploi du site web la Ville de Bruxelles en provenance des départements du Personnel (Ressources humaines), de l'Instruction publique, des associations paracommunales : http://www.bruxelles.be/artdet.cfm?function=rss&id=6308.\n"@fr . + "Localisation des murs (fresques) BD de la Ville de Bruxelles.\n"@fr . + "Localisation des parcs et des jardins publics sur le territoire de la Ville de Bruxelles.\n"@fr . + "\n Localisation des espaces de stationnement (parking sur la voie publique) pour les personnes à mobilité réduite (PMR) sur le territoire de la Ville de Bruxelles.(voiries communales & régionales).\n"@fr . + "\n Localisation des parkings publics payants sur le territoire de la Ville de Bruxelles (sur la base de l'OpenDataStore régional: http://opendatastore.brussels/fr\n\n\n"@fr . + "\n\tLocalisation des rues par secteur pour les détenteurs d'une carte de riverain de stationnement sur le territoire de la Ville de Bruxelles.\n"@fr . + "\n Statistiques des pages Facebook Ville de Bruxelles (nombre de mentions \"J'aime\") en français, néerlandais, anglais.\n"@fr . + "Salles de théâtre sur le territoire de la Ville de Bruxelles.\n"@fr . + "\n Localisation des toilettes publiques sur le territoire de la Ville de Bruxelles.\n\n"@fr . + "Classement par bibliothèque des livres les plus empruntés dans les bibliothèques de la Ville de Bruxelles en 2013.\n"@fr . + "\n Localisation des urinoirs publics (toilettes pour hommes) sur le territoire de la Ville de Bruxelles.\n\n"@fr . + "Localisation des points d'accès gratuits wifi sur le territoire de la Ville de Bruxelles.\n"@fr . + "Localisation des bureaux de vote sur le territoire de la Ville de Bruxelles (Lambert 72).\n"@fr . + "Emplacements de bus scolaires aux abords des écoles de la Ville de Bruxelles.\n"@fr . + "text/html" . + . + "text/html" . + . + "text/html" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/json" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "De begroting van het Brussels Hoofdstedelijk Gewest is een indicator van de middelen waarover de Brusselse regering beschikt om haar beleid uit te voeren.\n\nU vindt een overzicht van de toestand van de overheidsfinanciën van het Brussels Gewest: de ontvangsten en uitgaven van de gewestelijke overheidsdienst Brussel (ex-Ministerie van het Brussels Hoofdstedelijk Gewest), maar ook van de plaatselijke besturen en de pararegionale instellingen, alsook statistieken over de uitstaande schuld van het Gewest."@nl . + "e4587278-2501-48ae-94e7-f31185251f93" . + "2016-11-18T00:00:00"^^ . + "2016-12-01T11:15:23.585263"^^ . + "Finances publiques"@fr . + "Overheidsfinanciën"@nl . + . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "Le budget de la Région de Bruxelles-Capitale est un indicateur des moyens dont dispose le gouvernement de la Région pour mener à bien ses politiques.\n\nVous trouverez un aperçu de la situation des finances publiques de la Région bruxelloise : les recettes et dépenses du Service public régional de Bruxelles (ex- Ministère de la Région de Bruxelles-Capitale), mais également des pouvoirs locaux et des organismes para-régionaux ainsi que des statistiques quant à l’encours de la dette de la Région. \n"@fr . + "Public finance "@en . + "The Brussels-Capital Region budget is an indicator of the means available to the government of the Region to implement its policies.\n\nYou will find an overview of public finance in the Brussels Region: the revenue and expenditure of the Brussels Regional Public Service, the local authorities and para-regional bodies, and the statistics on the outstanding debt of the Region."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC3BA798CD14264694ECBBB701EEE77B7 . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + "Proposal of regional express network for cyclists."@en . + "2016-01-01T00:00:00"^^ . + "Proposition de réseau express régional pour les cyclistes."@fr . + "2017-06-23T12:52:29.275940"^^ . + "FietsGEN"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC05406AF2496A4EC701BBF514E344A0B . + "Het fiets-GEN strekt zich uit over een straal van circa 15 kilometer rond Brussel. Er zijn 15 fietsroutes geselecteerd, die prioritair worden aangelegd. Deze prioritaire routes zijn 280 kilometer lang, waarvan ongeveer 60 procent in het Brussels Gewest. Het nieuwe net wordt hoofdzakelijk gevormd door een combinatie van de al bestaande Gewestelijke Fietsroutes (GRS) in Brussel en het Vlaamse Bovenlokaal Functioneel Fietsnetwerk (BFF). De helft van het fiets-GEN komt voort uit de al bestaande plannen voor die twee."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7292344E623DD1E4E391F4CC0008B127 . + . + "f2d31fbf-f805-4b6e-88ee-0b2834ec87f5" . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "RER-vélo"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB059F53C6CBF020491A269F57C8EFEC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB059F53C6CBF020491A269F57C8EFEC "Hoeck Michèle" . + "Cycling regional express network"@en . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "f1ddc86c-98b5-4899-bfea-f66a91b6f02b" . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Le niveau de vie des ménages peut être approché par leur revenu ou leurs dépenses. Ceux-ci permettent de mesurer le pouvoir d’achat de la population et par conséquent son accès plus ou moins facile aux biens et aux services tels que le logement, les biens d’équipement, l’alimentation, etc.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + "2016-11-18T00:00:00"^^ . + "Household income and expenditure "@en . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Income and expenditure are indications of the living standard of households. It makes it possible to measure the purchasing power of the population and, consequently, whether or not it has easy access to goods and services such as housing, capital goods, food, etc.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/\n"@en . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD14379397DDF8EBC58E29CB474A75CCF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD14379397DDF8EBC58E29CB474A75CCF "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD14379397DDF8EBC58E29CB474A75CCF "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + "Revenus et dépenses des ménages"@fr . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + "De levensstandaard van de huishoudens kan worden benaderd vanuit het standpunt van hun inkomen of hun uitgaven. Op die manier kan de koopkracht van de bevolking worden gemeten en dus ook de mate waarin zij al dan niet makkelijk toegang heeft tot goederen en diensten zoals huisvesting, uitrustingsgoederen, voeding enz.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + "2016-12-01T11:31:16.360362"^^ . + . + . + "Région de Bruxelles-Capitale"@fr . + . + "Inkomens en uitgaven van de huishoudens"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE0FBA649E5A635B967E4A06F6B5B21D7 . + . + . + . + "2016-01-27T08:13:32.460178"^^ . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "ZenCar Vehicles"@en . + "2017-10-30T09:48:28.842066"^^ . + "81834c61-5924-45aa-a3f6-80457f32bece" . + "ZenCar Autos"@nl . + "Véhicules ZenCar"@fr . + "ZenCar Vehicles"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDDF03058F3C31E4CE65A731D8E8DB9A0 . + "ZenCar Autos"@nl . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3D40291E6B142E8C8E57D0D3390644E8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3D40291E6B142E8C8E57D0D3390644E8 "BEW- BEE" . + "Région de Bruxelles-Capitale"@fr . + . + "Véhicules ZenCar"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + "Bicycle pumps"@en . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d594EC1A0600CF3B0B7AAF4449915B4EC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d594EC1A0600CF3B0B7AAF4449915B4EC "Team BruGIS" . + "De fietspompen die openbaar beschikbaar zijn en gekend zijn voor Brussel Mobiliteit."@nl . + "Les pompes à vélo publiques disponibles et connues de Bruxelles Mobilité."@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4472F8AB705A0E73BA04F7D076ABB2B8 . + "Pompes à vélo"@fr . + "Région de Bruxelles-Capitale"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9D93C8B1532C7ADFB5F5DFD3B9942D89 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9D93C8B1532C7ADFB5F5DFD3B9942D89 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9D93C8B1532C7ADFB5F5DFD3B9942D89 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "c9258fb3-5407-45f8-b5a5-39a72c27c2a9" . + "2016-01-01T00:00:00"^^ . + . + . + . + . + "2016-12-07T17:37:19.855610"^^ . + "Fietspompen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d2AA3A9D780D06DF4C8667E425661452F . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Public bicycle pumps known by Brussels Mobility."@en . + . + "ZenCar Stations "@en . + "Stations ZenCar "@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "97f14b34-fbef-4e43-9b34-d2eb24484437" . + "2017-10-30T09:46:57.931523"^^ . + "2016-01-27T08:07:40.814586"^^ . + "ZenCar Stations "@en . + "ZenCar Stations "@nl . + . + . + . + "Stations ZenCar "@fr . + "ZenCar Stations "@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDC19E65FB6FDC5E408907096853822F8 . + "Région de Bruxelles-Capitale"@fr . + . + "2018-01-23T09:36:45.415860"^^ . + "Bâtiments Bruxelles - 3DTiles"@fr . + . + . + "3D Tiles are an open specification for streaming massive heterogeneous 3D geospatial datasets. "@en . + "2017-10-06T00:00:00"^^ . + "3D Tiles are an open specification for streaming massive heterogeneous 3D geospatial datasets. "@nl . + . + . + "3D Tiles are an open specification for streaming massive heterogeneous 3D geospatial datasets. "@fr . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d39CB997EEFDD3014D3250EF9A3B854C8 . + "93b1bcb1-2adb-4cf8-9e5d-381e9904536c" . + "Brussels Buildings - 3DTiles"@en . + "Brussels Gebouwen - 3DTiles"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d70ECFB220485EFD19AEB97CC9E6AC348 . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3CB021CC946B6B8F3930E8A428564D2B . + "Stations d'épuration"@fr . + . + . + "Région de Bruxelles-Capitale"@fr . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE3367817AA96B85EF16C21789A474A66 . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de twee waterzuiveringsstations die het afvalwater en het regenwater zuiveren. Het waterzuiveringsstation Brussel-Noord (AQUIRIS, in dienst genomen in 2007) en het waterzuiveringsstation Brussel-Zuid (in dienst genomen in 2000)."@nl . + . + . + . + . + "http://opendatastore.brussels/catalog.xml?page=1" . + "100"^^ . + "http://opendatastore.brussels/catalog.xml?page=3" . + "http://opendatastore.brussels/catalog.xml?page=2" . + "205"^^ . + . + "{u'fr': u'Easybrussels', u'en': u'Easybrussels', u'nl': u'Easybrussels'}" . + "Brussels-Capital Region: localization of the two treatment stations of wastewater and rainwater. The Brussels-North waste water treatment plant (AQUIRIS commissioned in 2007) and the waste water treatment plant Brussels-South (commissioned in 2000)."@en . + "Waste water treatment plants"@en . + . + . + "2015-09-26T00:00:00"^^ . + "2f2ab1f0-19a2-43fe-940b-8d906ac19cc7" . + "Waterzuiveringsstations"@nl . + . + . + . + . + "Région de Bruxelles-Capitale : localisation des deux stations d'épuration qui traitent les eaux résiduaires et les eaux de pluie. La station d'épuration de Bruxelles-Nord (AQUIRIS, active depuis 2007) et la station d'épuration Bruxelles-Sud (active depuis 2000)."@fr . + "2016-01-22T18:45:38.176382"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d595AD2218C87A24FFC81333B2FCE9D88 . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Tunnelsecties van de tunnels van het Brussels Gewest."@nl . + "Sections de tunnel"@fr . + "2016-01-01T00:00:00"^^ . + "Tunnel sections"@en . + . + "0868986b-3239-414f-9c39-ae24ffac4513" . + "Tunnelsecties"@nl . + "Localisation des sections de tunnel dans la Région de Bruxelles-Capitale."@fr . + "2016-12-07T17:06:57.317807"^^ . + . + . + . + "Locations of tunnels in the Brussels-Capital Region."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3059C15FE07AB4D5AD70DA29E982ABF3 . + . + "2018-02-13T14:14:02.149921"^^ . + "Ce dataset vous donne l'accès aux informations concernant les positions des véhicules en temps réel, qui sont fournies par la société des transports intercommunaux de Bruxelles (STIB) en Belgique."@fr . + "1fe9c5a2-7af3-4b15-a356-07801393b34f" . + . + . + "2017-12-01T00:00:00"^^ . + "Position des véhicules (temps réel)"@fr . + "Positie van de voertuigen (real-time)"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0C0A61ABCDEF1C9E7CD847EF0F953413 . + . + "Deze dataset geeft u toegang tot real-time informatie voor de positie van de voertuigen die voorzien wordt door het Brussels intercommunaal openbaar transportbedrijf in België."@nl . + . + . + . + . + "This dataset gives you acces to the real-time information for the vehicles positions, which is provided by Brussels Intercommunal Transport Company in Belgium."@en . + "Vehicles position (real-time)"@en . + . + "Gemeentelijke website"@nl . + "2018-04-12T13:26:14.382943"^^ . + . + . + . + "Fréquentation et contenu du site internet communal d'Auderghem"@fr . + "Gebruik en inhoud van de gemeentelijke website van Oudergem"@nl . + "d1c4571c-8103-423b-8910-be65d4759913" . + "2018-04-18T07:31:06.931740"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d808A44B6E0581FBAA9774B85368FAC3E . + "Site internet communal"@fr . + . + . + "Orthophotoplan 2012"@en . + "Brussels Hoofdstedelijk Gewest : de orthofoto van het Brussels Hoofdstedelijk Gewest wordt gemaakt op basis van grootschalige luchtfoto's.\nDeze orthofoto omvat het hele grondgebied van het Gewest.\nOrthofoto's beschikbaar van 2004, 2009 en 2012."@nl . + . + . + "Brussels-Capital Region : the orthophotos of the Brussels-Capital are made on the basis of large-scale aerial photographs.\nThis orthophoto covers the whole territory of the region.\nOrthophotos available from 2004, 2009 and 2012."@en . + "2016-01-26T14:38:58.342356"^^ . + "2015-09-28T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d1F5658119AAABFC8EED472A7E0FBCBE3 . + . + . + "Région de Bruxelles-Capitale : les orthophotoplans de la Région de Bruxelles-Capitale sont réalisés sur base de photographies aériennes à grande échelle.\nCet orthophotoplan couvre l'ensemble du territoire de la région.\nLes orthophotos disponibles datent de 2004, 2009 et 2012."@fr . + "db2e0ac3-a8ac-464b-bfef-514dfa37d4e6" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d752518BE235D70A379AC74E67026CE41 . + "Orthophotoplan"@fr . + . + . + . + . + . + "Orthofoto 2009"@nl . + . + "De transitparkings worden beheerd door het Parkeeragentschap en dienen als verbinding met het openbaar vervoer."@nl . + . + "Transit parkings"@en . + "Localisation des parkings de transit.\n"@fr . + "Transit parking locations in the Brussels Capital Region.\n"@en . + "2016-01-01T00:00:00"^^ . + "2016-12-07T17:20:43.860590"^^ . + "a6c9af56-d4f6-4544-98cc-78bc24385099" . + . + . + "Parkings de transit"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d57D7639AD10526460B6E3FBFEC85E525 . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d1727855354562ADA631A0D35DAD8A131 . + . + . + "Transitparkings"@nl . + . + "Estimated number of off-road parking places, aggregated by block.\n(Joint data management with the parking agency)"@en . + "b29980e7-19e5-4873-8fb4-0f672eb021ec" . + "Parking hors voirie (par bloc)"@fr . + "Parking buiten de weg (per blok)"@nl . + "Door het parkeeragentschap van het Brussels Gewest wordt een lijst bijgehouden met de parkingen buiten de weg. De gegevens van deze lijsten worden gekoppeld aan de laag Urbadm_blocks zodat de totalen per huizenblok kunnen worden weergegeven. Parkings die niet binnen een huizenblok gelegen zijn worden gekoppeld aan de dichtstbijzijnde huizenblok in de gemeente."@nl . + "Estimation du nombre de places hors voiries, agrégées par bloc. "@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3AADB07C3EB3FAF22A058D9E16AD998A . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9466C49419BEBA145CD55285209E6095 . + "Parking hors voirie (par block)"@en . + . + . + . + . + "2016-01-01T00:00:00"^^ . + "2017-06-23T12:29:59.839642"^^ . + . + . + "Mobiliteit en vervoer"@nl . + . + . + . + . + . + . + "48f15d6f-43c6-4e42-8c8e-761356bcecb8" . + "Mobilité et transport"@fr . + . + . + . + . + "Mobility and Transport "@en . + . + . + . + . + . + . + . + "The statistics gathered by the BISA relate to road traffic, soft mobility, collective transport and shared mobility, freight transport, road safety and mobility practices.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Les statistiques rassemblées par l’IBSA portent sur la circulation routière, la mobilité douce, le transport collectif et partagé, le transport de marchandises, la sécurité routière et les pratiques de déplacements.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + . + . + . + "2016-11-22T00:00:00"^^ . + . + . + . + . + . + . + . + . + "2016-12-01T11:08:28.897760"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dEE482C133EF852BC112786DE9DEF9456 . + "De statistieken die door het IBSA verzameld werden hebben betrekking op het wegverkeer, zachte mobiliteit, collectief en gedeeld vervoer, vervoer van goederen, verkeersveiligheid en verplaatsingsgewoonten.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + . + . + . + "Localisation des bornes de recharge pour les véhicules électriques dans la région de Bruxelles-Capitale."@fr . + "Locatie van oplaadpunten voor elektrische voertuigen in het Brussels Hoofdstedelijk Gewest."@nl . + . + . + "2016-12-07T17:36:23.675916"^^ . + . + "Bornes de recharge électriques"@fr . + "Location of charging stations for electric vehicles in the Brussels-Capital Region."@en . + "2016-03-23T08:13:23.436495"^^ . + "Elektrische oplaadpunten"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d2382AA13E8EC6194CF4A54A19F437717 . + . + . + "e5b9ff72-7ee6-407d-84d4-fc8dad8fa1a7" . + "Electric charging stations"@en . + . + "Brussels Hoofdstedelijk Gewest : gegevensbestand van de groene ruimten en de recreatieve ruimten in het Brussels Hoofdstedelijk Gewest, toegankelijk voor het publiek"@nl . + "Openbare groene ruimten"@nl . + . + . + . + "Public green spaces"@en . + . + . + . + . + . + "2016-09-14T12:47:48.385384"^^ . + "Brussels-Capital Region : database of green spaces and recreational areas in the Brussels-Capital Region, accessible to the public "@en . + "2015-09-26T00:00:00"^^ . + "Région de Bruxelles-Capitale : inventaire des espaces verts et espaces récréatifs accessibles au public en Région de Bruxelles–Capitale"@fr . + "Espaces verts publics"@fr . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dADB06977A6666F6DFA57903AA82D5A8D . + . + . + "61f41610-beb6-4c0c-bfde-2e7475fda17e" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB10E6C6414C0D257D759555E765A605C . + . + . + . + . + "Brussels-Capital Region : Brussels post box is the cutting postal zones according to municipal boundaries. This division is made by the CIRB based on information provided by the National Register."@en . + "Zones postales communales"@fr . + "3452edb8-641e-4c79-96ba-516d0119291e" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFB7F9D9C3D3194E97F3E0675E89561BA . + . + . + "Brussels Hoofdstedelijk Gewest : de gemeentepostzone stemt met het snijden van de postzones in functie van de gemeentegrenzen overeen. Dit snijden wordt door CIRB op basis van inlichtingen verwezenlijkt die door het Nationale Register worden verstrekt."@nl . + "Région de Bruxelles-Capitale : la zone postale communale correspond au découpage des zones postales en fonction des limites communales. Ce découpage est réalisé par le CIRB sur base d'informations fournies par le Registre National."@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD126B63FEBC4C7D0E03F6240073DDD32 . + . + . + "2016-01-28T13:41:39.556362"^^ . + "Gemeentelijke postzone"@nl . + . + . + . + "2015-09-28T00:00:00"^^ . + "Postal zones"@en . + . + "00975d39-a062-45ee-abeb-ae13a8d80e87" . + "Organisaties actief op het terrein van gendergelijkheid in het Brussels Gewest"@nl . + "Organisations committed to gender equality in the Brussels Region"@en . + "This dataset contains: (1) the list of public services for equal opportunity (gender equality included) in the Brussels Region (at regional and local levels) ; (2) the list of organisations committed to gender equality established in the Brussels Region"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB8D93DF50D9BF460DA0B07D713E84B17 . + . + . + . + . + "Deze dataset bevat: (1) de lijst van de openbare diensten voor gelijke kansen (incl. gendergelijkheid) in het Brussels Gewest (op regionaal niveau en lokaal niveau) ; (2) de lijst van de organisaties actief op het terrein van gendergelijkheid en gevestigd in het Brussels Gewest "@nl . + "Organisations pour l'égalité de genre en Région de Bruxelles-Capitale"@fr . + . + . + . + . + "2016-11-22T00:00:00"^^ . + "2016-11-22T00:00:00"^^ . + "Ce jeu de données contient: (1) la liste des services publics pour l'égalité de genre en Région bruxelloise (aux niveaux régional et local) ; (2) la liste des organisations engagées en faveur de l'égalité de genre et établies en Région bruxelloise"@fr . + . + . + "Geografische inventarisatie van de monumenten en kunstwerken langs de gewestwegen."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA7B82A94F76AC859CA9016D97ED8CC30 . + "Artistic heritage of regional roads"@en . + "1a871ae1-335b-4624-a736-ac1f7dc80816" . + . + . + . + "Recensement georéférencé des fontaines ou pièces d'eau ainsi que des monuments et œuvres d'art situés le long des voiries régionales."@fr . + "Kunstpatrimonium gewestwegen"@nl . + "2016-12-07T17:10:20.204620"^^ . + "2016-01-01T00:00:00"^^ . + "Georeferenced census of fountains or water features as well as monuments and works of art situated along regional roads."@en . + . + "Patrimoine artistique de voirie régionale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d425426E22B86B83FA02F4EF7369C0BFB . + . + . + "Sites Natura 2000"@fr . + "Brussels Hoofdstedelijk Gewest : stations gerangschikt volgens code, in de Natura 2000 gebieden. Info over het type en de naam van de zone, het type en de naam van het station."@nl . + "Région de Bruxelles-Capitale : stations classées selon leur code, dans les sites Natura 2000. On dispose du type et du nom du site, du code et du nom de la station."@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4127D107771FDA7E9F8A00E87D059913 . + . + . + . + "2016-01-22T18:45:42.667313"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dEEB0094A2E7D15DCFDAB95A7B46B9870 . + "Natura 2000 gebieden"@nl . + "natura_2000_stations.xml" . + . + . + . + . + . + . + . + . + "2015-09-26T00:00:00"^^ . + "Natura 2000 sites"@en . + "Brussels-Capital Region : subsites (stations) classified by their code, within the Natura 2000 sites. Information on the nature and name of each site, code and name of euch subsite."@en . + . + . + . + . + "7ffdc37a-525f-4c5b-be3d-932637334e98" . + "2018-04-09T12:51:13.537928"^^ . + "Liste des rues d'Auderghem"@fr . + . + . + "Lijst van de straten in Oudergem"@nl . + "2018-04-18T07:31:55.286162"^^ . + "Rues d'Auderghem"@fr . + "Oudergemse straten"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDD955DC0FDB19D52F2A0CD5975109B99 . + . + "Images caméras"@fr . + "Cameras images visible on the Brussels Mobility portal."@en . + "aa777e51-9c99-4721-b2af-a0f5be104727" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAAE86DBCB526542448A9F90F078EB5AB . + . + . + . + . + . + "Beelden cameras"@nl . + . + . + . + "Images caméras visibles sur le portail Bruxelles Mobilité."@fr . + "2015-12-18T00:00:00"^^ . + "2016-03-02T13:31:17.867721"^^ . + "Cameras images"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dACD91713CF6E6FA67ADB944192B74F7F . + . + . + "Beelden cameras zichtbaar op de portal Brussel Mobiliteit."@nl . + . + . + . + . + . + "691fd4a0-68ab-407a-b6fd-652299ed08f9" . + "2017-08-07T07:44:14.402128"^^ . + "2018-07-12T06:23:28.826805"^^ . + "Begrotingsrekening"@nl . + "Compte budgétaire"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6BFE13A386C35965B19F74BB97AD1F13 . + . + . + . + "Réserves naturelles et forestières"@fr . + "2015-09-26T00:00:00"^^ . + "Nature and forest reserves"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d43ABD3120EC1C1A6813E6AEF7C545BDA . + "Région de Bruxelles-Capitale : données sur les réserves naturelles et forestières en Région de Bruxelles-Capitale. Noms officiels des réserves; date de l'acte de désignation; type de réserve"@fr . + "Brussels Hoofdstedelijk Gewest : gegevens over de natuur- en bosreservaten in het Brussels Hoofdstedelijk Gewest. Officiële naam van de reservaten; datum van het erkenningsbesluit; type reservaat"@nl . + . + . + . + . + . + "fd03abe0-6cfc-4b75-a8cd-36e2c961421e" . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD1884F002E90538E4433E4FD8A97C07D . + . + . + . + . + "Brussels-Capital Region : data on the nature and forest reserves in the Brussels-Capital Region . Official name of the reserve; date of the legal act identifying the reserve; type of reserve"@en . + "2016-01-22T18:45:40.005866"^^ . + "Natuur- en bosreservaten"@nl . + . + "2016-01-22T18:45:44.491667"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d814AEE2B85DA57FA2EE0D221DB90919B . + "Brussels-Capital Region : location of Brussels drinking water catchment drainage gallery operated by VIVAQUA. Each section of the drainage gallery is named. Classified as protection area 1 of water extraction (with the extraction wells)."@en . + . + . + . + . + . + "2015-09-26T00:00:00"^^ . + "Beschermingszone 1 van winningen - drainage galerij"@nl . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d534D37BA97474E37563EB9DD1EF9B643 . + "Brussels Hoofdstedelijk Gewest : Lokalisatie van de drainage galerij voor drinkwaterwinning in Brussel uitgebaat door VIVAQUA. Bevat de naam van elk drainage galerij gedeelte. Gerangschikt als beschermingszone 1 van winningen (met de waterwinningsputten)"@nl . + "Protection area 1 of water extraction - drainage gallery"@en . + . + . + . + . + . + . + "Région de Bruxelles-Capitale : localisation de la galerie drainante de captage d'eau potable de Bruxelles exploitée par VIVAQUA. On dispose du nom de chaque tronçon de la galerie drainante. Classée comme zone 1 de protection de captage (ainsi que les puits captants)"@fr . + "Zone1_protection_captage_galerie.xml" . + "Zone 1 de protection de captage - galerie drainante"@fr . + . + . + "Associations / bike services"@en . + "De fietsverenigingen en fietsdiensten gekend door Brussel Mobiliteit."@nl . + . + "Associations and bike services known by Brussels Mobility."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD937FFEB481A3833A131484187E7A34A . + . + . + "Les associations et services vélos connus de Bruxelles Mobilité."@fr . + "Associations / services vélos"@fr . + "214ef2a2-fc26-4cb8-b896-0079cac3b8ed" . + "2017-06-23T12:28:40.777064"^^ . + "2016-01-01T00:00:00"^^ . + "Fietsverenigingen / -diensten"@nl . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA34DE8F29628F265CF191304EA59869C . + . + "Toponiemen van de openbare ruimtes"@nl . + "2015-09-28T00:00:00"^^ . + "Toponymie des espaces publics"@fr . + . + . + . + "Brussels-Capital Region : toponymy public spaces is the name of :\n- Highways;\n- Areas of water;\n- Green areas;\n- Cemeteries."@en . + "2016-01-28T10:43:43.660131"^^ . + . + . + . + "Région de Bruxelles-Capitale : la toponymie des espaces publics correspond à la dénomination des :\n- Voies publiques ;\n- Zones d’eau ;\n- Zones vertes ;\n- Cimetières."@fr . + "Brussels Hoofdstedelijk Gewest : de toponiemen komen overeen :\n- Openbare wegen ;\n- Waterzones ;\n- Groenzones ;\n- Kerkhoven en begraafplaatsen."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d865BA987312B3780A14D76AA465C04F5 . + "e53dea13-3be1-48dc-8d72-eeefb8f64bd2" . + "Toponymy"@en . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE4EB246A39AC218EDB19CD5276DEB00A . + . + . + . + . + . + . + . + . + "Population"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8451A75AD09223559923F5074B053D2D . + "2016-10-26T00:00:00"^^ . + . + . + . + . + . + . + . + . + "L'IBSA rassemble et produit de nombreuses données statistiques qui apportent un éclairage chiffré sur la population en Région bruxelloise : population totale, structure de la population par groupes d'âges, sexes ou nationalités, structure des ménages, mouvements de population, projections de population...).\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + . + . + . + . + . + . + . + . + . + "The BISA (Brussels Institute for Statistics en Analysis) compiles and produces a range of statistical data to give quantified insights into the population in the Brussels Region: total population, structure of population by age, sex or nationality, structure of households, movements of population, demographic projections etc.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Population"@en . + . + . + . + . + . + . + . + . + . + "Het BISA verzamelt en produceert tal van statistische gegevens die een cijfermatig beeld schetsen van de bevolking van het Brussels Hoofdstedelijk Gewest: totale bevolking, structuur van de bevolking per leeftijdscategorie, geslacht of nationaliteit, structuur van de huishoudens, loop van de bevolking, bevolkingsprojecties.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + . + . + . + "b3e3ff15-4c5c-4e39-acc7-dd5c9f4ffc23" . + "2016-12-01T13:09:36.305045"^^ . + "Bevolking"@nl . + . + . + . + . + "Ce dataset vous donne l'accès aux informations concernant le temps d'attente aux arrêts en temps réel, qui sont fournies par la société des transports intercommunaux de Bruxelles (STIB) en Belgique.\n"@fr . + "2017-01-16T00:00:00"^^ . + "This dataset gives you acces to the real-time information for the next departure time at stop(s), which is provided by Brussels Intercommunal Transport Company in Belgium."@en . + "Le temps d'attente aux arrêts (temps réel)"@fr . + "Waiting time at stops (real-time)"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3771DDD545C7FF833EAADDD0B8C16985 . + "Wachttijden aan de haltes (real-time)"@nl . + "2018-02-13T14:00:58.321650"^^ . + . + . + . + . + "ff05bc1f-73ec-46aa-8251-f167e806d552" . + "Deze dataset geeft u toegang tot real-time informatie voor de volgende vertrektijden aan de halte(s), die voorzien wordt door het Brussels intercommunaal openbaar transportbedrijf in België."@nl . + . + . + . + . + "Taxi stops"@en . + "De locaties van de taxihaltes in het Brussels Hoofdstedelijk Gewest."@nl . + "Arrêts taxis"@fr . + "Taxi stop"@nl . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF01E40D8DF7FD02ED41C805485FED73F . + "Localisation des aires de stationnements de taxis sur le territoire de la Région de Bruxelles-Capitale."@fr . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8A205C93A6B873873D785461591DA0F5 . + "2016-12-07T17:05:12.426289"^^ . + "2016-01-01T00:00:00"^^ . + "The locations of the Taxi stops in the Brussels-Capital Region. "@en . + "7b0e017f-ef5d-4011-8684-fa40598d0079" . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dECD4EDA046583F5D2E84C1FD67988073 . + "2016-03-01T13:34:42.652737"^^ . + "2016-12-07T17:39:05.042470"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5253BC8E88B23C4F192ADBB6729E406F . + "The road network in the Brussels-Capital Region is divided in regional (generally the most structuring traffic axes) and municipal (generally roads of local character) roads. Approximately 20% of the road network is managed by the region."@en . + "fc062424-139c-481f-93c3-ffb6520df3cc" . + . + . + . + "Gewestwegen"@nl . + "Regional roads"@en . + "Le réseau routier de la Région de Bruxelles-Capitale est réparti en voiries régionales (généralement les axes les plus structurants) et communales (généralement les voiries à caractère local). A peu près 20% de la voirie sont gérés par la Région."@fr . + "Voiries régionales"@fr . + . + "SLD" . + . + . + "In het Brussels Hoofdstedelijk Gewest zijn de openbare wegen opgedeeld in gewestelijke (doorgaans de meest structurerende verkeersassen) en gemeentelijke wegen (doorgaans wegen van lokale aard). Ongeveer 20% van het wegennet worden door het gewest beheerd."@nl . + . + "Education "@en . + "Onderwijs"@nl . + . + . + "Enseignement"@fr . + . + . + . + . + . + . + "Onderwijs is een Gemeenschapsbevoegdheid. Het BISA maakt statistieken die de gegevens van de Vlaamse Gemeenschap en de Franse Gemeenschap combineren : schoolbevolking, aantal scholen. Het BISA stelt ook gegevens op over schoolbevolking buiten de gemeenschappen.\n \nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + "4850d3bf-4c4f-4752-bb2b-019224b4957c" . + . + . + . + "Education being a community competence, the BISA provides statistics that combine data from the Flemish Community and the French Community: student population, number of schools… The BISA also provides student population data for education not provided by the communities.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "2016-11-21T00:00:00"^^ . + . + . + . + . + . + . + . + "2016-12-01T11:22:28.048075"^^ . + . + "L’enseignement étant une compétence communautaire, l’IBSA propose des statistiques qui combinent des données de la Communauté flamande et de la Communauté française : population scolaire, nombre d’établissements scolaires…\nL’IBSA présente également des données de population scolaire pour l’enseignement hors communautés.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4455D8966A2B7AD708AC83922EA55488 . + . + . + . + . + "Information from https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin"@en . + . + . + . + . + . + . + "19181ead-b37f-44d8-a708-d9bddd347c3b" . + . + "Information from https://agenda.brussels"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCF726A6A5E46023651ABC5AA084186DB . + . + . + . + . + "Information provenant de https://agenda.brussels"@fr . + "Agenda.brussels"@en . + . + . + "Agenda.brussels"@nl . + . + . + . + "2017-06-13T00:00:00"^^ . + "2017-06-13T00:00:00"^^ . + "Agenda.brussels"@fr . + . + "Parkings à vélo à contrôle d'accès disponibles sur le territoire de la Région Bruxelloise gérés par CycloParking"@fr . + "b8748e7e-87e5-4599-9d0c-669e086a6e80" . + "2017-09-14T15:36:36.841813"^^ . + "CycloParking"@fr . + "2017-09-13T12:43:52.027637"^^ . + "CycloParking"@nl . + "Fietsparkings met toegangscontrole beschikbaar in het Brussels Hoofdstedelijk Gewest beheerd in het kader van het CyloParking project."@nl . + "CycloParking"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5C3F7A2F5F30A786C7D7CC7E62FC8761 . + . + . + . + . + "Bicycle parking with acces control available in Brussels and managed by the CycloParking project."@en . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA0C35FA6E956E4D4CA0CE01DE7452099 . + . + . + . + . + "{u'fr': u'visit.brussels', u'en': u'visit.brussels', u'nl': u'visit.brussels'}" . + . + "Economy "@en . + "U vindt een beeld van de economische activiteit in het Brussels Hoofdstedelijk Gewest in cijfers: bedrijvendemografie, omzetcijfers, toegevoegde waarde, investeringen en export.\nAl deze gegevens zijn opgesplitst per activiteitstak.\nHier staan ook de resultaten van de vertrouwensenquête bij de Brusselse consumenten die elke maand door de Nationale Bank van België uitgevoerd is.\n"@nl . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCBB7115B7FCCD6D72FB4F18708013013 . + . + . + . + . + . + . + "You will find figures on the economic activity in the Brussels-Capital Region: demography of companies, company turnover, value added, investments and exports.\nAll this data is split according to sectors of activity.\nYou will also find the results regarding the consumer survey realized each month by the National Bank of Belgium."@en . + "2016-12-01T11:18:01.865849"^^ . + "Économie"@fr . + "Vous trouverez un portrait chiffré de l’activité économique en Région de Bruxelles-Capitale: démographie d'entreprises, chiffres d'affaires de celles-ci, valeur ajoutée, investissements et exportations.\nToutes ces données sont réparties par branche d'activité.\nRetrouvez également les résultats de l’enquête de confiance des consommateurs effectuée chaque mois par la Banque Nationale de Belgique.\n"@fr . + . + . + . + . + . + . + . + . + "Economie "@nl . + . + . + . + . + "7b85e211-841a-4b72-9aac-b2c982cc65d6" . + "2016-11-21T00:00:00"^^ . + . + "Brussels-Capital Region : The municipality is the smallest administrative division of the territory of the Federal Belgium. The Brussels Region is composed of nineteen municipalities."@en . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8CA3E4585E2142B0B433B9FE86D089BE . + "Communes"@fr . + "0e1a4a49-52af-4a1d-976a-a7d64f18204e" . + "Municipalities"@en . + "Gemeente"@nl . + . + . + "Brussels Hoofdstedelijk Gewest : de gemeente is de kleinste administratieve indeling van het grondgebied van het federale België. Het Brussels Gewest bestaat uit negentien gemeenten."@nl . + . + . + "2016-01-28T13:48:20.932747"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA93A78FA0ED565530B48133B51C80D47 . + . + . + "TXT" . + "2015-09-28T00:00:00"^^ . + . + . + "Région de Bruxelles-Capitale : la commune est la plus petite division administrative du territoire de la Belgique fédérale. La Région bruxelloise est composée de dix-neuf communes."@fr . + . + "Incidents Fix My Street"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "2018-03-27T00:00:00"^^ . + "Het mobiel en Internet platform Fix My Street laat zowel de burgers als de administraties toe incidenten \nte signaleren binnen de Brusselse openbare ruimte (bepaalde degradaties, verloedering, enz) alsook elke stap van de oplossing van het incident op te volgen.\n\nDe gegevens die ter beschikking worden gesteld zijn :\n•\tIdentificatie van het incident \n•\tCreatie datum\n•\tDatum van de update\n•\tStatus\n•\tLocatie : XY en adres\n•\tInformatie van de verantwoordelijke administratie van het incident : identificatie, naam en type (gemeente, nutsbedrijven, instituten) \n•\tCategorie\n•\tNaam van de verantwoordelijke groep van het incident\n\nAPI beschikbaar op https://api.brussels/store/apis/info?name=fixmystreet&version=1.0.0&provider=admin"@nl . + . + . + . + "The mobile and web platform Fix My Street allows citizens and administrations to flag incidents in the Brussels public space (deterioration, litter, etc.) and to follow its resolution step by step. \nThe available data are: \n•\tIncident identification\n•\tCreation date\n•\tUpdate date\n•\tStatus\n•\tLocation: XY and address\n•\tInformation on the organization responsible for the incident resolution: identification, name and type (municipality, utilities, institution)\n•\tCategory\n•\tName of the group responsible for the incident resolution\n\nAPI available on https://api.brussels/store/apis/info?name=fixmystreet&version=1.0.0&provider=admin "@en . + "01593a26-ed57-498e-bec0-13011a75a773" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC060AC25CC5358540102F99C12D041EC . + "La plateforme internet et mobile Fix My Street permet au citoyen ainsi qu’à l’administration de signaler des incidents dans l’espace public bruxellois (dégradations, malpropreté, etc.) et de suivre chaque étape de résolution de l’incident.\nLes données mises à disposition sont: \n•\tIdentifiant de l’incident\n•\tDate de création\n•\tDate de mise à jour\n•\tStatut \n•\tLocation : XY et l’adresse\n•\tInformation sur l’organisation responsable de l’incident : identifiant, nom et type (commune, impétrant, institution)\n•\tCatégorie\n•\tNom du groupe responsable de l’incident\n\nAPI disponible sur https://api.brussels/store/apis/info?name=fixmystreet&version=1.0.0&provider=admin"@fr . + . + . + "2018-03-27T00:00:00"^^ . + "Incidents Fix My Street"@en . + "Incidents Fix My Street"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d28CE2430A751CE255CD7E68CE15E4DD2 . + . + . + . + . + "Les données rassemblées portent sur le climat, la qualité de l’air, la consommation et la qualité de l’eau, les collectes de déchets, les espaces verts, la biodiversité, la consommation énergétique, etc.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + "De verzamelde gegevens hebben betrekking op het klimaat, de luchtkwaliteit, de consumptie en de kwaliteit van water, ophalingen van afval, groene ruimtes, biodiversiteit, energie, enz.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + . + . + . + "The data compiled relate to climate, air quality, consumption and quality of water, waste collection, green spaces, biodiversity, energy consumption, etc.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Environment and Energy "@en . + "2016-11-22T00:00:00"^^ . + . + "WFS" . + "e8c8d970-e994-4b3b-8a2e-0a416c663342" . + . + . + . + . + . + . + . + "Environnement et énergie"@fr . + . + . + "Milieu en energie"@nl . + . + . + "2016-12-01T11:11:29.746134"^^ . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD1D8D21F78E196C0EAC4F630C62338F3 . + . + . + . + . + . + . + . + "Parce qu’il y en a pour tous les goûts, les idéaux et les budgets, U Talk Freelance a dressé une liste des espaces de coworking à Bruxelles."@fr . + "310f1955-2326-47f4-96f3-5da9899e54bc" . + "2016-09-19T09:23:45.170683"^^ . + "Lijst van coworking in Brussel"@nl . + . + . + "Brussels coworking spaces list"@en . + "Liste des espaces de coworking à Bruxelles"@fr . + "Want er is plaats voor alle smaken, idealen en budgetten, U Talk Freelance een lijst van coworking ruimten in Brussel opgesteld ."@nl . + . + . + . + "Because there is places for all tastes, ideals and budgets, U Talk Freelance compiled a list of coworking spaces in Brussels."@en . + "2018-02-22T13:58:22.043425"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dECF1CA1D321C9F212C6B1265BB43CAFC . + . + . + . + . + . + . + "Verkeersevenementen (incidenten, files...) en wegwerkzaamheden."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d460ACDF20173D1C2522D1EBC6AC6C612 . + "Evénements trafic & travaux"@fr . + "Evénements trafic (incidents, files...), ainsi que les chantiers perturbants."@fr . + . + . + "2016-01-01T00:00:00"^^ . + "Traffic events (incidents, traffic jams, ...) and obtrusive works."@en . + "Verkeersevementen en wegwerkzaamheden"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE8FED21376C255A706D0B32AD51C96C7 . + . + . + . + "915daf6d-2ca5-4ca5-b225-6b0deb935886" . + "2018-01-31T13:23:15.966648"^^ . + "Traffic events and works"@en . + . + "Données sur les parkings publics et leur taux d'occupation en temps réel."@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d971D6B6943E1553498571F3B1806FEC0 . + "2017-01-25T08:33:56.779274"^^ . + "Bezetting Parkings (real time)"@nl . + "Occupation des Parkings (real time)"@fr . + "Parking Occupancy (real time)"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d26462C6E1674C9311F60C22856699190 . + . + "Gegevens over openbare parkings en de bezettingsgraad in real time."@nl . + "486c2e3c-0fb9-48da-841e-2a244db0c39f" . + . + . + "Data on public car parks and their occupancy rate in real-time."@en . + "2018-03-28T07:13:08.042480"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4BA5BF8C8D9081A664F9A09BCB9A332F . + "2016-12-07T17:36:55.636732"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAE9CD16E75EDF652651A76650FB23230 . + "2016-01-01T00:00:00"^^ . + "Deze laag geeft de categorisering van de wegen weer volgens het Iris 2-plan. De hiërarchische indeling maakt een onderscheid tussen autosnelwegen, grootstedelijke wegen, hoofdwegen, verzamelwegen, interwijkwegen en wijkwegen (of lokale wegen). De indeling is geen bestaande toestand van de wegen in Brussel, maar een geplande toestand waarop het beleid moet worden getoetst."@nl . + "Classification hiérarchique des voiries."@fr . + . + . + . + "Hierarchical classification of roads."@en . + "da9e43be-5cf6-47da-9117-5503a6d14ef8" . + "Hiërarchie van wegen"@nl . + "Hiérarchie des voiries"@fr . + "Roads hierarchy"@en . + . + . + . + "a85c8fa5-a178-4000-9d07-3686415c1c7f" . + . + . + "Auderghem - publication des données contenues dans le rapport sur la transparence des rémunérations et avantages des mandataires publics bruxellois"@fr . + "2018-04-09T12:36:49.525381"^^ . + "Rémunérations et avantages des mandataires"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF17445C120E6312AB7E0349C1E2068 . + "Oudergem - gegevens uit het verslag \"ransparantie van bezoldigingen en voordelen van de Brusselse openbare mandatarissen\""@nl . + "Bezoldigingen en voordelen van de mandatarissen"@nl . + . + . + . + "2018-03-05T13:51:11.982825"^^ . + . + . + . + . + "2014-11-03T00:00:00"^^ . + "Région de Bruxelles-Capitale : données sur l'ensemble des 3 masses d'eau de surface de la Région bruxelloise, délimitées dans le cadre de la Directive et de l'Ordonnance Cadre Eau : code européen, code bruxellois et nom de la masse d'eau, district hydrographique."@fr . + "Brussels-Capital Region : data on the 3 Surface Water Bodies of the Area of Brussels, defined under the Directive and the Ordinance Water : code European, code of Brussels and name of the water mass, hydrographic district."@en . + . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD10AA77E877FA891AAA2CDE19EAD3006 . + "Oppervlaktewaterlichamen"@nl . + "Masses d'eau de surface"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d228CEDC4629B1AA6E352033023934C0C . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : gegevens over het geheel van de 3 oppervlaktewaterlichamen in het Brussels Gewest, afgebakend in kader van de Kaderrichtlijn- en Ordonnantie Water : Europese code, Brusselse code en naam van het oppervlaktewaterlichaam, stroomgebiedsdistrict."@nl . + "Surface water bodies"@en . + "80993dc2-303b-4798-b500-e38499d13e4c" . + "2016-01-22T18:45:43.614890"^^ . + . + . + . + "Brussels-Capital Region : the entity \"Rail Block\" (railway area) identifies portions of the territory occupied by the railway. These surfaces not lines representing the rails.\n\nThere are:\n\n1. areas of railway located at [0] (ie ground level \"natural\", abbreviated RB-0):\n- Areas in a physical island\n- Crossing\n2. areas of railway located at [-] (ie ground level, abbreviated RB-M):\n- Parts of railway under a road (invisible from the sky)\n- tunnels\n3. zones thereof located at [+] (ie higher level short RB-P):\n- Parts of railway over a road or a river (visible from the air)\n\nThe contours of the airspaces of the railway were built from the topographic map UrbIS-Topo (photogrammetric surveys).\n\nThe tunnels were designed based on drawings provided by SNCB."@en . + "2016-01-28T13:38:12.005742"^^ . + . + . + "2015-09-28T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4DA7ED9210B6D18176CED31134A30C72 . + "Rail block"@en . + "Spoorweg Zone"@nl . + . + . + "Brussels Hoofdstedelijk Gewest : de entiteit « Rail Block » (spoorwegzone) is dat deel van het grondgebied dat ingenomen wordt door de spoorweg. Het betreft oppervlakken en geen lijnen die de sporen of rails\nvoorstellen.\n\nMen maakt onderscheid tussen:\n\n1. de spoorwegzones gelegen op niveau [0] (dat wil zeggen op het \"maaiveld\", afgekort RB-0):\n- zones in een fysisch huizenblok\n- overwegen\n2. de spoorwegzones gelegen op niveau [-] (dat wil zeggen ondergronds, afgekort RB-M):\n- spoorwegdelen onder een weg (onzichtbaar vanuit de lucht)\n- tunnels\n3. de spoorwegzones gelegen op niveau [+] (dat wil zeggen bovengronds, afgekort RB-P):\n- spoorwegdelen boven een weg of een waterloop (zichtbaar vanuit de lucht)\nDe grenzen van de bovengrondse zones van de spoorweg werden opgebouwd met behulp van de topografische kaart UrbIS-Topo (fotogrammetrische opmetingen). De tunnels werden getekend op basis van de plannen zoals aangeleverd door de NMBS."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE3F6AC9451B0E3655CEA6A54FE63EA51 . + . + . + . + "5a08229f-d2ed-4b75-b588-1b07994aacc8" . + "Région de Bruxelles-Capitale : l'entité « Rail Block » (zone de chemin de fer) identifie les portions du territoire occupées par le chemin de fer. Il s'agit de surfaces et non de lignes représentant les rails. \n\nOn distingue:\n\n1. les zones de chemin de fer situées au niveau [0] (càd niveau du terrain \"naturel\", abrégé RB-0) :\n- zones dans un îlot physique\n- passages à niveau\n2. les zones de chemin de fer situées au niveau [-] (càd niveau souterrain, abrégé RB-M) :\n- parties de chemin de fer sous une voirie (invisibles du ciel)\n- tunnels\n3. les zones de chemin de fer situées au niveau [+] (càd niveau supérieur, abrégé RB-P) :\n- parties de chemin de fer au dessus d’une voirie ou d’un cours d'eau (visibles du ciel)\n\nLes contours des zones aériennes du chemin de fer ont été construits à partir de la carte topographique UrbIS-Topo (levés photogrammétriques). \n\nLes tunnels ont été dessinés sur base de plans fournis par la SNCB."@fr . + "Zones de chemin de fer"@fr . + . + . + . + "2015-03-30T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF2BF65EE86CB1984D6B13DB41966F8CA . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE8E36CBF344E596FDEB640126C17A5ED . + . + . + . + "Waterzone"@nl . + . + . + "Région de Bruxelles-Capitale : l'entité « Water Block » (ou zone d'eau) localise et identifie les différents corps d'eau présents sur le territoire de la Région bruxelloise (canal, étangs, Senne, cours d'eau,...).\n\nOn distingue:\n1. les zones d'eau situées au niveau [0] qui sont visibles du ciel (abrégé WB-0)\n2. les zones d'eau (uniquement pour le canal) situées au niveau [-] sous la voirie (invisibles du ciel) (abrégé WB-M)"@fr . + "Brussels-Capital Region : the entity \"Water Block\" (or water area) locates and identifies various water bodies in the territory of the Brussels-Capital Region (channel, ponds, Senne river, ...).\n\nThere are:\n1. water areas located at [0] which are visible from the air (abbreviated WB-0)\n2. water areas (for the channel) located at [-] on the road (invisible from the sky) (abbreviated WB-M)"@en . + "Zones d'eau"@fr . + . + . + "02a5920e-048c-480e-8ab8-8575731b409e" . + "2016-01-28T13:44:04.455084"^^ . + . + . + . + "Brussels Hoofdstedelijk Gewest : de entiteit « Water Block » (of waterzone) lokaliseert en identificeert de verschillende waterlichamen die aanwezig zijn op het grondgebied van het Brussels Gewest (kanaal, vijvers, Zenne, waterlopen...).\n\nMen maakt onderscheid tussen:\n\n1. de waterzones gelegen op niveau [0], die zichtbaar zijn vanuit de lucht (afgekort WB-0)\n2. de waterzones (alleen voor het kanaal) gelegen op niveau [-] onder de weg, en die onzichtbaar zijn vanuit de lucht (afgekort WB-M)"@nl . + "Water block"@en . + . + . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFB300147DD6B18D8497AD96D31217417 . + . + . + "2015-10-13T00:00:00"^^ . + "Beschermde gebieden op Europees niveau"@nl . + "Brussels-Capital Region : protected sites at the European level in implementation of the following European legislation: Habitats directive-92/43/EEC (Natura 2000 network), Directive concerning urban waste water treatment-91/271/EEC (sensitive areas) and Nitrates Directive-91/676/EEC (vulnerable zones"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d88A7660CEF7FFFA91F61CEF98F83D0A9 . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : zones in het Brussels gewest die een Europees beschermingsstatuut genieten op basis van de volgende richtlijnen : Habitatrichtlijn-92/43/EEC(Natura 2000 netwerk), Richtlijn Stedelijk Afvalwater-91/271/EEC (gevoelig gebied) en Nitraatrichtlijn-91/676/EEC (kwetsbare zone)"@nl . + . + . + "Région de Bruxelles-Capitale : zones bénéficiant d'un statut de protection européen en Région bruxelloise au titre des directives suivantes : Directive habitats-92/43/EEC (réseau Natura 2000), Directive Eaux Résiduaires Urbaines-91/271/EEC (zone sensible) et Directive Nitrates-91/676/EEC (zones vulnérables)"@fr . + "rpa.xml" . + "Zones protégées au niveau européen"@fr . + . + . + "2016-01-22T18:45:37.258450"^^ . + "Protected areas at the European level"@en . + . + . + "2018-04-18T07:31:32.485899"^^ . + . + . + . + "e745bf45-9557-482b-ad97-58aeb268cda8" . + "2018-04-12T13:14:49.417327"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7D085BBA789A8F90DD87B82AA46E764F . + "Oudergem - Bevolking"@nl . + "Données relatives à la Population d'Auderghem"@fr . + "Auderghem - Population"@fr . + . + . + . + "Gegevens over de bevolking van Oudergem"@nl . + . + "This dataset contains:\n* the municipal equal opportunity officers in the Brussels Region (legislature 2012-2018); \n* the Brussels signatory municipalities of the European Charter for Equality of Women and Men role in Local Life (CEMR) "@en . + "Gender equality at local level in the Brussels Region"@en . + . + . + "Deze dataset bevat:\n* de lijst van lokale mandatarissen bevoegd voor Gelijke Kansen in de 19 gemeenten van het Brussels Gewest (legislatuur 2012-2018); \n* de lijst van de Brusselse ondertekenende gemeenten van het Europees charter voor gelijkheid van vrouwen en mannen op lokaal vlak (CEMR)\n"@nl . + . + . + . + . + "Égalité de genre au niveau local en Région bruxelloise"@fr . + "2016-11-22T00:00:00"^^ . + "5230a13a-283f-4e36-a65c-72ad20b9b35a" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d66F755905DD7C8BC678B87D3F51BA727 . + "Gendergelijkheid op lokaal niveau in het Brussels Gewest"@nl . + "Ce jeu de données contient:\n* la liste des mandataires locaux à l'Egalité des Chances en Région bruxelloise (législature 2012-2018); \n* la liste des communes bruxelloises qui ont signé la Charte européenne pour l'égalité des femmes et des hommes dans la vie locale (CCRE)"@fr . + . + . + "{u'fr': u'Bruxelles Urbanisme et Patrimoine', u'en': u'Brussels Planning and Heritage', u'nl': u'Brussel Stedenbouw en Erfgoed'}" . + . + "2016-11-22T00:00:00"^^ . + . + "Cet ensemble de données concerne la représentation des femmes dans l'espace public symbolique en Région bruxelloise"@fr . + "Femmes et espace urbain symbolique (Région bruxelloise)"@fr . + "Deze dataset betreft de vertegenwoordiging van vrouwen in de symbolische openbare ruimte van het Brussels Gewest"@nl . + "Women and symbolic urban space (Brussels Region)"@en . + "2016-11-23T00:00:00"^^ . + . + . + . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d11C9DD3E5B6747E0910EF8D32C58FB41 . + . + . + . + "This dataset focuses on the representation of women in the symbolic public space of the Brussels Region"@en . + "2016-11-23T00:00:00"^^ . + "027694ab-3c73-4f04-b192-8aca009bddb8" . + "Vrouwen en symbolische stedelijke ruimte (Brussels Gewest)"@nl . + . + "dd6ffbbe-7af2-4c60-9a6f-e513a53eb0e3" . + "Région de Bruxelles-Capitale : l'entité SA correspond aux axes des tronçons de rues de la Région de Bruxelles-Capitale"@fr . + "2016-01-28T13:43:38.797472"^^ . + . + . + . + . + "Axes des tronçons de rues"@fr . + "Hoofdlijn van de stukken van straat"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d760861C56057322502B8EBBDEAF93EB2 . + "Street axis"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBC8157FD21DE40C42E8FCA06367923EA . + . + . + . + "Brussels Hoofdstedelijk Gewest : dit entiteit stemt met de hoofdlijnen van de stukken van straat overeen"@nl . + "Brussels-Capital Region : SA entity corresponds to the axes of street sections of the Region of Brussels-Capital"@en . + . + . + "2015-03-30T00:00:00"^^ . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAD7E987C49F350EAC509D704640C79E2 . + "Brussels Hoofdstedelijk Gewest : habitats gerangschikt volgens hun EU-code, in de Natura 2000 gebieden. Info over de code van het station en een beknopte beschrijving van het habitat"@nl . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5C16FC92568D654233E103BE5B50E2D0 . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "Brussels-Capital Region : habitats classified by their EU code, within the Natura 2000 sites. Informations on the code of the station and a brief description of the habitat."@en . + "Région de Bruxelles-Capitale : habitats classés selon leur EU-code, dans les sites Natura 2000. On dispose du code de la station, d'une description sommaire de l'habitat."@fr . + "2015-09-26T00:00:00"^^ . + "Natura 2000 habitats"@en . + . + . + . + . + . + . + "Habitats Natura 2000"@fr . + "2016-01-22T18:45:48.317059"^^ . + "Natura 2000 habitats"@nl . + "natura_2000_habitats.xml" . + . + . + . + "Although early childhood services are a Community competence, they are vitally important for the Brussels-Capital Region. There is a high demand due to the current demographic growth in the Region.\nThe statistics presented relate mainly to the number and accommodation capacity of early childhood facilities in the Brussels Region.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Jonge kinderen"@nl . + . + . + . + "Early childhood"@en . + "Petite enfance"@fr . + . + . + . + "Quoique de compétence communautaire, l’accueil de la petite enfance présente des enjeux essentiels pour la Région de Bruxelles-Capitale. L’essor démographique en cours dans la Région fait peser sur ce service une demande forte.\nLes statistiques présentées portent notamment sur le nombre de milieux d’accueil et la capacité de ceux-ci.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD669ACFEF50FC023209F2B0AE60F7120 . + "Hoewel kinderopvang tot de bevoegdheden van de gemeenschappen behoort, is dit thema zeer belangrijk voor het Brussels Hoofdstedelijk Gewest. De sterke demografische ontwikkeling in het Gewest veroorzaakt een zeer grote vraag naar deze dienst.\nDeze statistieken hebben met name betrekking op het aantal kinderopvangvoorzieningen en de capaciteit.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + . + . + . + "2016-11-18T00:00:00"^^ . + "2016-11-18T16:07:11.980479"^^ . + "461ca649-85cd-4b6a-9eb4-eed94d379845" . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "2016-05-10T20:56:25.585450"^^ . + "Brussels-Capital Region : location of the different groundwater level monitoring sites (or piezometric sites) managed by Brussels Environment (IBGE-BIM) under the surveillance monitoring of the groundwater status in accordance with the Ordonnance and the Water Framework Directive. Indication of the analyzed water body, the European Code and the Brussels code of the monitoring site."@en . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de meetstations (of piëzometers) van het niveau van het grondwater beheerd door Leefmilieu Brussel (BIM) in kader van de monitoringscontrole van de algemene staat van de grondwaterlichamen, conform aan de ordonnantie en de Kaderrichtlijn Water. Met aanduiding van het geanalyseerde grondwaterlichaam, de Europese code en de Brusselse code van het meetstation"@nl . + "Groundwater level monitoring network - European reporting"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d699D4871CF97CED30F05CBAF55BB9BA6 . + . + . + . + "8d65ba4a-2982-434a-9c91-33df3ee666f8" . + . + "{u'fr': u'innoviris', u'en': u'innoviris', u'nl': u'innoviris'}" . + "Région de Bruxelles-Capitale : localisation des stations de mesure du niveau (ou stations piézométriques) des eaux souterraines gérées par Bruxelles Environnement (IBGE), dans le cadre des contrôles de surveillance de l'état général des eaux souterraines, conformément à l'Ordonnance et à la Directive Cadre Eau. Indication de la masse d'eau analysée, du code européen et du code bruxellois de la station de mesure"@fr . + "Monitoring van het niveau van de grondwaterlichamen - Europese rapportering"@nl . + "Réseau de surveillance du niveau des eaux souterraines - reporting européen"@fr . + . + . + "2015-09-26T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFD1F4A91C5C32078059820585BA1C4FA . + . + . + . + "5253ae6b-78b9-4e79-b1e5-b6822ededf14" . + "2016-03-03T14:21:35.946129"^^ . + "Journal du Conseil"@fr . + "Journal du Conseil"@nl . + "Journal du Conseil"@en . + "Journal du Conseil"@en . + "Journal du Conseil"@fr . + "Journal du Conseil"@nl . + "2016-03-03T13:50:22.792222"^^ . + . + . + "Brussels Hoofdstedelijk Gewest : de stroomgebieden (of deelstroomgebieden) in het Brussels Gewest en zijn omgeving"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC3946BF809556E53D66CF2BD28D63A54 . + . + . + . + . + "Sous_bassin_hydro.xml" . + . + . + . + "Sub-drainage-basin (or catchment area) of the rivers"@en . + "Sous bassins versants"@fr . + "Région de Bruxelles-Capitale : étendue des sous bassins hydrographiques (ou sous bassins versants) en Région bruxelloise et sa périphérie"@fr . + "Deelstroomgebieden"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAE0DD8BAFFA0C1A0E67B71CAC6AFD7F3 . + . + . + "Brussels-Capital Region: extent of sub-drainage-basin (or sub-catchment area) of the rivers in the Brussels Region and surrounding area"@en . + "2016-01-22T18:45:36.362311"^^ . + . + . + "2015-09-26T00:00:00"^^ . + . + "aef6dcab-57b3-438b-91e6-9693d33b43b1" . + "2017-01-12T00:00:00"^^ . + "GAN – OPHAALKALENDER HUISAFVAL"@nl . + . + "Ce web service en JSOn renvoie pour une adresse validée UrbIS les jours de collectes entre 2 dates pour les différents flux de produit.\nIl renvoie également les informations relatives aux bulles à verres et les parcs à conteneurs pour une adresse validée UrbIS.\nIl dispose également d’une fonction permettant d’extraire la liste des adresses possibles.\n\nCe web service nécessite la signature d’une convention entre Bruxelles-Propreté et l’entité juridique souhaitant exploiter les données. Cette convention est dérivée de la Licence ouverte du CIRB (Cette licence a été créée à partir de la licence ouverte Etalab, mission sous l'autorité du Premier ministre français chargée de l'ouverture des données publiques et du développement de la plate-forme française Open Data (avec l'autorisation de Etalab). www.etalab.gouv.fr - http://www.etalab.gouv.fr/article-etalab-publie-la-licence-ouverte-open-licence-86708897.html .\n\nToute demande de renseignement ou d’accès doit se faire auprès de Monsieur Vincent Jumeau, Directeur général de Bruxelles-Propreté soit par email : (ict@arp-gan.brussels), soit par courrier avenue de Broqueville 12 à 1150 Bruxelles"@fr . + "ARP – Calendrier de collectes porte-à-porte des ménages"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4C811EFDF1207087A1AA510D02F09958 . + . + . + "Deze JSOn web service verstuurt voor een gevalideerd UrbIS adres de ophaaldagen tussen 2 data voor de verschillende productstromen.\nZe verstuurt ook informatie over glascontainers en containerparken voor een gevalideerd UrbIS adres.\n\nZe beschikt ook over een functie die mogelijke adressen kan selecteren.\n\nDeze web service vereist de handtekening van een overeenkomst tussen Net Brussel en de juridische entiteit die de gegevens wil gebruiken. Deze overeenkomst is afgeleid uit de open Licentie van het CIBG (Deze licentie werd gecreëerd op basis van de open Licentie Etalab, een missie onder gezag van de Franse eerste minister belast met het openbaar maken van publieke gegevens en de ontwikkeling van een Frans Open Data platform me de toestemming van Etalab.\n\nwww.etalab.gouv.fr - http://www.etalab.gouv.fr/article-etalab-publie-la-licence-ouverte-open-licence-86708897.html ).\n\nElke aanvraag voor inlichtingen of toegang moet gebeuren via Vincent Jumeau, algemeen directeur van Net Brussel, ofwel via mail (ict@arp-gan.brussels), ofwel via een brief naar de Broquevillelaan 12 in 1150 Brussel."@nl . + "2012-07-01T00:00:00"^^ . + "This web service in JSOn sends back for each UrbIS validated address the collection days between 2 dates for the different products.\nIt also sends back the information related to glass banks and container parks for any UrbIS validated address.\n\nIt also allows for the extraction of a list containing the possible addresses.\n\nThis web service requires the signature of a convention between Bruxelles-Propreté and the legal entity wishing to exploit the data. This convention is derived from CIRB’s open License (This license is based upon the Etalab open licence, mission under the authority of France’s Prime Minister in charge of opening the public data and developing the French Open Data open platform – with the authorization of Etalab)\n\nwww.etalab.gouv.fr - http://www.etalab.gouv.fr/article-etalab-publie-la-licence-ouverte-open-licence-86708897.html.\n\nAny request for information or access must be made to Mr. Vincent Jumeau, General Manager of Bruxelles-Propreté either by email : ict@arp-gan.brussels, either by courier : avenue de Broqueville 12, 1150 Brussels."@en . + "ARP – COLLECTIONS CALENDAR - HOUSEHOLDS HOUSE-TO-HOUSE COLLECTIONS."@en . + . + . + . + . + "Brussels Hoofdstedelijk Gewest: lokalisatie van de verschillende meetstations voor de luchtkwaliteitsmonitoring. Stations van telemetrisch, niet-telemetrisch (bemonstering met laboratoriumanalyse) en weernetwerken zijn inbegrepen. De verschillende gemeten parameters worden gespecificeerd voor elk station.\nDocumentatie :\nhttp://document.environnement.brussels/opac_css/elecfile/QAir%20Rpt0911%20ssAnn%20B%20C%20D%20E%20bis%20nl"@nl . + "80003a22-2e8b-4dd3-89ea-5b76433e6faf" . + "Monitoring van de luchtkwaliteit"@nl . + "Monitoring network of the air quality"@en . + "2015-10-02T00:00:00"^^ . + . + . + "Brussels Capital Region: location of the different stations measuring the air quality. Stations of the telemetric, non-telemetric (sampling with laboratory analysis) and weather networks are included. The measured parameters are specified for each station.\nNote :\nIn French : \nhttp://documentation.bruxellesenvironnement.be/documents/QAir_Rpt0911_corr_ssAnnexesB_C_D_E_fr.PDF\nIn Dutch :\nhttp://document.environnement.brussels/opac_css/elecfile/QAir%20Rpt0911%20ssAnn%20B%20C%20D%20E%20bis%20nl"@en . + "2016-01-22T18:45:45.479489"^^ . + . + . + . + . + "{u'fr': u'Bruxelles Mobilit\\xe9', u'en': u'Brussels Mobility', u'nl': u'Brussel Mobiliteit'}" . + . + . + "Région de Bruxelles-Capitale : localisation des différentes stations de mesure de la qualité de l'air. Sont reprises les stations du réseau télémétrique, non-télémétriques (échantillonnages avec analyse en laboratoire) et météorologique. Les différents paramètres mesurés sont précisés pour chaque station.\nDocument explicatif :\nhttp://documentation.bruxellesenvironnement.be/documents/QAir_Rpt0911_corr_ssAnnexesB_C_D_E_fr.PDF"@fr . + "Réseau de surveillance de la qualité de l'air"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7E7C0936E56BF82A522F26AE5A43FB59 . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6016774B7208E2622A844DB7CFFDA618 . + . + . + . + "Relevé de la signalisation verticale le long des voiries régionales de la région de Bruxelles-Capitale. Les panneaux sont représentés par leur géométrie (utiliser le fichier de style pour les visualiser dans un outil SIG)."@fr . + "Signalisation verticale"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA291F1876327E0F46C1592CAFC00625A . + . + . + "Locatie van de verticale signalisatie langs de gewestwegen van het Brussels Hoofdstedelijk gewest. De borden worden weergegeven als geometrie. Gebruik het stijlbestand om de juiste kleuren weer te geven in een GIS-omgeving."@nl . + . + "Road signs"@en . + "Verticale signalisatie"@nl . + "c2843c7e-9e1b-454c-b08e-24f8884c6da9" . + . + . + . + "2016-12-07T17:35:59.477262"^^ . + "2016-09-14T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d06DAC25EAEC984A70A02122B89D62E21 . + "Road signs along regional roads of the Brussels-Capital Region. The panels are represented by their geometry (use the style file to view in a GIS tool)."@en . + . + . + . + "Cambio carsharing vous donne accès 24h/24 et 7j/7 à différents modèles de voitures selon le besoin. C'est le plaisir de profiter d'une voiture sans les inconvénients (assurances, entretien, nettoyage). \n\nDe plus, les voitures partagées bénéficient d'avantages considérables en matière de stationnement dans tout Bruxelles. Vous ne payez que lorsque vous vous situez en zones rouges. C'est le moment de devenir membre ! "@fr . + "2016-02-09T00:00:00"^^ . + "2016-02-09T00:00:00"^^ . + . + . + . + "ec5a7b30-3610-4826-b992-3218dedd7dd1" . + "Stations Cambio"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d35CE5C409A4AB7C9FF58AA0B93F26BEF . + "Cambio standplaatsen"@nl . + "Cambio stations"@en . + "Met cambio reserveer je je auto naar keuze, op het moment dat je hem nodig hebt. Het comfort van een eigen wagen, zonder de bijhorende kopzorgen (onderhoud, kuisen, verzekering). Jij rijdt, cambio doet de rest. \n\nBovendien, hoeven cambio-gebruikers geen parkeergeld te betalen in het Brusselse Gewest (met uitzondering van de rode and de gele zones). "@nl . + "Cambio carsharing allows you to book your desired type of car when you need one. We offer you the comfort of a car without the hassle of owning one (maintenance, cleaning, insurance). You enjoy the ride, cambio takes care of the rest. \n\nFurthermore, cambio users are not required to pay parking fees in the Brussels-Capitale region (except in the red and yellow zones). \n"@en . + . + . + "Regional cycle routes"@en . + . + . + "ICR"@fr . + "De gewestelijke fietsroutes zijn aanbevolen fietswegen voor verplaatsingen op middellange en lange afstand die door verschillende gemeenten lopen. In het algemeen volgen deze routes lokale wegen, omdat het verkeer er minder druk is, minder snel verloopt en dus minder stresserend is dan op de hoofdwegen. Vermits bepaalde natuurlijke of kunstmatige obstakels (brug over een dal, een kanaal, een autosnelweg, doorgang onder een spoorweg, enz.) moeten worden genomen, lopen de routes echter soms ook over grotere verkeersaders."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d784D09B24EB57FC440535C124B24A192 . + "2017-09-13T22:40:44.070842"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6EF62B1621AA24B99DD086E71675F4C0 . + . + "The regional cycle routes are suggested paths for travel to medium and long-distance biking."@en . + "2016-01-01T00:00:00"^^ . + "Les itinéraires cyclables régionaux sont des cheminements recommandés pour des déplacements à vélo de moyenne et longue distance à travers plusieurs communes. En règle générale, ces itinéraires empruntent des voiries locales, où le trafic est moins dense, moins rapide, et donc moins stressant que sur les voiries principales. Mais le franchissement de certains obstacles naturels ou artificiels (pont franchissant une vallée, le canal, une autoroute, passage sous une ligne de chemin de fer, etc.) ramène parfois les itinéraires sur les grands axes."@fr . + . + . + . + "a4abf87b-5e18-4b90-9ab6-48d97dc64ff2" . + "GFR"@nl . + . + "Brussels-Capital Region : this directive has as an aim the prevention of the major accidents implying of dangerous substances and the limitation of their consequences for the man and the environment, in order to ensure in a coherent and effective way in all the country of the high levels of protection.\nThe geographical data file SEVESO consists of several classes of objects which make it possible to locate all the SEVESO sites on the territory of the area of Brussels."@en . + "0bacf5ca-c51d-42d2-8a5a-7519cae09564" . + "Seveso sites"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8A9A00C88B7C0DC5107860615AA8D5BA . + . + . + "2016-01-22T18:45:41.797188"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBB56969207EDE5D005DC5CB099E7769C . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest: deze richtlijn heeft als onderwerp de preventie van de belangrijkste ongevallen die gevaarlijke stoffen impliceren en de beperking van hun gevolgen voor de mens en het milieu, teneinde op samenhangende en efficiënte wijze in het heel land van de hoge beschermingsniveau's te waarborgen.\nHet spel van geografische gegevens SEVESO bestaat uit verschillende objectklassen die het mogelijk maken om alle plaatsen SEVESO op het grondgebied van Regio van Brussel te localiseren."@nl . + . + . + "Région de Bruxelles-Capitale : cette directive a pour objet la prévention des accidents majeurs impliquant des substances dangereuses et la limitation de leurs conséquences pour l'homme et l'environnement, afin d'assurer de façon cohérente et efficace dans tout le pays des niveaux de protection élevés.\nLe jeu de données géographiques SEVESO est constitué de plusieurs classes d'objets qui permettent de localiser tous les sites SEVESO sur le territoire de la Région de Bruxelles."@fr . + "Sites Seveso"@fr . + . + . + . + . + "2015-09-26T00:00:00"^^ . + "Seveso sites"@en . + . + . + . + . + "La Région de Bruxelles-Capitale compte une large population au travail ou en âge de travailler et attire également de très nombreux travailleurs habitant dans les deux autres régions du pays.\nCombien de travailleurs compte la Région ? Dans quels secteurs d'activité sont-ils actifs et pour quel salaire ? Où se retrouve la population active occupée bruxelloise ? Qu'en est-il des chiffres du chômage ?\nRetrouvez les statistiques de l'IBSA concernant le marché du travail, organisées par :\nlieu de résidence des actifs (population en âge de travailler, population active occupée, chômage, etc..) et\nlieu de travail (nombre de salariés, indépendants, salaires,...)\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + . + . + "Arbeidsmarkt "@nl . + "Labour market "@en . + "Marché du travail"@fr . + . + . + . + . + . + . + . + . + . + . + . + "In het Brussels Hoofdstedelijk Gewest is een groot deel van de bevolking actief of op arbeidsleeftijd; het Gewest trekt eveneens veel werknemers aan die in de twee andere gewesten wonen.\nHoeveel werknemers telt het Gewest? In welke sectoren werken zij en voor welk loon? Waar concentreert de Brusselse werkende beroepsbevolking zich? Hoe zit het met de werkloosheidscijfers?\nU vindt de statistieken van het BISA over de arbeidsmarkt, georganiseerd per:\n woonplaats van de actieve bevolking (bevolking op arbeidsleeftijd, werkende beroepsbevolking, werkloosheid enz.) en\n werkplaats (aantal loontrekkenden, zelfstandigen, lonen,...).\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + "2016-12-01T11:20:48.433485"^^ . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "df093b9f-a601-4f51-ae92-752e0eba7650" . + . + . + . + . + "2016-11-21T00:00:00"^^ . + "The Brussels-Capital Region has a large working population or population of working age and also attracts a very high number of workers who live in the two other Regions of the country.\nHow many workers does the Region have ? In which sectors are they employed and at which salary ? Where is the employed population in Brussels? What about the unemployment figures ?\nFind BISA statistics on the job market sorted by:\nplace of residence of the labour force (population of working age, working population, unemployment, etc.) and place of work (numbers of employees, self-employed, salaries, etc.).\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + . + . + . + . + . + "{u'fr': u'Bruxelles \\xe9conomie et emploi', u'en': u'Bruxelles \\xe9conomie et emploi', u'nl': u'Brussel economie en werkgelegenheid'}" . + . + "{u'fr': u\"Le r\\xe9gulateur bruxellois pour l'\\xe9nergie\", u'en': u'Brussels energy regulator', u'nl': u'De Brusselse regulator voor energie'}" . + . + . + . + . + . + "{u'fr': u'parking.brussels', u'en': u'parking.brussels', u'nl': u'parking.brussels'}" . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD63D9C34B295EBB7A9984D672FA59E5A . + "Perspectives économiques régionales"@fr . + . + . + "2017-06-23T12:29:02.059642"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFBDE59B26A85712614D2459BF35134B6 . + "5a3ddb52-5465-4703-9557-f315b1e057c6" . + . + . + "Collecto is a collective taxi service available every day between 11 PM and 6 AM everywhere in the Brussels-Capital Region."@en . + "Collecto haltes"@nl . + "2016-01-01T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB30FF5D77D3D091E88D5B3487AF7B7D2 . + "Région de Bruxelles-Capitale"@fr . + . + "Collecto est un service de taxis collectifs disponible 7 jours sur 7 entre 23 heures et 6 heures du matin sur tout le territoire de la Région de Bruxelles-Capitale."@fr . + "Collecto stops"@en . + "Arrêts Collecto"@fr . + "De locaties van de Collecto stopplaatsen in het Brussels Hoofdstedelijk Gewest. Collecto is een collectieve taxidienst die zeven dagen op zeven beschikbaar is tussen 23 uur 's avonds en 6 uur 's ochtends, en dit in het hele Brussels Hoofdstedelijk Gewest. Collecto bedient momenteel meer dan 200 vertrekpunten (gelegen aan de MIVB-haltes).\n"@nl . + . + . + . + "Publicaties over gendergelijkheid in de Brusselse regio"@nl . + "publications"@fr . + "Publications sur l'égalité de genre en région bruxelloise"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF383B1DB8C9D1A1DA642D66F5A9AA438 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF383B1DB8C9D1A1DA642D66F5A9AA438 "Mathieu Tonglet" . + "Deze dataset bevat bibliografische referenties aan ondermeer wetgeving en beleidsteksten, aan lobbydocumenten en aan statistische studies met betrekking tot gendergelijkheid en de situatie van vrouwen in het Brussels Hoofdstedelijk Gewest. Zij zijn afkomstig uit de online catalogus van het Amazone Documentatiecentrum Genderbeleid."@nl . + "2016-11-24T11:33:58.341408"^^ . + "2016-12-23T13:02:41.407536"^^ . + "Région de Bruxelles-Capitale"@fr . + . + "Ce jeu de données contient des références bibliographiques de textes législatifs, documents politiques, notes, communications, rapports statistiques, etc. relatifs à l’égalité de genre et à la situation des femmes et des hommes dans la Région bruxelloise. Elles proviennent du catalogue bibliographique en ligne du Centre de Documentation d’Amazone sur la Politique de Genre. "@fr . + "Publications on gender equality in the Brussels region"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE41F72C47DA8F5682351D2450B0B4EEB . + "0cdaf543-0038-4723-849e-ff1197c54a57" . + "bibliographic resources"@fr . + . + . + "Brussels"@fr . + "gender equality"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d81D532A18EC908CA7102BCBAEE0DF398 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6F96C7ECE63147054BCFA5EA250CB164 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6F96C7ECE63147054BCFA5EA250CB164 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6F96C7ECE63147054BCFA5EA250CB164 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Brussel Mobiliteit heeft het Centre d’études sociologiques (CES) de l'Université Saint-Louis (USL-B) gevraagd een samenvatting te maken van de beschikbare gegevens in Brussel. Deze opdracht gebeurt in nauwe samenwerking met andere onderzoekers (VUB en ULB), zodat uitwisseling en een interdisciplinaire benadering van het mobiliteitsprobleem aangemoedigd worden."@nl . + "2016-03-02T14:10:25.578113"^^ . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7597974B9D32C6C590CB13BBC38C8267 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7597974B9D32C6C590CB13BBC38C8267 "CIRB" . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Bruxelles Mobilité a confié une mission de synthèse des très nombreuses données disponibles au Centre d’études sociologiques (CES) de l'Université Saint-Louis - Bruxelles (USL-B). Ce travail s’effectue en collaboration avec des chercheurs d’autres universités (ULB et VUB), de manière à favoriser les échanges et une approche interdisciplinaire de la problématique des mobilités."@fr . + . + "Katernen van het Kenniscentrum van de mobiliteit"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC420368CAA042F2B405D9E8C7878087C . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Brussels Mobility has entrusted the task of synthesis of many data available to the Centre for Sociological Studies (CES) at the Saint Louis University - Brussels (USL-B). This work is done in collaboration with researchers from other universities (ULB and VUB), in order to promote exchanges and an interdisciplinary approach to the issue of mobility."@en . + "d23ddde7-2451-4444-8821-cf72f3f61b75" . + "2016-01-01T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE5C3AB4885F967E473A5B3181FC3CEF0 . + "Notebooks of the Observatory of mobility"@en . + . + . + . + . + "Cahiers de l’Observatoire de la mobilité"@fr . + . + "Autres formes d'aide sociale"@fr . + "Belgium"@fr . + "Précarité et aide sociale"@fr . + "België"@fr . + "Brussel"@fr . + "Right to social integration"@fr . + "Statistiques"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "In Brussel, net als in de rest van België, wonen veel mensen die over onvoldoende bestaansmiddelen beschikken. Onder bepaalde voorwaarden hebben zij recht op maatschappelijke hulp. Die wordt georganiseerd om de volledige bevolking een minimuminkomen te garanderen.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + "Insecurity and Welfare benefits "@en . + "2016-11-18T00:00:00"^^ . + "Insecurity"@fr . + "Right to social assistance"@fr . + . + . + "À Bruxelles comme dans le reste de la Belgique, de nombreuses personnes disposent de moyens de subsistance insuffisants. Sous certaines conditions, ces personnes peuvent bénéficier d’une aide sociale. Celle-ci est organisée dans le but de garantir un revenu minimum à l'ensemble de la population.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + "52f28221-7542-4abe-9110-5082d49d1f1f" . + "Andere vormen van maatschappelijke hulp"@fr . + "Belgique"@fr . + "Droit à l'aide sociale"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d75AE666460BA239B3A1D79E599C7571F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d75AE666460BA239B3A1D79E599C7571F "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d75AE666460BA239B3A1D79E599C7571F "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Brussels"@fr . + "Other forms of social aid"@fr . + "Droit à l'intégration sociale"@fr . + "Recht op maatschappelijke hulp"@fr . + "Bestaansonzekerheid en sociale bijstand"@nl . + "In Brussels as in the rest of Belgium, many people do not have sufficient means of subsistence. In some circumstances, these people may qualify for welfare benefits. These are aimed at ensuring that all the population has a minimum income.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Geslacht"@fr . + "Recht op maatschappelijke integratie"@fr . + "Bruxelles"@fr . + "Sex"@fr . + "Statistics"@fr . + "Statistieken"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dEF289B5A571AC494ED326B4782E7E8AA . + "2016-12-01T11:29:47.219099"^^ . + "Bestaansonzekerheid"@fr . + "Sexe"@fr . + "Précarité"@fr . + . + . + "Brussels Hoofdstedelijk Gewest : een straatknooppunt staat voor een verzameling van punten op de uiteinden van de assen van het wegennet (« straatas»). Het komt overeen met een kruising van assen of met een asuiteinde."@nl . + "Belgique"@fr . + "Reporting Inspire"@fr . + "Noeuds de carrefours"@fr . + "route"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Brussels-Capital Region : Street nodes designate the set of points at the ends of lines of road network (\"Axe Street\"). It corresponds to an intersection of axes or a shaft end."@en . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8C818FBB8FC590D24B3C9D048F198981 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8C818FBB8FC590D24B3C9D048F198981 "Bruxelles Environnement : Département eau" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4834F417B13589F07B111F4E110059A5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4834F417B13589F07B111F4E110059A5 "POLYGON ((4.2270 50.7504, 4.2270 50.9212, 4.4968 50.9212, 4.4968 50.7504, 4.2270 50.7504))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4834F417B13589F07B111F4E110059A5 "{\"type\" : \"Polygon\", \"coordinates\" : [[[4.227048, 50.750431], [4.227048, 50.9212455], [4.4967503, 50.9212455], [4.4967503, 50.750431], [4.227048, 50.750431]]]}"^^ . + "Région de Bruxelles-Capitale"@fr . + . + "Région de Bruxelles-Capitale : les nœuds de rue désignent l’ensemble des points situés aux extrémités des axes du réseau viaire (« Axe de Rue »). Il correspond à une intersection d’axes ou à une extrémité d’axe."@fr . + "2015-09-28T00:00:00"^^ . + "Straatknooppunt"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8D7CB2C93FE0C4ECC8CD2E7833D3B993 . + "Street node"@en . + "Espace public"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Région de Bruxelles-Capitale"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + "6c5fef0f-81ac-4da9-8cdc-ad3efb945655" . + "2016-01-28T13:39:56.443407"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD0315E997F010592FC0CF37C8DD54505 . + . + . + "Defibrillator"@fr . + "Cardio"@fr . + "Défibrilator"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "2016-04-29T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDBABE43D2541D6DA028A3E1757D4E687 . + "Défibrilateurs"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d307297A6F5D355AAE540F56F7AE70AF6 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d307297A6F5D355AAE540F56F7AE70AF6 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d307297A6F5D355AAE540F56F7AE70AF6 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "2016-04-29T00:00:00"^^ . + . + "AED"@fr . + "DAE"@fr . + "Defibrillator"@nl . + "Défibrillateurs"@en . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "33fb13c2-3cd5-423c-830a-33b508d31b15" . + . + "Lieux et dates des constats."@fr . + "3e9030ac-06fd-4326-bf61-0321a12971a3" . + "2018-04-09T12:41:14.100828"^^ . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Plaats en datum van de vaststellingen"@nl . + "2018-04-18T07:32:09.611475"^^ . + "Sanctions administratives communales"@fr . + "Région de Bruxelles-Capitale"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9C7362DA652CBFC49685E9212D05FDDE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3A2EC9D16B62E3C857B4607EF327074F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3A2EC9D16B62E3C857B4607EF327074F "Corentin Descamps" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE5EFF87757D79631306151414E62BC09 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE5EFF87757D79631306151414E62BC09 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE5EFF87757D79631306151414E62BC09 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Gemeentelijke administratieve sanctie"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d93075D71840A35CAF1F6870376F56F31 . + . + . + "Ce web service en JSOn permet Introduction d’une demande relative à un problème de collecte porte-à-porte des ménages ou de malpropreté dans la région. Toutes les communes de la Région à l’exception d’Auderghem et Woluwé-Saint-Pierre sont connectées à l’application AlloWeb (https://www.arp-gan.be/fr/un-probleme.html).\n\nCe web service nécessite la signature d’une convention entre Bruxelles-Propreté et l’entité juridique souhaitant exploiter la possibilité d’introduire une damande. Cette convention est dérivée de la Licence ouverte du CIRB (Cette licence a été créée à partir de la licence ouverte Etalab, mission sous l'autorité du Premier ministre français chargée de l'ouverture des données publiques et du développement de la plate-forme française Open Data (avec l'autorisation de Etalab).\n\nwww.etalab.gouv.fr - http://www.etalab.gouv.fr/article-etalab-publie-la-licence-ouverte-open-licence-86708897.html.\n\nToute demande de renseignement ou d’accès doit se faire auprès de Monsieur Vincent Jumeau, Directeur général de Bruxelles-Propreté soit par email : (ict@arp-gan.brussels), soit par courrier avenue de Broqueville 12 à 1150 Bruxelles."@fr . + "\nDeze JSOn web service maakt het mogelijk om een vraag te stellen in verband met een probleem rond huisvuilophaling of netheid in de buurt. Alle gemeenten in het Gewest, met uitzondering van Oudergem en Sint-Pieters-Woluwe, zijn verbonden aan de app AlloWeb https://www.arp-gan.be/en/problem.html\n\nDeze web service vergt de handtekening van een overeenkomst tussen GAN en de juridische entiteit die een vraag wil indienen. Deze overeenkomst is afgeleid uit de open Licentie van het CIBG (Deze licentie werd gecreëerd op basis van de open Licentie Etalab, een missie onder gezag van de Franse eerste minister belast met het openbaar maken van publieke gegevens en de ontwikkeling van een Frans Open Data platform me de toestemming van Etalab.\n\nwww.etalab.gouv.fr - http://www.etalab.gouv.fr/article-etalab-publie-la-licence-ouverte-open-licence-86708897.html ).\n\nElke aanvraag voor inlichtingen of toegang moet gebeuren via Vincent Jumeau, algemeen directeur van Net Brussel, ofwel via mail (ict@arp-gan.brussels), ofwel via een brief naar de Broquevillelaan 12 in 1150 Brussel."@nl . + "ARP – Introduction d’une demande relative à un problème de collecte porte-à-porte des ménages ou de malpropreté dans la région"@fr . + "GAN – INDIENING VAN EEN VRAAG IN VERBAND MET EEN PROBLEEM ROND DE HUISVUILOPHALING OF NETHEID IN HET GEWEST."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d67717C4B1928EFB561C32E4B9D146171 . + "propreté"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEC7B63F5F1FF9D5AC5B1BD8442B25C17 . + "Région de Bruxelles-Capitale"@fr . + "2017-02-20T10:49:30.633290"^^ . + "2017-02-20T10:48:54.874995"^^ . + "This web service in JSOn allows for the introduction of a request related to a household house-to-house collection issue or cleanliness in the region. All municipalities of the region excepted Auderghem and Woluwé-Saint-Pierre are connected to the AlloWeb application (https://www.arp-gan.be/en/problem.html).\n\n This web service requires the signature of a convention between Bruxelles-Propreté and the legal entity wishing to exploit the data. This convention is derived from CIRB’s open License (This license is based upon the Etalab open licence, mission under the authority of France’s Prime Minister in charge of opening the public data and developing the French Open Data open platform – with the authorization of Etalab)\n\nwww.etalab.gouv.fr - http://www.etalab.gouv.fr/article-etalab-publie-la-licence-ouverte-open-licence-86708897.html.\n\nAny request for information or access must be made to Mr. Vincent Jumeau, General Manager of Bruxelles-Propreté either by email : ict@arp-gan.brussels, either by courier : avenue de Broqueville 12, 1150 Brussels."@en . + "f7a19002-a3e6-4adc-b090-4fde9dfee117" . + "environnement"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "BRUXELLES-PROPRETÉ – INTRODUCTION OF A REQUEST RELATED TO A HOUSEHOLD HOUSE-TO-HOUSE COLLECTION ISSUE OR CLEANLINESS IN THE REGION."@en . + "déchet"@fr . + . + "Brussel"@fr . + "Brussels"@fr . + "Dit thema biedt zeer uitgebreide informatie over de bodembezetting, de kenmerken van de bestaande bebouwde oppervlakte en de verkoop van vastgoed.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + "Occupation du sol"@fr . + "2016-12-01T11:12:54.906761"^^ . + "Residential and non-residential buildings"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + "Aménagement du territoire et immobilier"@fr . + "Ruimtelijke ordening en vastgoed"@nl . + "Housing subsidies and grants"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2215620C61328A5282FAAF6997F73C19 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2215620C61328A5282FAAF6997F73C19 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2215620C61328A5282FAAF6997F73C19 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "2016-11-22T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA8A5F1A7AF64D58689B3974D2310781D . + "Premies en toelagen voor huisvesting"@fr . + "Bruxelles"@fr . + "België"@fr . + "Land-use planning and real estate"@en . + "Primes et allocations destinées au logement"@fr . + "Residentiële en niet-residentiële gebouwenparken"@fr . + "Statistieken"@fr . + "Parc de bâtiments résidentiels et non-résidentiels"@fr . + "Land occupancy"@fr . + "Industrieparken en -terreinen"@fr . + "Social housing"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Statistiques"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4BD30FA50D5441C983786D6D87FFD238 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4BD30FA50D5441C983786D6D87FFD238 "Bruxelles Environnement" . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Ce thème délivre de nombreuses informations sur l’occupation du sol, les caractéristiques du bâti existant et les ventes immobilières notamment.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + "Industrial sites and parks"@fr . + "11faf7bd-804d-4f52-80d8-793c5ed98531" . + "Parc de logements sociaux"@fr . + "This section provides a wealth of information, for instance on land occupancy, the characteristics of existing constructions and real estate sales.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Parcs et terrains industriels"@fr . + "Sociale woningen"@fr . + "Statistics"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Belgique"@fr . + "Bodembezetting"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8EAA4E7E979B49941A98CD221D199847 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8EAA4E7E979B49941A98CD221D199847 "François Du Mortier" . + "Belgium"@fr . + . + "Recherche et technologie"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD6DA6D78F929A0EA1F6C122736F5CA07 . + "Research and technology"@en . + "Brussel"@fr . + "dd511fc4-16df-48d8-b560-ae2d50f4e7d7" . + "Sex"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "2016-12-01T11:16:24.836990"^^ . + "Belgium"@fr . + "2016-11-22T00:00:00"^^ . + "Onderzoek en technologie"@nl . + "This section presents the statistics for Brussels on both R&D expenditure and staff working in this field.\nThe statistics on the use and equipment with regard to information technology and communication found in Brussels households are also presented in this theme.\n"@en . + "België"@fr . + "Informatie- en communicatietechnologieën"@fr . + "Information and communication technologies"@fr . + "Onderzoek en ontwikkeling"@fr . + "Statistics"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Brussels"@fr . + "Statistiques"@fr . + "Région de Bruxelles-Capitale"@fr . + . + "Bruxelles"@fr . + "Research and development"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d57BC8CC496136D9A959C5A4575945AD8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d57BC8CC496136D9A959C5A4575945AD8 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d57BC8CC496136D9A959C5A4575945AD8 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . + "Geslacht"@fr . + "Cette série présente des données statistiques bruxelloises qui se rapportent aussi bien aux dépenses de R&D qu’au personnel qui travaille dans ce domaine.\nLes statistiques relatives à l’utilisation et l’équipement en technologies de l'information et de la communication des ménages bruxellois sont également présentées sur cette thématique."@fr . + "Dit thema omvat Brusselse statistische gegevens die zowel betrekking hebben op de uitgaven inzake O&O als op het personeel dat in dit domein werkt.\nDe statistieken inzake ICT-gebruik en -uitrusting van de Brusselse huishoudens worden eveneens weergegeven in dit thema."@nl . + "Recherche et développement"@fr . + "Sexe"@fr . + "Statistieken"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Belgique"@fr . + "Technologies Information et Communication"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3A5D09C02632136D9CCA811E02105C8A . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5B2C092F46D53F24E814E00BA19A4B8C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5B2C092F46D53F24E814E00BA19A4B8C "CIRB" . + "Brussels-Capital Region : the territory of the Brussels-Capital Region is divided into six local police zones each including several municipalities."@en . + "Brussels Hoofdstedelijk Gewest : het grondgebied van het Brussels Gewest is opgedeeld in zes politiezones, waarin telkens verschillende gemeenten samengebracht zijn."@nl . + "8d89a535-35b3-4ded-894d-3cf1b4bad93e" . + "2016-01-28T13:49:55.040724"^^ . + "Belgique"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBF12EF78E65F7848216C431443A44B41 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBF12EF78E65F7848216C431443A44B41 "Bruxelles Environnement" . + "Zones de police"@fr . + "Politiezone"@nl . + "Police zone"@en . + "Région de Bruxelles-Capitale : le territoire de la Région bruxelloise est découpé en six zones de police locale rassemblant chacune plusieurs communes."@fr . + "Reporting Inspire"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "2015-09-28T00:00:00"^^ . + "Fond de plan"@fr . + "Harvested"@fr . + "Région de Bruxelles-Capitale"@fr . + "limite administrative"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA1E977449A1E885F69931FC632C07663 . + . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest: De entiteit « Side Walk » (of stoep) stelt de afbakening voor van de wegranden. Deze zones(veelhoeken) bestrijken de verhoogde gedeelten van het openbare domein en de fysische huizenblokken. Als geen stoepen bestaan, komen de omtrekken overeen met de grens van het fysische huizenblok.\nDe stoepen werden getekend op basis van de topografische kaart UrbIS Topo (fotogrammetrische en topografische opmetingen)."@nl . + "2016-01-28T13:36:57.213885"^^ . + "Brussels-Capital Region : The entity \"Side Walk\" (or sidewalk) is the delimitation of roadsides. these areas (polygons) cover raised in the public domain and physical islets parties.\nWhere sidewalks do not exist, the contours correspond to the limit of the physical island. The sidewalks were designed based on the topographic map UrbIS-Topo (raised Photogrammetric and topographic)."@en . + "Stoep"@nl . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale : L’entité « Side Walk » (ou trottoir) représente la délimitation des bords de routes. Ces zones(polygones) couvrent les parties surélevées dans le domaine public et les îlots physiques.\nLorsque les trottoirs n’existent pas, les contours correspondent à la limite de l’îlot physique.\nLes trottoirs ont été dessinés sur base de la carte topographique UrbIS-Topo (levés photogrammétriques et topographiques)."@fr . + "6621ad54-67e1-4ddf-a861-4f309852aa49" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0C32E09AFA9AF06FF3C4C2683A7FA1EF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0C32E09AFA9AF06FF3C4C2683A7FA1EF "Team BruGIS" . + "2015-03-30T00:00:00"^^ . + . + "Région de Bruxelles-Capitale"@fr . + "Trottoirs"@fr . + "Side Walk"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFBE8101373468F4276942802F9891CCA . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD0623B5F4A3F20854EFD0D7D4E75C9F3 . + . + "Belgique"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d1776C4A741C0B5D00EC851419A0CCA68 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2810F7DA2C240527467E62232D1848BD . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2810F7DA2C240527467E62232D1848BD "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2810F7DA2C240527467E62232D1848BD "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0B0E38893F0DE96B847B659D42B3DA3E . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0B0E38893F0DE96B847B659D42B3DA3E "Secrétariat communal" . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "2016-01-01T00:00:00"^^ . + "2016-12-07T17:39:54.229779"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6939BA1EECD7CFC42FBB0632763D1F4D . + "0467e059-7d11-4a63-bd4c-7ba8deb09762" . + "Belangrijkste openbare parkings in het Brussels Gewest. \n(Gemeenschappelijk beheer van de data met het parkeeragentschap)"@nl . + "Principaux parkings publics dans la Région de Bruxelles-Capitale. \n(Gestion conjointe des données avec l'agence de stationnement)"@fr . + "Parkings publics"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Openbare parkeerplaatsen"@nl . + "Main public car parks in the Brussels-Capital Region.\n(Joint management data with the parking agency)"@en . + "Public parkings"@en . + . + "2015-09-28T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3112D776FF578756F8B3A58E9172E268 . + "Straatoppervlak"@nl . + "Espace public"@fr . + "Brussels Hoofdstedelijk Gewest : het straatoppervlak komt overeen met een opdeling van de openbare weg in elementaire oppervlakken, die stukken straat vormen."@nl . + "Région de Bruxelles-Capitale"@fr . + "Région de Bruxelles-Capitale"@fr . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE68D1118D83F2FC08B1F9989E7669360 . + "Harvested"@fr . + "Région de Bruxelles-Capitale : la surface de rue correspond au découpage de la voie publique en surfaces élémentaires représentant des morceaux de rue."@fr . + "2016-03-08T15:17:15.746579"^^ . + . + . + . + "Région de Bruxelles-Capitale : la surface de rue correspond au découpage de la voie publique en surfaces élémentaires représentant des morceaux de rue."@en . + "Fond de plan"@fr . + "Belgique"@fr . + "b0abf800-8362-4d5a-aed7-e0f8b108aafd" . + "Surfaces de rue"@fr . + "Surfaces de rue"@en . + "Reporting Inspire"@fr . + . + "Health care services and senior housing facilities"@fr . + "life expectancy and mortality"@fr . + "Brussels"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Bruxelles"@fr . + "Sex"@fr . + "Naissances"@fr . + "espérance de vie et mortalité"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB68F6E219C7C466D25807D3E2A80FF9F . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C5D761E780A52799F055978589DF99C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C5D761E780A52799F055978589DF99C "Bruxelles Environnement" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4A0511EE8516A81E267E0670BFEF106A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4A0511EE8516A81E267E0670BFEF106A "Hoeck Michèle" . + "2016-11-18T00:00:00"^^ . + "Births"@fr . + "Offre de soins de santé"@fr . + "Gezondheidstoestand en medische praktijken"@fr . + "Sexe"@fr . + "Statistieken"@fr . + "Santé"@fr . + "Statistics"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Aanbod van gezondheidszorg en huisvestingsaanbod voor ouderen"@fr . + "Geslacht"@fr . + "Gezondheid"@nl . + "Belgique"@fr . + "Hoewel gezondheid onder de bevoegdheid van de gemeenschappen valt, maakt het BISA hier toch een analyse van (aanbod van gezondheidsdiensten, levensverwachting enz.) door gegevens van de Vlaamse Gemeenschap, van de Franse Gemeenschap en van de Gemeenschappelijke Gemeenschapscommissie te combineren.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/"@nl . + "Brussel"@fr . + "Si la santé relève des compétences communautaires, l’IBSA présente une sélection d’indicateurs (offre de soins de santé, espérance de vie, etc.), en combinant des données provenant de la Communauté flamande, de la Communauté française et de la Commission communautaire commune.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/"@fr . + "Homes"@fr . + "Statistiques"@fr . + "levensverwachting en sterfte"@fr . + "Health "@en . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Belgium"@fr . + "Although health is a Community competence, the BISA presents a selection of indicators (range of healthcare services on offer, life expectancy, etc.) by combining the data from the Flemish Community, the French Community and the Common Community Commission.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "2016-12-01T11:27:24.506329"^^ . + "België"@fr . + "036a01b4-6c08-4983-9f29-b9b6ddcc0301" . + "État de santé et pratiques médicales"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Geboortes"@fr . + "State of health and medical practices"@fr . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d129BB644EFCFAD8DF344199EFB36EC72 . + "2017-06-23T12:27:21.354036"^^ . + "Feux de signalisation"@fr . + "2016-12-12T10:37:15.284579"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d37682019D85EE4865C7459BA0C60267C . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "b55ffec9-2382-44b5-9d86-5400259c8de8" . + "Traffic lights"@en . + . + "Verkeerslichten"@nl . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF78FD2369E1D412FD3537AD7A087FF93 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF78FD2369E1D412FD3537AD7A087FF93 "Secrétariat communal" . + "Geolocation of kruispunten uitgerust met verkeerslichten beheerd door Brussel Mobiliteit."@nl . + "Geolocation of intersections equipped with traffic lights managed by Brussels Mobility."@en . + "Géolocalisation des carrefours équipés de feux de signalisation gérés par Bruxelles Mobilité."@fr . + "Région de Bruxelles-Capitale"@fr . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCA6F9AAE556630DBEF5AB1FE71402409 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCA6F9AAE556630DBEF5AB1FE71402409 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCA6F9AAE556630DBEF5AB1FE71402409 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Fichier GTFS de la STIB (Bruxelles)"@fr . + . + "2018-02-13T14:22:59.296264"^^ . + "arrets"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5A2450F85D06E0BB420AA39F4BA17189 . + "metro"@fr . + "2016-12-16T00:00:00"^^ . + "Brussels"@fr . + "tram"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC92AC9F4F75EC24B6544AC216A4E9458 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC92AC9F4F75EC24B6544AC216A4E9458 "Team BruGIS" . + "This dataset contains the GTFS file made by the public transport operator for Brussels city in Belgium:\nWhat is a GTFS file?\n\nThe General Transit Feed Specification (GTFS), also known as GTFS static or static transit to differentiate it from the GTFS realtime extension, defines a common format for public transportation schedules and associated geographic information. GTFS \"feeds\" let public transit agencies publish their transit data and developers write applications that consume that data in an interoperable way."@en . + "GTFS File by STIB-MIVB (Brussels)"@en . + "bus"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9D6ECEEC68F4256A0276CC7D01AE8A5D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4E36B081E1F68290399CD29F821A4DD3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4E36B081E1F68290399CD29F821A4DD3 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4E36B081E1F68290399CD29F821A4DD3 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Deze dataset bevat het GTFS-bestand van de stad Brussel in België, aangemaakt door de openbare vervoersmaatschappij MIVB :\n\nWat is een GTFS-bestand?\n\nDe General Transit Feed Specification (GTFS), ook bekend als statische GTFS of statische doorvoer om deze te onderscheiden van de realtime GTFS-uitbreiding, definieert een algemeen formaat voor openbaar vervoersschema's en bijbehorende geografische informatie. GTFS-feeds laten openbare vervoersmaatschappijen toe hun transitgegevens te publiceren en ontwikkelaars hun toepassingen die gegevens gebruiken op een interoperabele manier te ontwikkelen.\n"@nl . + "1bdd4128-9e3e-4b55-9ce1-cf2e4bd62c8a" . + "GTFS-bestand door MIVB (Brussel)"@nl . + "routes"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Ce dataset contient le fichier GTFS créé par la société des transports intercommunaux de Bruxelles (STIB) en Belgique.\n\nQu'est-ce qu'un fichier GTFS?\nGTFS (General Transit Feed Specification), également connu sous le nom de GTFS statique ou de flux statique pour le différencier de l'extension GTFS-realtime, définit un format de fichier commun pour les horaires de transports en commun et les informations géographiques associées. Les \"flux\" GTFS permettent aux agences publiques de publier leurs informations de transports en commun et aux développeurs de créer des applications qui utilisent ces données de manière interopérable.\n"@fr . + "public-transport"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + "Belgium"@fr . + "Brussels Hoofdstedelijk Parlement"@fr . + "Municipal councils"@fr . + "Élections"@fr . + "Parlement Européen"@fr . + "Statistics"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Bruxelles"@fr . + "Brussels"@fr . + "Federal elections"@fr . + "Gemeenteraden"@fr . + "The BISA compiles data on the results of the different elections held in the Brussels-Capital Region.\nYou will find the results of Regional, local, European and legislative elections held in the Brussels-Capital Region.\n\nVisit also the Monitoring des Quartiers website, which offers a range of indicators for the 145 districts within the Brussels-Capital Region ➜ https://monitoringdesquartiers.brussels/"@en . + "Région de Bruxelles-Capitale"@fr . + . + . + "Belgique"@fr . + "Parlement de la Région de Bruxelles-Capitale"@fr . + "België"@fr . + "Elections "@en . + "Parliament of the Brussels-Capital Region"@fr . + "L'IBSA rassemble les données des résultats des différentes élections tenues en Région de Bruxelles-Capitale.\nRetrouvez les résultats des élections régionales, communales, européennes et législatives tenues en Région de Bruxelles-Capitale.\n\nConsultez également le site du Monitoring des Quartiers qui propose une sélection d’indicateurs au niveau des 145 quartiers de la Région de Bruxelles-Capitale ➜ https://monitoringdesquartiers.brussels/\n"@fr . + "Verkiezingen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDCEC352826786DAC7828C69E4D2F24C0 . + "Statistieken"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Het BISA verzamelt de resultaten van de verschillende verkiezingen die gehouden worden in het Brussels Hoofdstedelijk Gewest.\nU vindt de resultaten van de regionale, gemeentelijke, Europese en federale verkiezingen in het Brussels Hoofdstedelijk Gewest.\n\nRaadpleeg ook de site van de Wijkmonitoring die informatie geeft over een reeks indicatoren in de 145 wijken van het Brussels Hoofdstedelijk Gewest ➜ https://wijkmonitoring.brussels/\n"@nl . + "2016-12-01T11:02:55.421508"^^ . + "4169896f-5b80-431a-9a51-b91ffa180ea3" . + "Élections fédérales"@fr . + "Région de Bruxelles-Capitale"@fr . + . + "Statistiques"@fr . + "Europees Parlement"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d66C8CC10117D818ED30650176B6E1DBC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d66C8CC10117D818ED30650176B6E1DBC "Bruxelles Environnement" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB5D2A066B2D1B497ED32707F7660583B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB5D2A066B2D1B497ED32707F7660583B "Andreas Boogaerts" . + "Brussel"@fr . + "Conseils communaux"@fr . + "Federale verkiezingen"@fr . + "European Parliament"@fr . + "2016-11-22T00:00:00"^^ . + "Région de Bruxelles-Capitale"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d21F0720EEB06598DCCDBD33C490D5CE9 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d21F0720EEB06598DCCDBD33C490D5CE9 "Hoeck Michèle" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d65C84745985BCE9A84593A7C8D9312AD . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d65C84745985BCE9A84593A7C8D9312AD "Bruxelles Environnement" . + . + "Brussels Hoofdstedelijk Gewest : België is een Federale Staat die uit drie gewesten bestaat: Vlaams Gewest, Waals Gewest en Brussels Gewest."@nl . + "Région de Bruxelles-Capitale : la Belgique est un Etat fédéral qui se compose de trois régions : la Région wallonne, la Région flamande et la Région bruxelloise. L'objet \"Région bruxelloise\" représente la limite de la Région bruxelloise."@fr . + "55c284c5-f1ea-4465-94a1-fd02f3b9c6a9" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3D14C039A7B9E5FC137156E00F319F9A . + "Brussels Gewest"@nl . + "2016-03-08T15:21:16.773801"^^ . + "Région bruxelloise"@fr . + "Belgique"@fr . + "Reporting Inspire"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + "Brussels-Capital Belgium is a federal state composed of three regions: the Walloon Region, the Flemish Region and the Brussels Region. The object \"Brussels-Capital Region\" represents the limit of the Brussels Region."@en . + "2015-09-28T00:00:00"^^ . + "Brussels-Capital Region"@en . + "Harvested"@fr . + "Fond de plan"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d98701581A576D98DFB69FA664803D106 . + "limite administrative"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7D9312F11CE8E15B0B4126CE264FE0BF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7D9312F11CE8E15B0B4126CE264FE0BF "Team BruGIS" . + "2016-11-22T00:00:00"^^ . + "Belgique"@fr . + "Culture"@fr . + "Statistieken"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Toerisme en cultuur "@nl . + "Tourism and Culture "@en . + "Tourisme et culture"@fr . + "Brussels"@fr . + "Bruxelles"@fr . + "Statistiques"@fr . + "Tourism"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "68c033f4-32aa-4b6e-bd9f-53b34c0a038d" . + "Cultuur"@fr . + "De toeristische statistieken betreffen overnachtingen van personen die in het Brussels Hoofdstedelijk Gewest verbleven hebben.\nVoor het culturele aspect vindt u statistieken over de bioscoopzalen en de voorstellingen.\n"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d1521993328C710C1FB52FC01D713EB61 . + "The statistics on tourism relate to the accommodation of visitors to the Brussels-Capital Region.\nAs regards culture, you will find statistics on cinemas and screenings.\n"@en . + "Statistics"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + "Belgium"@fr . + "Les statistiques relatives au tourisme concernent l’hébergement des personnes qui ont séjourné dans la Région de Bruxelles-Capitale.\nDans le domaine de la culture, vous trouverez des statistiques relatives aux salles de cinéma et aux projections."@fr . + "2016-12-01T11:05:19.438174"^^ . + "België"@fr . + "Brussel"@fr . + "Toerisme"@fr . + "Tourisme"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECF89F39C3C9CE4DA4C603C8367716D8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECF89F39C3C9CE4DA4C603C8367716D8 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECF89F39C3C9CE4DA4C603C8367716D8 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + "Reporting Inspire"@fr . + "Harvested"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3277833AB6D3EF1F582FDFD0E761293E . + "Brussels-Capital Region : location of the different groundwater quality monitoring sites managed by Brussels Environment (IBGE-BIM) and which results are transmitted through the reporting to Europe (under Article 8 of the Water Framework Directive). Indication of the analyzed water body, the European Code and the Brussels code of the monitoring site. Distinction between two types of monitoring: surveillance monitoring for all groundwater bodies (type_monitoring = 1) and operational monitoring for groundwater bodies identified as being at risk of failing to achieve the objectives of good status (type_monitoring = 2)"@en . + "b46e1454-3d1b-4e95-b697-1af015a5a20f" . + "Région de Bruxelles-Capitale"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de verschillende meetstations voor de kwaliteitsmonitoring van het grondwater beheerd door Leefmilieu Brussel (BIM) en waarvan de resultaten doorgegeven zijn in het kader van de Europese rapportering (volgens artikel 8 van de Kaderrichtlijn Water). Met aanduiding van het geanalyseerde grondwaterlichaam, de Europese code en de Brusselse code van het meetstation. 2 monitoring types: controle van de monitoring voor al de grondwaterlichamen (type_monitoring = 1) en operationele controle voor de grondwaterlichamen gerangschikt volgens risico van niet bereikte doelen van de goede staat (type_monitoring = 2)"@nl . + "Région de Bruxelles-Capitale : localisation des différentes stations de mesure de la qualité des eaux souterraines gérées par Bruxelles Environnement (IBGE) et dont les résultats sont transmis dans le cadre du reporting à l'Europe (selon l'article 8 de la Directive Cadre Eau). Indication de la masse d'eau analysée, du code européen et du code bruxellois de la station de mesure. Distinction des 2 types de monitoring : contrôle de surveillance pour toutes les masses d'eau souterraines (type_monitoring = 1) et contrôle opérationnel pour les masses d'eau souterraines classées en risque de non atteinte des objectifs de bon état (type_monitoring = 2)"@fr . + "Groundwater quality monitoring network - European reporting"@en . + "Monitoring van de kwaliteit van de grondwaterlichamen - Europese rapportering"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B4451505D7D8168810BFA33783C81E4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d07084B5259D1F169BC0067EA4B3878FC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d07084B5259D1F169BC0067EA4B3878FC "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d07084B5259D1F169BC0067EA4B3878FC "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Région de Bruxelles-Capitale"@fr . + . + "2016-05-10T23:40:33.847627"^^ . + "Réseau de surveillance de la qualité des eaux souterraines - reporting européen"@fr . + "2015-09-26T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d24770D1DD49A650094161B689ED65A02 . + "Belgique"@fr . + "Région de Bruxelles-Capitale"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + "Zones de gestion, de restriction ou de réglementat"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD55BB21F56FCAF670AFD2E92AD9187E5 . + "environnement"@fr . + "protection des réserves d'eau souterraines"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels-Capital Region : location of groundwater catchment sites protection areas 2 and 3 (i.e. extraction wells and drainage gallery) in the Brussels-Capital Region. Those sites were defined in the Brussels-Capital Government decree of 19 September 2002 delimiting a groundwater catchments protection area at the “Bois de la Cambre” and in the Sonian Forest (Belgian Monitor: June 10, 2008)"@en . + "2016-01-22T18:45:40.914429"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d2A35E2411AE99B9625EF85D4D410FF4B . + "zone protégée de captage d'eau"@fr . + . + . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de beschermingszones 2 en 3 rondom winningen (i.e. waterwinningsputten en drainage galerij) in het Brussels Hoofdstedelijk Gewest. Deze zones werden bepaald in het besluit van de Brusselse Hoofdstedelijke Regering van 19 september 2002 en bakent een beschermingszone rondom grondwaterwinningen af in het Ter Kamerenbos en in het Zoniënwoud (Belgisch Staatsblad : 10 juni 2008)"@nl . + "Région de Bruxelles-Capitale : localisation des zones 2 et 3 de protection des lieux de captage (i.e. puits captants et galerie drainante) en Région de Bruxelles-Capitale. Ces zones ont été définies dans l'arrêté du Gouvernement de la Région de Bruxelles-Capitale du 19 septembre 2002 délimitant une zone de protection des captages d'eau souterraine au Bois de la Cambre et en Forêt de Soignes (Moniteur belge : 10 juin 2008)"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Zones2_et3_protection_captage_Pg.xml" . + "Belgique"@fr . + "Région de Bruxelles-Capitale"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "2015-09-26T00:00:00"^^ . + "Groundwater catchment protection areas 2 and 3"@en . + "Zones 2 et 3 de protection de captage"@fr . + "Harvested"@fr . + "protection de zone de captage de l'eau"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Beschermingszones 2 en 3 van winningen"@nl . + "Reporting Inspire"@fr . + . + "Harvested"@fr . + "Brussels-Capital Region: localization of the extraction wells for drinking water in Brussels, run by VIVAQUA. Ranked as protected area 1 of the water extractions (with the drainage gallery)"@en . + "approvisionnement en eau potable"@fr . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de winningsputten voor drinkwaterwinning in Brussel uitgebaat door VIVAQUA. Gerangschikt als beschermingszone 1 van winningen (met de drainage gallerij)"@nl . + "Région de Bruxelles-Capitale"@fr . + "eaux souterraines"@fr . + "Beschermingszone 1 van winningen - waterwinningsputten"@nl . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBFB2BD9481D9A7A050F3C9C32DEBE83B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBFB2BD9481D9A7A050F3C9C32DEBE83B "Secrétariat communal" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d84547D4D31DB4E4BBEAA4707EA2813E2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d84547D4D31DB4E4BBEAA4707EA2813E2 "Mathieu Tonglet" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCFFC6492D60DC4EE53D84EFA56426256 . + "environnement"@fr . + "alimentation en eau de la ville"@fr . + "dd374aec-3802-4da1-8bad-59004e17460f" . + "protection de zone de captage de l'eau"@fr . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Région de Bruxelles-Capitale : localisation des puits de captage d'eau potable de Bruxelles exploitée par VIVAQUA. Classés comme zone 1 de protection de captage (ainsi que la galerie drainante)"@fr . + "2016-01-22T18:45:46.386154"^^ . + "Reporting Inspire"@fr . + "Zone 1 de protection de captage - puits captants"@fr . + "zone protégée de captage d'eau"@fr . + "2015-09-26T00:00:00"^^ . + "captage d'eau"@fr . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD29DC37AD2ED9FD0DE9BE817229AD25B . + "Belgique"@fr . + "Zones de gestion, de restriction ou de réglementat"@fr . + "Protection area 1 of water extraction - the water extraction wells"@en . + . + "b2aafd9b-d860-4337-bb90-b070f8727484" . + "ShapeFiles by STIB-MIVB (Brussels)"@en . + "tram"@fr . + "De shapefiles (ofwel \"vormbestand\") bevatten per definitie informatie met betrekking tot de geometrie van objecten die in het geval van de MIVB lijnen (reiswegen) en punten (haltes) zijn.\n\nTwee shapefiles worden momenteel verdeeld: \"ACTU_LINES\" en \"ACTU_STOPS\""@nl . + "metro"@fr . + "Région de Bruxelles-Capitale"@fr . + "ShapeFiles de la STIB (Bruxelles)"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d04E7157DE94B76D39C7A8E68066591C4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0B1692FA5D6921FED89D9C2256FA71F8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0B1692FA5D6921FED89D9C2256FA71F8 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0B1692FA5D6921FED89D9C2256FA71F8 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "brussels"@fr . + "ShapeFiles: Les shapefiles (ou ‘fichier de formes’) contiennent par définition l’information liée à la géométrie des objets, qui dans le cas de la STIB sont des lignes (itinéraires) et des points (arrêts).\n\nLes shapefiles actuellement distribués sont au nombre de deux : « ACTU_LINES » et « ACTU_STOPS »"@fr . + "The shapefiles contain information linked to the objects' geometry which, for STIB/MIVB, are lines (itineraries) and dots (stops).\n\nThere are currently two shapefiles being distributed: \"ACTU_LINES\" and \"ACTU_STOPS\""@en . + "bus"@fr . + . + "2018-02-13T14:01:26.548297"^^ . + "shapefiles"@fr . + . + . + "2018-01-25T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDC3F5FCB10BF47B02C7D43558BEB3FC8 . + "MIVB ShapeFiles (Brussel)"@nl . + "transport-public"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4786B3B2A524A1841D7D277A4B5B787F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4786B3B2A524A1841D7D277A4B5B787F "Bruxelles Environnement" . + . + "Statistical district"@en . + "Fond de plan"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d476328F7A9A106802C0F5063583AB220 . + "2016-03-08T15:19:34.965487"^^ . + . + "Région de Bruxelles-Capitale"@fr . + . + "2015-12-22T00:00:00"^^ . + "Région de Bruxelles-Capitale : Le secteur statistique désigne une zone regroupant un ensemble d'adresses défini au niveau communal par l’Institut National de Statistiques (INS) comme unité de base pour le recensement de la population. Ce regroupement est basé sur une analyse géographique de la commune qui tient compte de ses caractéristiques structurelles morphologiques, urbanistiques, sociales et économiques."@fr . + "Belgique"@fr . + "Brussels Hoofdstedelijk Gewest: De statistische sector staat voor een groep adressen, die op gemeentelijk vlak door het Nationaal Instituut voor de Statistiek (NIS) vastgelegd wordt als basiseenheid voor de volkstelling. Deze indeling is gebaseerd op een geografische analyse van de gemeente, die rekening houdt met de structurele, morfologische, stedenbouwkundige, maatschappelijke en economische kenmerken."@nl . + "Brussels-Capital Region : Statistical district means an area containing a set of addresses defined at the municipal level by the National Statistics Institute (INS) as the basic unit for the population census. This grouping is based on a geographic analysis of the municipality that reflects its urban, social and economic structural morphological characteristics."@en . + "Secteurs statistiques"@fr . + "Reporting Inspire"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7CB0731BC5A077DD6F04D041B3EA0953 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7CB0731BC5A077DD6F04D041B3EA0953 "Florine Deladrière" . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6F7EC3F14D0A745F4530A05C21555D32 . + "aca1bd59-f851-43cc-8dcb-250d48b24a46" . + "Statistische sector"@nl . + "Harvested"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Deze laag bevat alle bomenrijen gesitueerd langs de gewestwegen. Deze bomen worden beheerd door de cel Beplantingen, fonteinen en kunstwerken. De databank bevat ongeveer 27000 bomen. De databank bevat zowel data bekomen via luchtfoto's, als data bekomen door terreinwaarnemingen."@nl . + "Inventaire georéférencé des arbres d'alignement situés le long des voiries régionales. Ces arbres sont gérés par la cellule Plantations, fontaines et œuvres d'art. La base de données contient environ 27000 arbres. La base de données contient aussi bien des données provenant de photos aériennes, que des données provenant d'observations de terrain."@fr . + "2016-01-01T00:00:00"^^ . + "Georeferenced inventory of trees along regional roads."@en . + . + "92093e5e-4552-46b9-8f8b-7110ff2f3037" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6969AC30084DF821A73AB1EF015F0EA4 . + "Arbres d'alignement"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d930F0717549000DF4BD2F30F75558791 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE373CAE178BD99E5056F38D325A666E1 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE373CAE178BD99E5056F38D325A666E1 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE373CAE178BD99E5056F38D325A666E1 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Région de Bruxelles-Capitale"@fr . + "2016-12-07T17:40:36.386809"^^ . + "Bomenrijen"@nl . + "Trees along regional roads"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBFCEC5ED9BFD8305D29D26A551B18A04 . + . + . + . + "2018-12-04T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB4DE57D4E43FE07964C515AD04BCB07D . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "In deze dataset heeft u toegang tot de volgende informatie:\n\nDe informatie per halte betreffende geplande werkzaamheden en de wijzigingen in de reisweg. U kunt deze bekomen via de volgende methodes\n\nBoodschappen per lijn : boodschappen per halte voor bepaalde lijnen, via de id(‘s) van de gewenste lijn(en) in de parameters.\n\nBoodschappen per halte : boodschappen per halte via de id(‘s) van de gewenste halte(s) in de paramters."@nl . + "Dans ce jeu de données, vous avez accès aux informations suivantes :\n\nLes informations concernant les travaux planifiés et les changements dans l'itinéraire qui sont liés aux arrêts. Vous pouvez les avoir via les méthodes suivantes :\n\nMessages par ligne : messages liés aux arrêts pour certaines lignes, obtenus en passant le ou les id de la ou des ligne(s) souhaitée(s) dans les paramètres.\n\nMessage par arrêt : messages liés aux arrêts, obtenus en passant le ou les id de la ou des arrêt(s) souhaité(s) dans les paramètres. "@fr . + "In this dataset you’ll find:\n\nInformation about planned works and/or changes in itinerary related stop(s). You can retrieve this information via the following methods:\n\nMessages By Line: contains series of messages related to the stops of specific line(s). These messages can be retrieved by passing line(s) id(s).\n\nMessage By Stop: contains series of messages related to stop(s). These messages can be retrieved by passing stop(s) id(s). "@en . + "95527279-2be7-4a6b-9e00-bd829c504777" . + "2018-05-04T13:41:50.174640"^^ . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4B578D98233B7CD59C58F982A70EDA06 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4B578D98233B7CD59C58F982A70EDA06 "Mathieu Tonglet" . + . + "MESSAGES D'INFO VOYAGEURS (TEMPS-RÉEL)"@fr . + "Information"@fr . + "Messages"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Disruption"@fr . + "STIB-MIVB"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE358BB0B4FC69794FB3C2C9BD3069E4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE358BB0B4FC69794FB3C2C9BD3069E4 "Bruxelles Environnement" . + "REIZIGERSINFORMATIE BOODSCHAPPEN (REAL-TIME)"@nl . + "TRAVELERS INFORMATION (REAL-TIME)"@en . + "real-time"@fr . + . + "Stations Villo (Bruxelles)"@fr . + "bike"@fr . + "villo"@fr . + "Villo Stations (Bruxelles)"@nl . + "vélo"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "2017-10-03T09:18:42.213732"^^ . + "Villo Stations (Brussels)"@en . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2915FE56A67014F452831C4FEA83BAC2 . + . + "Villo stations in the Brussels-Capitale Region"@en . + "Villo stations in the Brussels-Capitale Region"@nl . + "Liste des stations Villo dans la Région Bruxelles-Capitale"@fr . + "fietsen"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD6B533F52631E4183B5737793C7701C1 . + "5836498b-e767-480d-8d0d-c6816213ff83" . + "2016-01-25T14:01:00.551137"^^ . + . + "Région de Bruxelles-Capitale"@fr . + . + . + "d375bb6b-a7aa-4496-9b2e-2b73f989c6db" . + "Accessibilité de cafés bruxellois pour utilisateurs de fauteuils roulants"@fr . + "cafés"@fr . + "Accessibility of Brussels bars list for wheelchair users"@en . + "wheelchairs"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + "Brussels Jazz Marathon wil dat iedereen van de gratis concerten kan genieten. Rolstoelgebruikers kunnen hier nagaan welke indoor locaties (bars & cafés) voor hen toegankelijk zijn en welke faciliteiten beschikbaar zijn."@nl . + "2016-06-05T00:00:00"^^ . + "2016-06-15T00:00:00"^^ . + "bars"@fr . + "disabled"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7998C8E3BA28D8B13FE8D431DADB9675 . + "Région de Bruxelles-Capitale"@fr . + . + . + "Brussels Jazz Marathon wants everyone to enjoy the free concerts. Wheelchair users can check here which indoor locations (bars & cafes) are accessible to them and which facilities are available."@en . + "Toegankelijkheid Brusselse café's voor rolstoelgebruikers"@nl . + "Brussels Jazz Marathon souhaite que tout le monde puisse profiter des concerts gratuits. Les utilisateurs en fauteuils roulants peuvent vérifier les infrastructures indoor (bars et & cafés) qui leur sont accessibles et les installations disponibles."@fr . + "accessibility"@fr . + . + "Région de Bruxelles-Capitale : Les faces de rues sont situées le long des voies publiques. L'entité « Street Side » (ou face de rue) est représentée par une ligne et par un centroïde\n(point à proximité de la ligne).\nLes faces de rues sont dessinées à l’interface entre deux tronçons de rue ou à l’interface entre un tronçon de rue et un îlot. Les faces de rues associées aux galeries et aux voiries locales font exception à cette règle. Elles sont dessinées, à l’intérieur des îlots, le long des surfaces des galeries et de voies locales.\nLes faces de rues sont complétées par deux petits segments de lignes situés à leurs extrémités (= entité géographique \"SILIMIT\" sans aucune information alphanumérique). Ces lignes sont orientées vers l’intérieur des îlots.\nLes centroïdes des faces de rue sont dessinés à l’intérieur des îlots, à proximité de leur milieu.\nIls servent de point d’appui pour les textes des plages d’adresses ."@fr . + "2016-01-28T13:42:58.846417"^^ . + "Street Sides"@en . + "Belgique"@fr . + "Harvested"@fr . + "Straatzijden"@nl . + "Région de Bruxelles-Capitale"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Brussels Hoofdstedelijk Gewest : De straatzijden bevinden zich langs de openbare wegen. De entiteit « Street Side » (of straatzijde) wordt weergegeven met een lijn en een centroïde(punt nabij de lijn).\nZij worden getekend op het raakpunt tussen twee stukken straat of op het raakpunt tussen een stuk straat en een huizenblok. De straatzijden die verbonden zijn met galerijen en lokale wegen zijn uitzonderingen op deze regel. Zij worden binnen de huizenblokken getekend langs stukken galerijen en lokale wegen.\nDe straatzijden worden aangevuld door twee korte lijnsegmenten op de uiteinden ervan (= geografische entiteit \"SILIMIT\" zonder enige alfanumerieke informatie). Deze lijnen zijn naar de binnenkant van de huizenblokken gericht.\nDe centroïden van de straatzijden zijn getekend binnen de huizenblokken, tegen het middelpunt ervan. Zij dienen als steunpunt voor de teksten van de adressenreeksen."@nl . + "05524955-25cf-4a31-ac9c-539b9be709f9" . + "Brussels-Capital Region : The street ends are located along the public roads. The entity \"Streetside\" (or street) is shown with a line and a centroid (point near the line).\nThey are drawn on the interface between two pieces of street or at the interface between a piece of street and a block. The street sides connected with galleries and local roads are exceptions to this rule. They are drawn along pieces galleries and local roads. Within the blocks\nThe road sides are complemented by two short line segments at their ends (= geographical entity \"SILIMIT\" without any alphanumeric information). These lines are directed towards the inside of the housing blocks.\nThe centroids of the street sides are drawn within the blocks, at the center of it. They serve as a focal point for the text of the address ranges."@en . + "Fond de plan"@fr . + "Faces de rue"@fr . + "Espace public"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC66892A1896E5A1E39D95582132ECAED . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC66892A1896E5A1E39D95582132ECAED "Hoeck Michèle" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7885F0EED34D8262E15EE7A62B2A4EE5 . + . + "Brussels Hoofdstedelijk Gewest"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dABF2FEC713E69EA3A8D15199C5F52546 . + "2015-03-30T00:00:00"^^ . + "Reporting Inspire"@fr . + . + . + "2016-01-22T18:45:47.385502"^^ . + "Réseau de mesure du bruit"@fr . + "Harvested"@fr . + "Région de Bruxelles-Capitale"@fr . + "bruit"@fr . + "Région de Bruxelles-Capitale"@fr . + . + "Région de Bruxelles-Capitale : localisation des différentes stations de mesures des niveaux sonores, gérées par Bruxelles Environnement (IBGE). Les stations de mesures sont dédiées au bruit ambiant, routier, ferroviaire et/ou aérien. Indication du nom (correspondant à la localisation) et de la source sonore prépondérante.\nLes résultats des mesures sont accessibles via le module WebNoise sur le site de Bruxelles Environnement."@fr . + "2015-09-26T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d355B4672B9BB90EC7333D8B094F6BD72 . + "bruit ambiant"@fr . + "Netwerkmonitoring van het geluid"@nl . + "bruit routier"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels-Capital Region: location of various monitoring stations sound levels, managed by Brussels Environment (IBGE). The monitoring stations are dedicated to ambient noise, road, rail and/or air.\nIndication of the name (corresponding to the location) and the dominant sound source.\nMeasurement results are available via the module WebNoise on the website of Brussels Environment."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBA01187D8CB6EC6968917EA33FA12AF9 . + "bruit aérien"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "dfcb6396-9c89-4f2f-b125-6695a557a959" . + "Reporting Inspire"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC9940906CA143F5014C12574324BDC68 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC9940906CA143F5014C12574324BDC68 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC9940906CA143F5014C12574324BDC68 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Network monitoring of the noise"@en . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3D63891F27CA83732EC6F76E0C532FA0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3D63891F27CA83732EC6F76E0C532FA0 "Corentin Descamps" . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de verschillende meetstations geluidsniveaus, beheerd door Leefmilieu Brussel (BIM). De meetstations zijn gewijd aan omgevingsgeluid, weg-, spoor- en/of lucht. Vermelding van de naam (die overeenkomt met de lokalisatie) en de dominante geluidsbron.\nMeetresultaten zijn beschikbaar via de module WebNoise op de website van Leefmilieu Brussel."@nl . + "Belgique"@fr . + "environnement"@fr . + "station de surveillance"@fr . + . + . + "Bruit ferroviaire "@fr . + "suveillance du bruit"@fr . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9A7A7A669CEF5DB6A33CF50B16EF4C53 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9A7A7A669CEF5DB6A33CF50B16EF4C53 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9A7A7A669CEF5DB6A33CF50B16EF4C53 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Paved roads"@en . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d813AF87EE3691B319FF77DD20808DC93 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCB8C9776E516BCC67F03250592356723 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d362D1DE2134A3B705FD485B05348DBEE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d362D1DE2134A3B705FD485B05348DBEE "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d362D1DE2134A3B705FD485B05348DBEE "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Roads whose coating consists mostly of paved blocks."@en . + "Routes dont le revêtement est principalement constitué de pavés."@fr . + "Kasseien"@nl . + "Routes pavées"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "b9ab764f-9767-4860-9ec1-632e3fd75272" . + "2016-01-01T00:00:00"^^ . + "2016-12-07T17:31:09.041330"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d24590D32D072DAC1FF8453A0406E9654 . + . + . + "Région de Bruxelles-Capitale"@fr . + . + "De wegen met kasseiverharding zijn geïnventariseerd in het kader van de fietskaart. De arceringen van de fietskaart zijn geprojecteerd op de Urbis Street Sections om een correctere geografische representatie te bekomen. Een aanduiding van kasseien sluit niet uit dat er een vlak voetpad of fietspad aanwezig is."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7D9BA1AE2285E55795F5BC73249D5C55 . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d39BFF1F43BA8374DAA4BCFBA7EE1E5A0 . + "Région de Bruxelles-Capitale"@fr . + "eau de surface"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + "Brussels-Capital Region : location of the different surface water quality monitoring sites under the Water Framework Ordonnance and Directive. Surface waters concerned are surface water bodies: Canal, Senne and Woluwe. Monitoring involves a series of chemical, physico-chemical parameters... The code and the name of the monitoring site are given."@en . + "stationsSW_qualite_phisique_chimique.xml" . + "Réseau de surveillance de la qualité chimique et physico-chimique des eaux de surface"@fr . + "analyse chimique"@fr . + "2015-09-26T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFF54C6CDE330B1EBC1739E32749C1F51 . + "Harvested"@fr . + "Monitoring network of the chemical and physico-chemical quality for surface waters"@en . + "Région de Bruxelles-Capitale : localisation des différentes stations de mesure pour le monitoring qualitatif des eaux de surface, dans le cadre de la Directive et de l'Ordonnance Cadre Eau. Les eaux de surface concernées sont les masses d'eau de surface : Canal, Senne et Woluwe. Le monitoring porte sur une série de paramètres chimiques, physico-chimiques... On dispose du code et du nom de la station de mesure"@fr . + "Chemische en fysisch-chemische kwaliteitsmonitoring van de oppervlaktewateren"@nl . + "analyse physico-chimique"@fr . + "Reporting Inspire"@fr . + "2016-01-22T18:45:53.132299"^^ . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de verschillende meetstations voor de kwaliteitsmonitoring van de oppervlaktewateren in het kader van Kaderrichtlijn- en Ordonnantie Water. De betreffende oppervlaktewateren zijn de wateroppervlakte lichamen : Kanaal, Zenne en Woluwe. De monitoring bestaat uit verschillende chemische, fysisch-chemische parameters... Info over de code en de naam van het meetstation"@nl . + "cours d'eau"@fr . + "Belgique"@fr . + "environnement"@fr . + "qualité de l'eau"@fr . + "réseau de mesure"@fr . + "station de surveillance"@fr . + "surveillance de l'eau"@fr . + . + . + "9b86c7fa-bbc0-473f-9a78-c8b89de5ab66" . + "Les vélocistes connus de Bruxelles Mobilité."@fr . + "Bicycle shops known by Brussels Mobility."@en . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD37F7889BEF13C2C2B535963EA5A376C . + "bicycle shops"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dEADA5DDC6749E836152AABC65C90DB04 . + "De fietswinkels gekend door Brussel Mobiliteit."@nl . + "2017-06-23T12:22:33.839708"^^ . + "Fietswinkels"@nl . + "Vélocistes"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + "2017-06-12T00:00:00"^^ . + . + "eaux souterraines"@fr . + "ressource en eau"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA0CC1F2B7FDF3770403B89540D78DC94 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA0CC1F2B7FDF3770403B89540D78DC94 " Tonglet Mathieu" . + "Harvested"@fr . + . + "Région de Bruxelles-Capitale"@fr . + "gwb.xml" . + "Brussels-Capital Region : data on the 5 Groundwaterbody of the Area of Brussels, defined under the Directive and the Ordinance Water : code European, code of Brussels and name of the water mass, hydrographic district"@en . + "Groundwaterbody"@en . + "Brussels Hoofdstedelijk Gewest : gegevens over het geheel van de 5 grondwaterlichamen in het Brussels Gewest, afgebakend in kader van de Kaderrichtlijn- en Ordonnantie Water : Europese code, Brusselse code en naam van het grondwaterlichaam, stroomgebiedsdistrict en geologische formatie waarbij het hoort"@nl . + "Région de Bruxelles-Capitale : données sur l'ensemble des 5 masses d'eau souterraine de la Région bruxelloise, délimitées dans le cadre de la Directive et de l'Ordonnance Cadre Eau : code européen, code bruxellois et nom de la masse d'eau, district hydrographique et formation géologique à laquelle elle appartient"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB142802C5B50FDAA41C7D61A8F672670 . + "2016-01-22T18:45:49.324751"^^ . + "Région de Bruxelles-Capitale"@fr . + "nappe phréatique"@fr . + . + . + "Belgique"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d671188421C8CB283BDE6C472372A7AB5 . + "Masses d'eau souterraines"@fr . + "Reporting Inspire"@fr . + "environnement"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "2015-09-26T00:00:00"^^ . + "Grondwaterlichamen"@nl . + . + "Criminality and police personnel"@fr . + "Interventions du SIAMU"@fr . + "Road safety"@fr . + "2016-11-22T00:00:00"^^ . + "Sécurité routière"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "cb64c6b9-494f-4494-8518-117faf01856e" . + "Interventies van de DBDMH"@fr . + "Verkeersveiligheid"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Sécurité"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "Het BISA verzamelt de Brusselse statistieken over de veiligheid van de burger in het dagelijkse leven: verkeersveiligheid, interventies van de Dienst voor Brandbestrijding en Dringende Medische Hulp (DBDMH), vastgestelde criminele feiten en personeelssterkte van de politiezones..."@nl . + "The BISA compiles statistics on the safety of the public in their daily lives: road safety, fire and medical emergency service callouts, crime figures and police staffing, etc."@en . + "Safety "@en . + "Criminalité et effectifs policiers"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF844520D4A221D034F83C2BA8DAEC97C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF844520D4A221D034F83C2BA8DAEC97C "Hoeck Michèle" . + . + "L'IBSA rassemble des données statistiques concernant la sécurité des citoyens dans leur vie quotidienne: sécurité routière, interventions du Service d'Incendie et d'Aide Médicale Urgente (SIAMU), délits constatés et effectifs des zones de police..."@fr . + "2016-12-01T11:06:40.636938"^^ . + "Veiligheid"@nl . + "Criminaliteit en politiepersoneel"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9BB3084511EC83A4BA9DF889770AD96F . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d203888C329EFCDA286B0CCAEEFD862EA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d203888C329EFCDA286B0CCAEEFD862EA "Archiefdienst" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d34D10808BF30DFE1A6234634284FA575 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d34D10808BF30DFE1A6234634284FA575 "Patricia Dhaenens" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5DC62EEA39912626288A135B3526035A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5DC62EEA39912626288A135B3526035A "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5DC62EEA39912626288A135B3526035A "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC6046DEDFB0E49DE1AD8C1707AA3EDCB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD4D7FFA12F6262B33BCEA8F0D163C1D7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD4D7FFA12F6262B33BCEA8F0D163C1D7 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD4D7FFA12F6262B33BCEA8F0D163C1D7 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d27AF0D2E61FE64B61AB9A09D3F235C61 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d27AF0D2E61FE64B61AB9A09D3F235C61 "Team BruGIS" . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8B0CB7C05AB135583138AA768106BEDC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8B0CB7C05AB135583138AA768106BEDC "Team BruGIS" . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD9B6BD2314CE1A58F051691203DFF411 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD9B6BD2314CE1A58F051691203DFF411 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD9B6BD2314CE1A58F051691203DFF411 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCED86C842A76384DDF882B65DFACB591 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCED86C842A76384DDF882B65DFACB591 "Valérie Wispenninckx" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0F29FFFBEBBFC8606D143D87BCF08DF0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0F29FFFBEBBFC8606D143D87BCF08DF0 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0F29FFFBEBBFC8606D143D87BCF08DF0 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d20746414F0BBF064417566E1E9889C5A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB59437485B0B68D2073D428F2D6D2961 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB59437485B0B68D2073D428F2D6D2961 "Bruxelles Environnement" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d274B4ADCA66AB8397241B609AEA2A9D7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d274B4ADCA66AB8397241B609AEA2A9D7 "Bruxelles Environnement" . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC82FD5C3B36583D6637C3EE942CDF995 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d55C6A1C0C99A775344142EFAB20012AA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d55C6A1C0C99A775344142EFAB20012AA "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d55C6A1C0C99A775344142EFAB20012AA "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B992CA34F29349865C57BCD06B0DED0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B992CA34F29349865C57BCD06B0DED0 "Tonglet Mathieu" . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3E395C4EAF7FFBDFA9916363BF88B3FE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3E395C4EAF7FFBDFA9916363BF88B3FE "Bruxelles Environnement" . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF176D3E6AB2E51480AF1EC9583D49A91 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF176D3E6AB2E51480AF1EC9583D49A91 "Bruxelles Environnement " . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1A04E759091E88CD34D17FAF87544EEE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1A04E759091E88CD34D17FAF87544EEE "Hoeck Michèle" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d71C100175350EC6CB2AEFDCC89B4A868 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d71C100175350EC6CB2AEFDCC89B4A868 "Bruxelles Environnement" . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + "Région de Bruxelles-Capitale"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d601B1BB85C8CBDE07F17FD30746642C4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d601B1BB85C8CBDE07F17FD30746642C4 "CIRB" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0AD7A730011EA64106C15BBE33FFFC33 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0AD7A730011EA64106C15BBE33FFFC33 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0AD7A730011EA64106C15BBE33FFFC33 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d16A640AD7A30FA99BE9AB15566B7AD0B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d16A640AD7A30FA99BE9AB15566B7AD0B "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d16A640AD7A30FA99BE9AB15566B7AD0B "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC4E8C685D3B77B989104CAA288F7BA99 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC4E8C685D3B77B989104CAA288F7BA99 "Team BruGIS" . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d780DB9AE7F1EAB3F75CD6FA7B8656C63 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d780DB9AE7F1EAB3F75CD6FA7B8656C63 "Bruxelles Environnement" . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3AB75DE4BF8A787BD56D082D043C9EF7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3AB75DE4BF8A787BD56D082D043C9EF7 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3AB75DE4BF8A787BD56D082D043C9EF7 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d85112E4C1AFD42DA2821C3015879CF4A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d85112E4C1AFD42DA2821C3015879CF4A "Team BruGIS" . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d56213AB5E3C2ED629E055D687319D1D6 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d56213AB5E3C2ED629E055D687319D1D6 "CIRB" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0386BC6B2D9D1261CE53E69092663780 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0386BC6B2D9D1261CE53E69092663780 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0386BC6B2D9D1261CE53E69092663780 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4409DB53549CE3901C6F13728A2FB331 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4409DB53549CE3901C6F13728A2FB331 "Hoeck Michèle" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d852D7DF5713DC210E684C1414DAC6F84 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d852D7DF5713DC210E684C1414DAC6F84 "Mathieu Tonglet" . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6BDE8001DF2942F1AEF6CC60BDF527F4 . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8ABEB1AB5CA2A24AF103AD140EB3C62E . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8ABEB1AB5CA2A24AF103AD140EB3C62E "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8ABEB1AB5CA2A24AF103AD140EB3C62E "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF0312FCB86A5E16D9E03EFB4720EBB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF0312FCB86A5E16D9E03EFB4720EBB "Hoeck Michèle" . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3E17AF425BC148916597B80F2720C364 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3E17AF425BC148916597B80F2720C364 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3E17AF425BC148916597B80F2720C364 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5AA78B1425BE930523E3A6B870C4F1AF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5AA78B1425BE930523E3A6B870C4F1AF "Mathieu Tonglet" . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d44B94FAEB48CCB3C88E5D18D2AA11EFD . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d44B94FAEB48CCB3C88E5D18D2AA11EFD "Mathieu Tonglet" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCC8C70A633BD3117C9416C76C17FFF5B . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d03EFE23A906885D31F5D2FF3F547B61A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d03EFE23A906885D31F5D2FF3F547B61A "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d03EFE23A906885D31F5D2FF3F547B61A "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d790C843BED6D428E5A6EADE571F8C123 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d790C843BED6D428E5A6EADE571F8C123 "Bruxelles Environnement" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d07A839B5CB898E28C4AEF4C9D3A1DB6F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d07A839B5CB898E28C4AEF4C9D3A1DB6F "Bruxelles Environnement" . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d81ACDBAABB90150F7C85E0DE1D6C74C7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d81ACDBAABB90150F7C85E0DE1D6C74C7 "CIRB" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDB0CB2D98F00E32DFFBE2A2E5CED934B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDB0CB2D98F00E32DFFBE2A2E5CED934B "POLYGON ((4.2358 50.7575, 4.4981 50.7575, 4.4981 50.9251, 4.2358 50.9251, 4.2358 50.7575))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDB0CB2D98F00E32DFFBE2A2E5CED934B "{\"type\": \"Polygon\", \"coordinates\": [[[4.23579760742, 50.7575331421], [4.49809619141, 50.7575331421], [4.49809619141, 50.925074646], [4.23579760742, 50.925074646], [4.23579760742, 50.7575331421]]]}"^^ . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0675422B5FF221F19ADF6782B3C43C8A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0675422B5FF221F19ADF6782B3C43C8A "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0675422B5FF221F19ADF6782B3C43C8A "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7756E19FDDF6ED0DC6AFC24023F6B0E5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7756E19FDDF6ED0DC6AFC24023F6B0E5 "Hoeck Michèle" . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d322B10B6203A86CA015B9F44FD12B8DB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d62A3947A3A310C88D911C8D1D39DAA42 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d62A3947A3A310C88D911C8D1D39DAA42 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d62A3947A3A310C88D911C8D1D39DAA42 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B0A83B6462DEBA8741B6FA7600E0A36 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B0A83B6462DEBA8741B6FA7600E0A36 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B0A83B6462DEBA8741B6FA7600E0A36 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + "Région de Bruxelles-Capitale"@fr . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3B419BB42B9323D97DECCF73C9A8E522 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3B419BB42B9323D97DECCF73C9A8E522 "Team BruGIS" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5890E9EA4B22C403AD5E9E87ACE41839 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5890E9EA4B22C403AD5E9E87ACE41839 "CIRB" . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD903FB770CF839C911725C9A81852268 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD903FB770CF839C911725C9A81852268 "POLYGON ((4.2340 50.7606, 4.4840 50.7606, 4.4840 50.9158, 4.2340 50.9158, 4.2340 50.7606))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD903FB770CF839C911725C9A81852268 "{\"type\": \"Polygon\", \"coordinates\": [[[4.23404418945, 50.760592041], [4.4839831543, 50.760592041], [4.4839831543, 50.9157739258], [4.23404418945, 50.9157739258], [4.23404418945, 50.760592041]]]}"^^ . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d904C2429CA620CB91CE7C139CDC36DDB . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d839AEB4D9933D25447A9793486A08931 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d839AEB4D9933D25447A9793486A08931 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d839AEB4D9933D25447A9793486A08931 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF4AA0E7103612041E59554867E4A2306 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF4AA0E7103612041E59554867E4A2306 "Bruxelles Environnement" . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE3907014DAEF28ABE232BB3B7840BAB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE3907014DAEF28ABE232BB3B7840BAB "Bruxelles Environnement" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d36E341601DFF7A5B5E862A235F79C9CC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d36E341601DFF7A5B5E862A235F79C9CC "Corentin Descamps" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3B6081C4073B928C40D03B078E319E50 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d92B17BB0E4314D1C0B539DE5DF349601 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d92B17BB0E4314D1C0B539DE5DF349601 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d92B17BB0E4314D1C0B539DE5DF349601 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d964B0DAF9CF755F6E5FD8F555D459350 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d964B0DAF9CF755F6E5FD8F555D459350 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d964B0DAF9CF755F6E5FD8F555D459350 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d567EEC4D7A008F925B3D177DE80889B5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d23B8BB7B9ADBD9419C195E57E4CFE089 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d23B8BB7B9ADBD9419C195E57E4CFE089 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d23B8BB7B9ADBD9419C195E57E4CFE089 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d94EDE7F54CE24412D64FFBB70323540A . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1145EF867677852CE553D812ACFA63A5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1145EF867677852CE553D812ACFA63A5 "Hoeck Michèle" . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF59082FE2CE120EF836EB6373C323D8D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d14378FFF0C0BD3C80F90963B3FA4F7A6 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d14378FFF0C0BD3C80F90963B3FA4F7A6 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d14378FFF0C0BD3C80F90963B3FA4F7A6 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8849B003B708E7DD7746598C1F546BB3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCDA61420E8AB9F9A1CEDB119FF9A03C2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCDA61420E8AB9F9A1CEDB119FF9A03C2 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCDA61420E8AB9F9A1CEDB119FF9A03C2 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9C270F8D208124A178E6DB5D4083AA5D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9C270F8D208124A178E6DB5D4083AA5D "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9C270F8D208124A178E6DB5D4083AA5D "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE95269EBDCE3E26F69BC9706ED810A56 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE7EE0B40716784694B036509823F2A5E . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE7EE0B40716784694B036509823F2A5E "Sébastien Defrance" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6B3D9739545C9364D3DF204541FDA29F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6B3D9739545C9364D3DF204541FDA29F "Hoeck Michèle" . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE767622D4D208738EA4C0C970C431D89 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE767622D4D208738EA4C0C970C431D89 "Hoeck Michèle" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d362E839840D38165D85EAE6FCE2B9D71 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d362E839840D38165D85EAE6FCE2B9D71 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d362E839840D38165D85EAE6FCE2B9D71 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + . + . + "Région de Bruxelles-Capitale"@fr . + . + . + . + . + . + . + . + . + . + . + "Réseau hydrographique"@fr . + "Maillage bleu "@fr . + "Région de Bruxelles-Capitale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d842EF014A2B3FBB47E627B76265D1CE4 . + "eau de surface"@fr . + "Harvested"@fr . + "2016-01-22T18:45:32.732102"^^ . + "Région de Bruxelles-Capitale : typologie et codification de l'ensemble des Objets Bleus Unitaires (OBU) - cours d'eau, canal et étangs - de la Région bruxelloise. On dispose également du nom de l'objet, ainsi que pour les cours d'eau, de caractéristiques (à ciel ouvert, en pertuis...)"@fr . + "Belgique"@fr . + "Brussels-Capital Region: typology and codification of all the Unitarian Blue Objects (OBU) - rivers, channel and ponds - of the Brussels Region. We also have the name of the object, the municipality in which it is situated as well as for the rivers, their characteristics (open-air, pertuis)"@en . + "hydrographie"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + "River system"@en . + "Brussels Hoofdstedelijk Gewest : typologie en codificatie van het geheel van de Blauwe Eenheidsvoorwerpen (OBU) - waterlopen, kanaal en vijvers - in het Brussels Gewest. Bevat aanvullende informatie: de naam van het object, en voor de waterlopen de kenmerken van de waterloop (open bedding, overwelfd...)"@nl . + "2015-12-11T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8BED8BFC716A549519F5894A5525DDA5 . + . + . + "Hydrografisch netwerk"@nl . + "Reporting Inspire"@fr . + "environnement"@fr . + "49220e81-638b-4876-a259-dd87f0b270b1" . + "gestion des ressources en eau"@fr . + "occupation du sol"@fr . + "réseau hydrographique"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d635F101DA107D18104171D3AE85E95BF . + "Brussels Hoofdstedelijk Gewest"@nl . + . + . + . + "Région de Bruxelles-Capitale"@fr . + "stationsSW_cyprinicole.xml" . + "réseau de mesure"@fr . + . + "Harvested"@fr . + "Brussels Hoofdstedelijk Gewest"@nl . + "eau de surface"@fr . + "Région de Bruxelles-Capitale"@fr . + "Région de Bruxelles-Capitale : localisation des différentes stations de mesure pour le monitoring qualitatif des eaux piscicoles (cyprinicoles), conformément à l'arrêté du 18 juin 1992 de l'Exécutif de la Région de Bruxelles-Capitale établissant le classement des eaux de surface. Le monitoring porte sur des paramètres chimiques, physico-chimiques et microbiologiques"@fr . + "qualité de l'eau"@fr . + . + "station de surveillance"@fr . + "Brussels-Capital Region : location of different monitoring points for quality monitoring of waters capable of supporting fish life (cyprinid), in accordance with the decree of 18 June 1992 of the Executive of the Brussels-Capital determining the classification of surface water. Monitoring relates to chemical, physico-chemical and microbiological parameters."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE5616D9969FD3042A961F3A38D2AB86E . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d41F1A92353817736E4016E1DA28018BF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB8BECFF6BAF31300DF9D22C2B43500F9 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB8BECFF6BAF31300DF9D22C2B43500F9 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB8BECFF6BAF31300DF9D22C2B43500F9 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . + "2016-01-22T18:45:31.090132"^^ . + "poisson"@fr . + "environnement"@fr . + "cours d'eau"@fr . + . + "Brussels Hoofdstedelijk Gewest"@nl . + "Région de Bruxelles-Capitale"@fr . + . + "Brussels Hoofdstedelijk Gewest : lokalisatie van de verschillende meetstations voor de kwaliteitsmonitoring van de viswateren (karperachtige), conform aan het besluit van 18 juni 1992 van de uitvoerende macht van het Brussels Hoofdstedelijk Gewest naar de rangschikking van de oppervlaktewateren. De monitoring bestaat uit chemische, fysisch-chemische en microbiologische parameters"@nl . + "Réseau de surveillance de la qualité des eaux piscicoles"@fr . + "2015-09-26T00:00:00"^^ . + "Reporting Inspire"@fr . + "Belgique"@fr . + "Région de Bruxelles-Capitale"@fr . + "Monitoring network of the quality of fresh waters capable of supporting fish life"@en . + "Monitoring van de kwaliteit van de viswateren"@nl . + . + . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d39C3C9C48276C1689ABCA24432331B83 . + "2015-10-02T00:00:00"^^ . + "Zones de limitation du bruit des avions"@fr . + "environnement"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d118C647FBE0DB107B93D2D6D8526D1FE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d118C647FBE0DB107B93D2D6D8526D1FE "CIRB" . + "a7bcb16d-bbfb-42b9-939b-351211ef75e8" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d775FADB35013426678BF3391F4AD684B . + "Région de Bruxelles-Capitale : localisation des 3 zones de limitation du bruit des avions définies par l'arrêté du Gouvernement de la Région de Bruxelles-Capitale du 27 mai 1999 relatif à la lutte contre le bruit généré par le trafic aérien.\nDocument explicatif : \nhttp://geoportal.ibgebim.be/pdf/metadata_doc/19990527_agb_LutteBruit_TraficAerien.pdf"@fr . + "bruit"@fr . + "Gebieden van beperking van vliegtuiggeluid"@nl . + "Région de Bruxelles-Capitale"@fr . + "législation en matière de bruit"@fr . + . + . + "Brussels-Capital Region: location of the three areas of limitation of aircraft noise defined by the order of the Government of the Brussels-Capital Region of 27 May 1999 on the fight against the noise generated by air traffic.\nNote :\nhttp://geoportal.ibgebim.be/pdf/metadata_doc/19990527_agb_LutteBruit_TraficAerien.pdf"@en . + "Harvested"@fr . + "nuisance sonore"@fr . + "Reporting Inspire"@fr . + . + . + "Belgique"@fr . + "Areas of limitation of aircraft noise"@en . + "Brussels Hoofdstedelijk Gewest : localisatie van de drie gebieden van de beperking van het vliegtuiggeluid bepaald door het besluit van de Brusselse Hoofdstedelijke Regering van 27 mei 1999 betreffende de bestrijding van geluidshinder voortgebracht door het luchtverkeer."@nl . + "WMS" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d208362642605765D41E97A5F8191E91B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d208362642605765D41E97A5F8191E91B "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d208362642605765D41E97A5F8191E91B "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . + "Zones de gestion, de restriction ou de réglementat"@fr . + "2016-01-22T18:45:35.440291"^^ . + "bruit aérien"@fr . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : de entiteit « Tube Block » (of metrozone) identificeert de delen van het grondgebied die ingenomen worden door de tunnels en de bovengrondse delen (stations inbegrepen) van de metro en de premetro/tram."@nl . + "Tube block"@en . + "2016-01-22T18:45:10.761149"^^ . + "Espace public"@fr . + . + . + "Belgique"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBEE33AE874DC0320C0075AFC23F94BF7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBEE33AE874DC0320C0075AFC23F94BF7 "CIRB" . + "2b78e1a2-8e40-4135-90b2-32d7951e9391" . + "Bruit ferroviaire "@fr . + "2015-03-30T00:00:00"^^ . + . + . + "Reporting Inspire"@fr . + "Région de Bruxelles-capitale : l'entité « Tube Block » (ou zone de métro) identifie les emprises au sol des tunnels et des parties aériennes (stations comprises) du métro et du pré-métro/tram."@fr . + "Mobilité"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9BFC68D6E345B4FA38D2F2D172593DA8 . + "Région de Bruxelles-Capitale"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6DD8823B931E079AA77D967405159BE7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6DD8823B931E079AA77D967405159BE7 "Bruxelles Environnement " . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7A86E59D559480E5063C00374C120C60 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7A86E59D559480E5063C00374C120C60 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7A86E59D559480E5063C00374C120C60 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . + "Metrozone"@nl . + "Brussels-Capital Region : the entity \"Tube Block\" (or metro area) identifies rights-ground tunnels and aerial parts (including stations) metro and pre-metro/tram."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d65CF7FEA19BA8162DC1F8E9C6C815D0E . + "Fond de plan"@fr . + . + . + . + "Tunnels du métro"@fr . + . + . + "Harvested"@fr . + . + . + "95cb54b3-6e5f-4697-ba6d-7b2dc2f3c5c5" . + "Forêt de Soignes"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "Belgique"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7B0F0F53F6C395234D8C495B11644AF8 . + "espaces verts"@fr . + "environnement"@fr . + "forêt"@fr . + "Zones de protection en forêt de Soignes"@fr . + . + . + "Brussels-Capital Region : protected areas in the Brussels Sonian Forest: vulnerable planting or regeneration plots, refuges for wildlife, fragile being recolonized areas. In those areas, walkers must always stay on the paths and dogs must be leashed (Order of 30 March 1995)."@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d502DE09CBA90E4669C531E318331CE7A . + "Brussels Hoofdstedelijk Gewest : beschermingsgebieden in het Brusselse deel van het Zoniënwoud : kwetsbare aanplantingen of regeneratiepercelen, toevluchtsgebieden voor fauna, kwetsbare gebieden waar de vegetatie zich aan het herstellen is. In al deze gebieden moeten de wandelaars op de paden blijven en hun honden aan de leiband houden (Ordonnantie van 30 maart 1995)."@nl . + "protection de la forêt"@fr . + "Beschermingsgebieden in het Zoniënwoud"@nl . + . + . + "2015-09-26T00:00:00"^^ . + . + . + "Région de Bruxelles-Capitale : zones de protection en forêt de Soignes bruxelloise : parcelles de plantation ou de régénération vulnérables, zones refuges pour la faune, zones fragilisées en voie de recolonisation. Dans ces zones, les promeneurs doivent impérativement rester sur les chemins et les chiens doivent être tenus en laisse (Ordonnance du 30 mars 1995)."@fr . + "2016-01-22T18:45:33.810168"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d52C0E83B4FADCB042AF748DA915CB072 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d52C0E83B4FADCB042AF748DA915CB072 "CIRB" . + . + "Harvested"@fr . + "Protected areas in the Sonian Forest"@en . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d682525B6925614F6D76780E080E695DE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d682525B6925614F6D76780E080E695DE "Bruxelles Environnement" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d33DEF9BBC393C26B6F0705312C1B20FC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d33DEF9BBC393C26B6F0705312C1B20FC "Florine Deladrière" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC3BA798CD14264694ECBBB701EEE77B7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC3BA798CD14264694ECBBB701EEE77B7 "IBSA-IBSA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC05406AF2496A4EC701BBF514E344A0B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC05406AF2496A4EC701BBF514E344A0B "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC05406AF2496A4EC701BBF514E344A0B "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7292344E623DD1E4E391F4CC0008B127 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE0FBA649E5A635B967E4A06F6B5B21D7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE0FBA649E5A635B967E4A06F6B5B21D7 "IBSA-BISA" . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDDF03058F3C31E4CE65A731D8E8DB9A0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDDF03058F3C31E4CE65A731D8E8DB9A0 "Bertrand Castillon" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4472F8AB705A0E73BA04F7D076ABB2B8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4472F8AB705A0E73BA04F7D076ABB2B8 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4472F8AB705A0E73BA04F7D076ABB2B8 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2AA3A9D780D06DF4C8667E425661452F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDC19E65FB6FDC5E408907096853822F8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDC19E65FB6FDC5E408907096853822F8 "Bertrand Castillon" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "XLS" . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d39CB997EEFDD3014D3250EF9A3B854C8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d39CB997EEFDD3014D3250EF9A3B854C8 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d39CB997EEFDD3014D3250EF9A3B854C8 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d70ECFB220485EFD19AEB97CC9E6AC348 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d70ECFB220485EFD19AEB97CC9E6AC348 "CIRB" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3CB021CC946B6B8F3930E8A428564D2B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3CB021CC946B6B8F3930E8A428564D2B "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE3367817AA96B85EF16C21789A474A66 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE3367817AA96B85EF16C21789A474A66 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE3367817AA96B85EF16C21789A474A66 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d595AD2218C87A24FFC81333B2FCE9D88 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3059C15FE07AB4D5AD70DA29E982ABF3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3059C15FE07AB4D5AD70DA29E982ABF3 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3059C15FE07AB4D5AD70DA29E982ABF3 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0C0A61ABCDEF1C9E7CD847EF0F953413 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0C0A61ABCDEF1C9E7CD847EF0F953413 "Emad Alsous" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d808A44B6E0581FBAA9774B85368FAC3E . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d808A44B6E0581FBAA9774B85368FAC3E "Secretariat" . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1F5658119AAABFC8EED472A7E0FBCBE3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1F5658119AAABFC8EED472A7E0FBCBE3 "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d752518BE235D70A379AC74E67026CE41 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d752518BE235D70A379AC74E67026CE41 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d752518BE235D70A379AC74E67026CE41 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d57D7639AD10526460B6E3FBFEC85E525 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d57D7639AD10526460B6E3FBFEC85E525 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d57D7639AD10526460B6E3FBFEC85E525 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + "begeleiding"@fr . + "formation"@fr . + "numérique"@fr . + "accompagnement"@fr . + "opleiding"@fr . + "ordinateur"@fr . + "computer"@fr . + "pc"@fr . + . + . + . + "agences de voyages"@fr . + "travel agencies"@fr . + . + . + . + "reisagentschappen"@fr . + "Crèche"@fr . + "enfance"@fr . + "garderie"@fr . + "jeunesse"@fr . + . + . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + . + "couche"@fr . + "base de données"@fr . + "dikte"@fr . + "geology"@fr . + "geologisch model"@fr . + "stratigrafische eenheden"@fr . + . + . + . + "geological model"@fr . + "layer"@fr . + "épaisseur"@fr . + "modèle géologique"@fr . + "hydrogeology"@fr . + "geologie"@fr . + "top"@fr . + . + . + "stratigraphy"@fr . + "thickness"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1727855354562ADA631A0D35DAD8A131 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3AADB07C3EB3FAF22A058D9E16AD998A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9466C49419BEBA145CD55285209E6095 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9466C49419BEBA145CD55285209E6095 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9466C49419BEBA145CD55285209E6095 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + "gegevensbank"@fr . + "géologie"@fr . + "hydrogéologie"@fr . + "database"@fr . + "stratigraphic units"@fr . + "gis"@fr . + "sig"@fr . + "toit"@fr . + "unités stratigraphiques"@fr . + "laag"@fr . + "stratigrafie"@fr . + "stratigraphie"@fr . + . + . + "critère européen"@fr . + "déchets"@fr . + "installations"@fr . + "agenda"@fr . + "tango"@fr . + . + "Fond de plan"@fr . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "maison individuelle"@fr . + . + "centre de demontage"@fr . + "exploitants"@fr . + "véhicules"@fr . + . + . + "cobrace"@fr . + "évaluation"@fr . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEE482C133EF852BC112786DE9DEF9456 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEE482C133EF852BC112786DE9DEF9456 "IBSA-BISA" . + "incidences"@fr . + . + "http://opendatastore.brussels/catalog.xml?page=1" . + "100"^^ . + "http://opendatastore.brussels/catalog.xml?page=3" . + "http://opendatastore.brussels/catalog.xml?page=3" . + "http://opendatastore.brussels/catalog.xml?page=1" . + "205"^^ . + "marché"@fr . + "markt"@fr . + "collete"@fr . + "déchets"@fr . + "traitement"@fr . + "Innovation"@fr . + "Research and development"@fr . + "boek"@fr . + "lezen"@fr . + "lire"@fr . + "livre"@fr . + "fournisseurs"@fr . + "gaz"@fr . + "commerce"@fr . + "marché"@fr . + "counter"@fr . + "bike"@fr . + "compteur"@fr . + "bike count"@fr . + "comptage"@fr . + "counting"@fr . + "fietspaal"@fr . + "fietstelpaal"@fr . + "vélo"@fr . + "fournisseurs"@fr . + "éléctricité"@fr . + "60-7"@fr . + "social economy"@fr . + "sociale economie"@fr . + "économie sociale"@fr . + . + "Bibliothèque"@fr . + "Prêt"@fr . + "média"@fr . + "simplification"@fr . + "simplification administrative"@fr . + "simplify"@fr . + "XLSX" . + . + . + "administratieve vereenvoudiging"@fr . + "easy"@fr . + "football"@fr . + "fitness"@fr . + "omnisport"@fr . + "salle"@fr . + "omnisports"@fr . + "sport"@fr . + "terrain"@fr . + "veld"@fr . + "voetbal"@fr . + "zaal"@fr . + "hotspot"@fr . + "internet access"@fr . + "urbizone"@fr . + "wifi"@fr . + "bâtiments"@fr . + "omnisports"@fr . + "sports"@fr . + "activiteit"@fr . + "activité"@fr . + "culture"@fr . + "loisirs"@fr . + "cultuur"@fr . + "recreatie"@fr . + "récréatif"@fr . + "vrije tijd"@fr . + "Bruxelles Coordination Régionale"@fr . + "Ordonnance relative à la publicité de l'administration"@fr . + "Brussel Gewestelijke Coordinatie"@fr . + "Ordonnantie betreffende de openbaarheid van bestuur"@fr . + "Publicité"@fr . + "Bestuur"@fr . + "Gewestelijke Overheidsdienst Brussel"@fr . + "Administration"@fr . + "Openbaarheid"@fr . + "Registre des études"@fr . + "Service public régional de Bruxelles"@fr . + "Studieregister"@fr . + "SINE"@fr . + "CPAS"@fr . + "commune"@fr . + "centrum algemeen welzijnswerk"@fr . + "caw"@fr . + "gemeente"@fr . + "OCMW"@fr . + "organisation"@fr . + "service"@fr . + "service social"@fr . + "sociale dienst"@fr . + "exploitants"@fr . + "destruction"@fr . + "hors usage"@fr . + "recyclage"@fr . + "type c"@fr . + "véhicules"@fr . + "déchets"@fr . + "entreposage"@fr . + "caveau"@fr . + "cimetière"@fr . + "concession"@fr . + "animaux"@fr . + "déchets"@fr . + "traitement"@fr . + "critère bruxelois"@fr . + "fin de déchets"@fr . + "installations"@fr . + "baby"@fr . + "enfant"@fr . + "bébé"@fr . + "geboorte"@fr . + "kind"@fr . + "naissance"@fr . + "Belgique"@fr . + "Espace public"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "climatisation"@fr . + "formation"@fr . + "véhicules"@fr . + "Belgique"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Topographie"@fr . + "enseignement"@fr . + "klasse"@fr . + "classe"@fr . + "enfant"@fr . + "onderwijs"@fr . + "opvoeding"@fr . + "kind"@fr . + "school"@fr . + "école"@fr . + "éducation"@fr . + "enseignement"@fr . + "école"@fr . + "balançoire"@fr . + "glijbaan"@fr . + "kinderen"@fr . + "enfant"@fr . + "jeu"@fr . + "schommel"@fr . + "spelen"@fr . + "toboggan"@fr . + "numérique"@fr . + "ordinateur"@fr . + "bâtiments"@fr . + "infrastructure"@fr . + "services"@fr . + "Bio"@fr . + "Installations"@fr . + "classées"@fr . + "certificat"@fr . + "destruction"@fr . + "exploitant"@fr . + "hors usqge"@fr . + "véhicules"@fr . + "Belgique"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "limite administrative"@fr . + "conseil"@fr . + "conseiller"@fr . + "echevin"@fr . + "raadslid"@fr . + "schepen"@fr . + "Belgique"@fr . + . + . + "Fond de plan"@fr . + "Harvested"@fr . + "Patrimoine"@fr . + "Reporting Inspire"@fr . + "Tourism"@fr . + "centre"@fr . + "examen"@fr . + "froid"@fr . + "technique"@fr . + "collecteurs"@fr . + "courtiers"@fr . + "déchets dangereux"@fr . + "négociants"@fr . + "sépulture"@fr . + "Agences d'emploi privées"@fr . + "Particuliere bureaus voor arbeidsbemiddeling"@fr . + "aankoop"@fr . + "bestek"@fr . + "achats"@fr . + "cahier des charges"@fr . + "fournisseurs"@fr . + "leveranciers"@fr . + "offerte"@fr . + "offre"@fr . + "boucle"@fr . + "loop"@fr . + "camera"@fr . + "real-time"@fr . + "counter"@fr . + "telling"@fr . + "compteur"@fr . + "tellus"@fr . + "traffic"@fr . + "verkeer"@fr . + . + . + "activiteit"@fr . + "activité"@fr . + "club"@fr . + "sport"@fr . + "boulevard"@fr . + "chaussée"@fr . + "chemin"@fr . + "avenue"@fr . + "binnenhof"@fr . + "clos"@fr . + "rue"@fr . + "passage"@fr . + "laan"@fr . + "steenweg"@fr . + "straat"@fr . + "weg"@fr . + "formation"@fr . + "opleiding"@fr . + "training"@fr . + "Belgique"@fr . + "Espace public"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Maillage vert"@fr . + "Maillage écologique"@fr . + "Reporting Inspire"@fr . + "Fond de plan"@fr . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "altitude"@fr . + "PEB"@fr . + "batiment"@fr . + "certfication"@fr . + "chauffage"@fr . + "climatisation"@fr . + "energie"@fr . + "travaux"@fr . + "Belgique"@fr . + "Espace public"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "België"@fr . + "Bruxelles"@fr . + "Belgique"@fr . + "Finances publiques"@fr . + "Belgium"@fr . + "Brussel"@fr . + "Brussels"@fr . + "Public finance"@fr . + "Overheidsfinanciën"@fr . + "Statistics"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Belgique"@fr . + "Enquête sur le budget des ménages"@fr . + "Brussel"@fr . + "Fiscale statistiek van de inkomens"@fr . + "Brussels"@fr . + "Belgium"@fr . + "Huishoudbudgetonderzoek"@fr . + "Inkomensrekeningen van de huishoudens"@fr . + "Lonen"@fr . + "Salaires"@fr . + "Household income accounts"@fr . + "Comptes des revenus des ménages"@fr . + "Fiscal statistics on income"@fr . + "België"@fr . + "Salaries"@fr . + "Statistics"@fr . + "Bruxelles"@fr . + "Household budget survey"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Statistiques fiscales de revenus"@fr . + "Car Sharing"@fr . + "ZenCar"@fr . + "Car Sharing"@fr . + "ZenCar"@fr . + "3D"@fr . + "Residential and non-residential buildings"@fr . + "Région de Bruxelles-Capitale"@fr . + "UrbIS"@fr . + "eaux usées résiduaires industrielles"@fr . + "Région de Bruxelles-Capitale"@fr . + "Harvested"@fr . + "Belgique"@fr . + "charge en eaux usées"@fr . + "environnement"@fr . + "Reporting Inspire"@fr . + "pollution de l'eau"@fr . + "qualité des eaux usées"@fr . + "station d'épuration des eaux usées"@fr . + "traitement des eaux usées"@fr . + "épuration de l'eau"@fr . + "belgium"@fr . + "brussels"@fr . + "real-time"@fr . + "transport-public"@fr . + "vehicle-position"@fr . + "Belgique"@fr . + "Bruxelles"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "Topographie"@fr . + "Active mobility"@fr . + "Belgique"@fr . + "Collective transport and shared mobility"@fr . + "Freight transport"@fr . + "Belgium"@fr . + "Brussel"@fr . + "België"@fr . + "Actieve mobiliteit"@fr . + "Pratiques de déplacements"@fr . + "Sécurité routière"@fr . + "Statistiques"@fr . + "Verkeersveiligheid"@fr . + "Mobility practices"@fr . + "Statistics"@fr . + "Verplaatsingsgewoonten"@fr . + "Transport de marchandises"@fr . + "Mobilité active"@fr . + "Bruxelles"@fr . + "Brussels"@fr . + "Statistieken"@fr . + "Collectief en gedeeld vervoer"@fr . + "Vehicles and road network"@fr . + "Transport collectif et partagé"@fr . + "Road safety"@fr . + "Vervoer van goederen"@fr . + "Voertuigen en wegennet"@fr . + "Véhicules et réseau routier"@fr . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "classification de l'usage des sols"@fr . + "affectation des sols"@fr . + "environnement"@fr . + "espace vert"@fr . + "occupation du sol"@fr . + "paysage"@fr . + "Belgique"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "limite administrative"@fr . + "civil society organisations"@fr . + "Brussels"@fr . + "gender equality"@fr . + "grassroots organisations"@fr . + "public sector"@fr . + "Reporting Inspire"@fr . + "Belgique"@fr . + "environnement"@fr . + "Région de Bruxelles-Capitale"@fr . + "espace vert"@fr . + "Harvested"@fr . + "conservation des ressources naturelles"@fr . + "faune"@fr . + "flore"@fr . + "législation en matière de préservation de la natur"@fr . + "préservation de la nature"@fr . + "site naturel protégé"@fr . + "Harvested"@fr . + "Mobilité"@fr . + "Bruxelles"@fr . + "Belgique"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "contrôle de la circulation"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "Belgique"@fr . + "environnement"@fr . + "législation en matière de préservation de la natur"@fr . + "protection de la faune et de la flore"@fr . + "protection des espaces naturels"@fr . + "protection des espèces"@fr . + "Harvested"@fr . + "Région de Bruxelles-Capitale"@fr . + "Zones de gestion, de restriction ou de réglementat"@fr . + "approvisionnement en eau potable"@fr . + "Belgique"@fr . + "alimentation en eau de la ville"@fr . + "captage d'eau"@fr . + "Reporting Inspire"@fr . + "eaux souterraines"@fr . + "environnement"@fr . + "protection de zone de captage de l'eau"@fr . + "zone protégée de captage d'eau"@fr . + "Belgique"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "Belgique"@fr . + "Brussels"@fr . + "Brussel"@fr . + "Femmes"@fr . + "Age structure"@fr . + "Households"@fr . + "Nationaliteiten"@fr . + "Ménages"@fr . + "Belgium"@fr . + "Demographic projections"@fr . + "Huishoudens"@fr . + "Projections démographiques"@fr . + "Loop van de bevolking"@fr . + "België"@fr . + "Annual change"@fr . + "Statistieken"@fr . + "Bruxelles"@fr . + "Movement of the population"@fr . + "Mannen"@fr . + "Jaarlijkse evolutie"@fr . + "Statistics"@fr . + "Nationalities"@fr . + "Hommes"@fr . + "Mouvement de la population"@fr . + "Statistiques"@fr . + "Bevolkingsprojecties"@fr . + "Nationalités"@fr . + "Men"@fr . + "Leeftijdsstructuur"@fr . + "Structure par âge"@fr . + "Vrouwen"@fr . + "Women"@fr . + "Évolution annuelle"@fr . + "bus"@fr . + "metro"@fr . + "public-transport"@fr . + "Brussels"@fr . + "realtime"@fr . + "stops"@fr . + "tram"@fr . + "Bruxelles"@fr . + "Geslacht"@fr . + "Belgium"@fr . + "Population scolaire"@fr . + "Origin-destination of pupils"@fr . + "Brussels"@fr . + "Sexe"@fr . + "België"@fr . + "Herkomst-bestemming van leerlingen"@fr . + "School population"@fr . + "Onderwijsinstellingen"@fr . + "Brussel"@fr . + "Schools"@fr . + "Schoolbevolking"@fr . + "Origine et destination des élèves"@fr . + "Statistics"@fr . + "Sex"@fr . + "Belgique"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Établissements"@fr . + "Agenda"@fr . + "Culture"@fr . + "Cinéma"@fr . + "Exposition"@fr . + "Concert"@fr . + "Nightlife"@fr . + "Spectacle"@fr . + "Théatre"@fr . + "contrôle d'accès"@fr . + "fietsparking"@fr . + "parking"@fr . + "vélo"@fr . + "Activité économique"@fr . + "Belgique"@fr . + "Bruxelles"@fr . + "Conjoncture"@fr . + "Companies"@fr . + "Conjunctuur"@fr . + "Economic activity"@fr . + "Economic situation"@fr . + "Regional economic prospects"@fr . + "België"@fr . + "Belgium"@fr . + "Ondernemingen"@fr . + "Economische activiteit"@fr . + "Perspectives économiques régionales"@fr . + "Entreprises"@fr . + "Regionale economische vooruitzichten"@fr . + "Brussels"@fr . + "Brussel"@fr . + "Statistics"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Fond de plan"@fr . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "limite administrative"@fr . + "fix my street"@fr . + "incident"@fr . + "mobilite"@fr . + "mobiliteit"@fr . + "mobility"@fr . + "voirie"@fr . + "Brussel"@fr . + "Energy"@fr . + "Environment and society"@fr . + "Brussels"@fr . + "Belgium"@fr . + "Environnement et société"@fr . + "Environment and territory"@fr . + "Milieu en grondgebied"@fr . + "Energie"@fr . + "Belgique"@fr . + "Bruxelles"@fr . + "Milieu en maatschappij"@fr . + "Statistics"@fr . + "België"@fr . + "Environnement et territoire"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Énergie"@fr . + "brussel"@fr . + "brussels"@fr . + "bruxelles"@fr . + "coworking"@fr . + "espace de travail"@fr . + "freelance"@fr . + "indépendants"@fr . + "u talk freelance"@fr . + "Events"@fr . + "Traffic"@fr . + "Works"@fr . + "Reporting Inspire"@fr . + "Harvested"@fr . + "Région de Bruxelles-Capitale"@fr . + "Belgique"@fr . + "eau de surface"@fr . + "environnement"@fr . + "occupation du sol"@fr . + "ressource en eau"@fr . + "Belgique"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Mobilité"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "Espace public"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Gezondheid"@nl . + "Health"@en . + "Santé"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Environnement"@fr . + "Milieu"@nl . + . + . + "Environment"@en . + "Nutsdiensten en overheidsdiensten"@nl . + "Services d'utilité publique et services publics"@fr . + "Utility and governmental services"@en . + "Environment"@en . + "Environnement"@fr . + "Gebouwen"@nl . + "Habitats en biotopen"@nl . + "Land use"@en . + "Bâtiments"@fr . + "Bodemgebruik"@nl . + "Buildings"@en . + "Habitats and biotopes"@en . + "Habitats et biotopes"@fr . + "Land cover"@en . + "Landgebruik"@nl . + "Milieu"@nl . + "Occupation des terres"@fr . + "Usage des sols"@fr . + "Science and technology"@en . + "Science et technologie"@fr . + "Wetenschap en technologie"@nl . + "Administratieve eenheden"@nl . + "Administrative units"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Unités administratives"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Gezondheid"@nl . + "Health"@en . + "Santé"@fr . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Government and public sector"@en . + "Gouvernement et secteur public"@fr . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Administratieve eenheden"@nl . + "Administrative units"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Unités administratives"@fr . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Installations de suivi environnemental"@fr . + "Environmental monitoring facilities"@en . + "Milieubewakingsvoorzieningen"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Statistical units"@en . + "Statistische eenheden"@nl . + "Unités statistiques"@fr . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Addresses"@en . + "Adressen"@nl . + "Adresses"@fr . + "Gezondheid"@nl . + "Santé"@fr . + . + "{u'fr': u'STIB', u'en': u'STIB-MIVB', u'nl': u'MIVB'}" . + "Health"@en . + "Addresses"@en . + "Adressen"@nl . + "Adresses"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Installations de suivi environnemental"@fr . + "Milieubewakingsvoorzieningen"@nl . + "Environmental monitoring facilities"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Transport"@en . + "Transport networks"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Environmental monitoring facilities"@en . + "Installations de suivi environnemental"@fr . + "Milieubewakingsvoorzieningen"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Geologie"@nl . + "Geology"@en . + "Géologie"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Justice, legal system and public safety"@en . + "Justice, système juridique et sécurité publique"@fr . + "Justitie, rechtsstelsel en openbare veiligheid"@nl . + "Hydrografie"@nl . + "Hydrographie"@fr . + "Hydrography"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Installations de suivi environnemental"@fr . + "Milieubewakingsvoorzieningen"@nl . + "Environmental monitoring facilities"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Régions et villes"@fr . + "Regions and cities"@en . + "Regio's en steden"@nl . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Protected sites"@en . + "Beschermde gebieden"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Sites protégés"@fr . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "{u'fr': u'JC Decaux', u'en': u'JC Decaux', u'nl': u'JC Decaux'}" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2382AA13E8EC6194CF4A54A19F437717 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dADB06977A6666F6DFA57903AA82D5A8D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dADB06977A6666F6DFA57903AA82D5A8D "Equipe Geodata" . + . + . + . + . + . + . + . + . + "{u'fr': u'Ligue Cardiologique Belge / Belgische Cardiologische Liga', u'en': u'Ligue Cardiologique Belge / Belgische Cardiologische Liga', u'nl': u'Ligue Cardiologique Belge / Belgische Cardiologische Liga'}" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "{u'fr': u'Bruxelles-Propret\\xe9', u'en': u'Bruxelles-Propret\\xe9', u'nl': u'Bruxelles-Propret\\xe9'}" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "ZIP" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB10E6C6414C0D257D759555E765A605C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB10E6C6414C0D257D759555E765A605C "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB10E6C6414C0D257D759555E765A605C "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB7F9D9C3D3194E97F3E0675E89561BA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB7F9D9C3D3194E97F3E0675E89561BA "Sébastien DEFRANCE" . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD126B63FEBC4C7D0E03F6240073DDD32 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD126B63FEBC4C7D0E03F6240073DDD32 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD126B63FEBC4C7D0E03F6240073DDD32 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB8D93DF50D9BF460DA0B07D713E84B17 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB8D93DF50D9BF460DA0B07D713E84B17 "Virginie Tumelaire" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA7B82A94F76AC859CA9016D97ED8CC30 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA7B82A94F76AC859CA9016D97ED8CC30 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA7B82A94F76AC859CA9016D97ED8CC30 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d425426E22B86B83FA02F4EF7369C0BFB . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4127D107771FDA7E9F8A00E87D059913 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4127D107771FDA7E9F8A00E87D059913 "Equipe Geodata" . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEEB0094A2E7D15DCFDAB95A7B46B9870 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEEB0094A2E7D15DCFDAB95A7B46B9870 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEEB0094A2E7D15DCFDAB95A7B46B9870 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDD955DC0FDB19D52F2A0CD5975109B99 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDD955DC0FDB19D52F2A0CD5975109B99 "Secretariat" . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAE86DBCB526542448A9F90F078EB5AB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAE86DBCB526542448A9F90F078EB5AB "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAE86DBCB526542448A9F90F078EB5AB "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dACD91713CF6E6FA67ADB944192B74F7F . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6BFE13A386C35965B19F74BB97AD1F13 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6BFE13A386C35965B19F74BB97AD1F13 "Thibault Delforge" . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d43ABD3120EC1C1A6813E6AEF7C545BDA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d43ABD3120EC1C1A6813E6AEF7C545BDA "Equipe Geodata" . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD1884F002E90538E4433E4FD8A97C07D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD1884F002E90538E4433E4FD8A97C07D "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD1884F002E90538E4433E4FD8A97C07D "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d814AEE2B85DA57FA2EE0D221DB90919B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d814AEE2B85DA57FA2EE0D221DB90919B "Equipe Geodata" . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d534D37BA97474E37563EB9DD1EF9B643 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d534D37BA97474E37563EB9DD1EF9B643 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d534D37BA97474E37563EB9DD1EF9B643 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD937FFEB481A3833A131484187E7A34A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA34DE8F29628F265CF191304EA59869C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA34DE8F29628F265CF191304EA59869C "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA34DE8F29628F265CF191304EA59869C "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d865BA987312B3780A14D76AA465C04F5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d865BA987312B3780A14D76AA465C04F5 "POLYGON ((4.2432 50.7607, 4.4822 50.7607, 4.4822 50.9132, 4.2432 50.9132, 4.2432 50.7607))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d865BA987312B3780A14D76AA465C04F5 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24320180859, 50.7607222666], [4.48215444531, 50.7607222666], [4.48215444531, 50.9131575693], [4.24320180859, 50.9131575693], [4.24320180859, 50.7607222666]]]}"^^ . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE4EB246A39AC218EDB19CD5276DEB00A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE4EB246A39AC218EDB19CD5276DEB00A "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8451A75AD09223559923F5074B053D2D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8451A75AD09223559923F5074B053D2D "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3771DDD545C7FF833EAADDD0B8C16985 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3771DDD545C7FF833EAADDD0B8C16985 "Emad Alsous" . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF01E40D8DF7FD02ED41C805485FED73F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF01E40D8DF7FD02ED41C805485FED73F "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF01E40D8DF7FD02ED41C805485FED73F "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8A205C93A6B873873D785461591DA0F5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5253BC8E88B23C4F192ADBB6729E406F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5253BC8E88B23C4F192ADBB6729E406F "Mobigis" . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECD4EDA046583F5D2E84C1FD67988073 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECD4EDA046583F5D2E84C1FD67988073 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECD4EDA046583F5D2E84C1FD67988073 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4455D8966A2B7AD708AC83922EA55488 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4455D8966A2B7AD708AC83922EA55488 "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCF726A6A5E46023651ABC5AA084186DB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCF726A6A5E46023651ABC5AA084186DB "Visit Brussels" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C3F7A2F5F30A786C7D7CC7E62FC8761 . + . + . + . + . + . + . + . + . + . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA0C35FA6E956E4D4CA0CE01DE7452099 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA0C35FA6E956E4D4CA0CE01DE7452099 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA0C35FA6E956E4D4CA0CE01DE7452099 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCBB7115B7FCCD6D72FB4F18708013013 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCBB7115B7FCCD6D72FB4F18708013013 "IBSA-BISA" . + . + . + . + . + . + . + . + . + . + "http://opendatastore.brussels/catalog.xml?page=1" . + "100"^^ . + "http://opendatastore.brussels/catalog.xml?page=3" . + "http://opendatastore.brussels/catalog.xml?page=2" . + "205"^^ . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Addresses"@en . + "Adressen"@nl . + "Adresses"@fr . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Coordinate reference systems"@en . + "Bâtiments"@fr . + "Education, culture and sport"@en . + "Buildings"@en . + "Onderwijs, cultuur en sport"@nl . + "Gebouwen"@nl . + "Référentiels de coordonnées"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Éducation, culture et sport"@fr . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Cadastral parcels"@en . + "Kadastrale percelen"@nl . + "Parcelles cadastrales"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Environnement"@fr . + "Environment"@en . + "Geologie"@nl . + "Geology"@en . + "Géologie"@fr . + "Milieu"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Environnement"@fr . + "Environment"@en . + "Overheid en publieke sector"@nl . + "Population and society"@en . + "Bevolking en samenleving"@nl . + "Milieu"@nl . + "Population et société"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Adresses"@fr . + "Addresses"@en . + "Adressen"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Energie"@nl . + "Energy"@en . + "Énergie"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Science and technology"@en . + "Science et technologie"@fr . + "Wetenschap en technologie"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Environnement"@fr . + "Gouvernement et secteur public"@fr . + "Milieu"@nl . + "Government and public sector"@en . + "Bevolking en samenleving"@nl . + "Overheid en publieke sector"@nl . + "Population et société"@fr . + "Population and society"@en . + "Environment"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Bodemgebruik"@nl . + "Economie en financiën"@nl . + "Coordinate reference systems"@en . + "Economy and finance"@en . + "Land cover"@en . + "Occupation des terres"@fr . + "Référentiels de coordonnées"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Économie et finances"@fr . + "Transport"@en . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Government and public sector"@en . + "Gouvernement et secteur public"@fr . + "Overheid en publieke sector"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Coordinate reference systems"@en . + "Nutsdiensten en overheidsdiensten"@nl . + "Onderwijs, cultuur en sport"@nl . + "Education, culture and sport"@en . + "Référentiels de coordonnées"@fr . + "Services d'utilité publique et services publics"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Utility and governmental services"@en . + "Éducation, culture et sport"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Energie"@nl . + "Energy"@en . + "Énergie"@fr . + "Onderwijs, cultuur en sport"@nl . + "Regions and cities"@en . + "Transports"@fr . + "Science and technology"@en . + "Education, culture and sport"@en . + "Population and society"@en . + "Transport"@en . + "Régions et villes"@fr . + "Regio's en steden"@nl . + "Population et société"@fr . + "Bevolking en samenleving"@nl . + "Science et technologie"@fr . + "Vervoer"@nl . + "Wetenschap en technologie"@nl . + "Éducation, culture et sport"@fr . + "Bâtiments"@fr . + "Buildings"@en . + "Education, culture and sport"@en . + "Gebouwen"@nl . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Administrative units"@en . + "Government and public sector"@en . + "Nutsdiensten en overheidsdiensten"@nl . + "Overheid en publieke sector"@nl . + "Administratieve eenheden"@nl . + "Services d'utilité publique et services publics"@fr . + "Unités administratives"@fr . + "Utility and governmental services"@en . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Government and public sector"@en . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Overheid en publieke sector"@nl . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Gouvernement et secteur public"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Government and public sector"@en . + "Gouvernement et secteur public"@fr . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Gouvernement et secteur public"@fr . + "Nutsdiensten en overheidsdiensten"@nl . + "Overheid en publieke sector"@nl . + "Bevolking en samenleving"@nl . + "Population et société"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Population and society"@en . + "Government and public sector"@en . + "Régions et villes"@fr . + "Services d'utilité publique et services publics"@fr . + "Utility and governmental services"@en . + "Energie"@nl . + "Energy"@en . + "Énergie"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Regio's en steden"@nl . + "Régions et villes"@fr . + "Regions and cities"@en . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Agriculture, fisheries, forestry and food"@en . + "Agriculture, pêche, sylviculture et alimentation"@fr . + "Gouvernement et secteur public"@fr . + "Overheid en publieke sector"@nl . + "Landbouw, visserij, bosbouw en voeding"@nl . + "Government and public sector"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Bodemgebruik"@nl . + "Land cover"@en . + "Occupation des terres"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Education, culture and sport"@en . + "Bâtiments"@fr . + "Onderwijs, cultuur en sport"@nl . + "Buildings"@en . + "Coordinate reference systems"@en . + "Gebouwen"@nl . + "Référentiels de coordonnées"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Éducation, culture et sport"@fr . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Coordinate reference systems"@en . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Référentiels de coordonnées"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Éducation, culture et sport"@fr . + "Coordinate reference systems"@en . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Référentiels de coordonnées"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Éducation, culture et sport"@fr . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Statistical units"@en . + "Statistische eenheden"@nl . + "Unités statistiques"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Energie"@nl . + "Energy"@en . + "Énergie"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Buildings"@en . + "Bâtiments"@fr . + "Gebouwen"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Government and public sector"@en . + "Justice, système juridique et sécurité publique"@fr . + "Menselijke gezondheid en veiligheid"@nl . + "Population and society"@en . + "Bevolking en samenleving"@nl . + "Regio's en steden"@nl . + "Justice, legal system and public safety"@en . + "Justitie, rechtsstelsel en openbare veiligheid"@nl . + "Human health and safety"@en . + "Regions and cities"@en . + "Health"@en . + "Population et société"@fr . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Régions et villes"@fr . + "Santé"@fr . + "Onderwijs, cultuur en sport"@nl . + "Santé et sécurité des personnes"@fr . + "Gezondheid"@nl . + "Nutsdiensten en overheidsdiensten"@nl . + "Education, culture and sport"@en . + "Services d'utilité publique et services publics"@fr . + "Utility and governmental services"@en . + "Éducation, culture et sport"@fr . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Bevolking en samenleving"@nl . + "Bâtiments"@fr . + "Population and society"@en . + "Buildings"@en . + "Coordinate reference systems"@en . + "Gebouwen"@nl . + "Population et société"@fr . + "Référentiels de coordonnées"@fr . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Energie"@nl . + "Energy"@en . + "Énergie"@fr . + "Energie"@nl . + "Energy"@en . + "Énergie"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Buildings"@en . + "Gebouwen"@nl . + "Bâtiments"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Land cover"@en . + "Occupation des terres"@fr . + "Bodemgebruik"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Altitude"@fr . + "Elevation"@en . + "Hoogte"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Energy"@en . + "Energie"@nl . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Énergie"@fr . + "Dénominations géographiques"@fr . + "Geografische namen"@nl . + "Geographical names"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Nutsdiensten en overheidsdiensten"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Services d'utilité publique et services publics"@fr . + "Utility and governmental services"@en . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Ortho-imagerie"@fr . + "Orthobeeldvorming"@nl . + "Orthoimagery"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Transport"@en . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Transport networks"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Land cover"@en . + "Bodemgebruik"@nl . + "Occupation des terres"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Administratieve eenheden"@nl . + "Administrative units"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Unités administratives"@fr . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Beschermde gebieden"@nl . + "Regio's en steden"@nl . + "Protected sites"@en . + "Regions and cities"@en . + "Régions et villes"@fr . + "Sites protégés"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Regions and cities"@en . + "Transport networks"@en . + "Transport"@en . + "Regio's en steden"@nl . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Economy and finance"@en . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Economie en financiën"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Économie et finances"@fr . + "Beschermde gebieden"@nl . + "Regio's en steden"@nl . + "Protected sites"@en . + "Regions and cities"@en . + "Régions et villes"@fr . + "Sites protégés"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Transport networks"@en . + "Transport"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Dénominations géographiques"@fr . + "Geografische namen"@nl . + "Geographical names"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Bevolking en samenleving"@nl . + "Population distribution — demography"@en . + "Population and society"@en . + "Population et société"@fr . + "Répartition de la population — démographie"@fr . + "Spreiding van de bevolking — demografie"@nl . + "Régions et villes"@fr . + "Regions and cities"@en . + "Regio's en steden"@nl . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Transport networks"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Education, culture and sport"@en . + "Onderwijs, cultuur en sport"@nl . + "Éducation, culture et sport"@fr . + "Coordinate reference systems"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Population and society"@en . + "Population et société"@fr . + "Référentiels de coordonnées"@fr . + "Régions et villes"@fr . + "Services d'utilité publique et services publics"@fr . + "Transport"@en . + "Systemen voor verwijzing door middel van coördinaten"@nl . + "Bevolking en samenleving"@nl . + "Nutsdiensten en overheidsdiensten"@nl . + "Transports"@fr . + "Utility and governmental services"@en . + "Vervoer"@nl . + "Economie en financiën"@nl . + "Economy and finance"@en . + "Économie et finances"@fr . + "Administratieve eenheden"@nl . + "Regio's en steden"@nl . + "Administrative units"@en . + "Regions and cities"@en . + "Régions et villes"@fr . + "Unités administratives"@fr . + "Gouvernement et secteur public"@fr . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Government and public sector"@en . + "Régions et villes"@fr . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Energie"@nl . + "Energy"@en . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Énergie"@fr . + "Addresses"@en . + "Adressen"@nl . + "Adresses"@fr . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Transport"@en . + "Transport networks"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Réseaux de transport"@fr . + "Transport"@en . + "Transport networks"@en . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Hydrografie"@nl . + "Hydrographie"@fr . + "Hydrography"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Régions et villes"@fr . + "Regions and cities"@en . + "Regio's en steden"@nl . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Land cover"@en . + "Bodemgebruik"@nl . + "Occupation des terres"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Beschermde gebieden"@nl . + "Regio's en steden"@nl . + "Protected sites"@en . + "Regions and cities"@en . + "Régions et villes"@fr . + "Sites protégés"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Regions and cities"@en . + "Regio's en steden"@nl . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport networks"@en . + "Vervoersnetwerken"@nl . + "Beschermde gebieden"@nl . + "Regio's en steden"@nl . + "Protected sites"@en . + "Regions and cities"@en . + "Régions et villes"@fr . + "Sites protégés"@fr . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Environmental monitoring facilities"@en . + "Installations de suivi environnemental"@fr . + "Milieubewakingsvoorzieningen"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Gouvernement et secteur public"@fr . + "Government and public sector"@en . + "Overheid en publieke sector"@nl . + "Hydrographie"@fr . + "Hydrography"@en . + "Hydrografie"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Environment"@en . + "Environnement"@fr . + "Milieu"@nl . + "Nutsdiensten en overheidsdiensten"@nl . + "Services d'utilité publique et services publics"@fr . + "Utility and governmental services"@en . + "Environmental monitoring facilities"@en . + "Installations de suivi environnemental"@fr . + "Milieubewakingsvoorzieningen"@nl . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Transport networks"@en . + "Transport"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Transport"@en . + "Transports"@fr . + "Vervoer"@nl . + "Transport"@en . + "Transport networks"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Faciliteiten voor productie en industrie"@nl . + "Lieux de production et sites industriels"@fr . + "Production and industrial facilities"@en . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Bevolking en samenleving"@nl . + "Population and society"@en . + "Population et société"@fr . + "Transport"@en . + "Transport networks"@en . + "Réseaux de transport"@fr . + "Transports"@fr . + "Vervoer"@nl . + "Vervoersnetwerken"@nl . + "Government and public sector"@en . + "Bevolking en samenleving"@nl . + "Justice, legal system and public safety"@en . + "Overheid en publieke sector"@nl . + "Population and society"@en . + "Population et société"@fr . + "Justitie, rechtsstelsel en openbare veiligheid"@nl . + "Gouvernement et secteur public"@fr . + "Justice, système juridique et sécurité publique"@fr . + "Regio's en steden"@nl . + "Regions and cities"@en . + "Régions et villes"@fr . + "Réseaux de transport"@fr . + "Transport"@en . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "GTFS" . + . + . + . + . + "HTML" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + "application/pdf" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/pdf" . + . + "Kalender van de zittingen van de Gemeenteraad 2018" . + "2018 - Kalender van de zittingen van de Gemeenteraad " . + . + . + "power bi" . + "2016 - Clients protégés et hivernaux.pbix" . + . + "Activiteitenverslag 2017" . + . + . + . + "power bi" . + "2018 - Beschermde en winterklanten.pbix" . + . + "WFS" . + . + "application/pdf" . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + . + "power bi" . + "2018 - T1 - Clientèle sociale/Beschermde klanten.pbix" . + . + . + "Composition du Conseil communal au 21/12/2017 " . + "21/12/2017 " . + . + . + "CSV" . + . + . + . + "application/pdf" . + "Calendrier des séances du conseil communal 2018" . + "2018 - Calendrier des séances du conseil communal " . + . + "lijst_installaties_einde_afvalfase_europese_criteria" . + . + "PDF" . + . + . + "Lijst van de wekelijkse markten 2018 (jaarlijkse bijwerking)" . + "2018 - Wekelijkse markten " . + . + . + "Agenda de toutes les activités liées au tango en Belgique" . + "application/pdf" . + . + "Milonga en Belgique" . + . + "Crèches d'Auderghem FR" . + . + . + "Rapport Annuel 2003 Port de Bruxelles" . + . + . + "Ce tableur présente les 8 écoles issues de l'enseignement obligatoire." . + . + "application/pdf" . + "Ecoles communales" . + . + "Rapport annuel 2016" . + . + "power bi" . + "2017 - T3 - Parts de marché/Maarktaandelen.pbix" . + . + . + "power bi" . + "2018 - T4 - Electricité Verte / Groenestroom.pbix" . + . + "Liste des écoles d'Auderghem" . + "Écoles d'Auderghem" . + . + . + . + "Rapport Annuel 2015 Port de Bruxelles" . + "application/pdf" . + . + "GeoJSON" . + . + . + "Jaarverslag 2016 Haven van Brussel" . + . + . + "Gemeentelijke school - schooljaar 2017/2018 (jaarlijkse bijwerking)" . + "2017/2018 - Gemeentelijke school" . + "application/pdf" . + . + . + "Liste des exploitants d'un centre de démontage de véhicules hors d'usage (type A), enregistrés en Région de Bruxelles-Capitale" . + "liste_exploitants_centre_demontage_vehicules_hors_usage_type_A" . + . + . + "CSV-bestand met gestandardiseerde en geolokaliseerbare informatie over de publieke en private organisaties en diensten die algemene sociale dienstverlening bieden in het Brussels Gewest. Zie ook: https://social.brussels/sector/53.\nLicentie: CC-By\nBevat ook geo-coördinaten Lambert" . + "Algemene sociale dienstverlening" . + . + "application/pdf" . + . + "GeoJSON" . + . + . + "lijst_opdrachthouders_effectenbeoordeling" . + . + . + "wifi.brussels" . + . + "application/pdf" . + . + "Registratie als exploitant van een demonteercentrum voor afgedankte voertuigen (type A) in het Brussels Hoofdstedelijk Gewest" . + "lijst_exploitant_demonteercentrum_afgedankte_voertuigen_type_A" . + . + . + "Jaarverslag 2002 Haven van Brussel" . + . + . + "power bi" . + "2018 - T2 - Clientèle sociale/Beschermde klanten.pbix" . + . + "MapViewer" . + . + . + "lijst_vergunde_inzamel_verwerkingsinrichtingen_afvalstoffen" . + . + . + . + "Infrastructures sportives" . + "application/pdf" . + . + . + "liste_installations_classees" . + . + . + "Activiteitenverslag 2014" . + . + "application/pdf" . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + . + . + "JSON" . + . + . + "article 60 -7 – économie sociale" . + . + . + "API for traffic counts" . + "API" . + "API" . + . + . + "lijst_einde_afvalfase_brusselse_criteria" . + "application/pdf" . + . + . + "power bi" . + "2018 - Actieve vermogenbegrenzers.pbix" . + . + "lijst_studiebureaus_opslaginstallaties" . + . + . + "lijst_elektriciteitsleveranciers" . + . + . + . + "06/12/2018" . + . + "application/pdf" . + "Composition du Conseil communal au 06/12/2018" . + . + "Jaarverslag 2013 Haven van Brussel" . + . + . + "Le rapport annuel 2017 complet est disponible en ligne, sur le site \nhttp://rapportannuel.port.brussels" . + "Rapport Annuel 2017 Port de Bruxelles" . + . + . + "Liste des bibliothèques communales 2018 (mise à jour annuelle)" . + "2018 - Bibliothèques communales" . + "application/pdf" . + . + . + "Jaarverslag 2015 Haven van Brussel" . + . + "Lijst van de vergunde reisagentschappen in het BHG" . + "Lijst van de vergunde reisagentschappen in het BHG" . + . + . + . + "ILDE EI" . + "application/pdf" . + . + "power bi" . + "2018_Aantal leveringspunten per leverancier.pbix" . + . + . + "power bi" . + "2018 - Désactivations sur ordre du juge de paix.pbix" . + . + "Preview" . + . + . + "Arrivals & Overnights - Data History" . + . + . + "power bi" . + "2017 - Actieve vermogenbegrenzers.pbix" . + . + . + . + "liste_charges_evaluation_incidences" . + . + . + "application/pdf" . + "Deze dataset omvat alle door Innoviris geaccepteerde projecten (2015-2017). In een bijkomend tabblad worden enkele afkortingen van programma's verduidelijkt. " . + . + "Liste des crèches communales 2017 (mise à jour annuelle)" . + "2017 - Crèches communales " . + . + "API for bicycle counts" . + . + . + "API" . + . + "power bi" . + "2018 - Limiteurs de puissance actifs.pbix" . + . + "Bike counting poles - CSV" . + . + "application/pdf" . + . + "2017_Parts de marché_Maarktaandeel.xlsx" . + . + "Employeurs Sine" . + . + . + . + "power bi" . + "2019 - Actieve vermogenbegrenzers.pbix " . + . + "JSON" . + . + "application/pdf" . + . + "liste_bureaux_etude_incidences" . + . + . + "lijst_EPB_adviseurs_rechtspersonen" . + . + . + . + "Liste des marchés hebdomadaires 2018 (mise à jour annuelle)" . + "2018 - Marchés hebdomadaires " . + "application/pdf" . + . + "Lijst van sportverenigingen (gesubsidieerd door de gemeente) op 30 april 2018 (jaarlijkse bijwerking)" . + "2018 - Sportverenigingen gesubsidieerd door de gemeente" . + . + . + "Infrastructures sportives" . + . + . + "Preview" . + . + "application/pdf" . + . + "GeoJSON" . + . + . + "Lijst van de gecertificeerde biologische bedrijven in het Brussels Hoofdstedelijk Gewest" . + . + "application/pdf" . + . + "Infrastructures sportives 2017 (mise à jour annuelle)" . + "2017 - Infrastructures sportives " . + . + . + "Calendrier des séances du Conseil communal 2019" . + "2019 - Calendrier des séances du conseil communal" . + . + . + . + "liste_centres_formation_systemes_climatisation_automobiles" . + . + "Jaarverslag 2001 Haven van Brussel" . + . + "application/pdf" . + . + "Rapport Annuel 2008 Port de Bruxelles" . + . + . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + . + "power bi" . + "2017 - T3 - Taux de switches / Switch Percentage.pbix" . + . + . + "Liste des marchés hebdomadaires 2019 (mise à jour annuelle)" . + "2019 - Marchés hebdomadaires" . + "application/pdf" . + . + "2019_points_de_fourniture_leveringspunten.xlsx" . + . + . + "Rapport Annuel 2006 Port de Bruxelles" . + . + . + "Rapport Annuel 2002 Port de Bruxelles" . + . + "application/pdf" . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + . + . + "Rapport Annuel 2013 Port de Bruxelles" . + . + . + "power bi" . + "2018 - T4 - Taux de switches / Switch Percentage.pbix" . + . + . + "Composition du Conseil communal au 27/10/2016\n" . + "27/10/2016" . + "application/pdf" . + . + . + "Rapport d'activités 2016" . + . + "Jaarverslag 2004 Haven van Brussel" . + . + . + . + "power bi" . + "2018 - T3 - Taux de switches / Switch Percentage.pbix" . + . + "Bike - WFS, all layers" . + "application/pdf" . + . + . + "Fonds de formation titres-services - Formations admises" . + . + "Mapviewer" . + . + . + . + "The enclosed file shows the data from a panel of 20 guided tours organisations. \nSee the full interactive barometer via the following link: https://visit.brussels/en/article/tourism-barometer-of-the-brussels-capital-region" . + "Barometer of the Guided Tours" . + "application/pdf" . + . + . + "Liste et descriptions détaillées et géolocalisées des organisations publiques et privées qui offrent de l'aide sociale générale en Région de Bruxelles-Capitale" . + "Aide sociale générale" . + . + "WFS" . + . + . + "application/pdf" . + . + "WFS" . + . + . + "Ateliers du web" . + . + . + "Activiteitenverslag 2013" . + . + . + "Ecoles communales - année scolaire 2017/2018 (mise à jour annuelle)" . + "application/pdf" . + . + "2017/2018 - Ecoles communales" . + . + "Lijst van openbare computerruimtes 2017 (jaarlijkse bijwerking)" . + "2017 - Openbare computerruimtes " . + . + "power bi" . + "2018- Deactiveringen op bevel van de vrederechter.pbix" . + . + . + "Het Brussels agentschap voor administratieve vereenvoudiging en de gewestelijke besturen hebben in 2017 een reeks acties ontwikkeld, toegespitst op het uitvoeren van de doelstellingen van het plan voor administratieve vereenvoudiging 2015-2020. Dit verslag zet de grote werven van administratieve vereenvoudiging uiteen die gemeenschappelijk zijn voor alle besturen, zoals open data, het virtuele loket, de digitale inclusie, de elektronische facturatie, online overheidsopdrachten, de stappencatalogus, enz. Het verslag licht verder specifieke acties van vereenvoudiging toe die in de loop van het jaar tot stand zijn gebracht door specifieke partners van het agentschap. Tot slot, formuleert Easybrussels ook aanbevelingen op basis van de ervaringen en vaststellingen in 2017. " . + "Jaarverslag 2017 Easybrussels" . + . + . + "power bi" . + "2017 - T4 - Parts de marché/Maarktaandelen.pbix" . + . + . + "power bi" . + "2017- Deactiveringen op bevel van de vrederechter.pbix" . + . + "application/pdf" . + . + "Masterplan Horizon 2030" . + . + "liste_centres_examen_technique_froid" . + . + . + "liste_installations_collecte_traitement_dechets" . + . + . + . + "Liste des bibliothèques communales 2017 (mise à jour annuelle)" . + "2017 - Bibliothèques communales " . + "application/pdf" . + . + "power bi" . + "2017 - T3 - Electricité Verte / Groenestroom.pbix" . + . + . + "Sociaal Brussel is een gratis, tweetalig, online platform met gedetailleerde, geactualiseerde en gegeolokaliseerde informatie over de publieke en private diensten actief in de welzijns- en gezondheidssector in het Brussels Hoofdstedelijk Gewest.\n\nSociaal Brussel wordt beheerd door de vzw CMDC-CDCS die hiervoor gemandateerd werd door de Gemeenschappelijke Gemeenschapscommissie.\n\nIn het kader van de open data-politiek en een efficiënt gebruik van overheidsmiddelen worden de gegevens van Sociaal Brussel gratis ter beschikking gesteld onder de licentie CC BY.\n\nhttps://sociaal.brussels/page/over-sociaal-brussel-nl\n" . + "Sociaal Brussel | https://sociaal.brussels" . + . + "power bi" . + "2017 - Clients protégés et hivernaux.pbix" . + . + . + "power bi" . + "2017 - T3 - Clientèle sociale/Beschermde klanten.pbix" . + . + "Opleidingsfonds dienstencheques - Toegelaten opleidingen" . + . + . + . + . + "WFS" . + . + "2018_desactivations_afsluitingen.xlsx" . + "application/pdf" . + . + "This file contains\n- Occupation rate\n- Average price per room\n- Revenue per available room (RevPar)\n" . + "Perfomance Hôtelière" . + . + . + "WFS" . + . + . + . + "WFS" . + "application/pdf" . + . + "Jaarverslag 2006 Haven van Brussel" . + . + . + "lijst_gasleveranciers" . + . + . + . + "Le Service public régional de Bruxelles tient le Registre des études en respect de l'article 7 de l'ordonnance relative à la publicité de l'administration.\nDe Gewestelijke Overheidsdienst Brussel houdt het Studieregister bij in naleving van artikel 7 van de ordonnantie betreffende de openbaarheid van bestuur." . + "Studieregister / Registre des études" . + "application/octet-stream" . + . + "Preview" . + . + . + "Lijst van de wekelijkse markten 2019 (jaarlijkse bijwerking)" . + "2019 - Wekelijkse markten" . + . + . + "liste_bureaux_etude_installations_stockage" . + . + . + "Activiteitenverslag 2015" . + . + . + "Liste des plaines de jeux au 30 juin 2018 (mise à jour semestrielle)\n" . + "2018 - Plaines de jeux " . + . + "application/zip" . + . + "Bibliothèque" . + . + "Preview" . + . + . + "Rapport d'activités 2009-2012" . + . + . + "WFS" . + . + . + "CSV" . + "application/zip" . + . + . + "liste_installations_fin_statut_dechets_criteres_bruxellois" . + . + . + "JSON" . + . + . + "JSON" . + . + "application/zip" . + . + "power bi" . + "2018 - T1 - Parts de marché/Maarktaandelen.pbix" . + . + "JSON" . + . + . + "Bike counting poles - GEOJSON" . + . + . + "Infrastructures communales" . + . + "application/zip" . + . + "2019_Limiteurs de puissance_Vermogenbegrenzers.xlsx" . + . + . + "liste_etablissements_entreposage_dechets_animaux" . + . + . + . + "lijst_ingedeelde_inrichtingen" . + . + "application/zip" . + . + "Liste des associations culturelles (subsidiées par la commune) au 30 septembre 2017 (mise à jour annuelle)" . + "2017 - Associations culturelles subsidiées par la commune" . + . + "Informations standardisées et géolocalisées relatives aux organisations publiques et privées qui offrent de l'aide sociale générale en Région de Bruxelles-Capitale" . + . + . + "Aide sociale générale" . + . + "CSV" . + "application/zip" . + . + "power bi" . + "2018 - T3 - Clientèle sociale/Beschermde klanten.pbix" . + . + . + "Cette archive zip contient 19 rasters (.tif) d'altitudes des toits des Unités Stratigraphiques de la Région Bruxelles-Capitale (US/RBC), allant du socle paléozoïque (inclus) aux formations quaternaires. Les US/RBC quaternaires ne sont pas discrétisées dans ce jeu de données et seul le raster d'altitude du toit de l'unité hydrogéologique quaternaire (UH/RBC 01) est présent (l'élévation de ce toit est confondu avec la topographie). Tous ces rasters ont une résolution de 10x10 m et sont dans le système de coordonnées de référence EPSG 31370 : Lambert Belge 1972. Les valeurs d'élévations sont données en mètres Deuxième Nivellement Général (m-DNG).\n\nLes données contenues dans cette archive étant issues d’un modèle, elles peuvent contenir des erreurs, des imprécisions et des lacunes. Elles doivent être utilisées avec prudence et esprit critique. Elles ne peuvent en aucun cas remplacer une étude de terrain réalisée par un expert.\nDe manière générale, Bruxelles Environnement et le Service Géologique de Belgique ne peuvent, en aucun cas, être tenus pour responsables de dommage, direct ou indirect, résultant de l'utilisation de ces données ou de l'impossibilité de les utiliser pour quelque raison que ce soit.\n\n-------------------------------------------------------------------------------------\n\nDit ziparchief bevat 19 rasters (.tif) van de tophoogtes van de Stratigrafische Eenheden van het Brussels Hoofdstedelijk Gewest (SE/BHG), van de paleozoïsche sokkel (inbegrepen) tot de quartaire formaties. De quartaire SE’s/BHG worden niet gediscretiseerd in deze gegevensverzameling en alleen het aster van de tophoogtes van de quartaire hydrogeologische eenheid (HE/BHG 01) is aanwezig (de tophoogte wordt vermengd met de topografie). Al deze rasters hebben een resolutie van 10 x 10 m en bevinden zich in het referentiecoördinatensysteem EPSG 31370: Belgisch Lambert 1972. De hoogtewaarden worden aangegeven in meter Tweede Algemene Waterpassing (m-TAW).\n\nAangezien de gegevens in dit archief uit een model zijn ontstaan, kunnen ze fouten, onduidelijkheden en lacunes bevatten. Ze moeten omzichtig en met een kritische geest worden gebruikt. Ze mogen in geen geval de plaats innemen van een terreinstudie die wordt uitgevoerd door een expert. Over het algemeen kunnen Leefmilieu Brussel en de Belgische Geologische Dienst in geen geval aansprakelijk worden gesteld voor rechtstreekse of onrechtstreekse schade die het gevolg is van het gebruik van deze gegevens, of van de onmogelijkheid om ze om welke reden ook te gebruiken.\n" . + "Altitudes des toits des US/RBC / Tophoogtes van de SE/BHG" . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + . + . + "WFS" . + . + "Lijst van culturele verenigingen (gesubsidieerd door de gemeente) op 30 september 2017 (jaarlijkse bijwerking)" . + "2017 - Culturele verenigingen gesubsidieerd door de gemeente " . + "application/zip" . + . + . + "GeoJSON" . + . + "lijst_ophalers_dierlijk_afval" . + . + . + . + . + "application/zip" . + "Traffic counts - WFS" . + . + "GeoJSON" . + . + "2019_clientele-sociale-regionale_sociaal-regionaal-clienteel.xlsx" . + . + . + . + "liste_collecteurs_dechets_animaux" . + "text/plain" . + . + . + "Preview" . + . + . + "Sépultures militaires" . + . + . + "Musées et Attractions" . + . + . + "power bi" . + "2017 - T4 - Electricité Verte / Groenestroom.pbix" . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + . + . + . + "power bi" . + "2016 - Limiteurs de puissance actifs.pbix" . + . + . + "power bi" . + "2018 - T4 - Clientèle sociale/Beschermde klanten.pbix" . + . + "liste_fournisseurs_electricite" . + . + "application/xml" . + . + "CSV" . + . + . + "Cette archive zip contient 18 rasters (.tif) d'épaisseurs des Unités Stratigraphiques de la Région Bruxelles-Capitale (US/RBC), allant du socle paléozoïque (exclu) aux formations quaternaires. Les US/RBC quaternaires ne sont pas discrétisées dans ce jeu de données et seul le raster d'épaisseur totale de l'unité hydrogéologique quaternaire (UH/RBC 01) est présent. Tous ces rasters ont une résolution de 10x10 m et sont dans le système de coordonnées de référence EPSG 31370 : Lambert Belge 1972. Les valeurs de profondeurs sont données en mètres (m) depuis la surface topographique.\n\nLes données contenues dans cette archive étant issues d’un modèle, elles peuvent contenir des erreurs, des imprécisions et des lacunes. Elles doivent être utilisées avec prudence et esprit critique. Elles ne peuvent en aucun cas remplacer une étude de terrain réalisée par un expert.\nDe manière générale, Bruxelles Environnement et le Service Géologique de Belgique ne peuvent, en aucun cas, être tenus pour responsables de dommage, direct ou indirect, résultant de l'utilisation de ces données ou de l'impossibilité de les utiliser pour quelque raison que ce soit.\n\n---------------------------------------------------------------------------------------------\n\nDit ziparchief bevat 19 rasters (.tif) van de dikten van de Stratigrafische Eenheden van het Brussels Hoofdstedelijk Gewest (SE/BHG), van de paleozoïsche sokkel (buitengesloten) tot de quartaire formaties. De quartaire SE’s/BHG worden niet gediscretiseerd in deze gegevensverzameling en alleen de raster van de dikte van de quartaire hydrogeologische eenheid (HE/BHG 01) is aanwezig. Al deze rasters hebben een resolutie van 10 x 10 m en bevinden zich in het referentiecoördinatensysteem EPSG 31370: Belgisch Lambert 1972. De dikten worden aangegeven in meter (m).\n\nAangezien de gegevens in dit archief uit een model zijn ontstaan, kunnen ze fouten, onduidelijkheden en lacunes bevatten. Ze moeten omzichtig en met een kritische geest worden gebruikt. Ze mogen in geen geval de plaats innemen van een terreinstudie die wordt uitgevoerd door een expert. Over het algemeen kunnen Leefmilieu Brussel en de Belgische Geologische Dienst in geen geval aansprakelijk worden gesteld voor rechtstreekse of onrechtstreekse schade die het gevolg is van het gebruik van deze gegevens, of van de onmogelijkheid om ze om welke reden ook te gebruiken.\n" . + "Epaisseurs des US/RBC / Dikten van de SE/BHG" . + . + "MapViewer" . + . + . + "Marchés publics 2017 (mise à jour annuelle)" . + "2017 - Marchés publics" . + . + . + "power bi" . + "2018 - Clients protégés et hivernaux.pbix" . + . + . + "http://www.cirb.irisnet.be/fr/nos-solutions/urbis-solutions/urbis-data" . + . + . + "application/xml" . + "Json" . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + . + . + "power bi" . + "2018 - T4 - Parts de marché/Maarktaandelen.pbix" . + . + . + "liste_fournisseurs_gaz" . + . + . + . + . + "application/xml" . + "liste_installations_fin_statut_dechets_criteres_bruxellois" . + . + "Preview" . + . + . + "GeoJSON" . + . + "application/xml" . + . + "Gestandardiseerde informatie over de publieke en private organisaties en diensten die algemene sociale dienstverlening bieden in het Brussels Gewest. Zie ook: https://social.brussels/sector/53" . + "Algemene sociale dienstverlening" . + . + . + "Lijst van gemeentelijke bibliotheken 2017 (jaarlijkse bijwerking)" . + "2017 - Gemeentelijke bibliotheken " . + . + "Activiteitenverslag 2009-2012" . + . + . + . + "WFS" . + "application/xml" . + . + "liste_conseillers_PEB_personnes_morales" . + . + . + . + . + "CSV" . + . + "Jaarverslag 2009 Haven van Brussel" . + "application/xml" . + . + "Composition du Collège des Bourgmestre et Echevins et du Conseil communal sur www.auderghem.be\nVersion tenue à jour." . + "Le Collège des Bourgmestre et Echevins" . + . + . + "power bi" . + "2019 - Beschermde en winterklanten.pbix " . + . + "Lijst van gemeentelijke kinderdagverblijven 2017 (jaarlijkse bijwerking)" . + "2017 - Gemeentelijke kinderdagverblijven " . + . + . + "Kalender van de zittingen van de Gemeenteraad 2019" . + "2019 - Kalender van de zittingen van de Gemeenteraad " . + . + . + "GeoJSON" . + . + "application/xml" . + . + "2018_Parts de marché_Maarktaandeel.xlsx" . + . + "power bi" . + "2019_Volume d'énergie par fournisseur.pbix " . + . + . + "lijst_opleidingscentra_klimaatregelingssystemen_motorvoertuigen" . + . + . + . + "Liste des opérateurs certifiés bio situés en Région de Bruxelles-Capitale" . + "application/xml" . + . + "CSV" . + . + . + "Betaald educatief verlof - erkenningscommissie" . + . + . + "Jaarverslag 2012 Haven van Brussel" . + . + "application/xml" . + . + "CSV" . + . + "GeoJSON" . + . + . + . + "power bi" . + "2019 - Clients protégés et hivernaux.pbix" . + . + . + "application/xml" . + "lijst_verwerkingscentra_dierlijk_afval" . + . + "PIOW IO" . + . + . + "WFS" . + . + . + "Overheidsopdrachten 2017 (jaarlijkse bijwerking)" . + "2017 - Overheidsopdrachten" . + "application/xml" . + . + . + "power bi" . + "2016 - Actieve vermogenbegrenzers.pbix" . + . + "power bi" . + "2018_Volume d'énergie par fournisseur.pbix" . + . + . + "power bi" . + "2019 - Limiteurs de puissance actifs.pbix " . + . + "Rapport d'activités 2015" . + . + . + "2018_points_de_fourniture_leveringspunten.xlsx" . + . + "application/xml" . + . + "WFS" . + . + . + "2019_Parts de marché_Maarktaandeel.xlsx" . + . + "power bi" . + "2019_Aantal leveringspunten per leverancier.pbix " . + . + . + "Sportinfrastructuur 2017 (jaarlijkse bijwerking)" . + "2017 - Sportinfrastructuur" . + . + . + . + "application/xml" . + "Liste des espaces publics numériques 2017 (mise à jour annuelle)" . + "2017 - Espaces publics numériques " . + . + "power bi" . + "2017 - T4 - Clientèle sociale/Beschermde klanten.pbix" . + . + "power bi" . + "2017 - Beschermde en winterklanten.pbix" . + . + . + "Lijst van speelpleinen op 30 juni 2018 (halfjaarlijkse bijwerking)" . + "2018 - Speelpleinen" . + . + "power bi" . + "2018 - T2 - Parts de marché/Maarktaandelen.pbix" . + . + . + "power bi" . + "2017 - Limiteurs de puissance actifs.pbix" . + . + "Cimetières communaux" . + . + . + . + "liste_collecteurs_negociants_courtiers_dechets_dangereux" . + . + "application/xml" . + . + "Cimetières communaux" . + . + "Bike counting poles - WFS" . + . + . + "application/xml" . + . + "description" . + . + "power bi" . + "2017 - T4 - Taux de switches / Switch Percentage.pbix" . + . + . + "Rapport Annuel 2014 Port de Bruxelles" . + . + "Rapport Annuel 2012 Port de Bruxelles" . + . + . + . + "Rapport Annuel 2016 Port de Bruxelles" . + "application/xml" . + . + "Composition du Conseil communal au 28/04/2016" . + "28/04/2016" . + . + . + . + . + "Bibliothèque" . + . + "power bi" . + "2018 - T1 - Electricité Verte / Groenestroom.pbix" . + . + . + "power bi" . + "2016 - Beschermde en winterklanten.pbix" . + . + . + "application/xml" . + "liste_exploitants_centre_destruction_recyclage_vehicules_hors_usage_type_C" . + . + "Preview" . + . + . + "lijst_studiebureaus_effectenstudies" . + . + . + . + "Lijst van gemeentelijke bibliotheken 2018 (jaarlijkse bijwerking)" . + "2018 - Gemeentelijke bibliotheken" . + . + "application/xml" . + . + "power bi" . + "2018 - T3 - Electricité Verte / Groenestroom.pbix" . + . + "liste_exploitants_centre_demontage_vehicules_hors_usage_type_B" . + . + . + "Jaarverslag 2017 Haven van Brussel" . + . + . + "Het volledige verslag 2017 is online beschikbaar, op de website http://jaarverslag.port.brussels/" . + . + "lijst_exploitant_centrum_vernietiging_recycling_afgedankte_voertuigen_type_C" . + "application/xml" . + . + . + "Jaarverslag 2014 Haven van Brussel" . + . + "Jaarverslag 2005 Haven van Brussel" . + . + . + . + "lijst_opslagcentra_dierlijk_afval" . + . + "application/xml" . + . + "MapViewer" . + . + "power bi" . + "2017 - Désactivations sur ordre du juge de paix.pbix" . + . + . + "power bi" . + "2018 - T3 - Parts de marché/Maarktaandelen.pbix" . + . + "2017_points_de_fourniture_leveringspunten.xlsx" . + . + . + . + "Masterplan Horizon 2030" . + "application/xml" . + . + "Liste de associations sportives (subsidiées par la commune) au 30 avril 2018 (mise à jour annuelle)" . + "2018 - Associations sportives subsidiées par la commune" . + . + . + "CSV" . + . + . + "Rapport Annuel 2010 Port de Bruxelles" . + . + "application/xml" . + . + "JSON" . + . + "Bruxelles Social est une plateforme publiant des descriptions détaillées, actualisées et géolocalisées des organisations publiques et privées actifs dans le secteur social-santé en Région de Bruxelles-Capitale. Via cet URL vous trouverez des informations et métadonnées détaillées :\n- Brochure d'information générale\n- L'arborescence thématique construite sur mesure de l'offre sociale-santé bruxelloise\n- Une description détaillée des champs d'information standardisés\n\nhttps://social.brussels/page/a-propos-de-la-carte-sociale" . + "Bruxelles Social | https://social.brussels" . + . + . + "2019_desactivations_afsluitingen.xlsx" . + . + . + "Marchés" . + . + "application/xml" . + . + "Artikel 60-7 - sociale economie" . + . + "Het College van Burgemeester en Schepenen" . + . + . + "Rapport d'activités 2013" . + . + . + "Erkende particuliere bureaus voor arbeidsbemiddeling" . + "application/xml" . + . + . + "Jaarverslag 2011 Haven van Brussel" . + . + "liste_centres_traitement_dechets_animaux" . + . + . + . + "lijst_examencentra_koeltechniek" . + . + "application/xml" . + . + "Modèle Numérique de Terrain (raster) du modèle stratigraphique BRUSTRATI3D V1.1. Ce raster couvrant l'ensemble de la Région Bruxelles-Capitale a une résolution de 10x10 m et a comme système de coordonnées de référence : EPSG 31370 Lambert Belge 1972. Les valeurs d'élévations sont données en mètres Deuxième Nivellement Général (m-DNG). La construction de ce raster a nécessité l'utilisation de données issues de Brussels Urbis : https://cirb.brussels/fr/nos-solutions/urbis-solutions/urbis-data\n\nLes données de ce raster étant issues d’un modèle, elles peuvent contenir des erreurs, des imprécisions et des lacunes. Elles doivent être utilisées avec prudence et esprit critique. Elles ne peuvent en aucun cas remplacer une étude de terrain réalisée par un expert. De manière générale, Bruxelles Environnement et le Service Géologique de Belgique ne peuvent, en aucun cas, être tenus pour responsables de dommage, direct ou indirect, résultant de l'utilisation de ces données ou de l'impossibilité de les utiliser pour quelque raison que ce soit.\n\n--------------------------------------------------------------------------------------------\n\nHet Digitaal Terreinmodel van het stratigrafische model BRUSTRATI3D V1.1. Deze raster heeft een resolutie van 10 x 10 m en bevindt zich in het referentiecoördinatensysteem EPSG 31370: Belgisch Lambert 1972. De hoogtewaarden worden aangegeven in meter Tweede Algemene Waterpassing (m-TAW). Voor de opstelling van deze raster zijn de Brusselse Urbis gegevens gebruikt : https://cibg.brussels/nl/onze-oplossingen/urbis-solutions/urbis-data\n\nAangezien de gegevens in dit archief uit een model zijn ontstaan, kunnen ze fouten, onduidelijkheden en lacunes bevatten. Ze moeten omzichtig en met een kritische geest worden gebruikt. Ze mogen in geen geval de plaats innemen van een terreinstudie die wordt uitgevoerd door een expert. Over het algemeen kunnen Leefmilieu Brussel en de Belgische Geologische Dienst in geen geval aansprakelijk worden gesteld voor rechtstreekse of onrechtstreekse schade die het gevolg is van het gebruik van deze gegevens, of van de onmogelijkheid om ze om welke reden ook te gebruiken." . + "Modèle Numérique de Terrain (MNT) / Digitaal Terreinmodel (DTM) " . + . + . + "Depuis fin 2012, le CIRB offre une représentation tridimensionnelle de l'ensemble des bâtiments présents sur le territoire de la Région bruxelloise, et d'environ 200 ouvrages d'art.\nLes bâtiments et les ouvrages d'arts en 3D sont modélisés individuellement avec un niveau de détail équivalent au LoD 2 (Level of Detail 2), tel qu’il est défini dans la norme d’échange CityGML (City Geography Markup Language – Version 1).\nConcrètement, cela signifie qu'un bâtiment en 3D est représenté sous la forme d'un volume dont la structure du toit est simplifiée." . + . + . + "power bi" . + "2018 - T1 - Taux de switches / Switch Percentage.pbix" . + . + . + "2017_desactivations_afsluitingen.xlsx" . + "application/xml" . + . + "Crèches" . + . + . + "2018_clientele-sociale-regionale_sociaal-regionaal-clienteel.xlsx" . + . + . + "power bi" . + "2019_Volume energie per leverancier.pbix " . + . + . + "power bi" . + "2018_Volume energie per leverancier.pbix" . + . + "application/xml" . + . + "WFS" . + . + "MapViewer" . + . + "2017_clientele-sociale-regionale_sociaal-regionaal-clienteel.xlsx" . + . + . + . + "power bi" . + "2018 - T2 - Taux de switches / Switch Percentage.pbix" . + . + "Rapport Annuel 2001 Port de Bruxelles" . + "application/xml" . + . + . + "Liste des rues d'Evere - Straat lijst van Evere" . + "Rues d'Evere - Straten van Evere" . + . + "Rapport d'activités 2017" . + . + . + . + "CSV" . + "application/xml" . + . + "Jaarverslag 2007 Haven van Brussel" . + . + . + "power bi" . + "2018_Nombre de points de fourniture par founisseurs.pbix" . + . + "MapViewer" . + . + . + . + "GeoJSON" . + "application/xml" . + . + "lijst_inzamelaars_handelaars_makelaars_gevaarlijk_afval" . + . + . + . + . + "2018_Limiteurs de puissance_Vermogenbegrenzers.xlsx" . + . + "Lijst van SINE werkgevers" . + "application/xml" . + . + "WFS" . + . + . + "description" . + . + . + . + "application/xml" . + "Crèches" . + . + "Jaarverslag 2016" . + . + "power bi" . + "2016 - Désactivations sur ordre du juge de paix.pbix" . + . + . + "lijst_exploitant_demonteercentrum_afgedankte_voertuigen_type_B" . + . + . + . + "Agences d'emploi privées reconnues" . + "application/xml" . + . + "power bi" . + "2016- Deactiveringen op bevel van de vrederechter.pbix" . + . + . + "GeoJSON" . + . + "L’agence de simplification administrative bruxelloise et les administrations régionales au sens large ont développé tout au long de l’année 2017 une série d’actions pour la mise en œuvre des objectifs du plan de simplification administrative 2015-2020. Ce rapport détaille les grands chantiers communs de simplification administrative, comme l’Open Data, le guichet virtuel, l’inclusion numérique, la facturation électronique, les marchés publics en ligne, le catalogue des démarches, etc. Il décrit également des actions particulières de simplification menées au cours de l’année par certains partenaires de l’agence, ainsi que les recommandations d’Easybrussels sur base des expériences et constats réalisés en 2017." . + "Rapport annuel 2017 Easybrussels" . + . + . + "application/xml" . + . + "Rapport Annuel 2005 Port de Bruxelles" . + . + "Jaarverslag 2003 Haven van Brussel" . + . + . + "Activiteitenverslag 2016" . + . + . + "power bi" . + "2019_Nombre de points de fourniture par founisseurs.pbix" . + . + . + "Shapefile" . + "application/xml" . + . + "Rapport Annuel 2007 Port de Bruxelles" . + . + . + "power bi" . + "2018 - T2 - Electricité Verte / Groenestroom.pbix" . + . + "Jaarverslag 2010 Haven van Brussel" . + . + . + "application/xml" . + . + "2017_Limiteurs de puissance_Vermogenbegrenzers.xlsx" . + . + "Rapport Annuel 2009 Port de Bruxelles" . + . + . + "Rapport d'activités 2014" . + . + . + "Rapport Annuel 2011 Port de Bruxelles" . + . + "application/xml" . + . + "Jaarverslag 2008 Haven van Brussel" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/xml" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/vnd.ms-excel" . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "application/vnd.ms-excel" . + . + . + . + . + . + . + . + "Sessions, utilisateurs, pages vues en 2017." . + "2017 - fréquentation site internet" . + . + . + . + "Ce thème délivre de nombreuses informations sur l’occupation du sol, les caractéristiques du bâti existant et les ventes immobilières notamment." . + "Aménagement du territoire et immobilier" . + "application/vnd.ms-excel" . + . + "bicyclepump - csv - 2016-01-01" . + . + . + . + . + "Liste des rues - lijst van de straten" . + . + "De statistieken die door het IBSA verzameld werden hebben betrekking op het wegverkeer, zachte mobiliteit, collectief en gedeeld vervoer, vervoer van goederen, verkeersveiligheid en verplaatsingsgewoonten." . + "Mobiliteit en vervoer" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "De levensstandaard van de huishoudens kan worden benaderd vanuit het standpunt van hun inkomen of hun uitgaven. Op die manier kan de koopkracht van de bevolking worden gemeten en dus ook de mate waarin zij al dan niet makkelijk toegang heeft tot goederen en diensten zoals huisvesting, uitrustingsgoederen, voeding enz." . + "Inkomens en uitgaven van de huishoudens" . + . + . + "Relevé détaillé des présences en réunion - Collège" . + . + . + "CSV FR" . + . + . + "tunnelsections - geojson - 2016-01-01" . + . + . + "tunnelsections - csv - 2016-01-01" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "parkingpublic - csv - 2016-01-01" . + . + . + . + "WFS" . + . + "traffic_events - json" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "In Brussels as in the rest of Belgium, many people do not have sufficient means of subsistence. In some circumstances, these people may qualify for welfare benefits. These are aimed at ensuring that all the population has a minimum income." . + "Insecurity and Welfare benefits" . + . + . + "The BISA compiles statistics on the safety of the public in their daily lives: road safety, fire and medical emergency service callouts, crime figures and police staffing, etc." . + "Safety " . + . + . + "WFS" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + . + "Preview" . + . + . + . + . + "bikeservice - geojson - 2016-01-01" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Electric charging stations - Mobigis" . + . + "Signalisation verticale - fichier de style" . + . + . + "Transparence des rémunérations et avantages des mandataires publics bruxellois - rapport annuel - Exercice 2017" . + . + . + . + "parkingtransit - wfs" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "WFS" . + . + . + "The statistics presented below relate mainly to the number and accommodation capacity of early childhood facilities in the Brussels Region." . + "Early childhood " . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "WFS" . + . + "Income and expenditure are indications of the living standard of households. It makes it possible to measure the purchasing power of the population and, consequently, whether or not it has easy access to goods and services such as housing, capital goods, food, etc." . + "Household income and expenditure " . + . + "Signalisation verticale - Mobigis" . + . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "WFS" . + . + "traffic lights - CSV - 2017-01-01" . + . + "Dates et lieux des constats effectués en 2017." . + "2017 - sac - gas" . + . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "WFS" . + . + "Zone de police 5343 (Etterbeek, Woluwe-Saint-Pierre et Woluwe-Saint-Lambert)" . + . + . + "Accès aux données via API.Brussels : https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin" . + "Events (Json)" . + . + . + . + "bicyclepump - wfs" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Export du registre de population. Etat civil des personnes enregistrées à la commune d'Auderghem" . + "10/04/218 - Population - Etat civil" . + . + . + "bike_paves_ss - wfs" . + . + . + "Population totale, structure de la population par groupes d'âges, sexes ou nationalités, structure des ménages, mouvements de population, projections de population..." . + "Population" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "parkingtransit - Mobigis" . + . + . + "bikeservice - Mobigis" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Total population, structure of population by age, sex or nationality, structure of households, movements of population, demographic projections etc." . + "Population" . + . + . + "icr - Mobigis" . + . + . + "The \"Shapefiles\" available on the Open Data contain the basic information about the spatial structure of\nthe STIB/MIVB network: route of the lines and position of the stops of the \"commercial\" network, that is\nthe basic itineraries \"to\" and \"from\".\nThe Shapefiles are made available in a zip file of an average size of 1,5 MB. These are updated 2 or 3 times\na year.\n\nLes fichiers « Shapefiles » disponibles sur l’Open Data contiennent les informations de base relatives à la\nstructure spatiale du réseau STIB : tracé des lignes et position des arrêts du réseau « commercial », soit\nles itinéraires « aller » et « retour » de base.\nLes fichiers ShapeFiles sont mis à disposition dans un fichier de format zip d’une taille moyenne de 1,5\nMB. Ils sont mis à jour 2 ou 3 fois par an.\n\nDe \"shapefiles\" beschikbaar op de Open Data bevatten basisinformatie betreffende de ruimtelijke\nstructuur van het MIVB-net: tracé van de lijnen en locatie van de haltes van het \"commerciële\" net,\nnamelijk de basisreiswegen \"heen\" en \"terug\".\nDe shapefiles worden ter beschikking gesteld in een zipbestand van gemiddeld 1,5 MB. Ze worden 2 à 3\nkeer per jaar geüpdatet.\n\n" . + "ShapeFiles" . + . + . + "regional_roads - geojson - 2016-01-01" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "JSON" . + . + . + "Stations Villo (JSON)" . + . + . + . + "collecto_stops - Mobigis" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "rer_velo - wfs" . + . + . + "regional_roads - Mobigis" . + . + . + "Dit thema omvat Brusselse statistische gegevens die zowel betrekking hebben op de uitgaven inzake O&O als op het personeel dat in dit domein werkt.\nDe statistieken inzake ICT-gebruik en -uitrusting van de Brusselse huishoudens worden eveneens weergegeven in dit thema." . + "Onderzoek en technologie" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "Accès aux données via API.Brussels : https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin" . + "Organizers (Json)" . + . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_protectedsite_drinking_water_wells" . + . + "Institutions et services compétents pour l'égalité des chances (y compris l'égalité de genre) en région bruxelloise, aux niveaux régional et local" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "Services pour l'égalité des chances en Région bruxelloise" . + . + "collecto_stops - csv - 2016-01-01" . + . + "How many workers does the Region have ? In which sectors are they employed and at which salary ? Where is the employed population in Brussels? What about the unemployment figures ?" . + . + . + "Labour market " . + . + "Zone de police 5344 (Schaerbeek, Saint-Josse-ten-Noode et Evere)" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "parkingpublic - geojson - 2016-01-01" . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_natura_2000_habitat" . + . + . + . + "Cahier 4 : Le transport de marchandises et la logistique à Bruxelles" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "hierarchy - csv - 2016-01-01" . + . + . + "Het BISA verzamelt de resultaten van de verschillende verkiezingen die gehouden worden in het Brussels Hoofdstedelijk Gewest.\nU vindt de resultaten van de regionale, gemeentelijke, Europese en federale verkiezingen in het Brussels Hoofdstedelijk Gewest." . + "Verkiezingen" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "icr - wfs" . + . + . + "L'IBSA rassemble les données des résultats des différentes élections tenues en Région de Bruxelles-Capitale.\nRetrouvez les résultats des élections régionales, communales, européennes et législatives tenues en Région de Bruxelles-Capitale.\n" . + "Élections" . + . + . + . + "Katern 4: Goederentransport en logistiek in Brussel" . + . + "bicyclestore - csv - 2017-06-12" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Liste des pages créées sur le site internet communal d'Auderghem." . + "Liste des pages - Lijst van paginas" . + . + . + "icr - geojson - 2016-01-01" . + . + . + . + "WFS" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "Although health is a Community competence, the BISA presents a selection of indicators (range of healthcare services on offer, life expectancy, etc.) by combining the data from the Flemish Community, the French Community and the Common Community Commission." . + "Health " . + . + "The operation returns real time vehicle position for given\nline id’s" . + "Vehicles position (VehiclePositionByLine)" . + . + . + . + "bicyclepump - Mobigis" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Preview" . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_monitoring_groundwater_quality" . + . + . + . + "parkingoffroad_blocks - geojson - 2016-01-01" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "WFS" . + . + . + . + . + "Journal du conseil" . + . + "Electric charging stations - geojson - 2016-03-01" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Liste des défibrillateurs CSV" . + . + . + "parkingpublic - Mobigis" . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "Relevé détaillé des présences en réunion - Conseil" . + . + "Preview" . + . + . + "Liste des communes bruxelloises qui ont signé la Charte européenne pour l'égalité des femmes et des hommes dans la vie locale. La liste comprend également les noms des bourgmestres et échevins à l'Égalité des Chances." . + "Communes bruxelloises signataires de la Charte européenne pour l'égalité de genre" . + . + . + . + "The General Transit Feed Specification (GTFS) defines a common format for public transportation schedules\nand associated geographic information. GTFS \"feeds\" allow public transit agencies to publish their static\ntransit data and developers to write applications that consume that data in an interoperable way.\nYou can read more about the GTFS at https://developers.google.com/transit/gtfs/\nThe GTFS files are zipped within a file with a size of about 25 Mb. The GTFS files are updated every two\nweeks. Therefore getting that file once a day is quite enough." . + "GTFS" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "2015 - Compte budgétaire - Begrotingsrekening" . + . + "bikeservice - wfs" . + . + . + . + "Portrait chiffré de l’activité économique en Région de Bruxelles-Capitale: démographie d'entreprises, chiffres d'affaires de celles-ci, valeur ajoutée, investissements et exportations." . + "Économie" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Beeld van de economische activiteit in het Brussels Hoofdstedelijk Gewest in cijfers: bedrijvendemografie, omzetcijfers, toegevoegde waarde, investeringen en export." . + "Economie " . + . + . + . + . + "Relevé détaillé des présences en réunion - Commission" . + . + "Les données rassemblées portent sur le climat, la qualité de l’air, la consommation et la qualité de l’eau, les collectes de déchets, les espaces verts, la biodiversité, la consommation énergétique, etc." . + "Environnement et énergie" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "WFS" . + . + . + . + . + "2016 - Compte budgétaire - Begrotingsrekening" . + . + "parkingoffroad_blocks - Mobigis" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Gewestelijke en gemeentelijke diensten/instellingen voor gelijke kansen (gendergelijkheid incl.) in het Brussels Gewest" . + "Diensten voor gelijke kansen in het Brussels Gewest" . + . + . + "In Brussel, net als in de rest van België, wonen veel mensen die over onvoldoende bestaansmiddelen beschikken. Onder bepaalde voorwaarden hebben zij recht op maatschappelijke hulp. Die wordt georganiseerd om de volledige bevolking een minimuminkomen te garanderen." . + "Bestaansonzekerheid en sociale bijstand" . + . + . + . + "U vindt een overzicht van de toestand van de overheidsfinanciën van het Brussels Gewest: de ontvangsten en uitgaven van de gewestelijke overheidsdienst Brussel (ex-Ministerie van het Brussels Hoofdstedelijk Gewest), maar ook van de plaatselijke besturen en de pararegionale instellingen, alsook statistieken over de uitstaande schuld van het Gewest." . + "Overheidsfinanciën" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "JSON" . + . + . + "UrbIS-Online including a 3DTiles/Cesium viewer to view the 3D buildings of Brussels." . + "UrbIS-Online" . + . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_sewage_treatment" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "parkingtransit - csv - 2016-01-01" . + . + "Milieu en energie" . + . + . + "De verzamelde gegevens hebben betrekking op het klimaat, de luchtkwaliteit, de consumptie en de kwaliteit van water, ophalingen van afval, groene ruimtes, biodiversiteit, energie, enz." . + . + "Preview" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "parkingtransit - geojson - 2016-01-01" . + . + "Land-use planning and real estate " . + . + . + "This section provides a wealth of information, for instance on land occupancy, the characteristics of existing constructions and real estate sales." . + . + "À Bruxelles comme dans le reste de la Belgique, de nombreuses personnes disposent de moyens de subsistance insuffisants. Sous certaines conditions, ces personnes peuvent bénéficier d’une aide sociale. Celle-ci est organisée dans le but de garantir un revenu minimum à l'ensemble de la population. " . + "Précarité et aide sociale" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Cahier 2 : Les pratiques de déplacement à Bruxelles" . + . + . + "hierarchy - Mobigis" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Katern 3: De verplaatsingsgewoonten in Brussel - diepteanalyses" . + . + . + "2014 - Compte budgétaire - Begrotingsrekening" . + . + "L'IBSA rassemble des données statistiques concernant la sécurité des citoyens dans leur vie quotidienne: sécurité routière, interventions du Service d'Incendie et d'Aide Médicale Urgente (SIAMU), délits constatés et effectifs des zones de police..." . + "Sécurité" . + . + . + . + "Export du registre de population. Liste des rues où sont inscrits les habitants de la commune d'Auderghem." . + "10/04/2018 - Population - rues" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "WFS" . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_protectedsite_drinking_water_gallery" . + . + . + "artistic_heritage - wfs" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "URL To the 3DTiles dataset. A 3DTiles viewer is required to view the data.\nAn application to view Brussels buildings in 3D." . + "3DTiles Tileset - 3D tiles for Brussels" . + . + "Relevé de toute réduction opérée sur les rémunérations et avantages de toute nature" . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "bicyclestore - geojson - 2017-06-12" . + . + . + "parkingpublic - wfs" . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_protectedsite_drinking_water" . + . + . + . + "Figures on the economic activity in the Brussels-Capital Region: demography of companies, company turnover, value added, investments and exports." . + "Economy " . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Electric charging stations - CSV - 2016-03-01" . + . + . + "Preview" . + . + . + . + "bike_paves_ss - Mobigis" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Lijst van de lokale mandatarissen bevoegd voor Gelijke Kansen in de 19 gemeenten van het Brussels Gewest. 2 Brusselse gemeenten hebben geen schepen voor Gelijke Kansen aangewezen (legislatuur 2012-2018)" . + "Lokale mandatarissen voor Gelijke kansen in het Brussels Gewest" . + . + . + "collecto_stops - wfs" . + . + . + . + "Export du registre de population. Nationalités et lieux de naissance des personnes enregistrées à la commune d'Auderghem." . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "10/04/2018 - Population - Nationalités et lieux de naissance" . + . + "Electric charging stations - WFS" . + . + . + "XLS" . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Preview" . + . + "This section presents the statistics for Brussels on both R&D expenditure and staff working in this field.\nThe statistics on the use and equipment with regard to information technology and communication found in Brussels households are also presented in this theme." . + "Research and technology " . + . + . + . + "Preview" . + . + "Cette série présente des données statistiques bruxelloises qui se rapportent aussi bien aux dépenses de R&D qu’au personnel qui travaille dans ce domaine.\nLes statistiques relatives à l’utilisation et l’équipement en technologies de l'information et de la communication des ménages bruxellois sont également présentées sur cette thématique.\n" . + "Recherche et technologie" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "Occupancy rate in real time.\nFormat Datex II (http://www.datex2.eu)." . + "Parking Occupancy - Dynamic Data" . + . + . + . + "traffic lights - WFS" . + . + "Rapport_transparence_modele_generique_2017.csv" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + . + "Parkings actuellement gérés dans le cadre du projet CycloParking (nom, coordonnées, type de parking, capacité). Donnée non dynamique." . + "Parkings vélo CycloParking" . + . + "Travelers information" . + . + . + . + "Le niveau de vie des ménages peut être approché par leur revenu ou leurs dépenses. Ceux-ci permettent de mesurer le pouvoir d’achat de la population et par conséquent son accès plus ou moins facile aux biens et aux services tels que le logement, les biens d’équipement, l’alimentation, etc." . + "Revenus et dépenses des ménages" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Export du registre de population. Dates de naissances des personnes enregistrées à la commune d'Auderghem" . + "10/04/2018 - Population - date de naissance" . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_groundwaterbody" . + . + . + . + "Onderwijs is een Gemeenschapsbevoegdheid. Het BISA maakt statistieken die de gegevens van de Vlaamse Gemeenschap en de Franse Gemeenschap combineren : schoolbevolking, aantal scholen. Het BISA stelt ook gegevens op over schoolbevolking buiten de gemeenschappen." . + "Onderwijs" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "* Lijst van organisaties actief op het terrein van gendergelijkheid en gevestigd in het Brussels Gewest. De velden zijn in het Engels, de waarden zijn in het Nederlands en in het Frans. \n* Liste d'organisations engagées en faveur de l'égalité de genre et établies en Région de Bruxelles-Capitale. Les champs sont en anglais, les valeurs sont en français et en néerlandais. \n" . + "Organisations for gender equality in the Brussels Region" . + . + . + "Zone de police 5341 (Anderlecht, Saint-Gilles et Forest)" . + . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "tunnelsections - wfs" . + . + "haljson" . + "Incidents" . + . + "Relevé des Rémunérations – montants bruts" . + . + . + "collecto_stops - geojson - 2016-01-01" . + . + . + . + "Accès aux données via API.Brussels : https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin" . + "Places (xml)" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "taxi_stops - Mobigis" . + . + . + "parkingoffroad_blocks - wfs" . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "bike_paves_ss - csv - 2016-01-01" . + . + "Les statistiques relatives au tourisme concernent l’hébergement des personnes qui ont séjourné dans la Région de Bruxelles-Capitale.\nDans le domaine de la culture, vous trouverez des statistiques relatives aux salles de cinéma et aux projections." . + "Tourisme et culture" . + . + "WFS JSON" . + . + . + . + "regional_roads - wfs" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "approuvé par le conseil communal d'auderghem le 29.06.2018" . + "compte budgétaire 2017" . + . + . + "Katern 1: Het vervoersaanbod in Brussel" . + . + . + "Jonge kinderen" . + . + . + "Evere"@fr . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "Deze statistieken hebben met name betrekking op het aantal kinderopvangvoorzieningen en de capaciteit" . + . + "Carte des stations" . + . + . + . + . + "Evere"@en . + "Evere"@nl . + "Brussels Coworking List " . + . + "Milonga"@en . + "Milonga"@fr . + "Milonga"@nl . + "http://www.utalkbrussels.com/coworking-1" . + . + "Haven van Brussel"@nl . + "Port de Bruxelles"@fr . + "Port of Brussels"@en . + . + "The operation returns the waiting times for the next two vehicles of each line passing through the requested\nstop id’s." . + . + "Brussel Gewestelijke Coördinatoe"@nl . + "Brussels Region Coordination"@en . + "Bruxelles Coordination régionale"@fr . + "Waiting Time (PassingTimeByPoint)" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Auderghem"@fr . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_bodies_surface&SRSNAME=EPSG:31370" . + . + . + . + "Auderghem"@en . + "Oudergem"@nl . + "Zone de police 5339 (Ville de Bruxelles - Ixelles)" . + . + "BRIC"@en . + . + . + "Totale bevolking, structuur van de bevolking per leeftijdscategorie, geslacht of nationaliteit, structuur van de huishoudens, loop van de bevolking, bevolkingsprojecties..." . + "Bevolking " . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "traffic lights - GEOJSON - 2017-01-01" . + . + "2013 - Compte budgétaire - Begrotingsrekening" . + . + . + . + "Vous trouverez un aperçu de la situation des finances publiques de la Région bruxelloise : les recettes et dépenses du Service public régional de Bruxelles (ex- Ministère de la Région de Bruxelles-Capitale), mais également des pouvoirs locaux et des organismes para-régionaux ainsi que des statistiques quant à l’encours de la dette de la Région. " . + "Finances publiques" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "rer_velo - Mobigis" . + . + . + . + "CIBG"@nl . + "CIRB"@fr . + "2012 - Compte budgétaire - Begrotingsrekening" . + . + "Défibrillateur - Utilisation du DEA" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Accès aux données via API.Brussels : https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin" . + "Organizers (xml)" . + . + . + "Education being a community competence, the BISA provides statistics that combine data from the Flemish Community and the French Community: student population, number of schools… The BISA also provides student population data for education not provided by the communities." . + "Education " . + . + . + "Les statistiques rassemblées par l’IBSA portent sur la circulation routière, la mobilité douce, le transport collectif et partagé, le transport de marchandises, la sécurité routière et les pratiques de déplacements." . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "Mobilité et transport" . + . + "Accès aux données via API.Brussels : https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin" . + "Places (Json)" . + . + . + "parkingoffroad_blocks - csv - 2016-01-01" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Preview" . + . + . + "bicyclestore - wfs" . + . + . + . + . + "Saint-Gilles"@fr . + "Lijst van de Brusselse gemeenten die het Europees charter voor gelijkheid van vrouwen en mannen op lokaal vlak* hebben ondertekend, met de namens van de burgemeesters en schepenen voor gelijke kansen (legislatuur 2012-2018).\n*van de Council of European Municipalities and Regions (CEMR)" . + "Ondertekenende Brusselse gemeenten van het Europees charter voor gendergelijkheid " . + . + . + "Het BISA verzamelt de Brusselse statistieken over de veiligheid van de burger in het dagelijkse leven: verkeersveiligheid, interventies van de Dienst voor Brandbestrijding en Dringende Medische Hulp (DBDMH), vastgestelde criminele feiten en personeelssterkte van de politiezones..." . + "Veiligheid" . + "Saint-Gilles"@en . + "Sint-Gillis"@nl . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Center for social documentation and coordination"@en . + "Centre de Documentation et de Coordination Sociales"@fr . + "Centrum voor Maatschappelijke Documentatie en Coördinatie"@nl . + . + . + . + "Brussels Jazz Marathon"@en . + "Brussels Jazz Marathon"@fr . + "Brussels Jazz Marathon"@nl . + "Liste des mandataires locaux qui ont parmi leurs compétences l'égalité des chances (y compris l'égalité de genre). (Législature 2012-2018)" . + "Mandataires locaux pour l'égalité des chances en région bruxelloise" . + . + "IBSA-BISA"@en . + . + . + . + "The data compiled relate to climate, air quality, consumption and quality of water, waste collection, green spaces, biodiversity, energy consumption, etc." . + "Environment and Energy " . + . + "L’enseignement étant une compétence communautaire, l’IBSA propose des statistiques qui combinent des données de la Communauté flamande et de la Communauté française : population scolaire, nombre d’établissements scolaires…\nL’IBSA présente également des données de population scolaire pour l’enseignement hors communautés." . + "Enseignement" . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Accès aux données via API.Brussels : https://api.brussels/store/apis/info?name=agenda.brussels&version=0.0.1&provider=admin" . + "Events (xml)" . + "BISA"@nl . + "IBSA"@fr . + . + . + "CESRBC"@en . + . + . + "U Talk Freelance"@en . + . + "CyCLO"@en . + "CyCLO"@fr . + "CyCLO"@nl . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_air_monitoring_stations" . + . + "Amazone"@fr . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_natural_reserve" . + "Amazone"@en . + "Amazone"@nl . + . + . + "Cambio"@en . + "Cambio"@fr . + "Cambio"@nl . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8CA3E4585E2142B0B433B9FE86D089BE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8CA3E4585E2142B0B433B9FE86D089BE "POLYGON ((4.2437 50.7565, 4.4867 50.7565, 4.4867 50.9226, 4.2437 50.9226, 4.2437 50.7565))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8CA3E4585E2142B0B433B9FE86D089BE "{\"type\": \"Polygon\", \"coordinates\": [[[4.24365722656, 50.756472168], [4.48672973633, 50.756472168], [4.48672973633, 50.9226403809], [4.24365722656, 50.9226403809], [4.24365722656, 50.756472168]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA93A78FA0ED565530B48133B51C80D47 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA93A78FA0ED565530B48133B51C80D47 "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC060AC25CC5358540102F99C12D041EC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC060AC25CC5358540102F99C12D041EC "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC060AC25CC5358540102F99C12D041EC "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d28CE2430A751CE255CD7E68CE15E4DD2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD1D8D21F78E196C0EAC4F630C62338F3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD1D8D21F78E196C0EAC4F630C62338F3 "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECF1CA1D321C9F212C6B1265BB43CAFC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dECF1CA1D321C9F212C6B1265BB43CAFC "Shannon Rowies" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d460ACDF20173D1C2522D1EBC6AC6C612 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d460ACDF20173D1C2522D1EBC6AC6C612 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d460ACDF20173D1C2522D1EBC6AC6C612 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE8FED21376C255A706D0B32AD51C96C7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d971D6B6943E1553498571F3B1806FEC0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d971D6B6943E1553498571F3B1806FEC0 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d971D6B6943E1553498571F3B1806FEC0 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d26462C6E1674C9311F60C22856699190 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4BA5BF8C8D9081A664F9A09BCB9A332F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4BA5BF8C8D9081A664F9A09BCB9A332F "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4BA5BF8C8D9081A664F9A09BCB9A332F "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE9CD16E75EDF652651A76650FB23230 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF17445C120E6312AB7E0349C1E2068 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF17445C120E6312AB7E0349C1E2068 "Secrétariat communal" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD10AA77E877FA891AAA2CDE19EAD3006 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD10AA77E877FA891AAA2CDE19EAD3006 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD10AA77E877FA891AAA2CDE19EAD3006 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d228CEDC4629B1AA6E352033023934C0C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d228CEDC4629B1AA6E352033023934C0C "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4DA7ED9210B6D18176CED31134A30C72 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4DA7ED9210B6D18176CED31134A30C72 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4DA7ED9210B6D18176CED31134A30C72 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE3F6AC9451B0E3655CEA6A54FE63EA51 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE3F6AC9451B0E3655CEA6A54FE63EA51 "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE8E36CBF344E596FDEB640126C17A5ED . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE8E36CBF344E596FDEB640126C17A5ED "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF2BF65EE86CB1984D6B13DB41966F8CA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF2BF65EE86CB1984D6B13DB41966F8CA "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF2BF65EE86CB1984D6B13DB41966F8CA "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB300147DD6B18D8497AD96D31217417 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB300147DD6B18D8497AD96D31217417 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB300147DD6B18D8497AD96D31217417 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d88A7660CEF7FFFA91F61CEF98F83D0A9 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d88A7660CEF7FFFA91F61CEF98F83D0A9 "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7D085BBA789A8F90DD87B82AA46E764F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7D085BBA789A8F90DD87B82AA46E764F "Secrétariat communal" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d66F755905DD7C8BC678B87D3F51BA727 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d66F755905DD7C8BC678B87D3F51BA727 "Virginie Tumelaire" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d11C9DD3E5B6747E0910EF8D32C58FB41 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d11C9DD3E5B6747E0910EF8D32C58FB41 "Virginie Tumelaire" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d760861C56057322502B8EBBDEAF93EB2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d760861C56057322502B8EBBDEAF93EB2 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d760861C56057322502B8EBBDEAF93EB2 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBC8157FD21DE40C42E8FCA06367923EA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBC8157FD21DE40C42E8FCA06367923EA "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAD7E987C49F350EAC509D704640C79E2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAD7E987C49F350EAC509D704640C79E2 "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C16FC92568D654233E103BE5B50E2D0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C16FC92568D654233E103BE5B50E2D0 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C16FC92568D654233E103BE5B50E2D0 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD669ACFEF50FC023209F2B0AE60F7120 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD669ACFEF50FC023209F2B0AE60F7120 "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d699D4871CF97CED30F05CBAF55BB9BA6 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d699D4871CF97CED30F05CBAF55BB9BA6 "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFD1F4A91C5C32078059820585BA1C4FA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFD1F4A91C5C32078059820585BA1C4FA "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFD1F4A91C5C32078059820585BA1C4FA "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC3946BF809556E53D66CF2BD28D63A54 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC3946BF809556E53D66CF2BD28D63A54 "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE0DD8BAFFA0C1A0E67B71CAC6AFD7F3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE0DD8BAFFA0C1A0E67B71CAC6AFD7F3 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE0DD8BAFFA0C1A0E67B71CAC6AFD7F3 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4C811EFDF1207087A1AA510D02F09958 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4C811EFDF1207087A1AA510D02F09958 "Pierre Hotyat" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7E7C0936E56BF82A522F26AE5A43FB59 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7E7C0936E56BF82A522F26AE5A43FB59 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7E7C0936E56BF82A522F26AE5A43FB59 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6016774B7208E2622A844DB7CFFDA618 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6016774B7208E2622A844DB7CFFDA618 "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA291F1876327E0F46C1592CAFC00625A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d06DAC25EAEC984A70A02122B89D62E21 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d06DAC25EAEC984A70A02122B89D62E21 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d06DAC25EAEC984A70A02122B89D62E21 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d35CE5C409A4AB7C9FF58AA0B93F26BEF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d35CE5C409A4AB7C9FF58AA0B93F26BEF "Léonor Rennotte" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d784D09B24EB57FC440535C124B24A192 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6EF62B1621AA24B99DD086E71675F4C0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6EF62B1621AA24B99DD086E71675F4C0 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6EF62B1621AA24B99DD086E71675F4C0 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8A9A00C88B7C0DC5107860615AA8D5BA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8A9A00C88B7C0DC5107860615AA8D5BA "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8A9A00C88B7C0DC5107860615AA8D5BA "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBB56969207EDE5D005DC5CB099E7769C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBB56969207EDE5D005DC5CB099E7769C "Equipe Geodata" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD63D9C34B295EBB7A9984D672FA59E5A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD63D9C34B295EBB7A9984D672FA59E5A "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFBDE59B26A85712614D2459BF35134B6 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFBDE59B26A85712614D2459BF35134B6 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFBDE59B26A85712614D2459BF35134B6 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB30FF5D77D3D091E88D5B3487AF7B7D2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE41F72C47DA8F5682351D2450B0B4EEB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE41F72C47DA8F5682351D2450B0B4EEB "Inge Van der Stighelen" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC420368CAA042F2B405D9E8C7878087C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE5C3AB4885F967E473A5B3181FC3CEF0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE5C3AB4885F967E473A5B3181FC3CEF0 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE5C3AB4885F967E473A5B3181FC3CEF0 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEF289B5A571AC494ED326B4782E7E8AA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEF289B5A571AC494ED326B4782E7E8AA "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8D7CB2C93FE0C4ECC8CD2E7833D3B993 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8D7CB2C93FE0C4ECC8CD2E7833D3B993 "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD0315E997F010592FC0CF37C8DD54505 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD0315E997F010592FC0CF37C8DD54505 "POLYGON ((4.2439 50.7636, 4.4826 50.7636, 4.4826 50.9138, 4.2439 50.9138, 4.2439 50.7636))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD0315E997F010592FC0CF37C8DD54505 "{\"type\": \"Polygon\", \"coordinates\": [[[4.2439, 50.7636], [4.4826, 50.7636], [4.4826, 50.9138], [4.2439, 50.9138], [4.2439, 50.7636]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDBABE43D2541D6DA028A3E1757D4E687 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dDBABE43D2541D6DA028A3E1757D4E687 "Cécile Gasparri " . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d93075D71840A35CAF1F6870376F56F31 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d93075D71840A35CAF1F6870376F56F31 "Secretariat" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d67717C4B1928EFB561C32E4B9D146171 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d67717C4B1928EFB561C32E4B9D146171 "Pierre Hotyat" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA8A5F1A7AF64D58689B3974D2310781D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA8A5F1A7AF64D58689B3974D2310781D "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD6DA6D78F929A0EA1F6C122736F5CA07 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD6DA6D78F929A0EA1F6C122736F5CA07 "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3A5D09C02632136D9CCA811E02105C8A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3A5D09C02632136D9CCA811E02105C8A "POLYGON ((4.2203 50.7565, 4.5032 50.7565, 4.5032 50.9240, 4.2203 50.9240, 4.2203 50.7565))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3A5D09C02632136D9CCA811E02105C8A "{\"type\": \"Polygon\", \"coordinates\": [[[4.2203112793, 50.756472168], [4.50320922852, 50.756472168], [4.50320922852, 50.9240136719], [4.2203112793, 50.9240136719], [4.2203112793, 50.756472168]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA1E977449A1E885F69931FC632C07663 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA1E977449A1E885F69931FC632C07663 "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFBE8101373468F4276942802F9891CCA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFBE8101373468F4276942802F9891CCA "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFBE8101373468F4276942802F9891CCA "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD0623B5F4A3F20854EFD0D7D4E75C9F3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dD0623B5F4A3F20854EFD0D7D4E75C9F3 "DEFRANCE Sébastien" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1776C4A741C0B5D00EC851419A0CCA68 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6939BA1EECD7CFC42FBB0632763D1F4D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6939BA1EECD7CFC42FBB0632763D1F4D "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6939BA1EECD7CFC42FBB0632763D1F4D "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3112D776FF578756F8B3A58E9172E268 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3112D776FF578756F8B3A58E9172E268 "POLYGON ((4.2217 50.7606, 4.4908 50.7606, 4.4908 50.9213, 4.2217 50.9213, 4.2217 50.7606))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3112D776FF578756F8B3A58E9172E268 "{\"type\": \"Polygon\", \"coordinates\": [[[4.22168457031, 50.760592041], [4.49084960938, 50.760592041], [4.49084960938, 50.9212670898], [4.22168457031, 50.9212670898], [4.22168457031, 50.760592041]]]}"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE68D1118D83F2FC08B1F9989E7669360 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE68D1118D83F2FC08B1F9989E7669360 "Sébastien DEFRANCE" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB68F6E219C7C466D25807D3E2A80FF9F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB68F6E219C7C466D25807D3E2A80FF9F "IBSA-BISA" . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d129BB644EFCFAD8DF344199EFB36EC72 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d129BB644EFCFAD8DF344199EFB36EC72 "POLYGON ((4.2412 50.7632, 4.4798 50.7632, 4.4798 50.9133, 4.2412 50.9133, 4.2412 50.7632))"^^ . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d129BB644EFCFAD8DF344199EFB36EC72 "{\"type\": \"Polygon\", \"coordinates\": [[[4.24124261076, 50.7631624449], [4.47975783302, 50.7631624449], [4.47975783302, 50.9133209049], [4.24124261076, 50.9133209049], [4.24124261076, 50.7631624449]]]}"^^ . + . + "Région de Bruxelles-Capitale"@fr . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "Liste des espaces publics numériques (mise à jour annuelle)"@fr . + "Espaces publics numériques "@fr . + "2018-08-16T12:54:53.633058"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dFB059F53C6CBF020491A269F57C8EFEC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dFB059F53C6CBF020491A269F57C8EFEC . + "Maillage bleu"@fr . + "Belgique"@fr . + "Reporting Inspire"@fr . + . + "Reporting Inspire"@fr . + "nitrate"@fr . + "protection de la nature"@fr . + "50512393-709d-4a0b-8e91-caff46687fe6" . + "Lijst van openbare computerruimtes (jaarlijkse bijwerking)"@nl . + "2018-08-16T00:00:00"^^ . + "Openbare computerruimtes "@nl . + "Belgique"@fr . + . + "Een bijkomende erkenning of machtiging is vereist om bepaalde activiteiten uit te oefenen in het Brussels Hoofdstedelijk Gewest. Reisagentschappen vereisen deze erkenning."@nl . + "e4dc9bba-f655-44a0-b9ac-469419372467" . + "2019-02-04T15:56:28.676213"^^ . + "List of authorised travel agencies in BCR"@en . + . + "Liste des agences de voyages autorisées en RBC"@fr . + "environnement"@fr . + "protection des espaces naturels"@fr . + "2018-12-11T00:00:00"^^ . + "Defibrillator - Algoritme voor het gebruik van een AED" . + "pollution des eaux usées"@fr . + "Un agrément ou une autorisation supplémentaire est nécessaire pour exercer certaines activités en Région de Bruxelles-Capitale. Les agences de voyages ont besoin d'une autorisation."@fr . + "An accreditation or additional authorisation is needed for performing certain activities in the Brussels-Capital Region. Travel agencies require an authorisation. "@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD14379397DDF8EBC58E29CB474A75CCF . + "Lijst van vergunde reisagentschappen in het BHG"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3D40291E6B142E8C8E57D0D3390644E8 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3D40291E6B142E8C8E57D0D3390644E8 . + . + "Brussels Hoofdstedelijk Gewest: \"de zone rondom een monument, een geheel, een landschap of en archeologische vindplaats, waarvan de omtrek wordt vastgesteld volgens de vereisten van de vrijwaring van de omgeving van het onroerende erfgoed\" en dat het onderwerp uitmaakt van een beschermingsbesluit."@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d594EC1A0600CF3B0B7AAF4449915B4EC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d594EC1A0600CF3B0B7AAF4449915B4EC . + "Région de Bruxelles-Capitale : \"la zone établie autour d’un monument, d’un ensemble, d’un site ou d’un site archéologique dont le périmètre est fixé en fonction des exigences de la protection des abords du patrimoine en question\" ayant fait l'objet d'un arrêté de classement."@fr . + "Brussels-Capital Region : \"the area established around a monument, a unit, a landscape or an archaeological site whose perimeter is fixed according to the requirements of the protection of the surroundings of the heritage in question\" having been the subject of a decree for its conservation."@en . + "2018-08-24T07:10:29.770141"^^ . + "2018-08-24T07:33:02.583462"^^ . + . + "01838fa3-3a9d-424f-899b-7df9b3629540" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9D93C8B1532C7ADFB5F5DFD3B9942D89 . + "Protection area"@en . + "Vrijwaringszone"@nl . + "Zones de protection"@fr . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_monitoring_river_quality_phch" . + . + "Saint-Gilles dispose de 12 structures d’accueil pour les enfants de 0 à 3 ans. Celles-ci comprennent 9 infrastructures communales agréées et subventionnées par l’ONE. Il s’agit des crèches « Jourdan », « Les Bambins du coin », « Isabelle Blume », « Les Bengalis », « Adèle Hauwel », « Willy Peers », « Gabrielle Petit » et « Marie Janson ».\nLes 3 autres structures sont gérées par le Réseau saint-gillois des mini-crèches à vocation sociale et parentale. Il s’agit des mini-crèches « Ketje », Lily » et « L’Amandoline ».\n"@fr . + "2ea28ecb-563d-42c0-874c-dcf11aafc2ea" . + "Crèches"@fr . + "Harvested"@fr . + "Région de Bruxelles-Capitale"@fr . + "pollution de l'eau"@fr . + . + . + "2018-11-13T00:00:00"^^ . + "2019-01-18T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF383B1DB8C9D1A1DA642D66F5A9AA438 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF383B1DB8C9D1A1DA642D66F5A9AA438 . + "flore"@fr . + . + "On-street parking demand in Brussels-Capital Region"@en . + "b459dfcd-1e5f-4c27-92d4-d41777e08716" . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6F96C7ECE63147054BCFA5EA250CB164 . + "Demande de stationnement en voirie"@fr . + "Op de openbare weg parkeervraag in het Brussels Hoofdstedelijk Gewest"@nl . + . + "Demande de stationnement en voirie en Région de Bruxelles-Capitale"@fr . + "2018-10-23T07:34:43.505102"^^ . + "2017-06-23T11:11:32.813055"^^ . + "On-street parking demand"@en . + "Op de openbare weg parkeervraag"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d81D532A18EC908CA7102BCBAEE0DF398 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d81D532A18EC908CA7102BCBAEE0DF398 . + . + "Preview" . + . + "Brussel-Hoofdstad Gewest : kadastrale percelen van het Brussels Gewest"@nl . + "Région de Bruxelles-Capitale : Parcelles cadastrales de la Région bruxelloise"@fr . + "2015-03-30T00:00:00"^^ . + "Cadastral parcels"@en . + "Kadastrale percelen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d75AE666460BA239B3A1D79E599C7571F . + "58d00d48-daec-41dd-b9bf-14eaf8f552cb" . + "Brussels-Capital Region : cadastral plots of the Brussels Region"@en . + "Parcelles cadastrales"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7597974B9D32C6C590CB13BBC38C8267 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7597974B9D32C6C590CB13BBC38C8267 . + "2019-01-29T10:57:27.645083"^^ . + "faune"@fr . + "zone sensible"@fr . + . + "bike_paves_ss - geojson - 2016-01-01" . + "Brussels"@fr . + "gender equality"@fr . + "local government"@fr . + . + "Het geologische model BRUSTRATI3D v1.1 is een geheel van rasterbestanden die het Brussels Hoofdstedelijk Gewest bestrijken. Deze bestanden bevatten de tophoogten en de dikten van de Stratigrafische Eenheden (SE/BHG), evenals het Digitaal Terreinmodel (DTM) van de topografische oppervlakte die gebruikt werd in dit model. Deze rasters hebben een resolutie van 10 x 10 m en hebben als referentiecoördinatensysteem: EPSG 31370 Belgisch Lambert 1972. \n\nDe documentatie met betrekking tot de opmaak van deze gegevens is beschikbaar op de volgende adressen (enkel in het Frans): \n\n###BRUSTRATI3D V1.1\nhttp://document.environnement.brussels/opac_css/index.php?lvl=notice_display&id=10965\n\n###BRUSTRATI3D V1.0\nhttp://document.environnement.brussels/opac_css/index.php?lvl=notice_display&id=10964"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4834F417B13589F07B111F4E110059A5 . + "Geologisch model van het Brussels Hoofdstedelijk Gewest - BRUSTRATI3D v1.1"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8C818FBB8FC590D24B3C9D048F198981 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8C818FBB8FC590D24B3C9D048F198981 . + "Brussels"@fr . + "gender"@fr . + "public space"@fr . + "representation of women"@fr . + "streets"@fr . + "urban space"@fr . + "women"@fr . + "Belgique"@fr . + "Espace public"@fr . + "Fond de plan"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "route"@fr . + "The geological model BRUSTRATI3D v1.1 is a set of raster files covering the Brussels-Capital Region. These files contain the roof elevations and the thicknesses of the Stratigraphic Units (SU/BCR) and the Digital Terrain Model (DTM) of the topographic surface used in this model. These rasters have a 10x10m resolution and their reference coordinate system is EPSG 31370 Belgian Lambert 31370.\n\nThe documentation relating to the construction oh these data is available at the following addresses (French only) :\n\n###BRUSTRATI3D V1.1\nhttp://document.environnement.brussels/opac_css/index.php?lvl=notice_display&id=10965\n\n###BRUSTRATI3D V1.0\nhttp://document.environnement.brussels/opac_css/index.php?lvl=notice_display&id=10964"@en . + "3a62f9f9-4a3f-433f-9364-3787a0fc6760" . + "2019-03-06T09:12:47.129978"^^ . + "Brussels-Capital Region Geological Model - BRUSTRATI3D v1.1"@en . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "conservation des ressources naturelles"@fr . + "environnement"@fr . + "espace vert"@fr . + "flore"@fr . + "législation en matière de préservation de la natur"@fr . + "préservation de la nature"@fr . + "site naturel protégé"@fr . + "Le modèle géologique BRUSTRATI3D v1.1 est un ensemble de fichiers rasters couvrant la Région Bruxelles-Capitale. Ces fichiers contiennent les élévations des toits et les épaisseurs des Unités Stratigraphiques (US/RBC) ainsi que le Modèle Numérique de Terrain (MNT) de la surface topographique utilisée dans ce modèle. Ces rasters ont une résolution de 10x10 m et ont pour système de coordonnées de référence : EPSG 31370 Lambert Belge 1972. \n\nLa documentation relative à la construction de ces données est disponible aux adresses suivantes : \n\n###BRUSTRATI3D V1.1\nhttp://document.environnement.brussels/opac_css/index.php?lvl=notice_display&id=10965\n\n###BRUSTRATI3D V1.0\nhttp://document.environnement.brussels/opac_css/index.php?lvl=notice_display&id=10964"@fr . + "2019-01-04T00:00:00"^^ . + "Modèle géologique de la Région Bruxelles-Capitale - BRUSTRATI3D v1.1"@fr . + . + "c853d99c-3b2c-4f7a-ab0f-cb5f158cd450" . + "2018-12-14T00:00:00"^^ . + "2018-12-14T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d307297A6F5D355AAE540F56F7AE70AF6 . + "Lijst erkende inschakelingsondernemingen en plaatselijke initiatieven voor de ontwikkeling van werkgelgenheid"@nl . + "List of insertion companies and local initiatives for the development of employment"@en . + "Liste des initiatives locales de développement de l'emploi et des entreprises d'insertion"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9C7362DA652CBFC49685E9212D05FDDE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9C7362DA652CBFC49685E9212D05FDDE . + . + . + "De barometer van de musea en attracties wordt opgemaakt op basis van de verzameling van de gegevens over de bezoekersaantallen (vaste collecties en tijdelijke tentoonstellingen) van een panel van musea en attracties in Brussel. Dit panel evolueert in de tijd, door de toevoeging van nieuwe musea. Alle cijfers en evoluties worden meegedeeld op grond van een vaste perimeter: als een museum in het panel wordt opgenomen, worden ook al zijn vroegere gegevens verwerkt."@nl . + "Le baromètre des musées et attractions est réalisé sur la base de la collecte des données de fréquentation (collections permanentes et expositions temporaires) d’un panel de musées et attractions à Bruxelles. Ce panel évoluant avec le temps suite à l’ajout de nouveaux musées, tous les chiffres et évolutions sont donnés à périmètre constant : quand un musée rentre dans le panel, toutes ses données passées sont également incluses."@fr . + "The barometer of museums and attractions is built from the collection of visitors’ statistics (permanent collections and temporary exhibitions) from a panel of museums and attractions in Brussels. This panel can evolve in time by adding a new museum but all figures and evolutions are done on a constant scope : when a museum enters the panel, its past data are also included in the figures."@en . + "00932042-de14-411e-8da5-9a8755e31546" . + "2017-05-09T08:00:20.613271"^^ . + "2018-11-15T10:40:22.684087"^^ . + . + "Barometer of the museums and attractions"@en . + "Barometer van de musea en attracties"@nl . + "Baromètre des Musées et Attractions"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3A2EC9D16B62E3C857B4607EF327074F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3A2EC9D16B62E3C857B4607EF327074F . + "Preview" . + . + "Périmètres des Contrats de Rénovation urbaine"@fr . + "4ec9b4f2-744f-4c27-8964-05c51fda0ca8" . + "2018-08-23T08:34:32.410347"^^ . + "2018-08-23T10:50:47.481825"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE5EFF87757D79631306151414E62BC09 . + "Contrats de Rénovation urbaine (Programmes)"@fr . + "Stadsvernieuwingscontracten (Programma's)"@nl . + . + "bicyclepump - geojson - 2016-01-01" . + . + "Fietsparkeeraanbod op de openbare weg in het Brussels Hoofdstedelijk Gewest"@nl . + "On-street bicycle parking supply in Brussels-Capital Region"@en . + "Offre de stationnement vélo en voirie en Région de Bruxelles-Capitale"@fr . + "9ae57108-6bc4-4793-bd8e-93c1d28e1183" . + "2017-06-23T10:35:49.219986"^^ . + "2018-10-23T07:35:00.220829"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d2215620C61328A5282FAAF6997F73C19 . + "Arceaux vélo"@fr . + "Bicyle rack"@en . + "Fietsenstallingen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dEC7B63F5F1FF9D5AC5B1BD8442B25C17 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dEC7B63F5F1FF9D5AC5B1BD8442B25C17 . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "trees - Mobigis" . + . + "De installaties die einde afvalfase verkregen hebben volgens de Europese criteria"@nl . + "Installations ayant obtenu la fin de statut de déchets selon les critères européens"@fr . + "336c80cd-37ea-45b2-b512-842e1ffa01a8" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de installaties die einde afvalfase verkregen hebben volgens de Europese criteria"@nl . + "Liste des installations ayant obtenu la fin de statut de déchets selon les critères européens"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4BD30FA50D5441C983786D6D87FFD238 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4BD30FA50D5441C983786D6D87FFD238 . + "faune"@fr . + . + "Brussel"@fr . + "Brussels"@fr . + "Early childhood services"@fr . + "Statistics"@fr . + . + "Images cameras - mobility portal" . + . + "Agenda de toutes les activités liées au tango en Belgique"@fr . + "Agenda of all tango-related activities in Belgium"@en . + "Agenda van alle activiteiten mbt tango in België"@nl . + "c0c72946-a3ae-4b44-9b9e-710335605135" . + "2016-01-27T10:46:24.253624"^^ . + "2018-09-18T07:19:06.634336"^^ . + "Agenda de toutes les activités liées au tango en Belgique"@fr . + "Agenda of all tango-related activities in Belgium"@en . + "Agenda van alle activiteiten mbt tango in België"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8EAA4E7E979B49941A98CD221D199847 . + . + "Brussels-Capital Region : Entity Address Point (ADPT) is the location of the address points of the Brussels-Capital"@en . + "Brussels Hoofdstedelijk Gewest : een adrespunt is de grafische weergave van één of meer huisnummers die binnen gebouwen of percelen geplaatst zijn."@nl . + "12c4b7e5-1d76-4bc1-8157-cabb38bd3c35" . + "Région de Bruxelles-Capitale : l'entité point d'adresses (ADPT) correspond à la localisation des points d'adresses de la Région de Bruxelles-Capitale"@fr . + "2015-09-28T00:00:00"^^ . + "2019-01-29T11:06:40.206838"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d57BC8CC496136D9A959C5A4575945AD8 . + "Address Points"@en . + "Adrespunt"@nl . + "Points d'adresses"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5B2C092F46D53F24E814E00BA19A4B8C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5B2C092F46D53F24E814E00BA19A4B8C . + "Belgique"@fr . + "Belgium"@fr . + "België"@fr . + "Bruxelles"@fr . + "Milieux accueil pour la petite enfance"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Structuren voor kinderopvang"@fr . + . + "Cahier 3 : Les pratiques de déplacement à Bruxelles - analyses approfondies" . + . + "Liste des exploitants d'un centre de démontage de véhicules hors d'usage (type A), enregistrés en Région de Bruxelles-Capitale."@fr . + "Registratie als exploitant van een demonteercentrum voor afgedankte voertuigen (type A)in het Brussels Hoofdstedelijk Gewest"@nl . + "946879f1-7f6f-4a1e-9aa3-e6ed9aefc64a" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Liste des exploitants d'un centre de démontage de véhicules hors d'usage (type A)"@fr . + "Registratie als exploitant van een demonteercentrum voor afgedankte voertuigen (type A)"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBF12EF78E65F7848216C431443A44B41 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBF12EF78E65F7848216C431443A44B41 . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "e505b509-e80e-4fc5-aece-c7ba1904881b" . + "2018-08-24T06:41:53.056477"^^ . + "2018-08-24T06:46:40.046915"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d2810F7DA2C240527467E62232D1848BD . + "Vrijwaringszones UNESCO"@nl . + "Zones de protection UNESCO"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0C32E09AFA9AF06FF3C4C2683A7FA1EF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0C32E09AFA9AF06FF3C4C2683A7FA1EF . + . + "hierarchy - geojson - 2016-01-01" . + . + "Lijst van de scholen in Oudergem"@nl . + "Liste des écoles d'Auderghem"@fr . + "d0bcdbce-045d-4ef9-ae94-289ee2166f2d" . + "2019-02-06T08:16:54.857225"^^ . + "2019-02-12T13:07:11.454692"^^ . + "Scholen in Oudergem"@nl . + "Écoles d'Auderghem"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0B0E38893F0DE96B847B659D42B3DA3E . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d0B0E38893F0DE96B847B659D42B3DA3E . + . + "Bovenop de personen die opgenomen zijn in deze lijst, mag de beoordeling opgesteld conform artikel 2.3.54, §4 van het Brussels Wetboek van Lucht, Klimaat en Energiebeheersing (ordonnantie van 2/05/2013), uitgevoerd worden door personen die erkend zijn als opdrachthouder voor de effectenstudies."@nl . + "En plus des personnes reprises dans cette liste, l'évaluation établie conformément à l'article 2.3.54, §4 du Code bruxellois de l'Air, du Climat et de la Maîtrise de l'Energie (ordonnance du 2/05/2013), peut être exécutée par les personnes qui sont agréées en tant que chargé d'étude d'incidences. "@fr . + "7bf5f5b0-ef38-4f88-b25a-80e92c122cd6" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de opdrachthouders voor de effectenbeoordeling (op het vlak van parkeeraanbod - BWLKE) "@nl . + "Liste des chargés de l'évaluation des incidences (en matière de stationnement - COBRACE)"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5C5D761E780A52799F055978589DF99C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5C5D761E780A52799F055978589DF99C . + . + "Hoeveel werknemers telt het Gewest? In welke sectoren werken zij en voor welk loon? Waar concentreert de Brusselse werkende beroepsbevolking zich? Hoe zit het met de werkloosheidscijfers?" . + "Arbeidsmarkt " . + "Belgique"@fr . + "Espace public"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + . + . + "Lijst van de wekelijkse markten 2018 (jaarlijkse bijwerking)"@nl . + "Liste des marchés hebdomadaires 2018 (mise à jour annuelle)"@fr . + "b02acbb1-7e2b-44a6-bf2c-f1f4f54d221d" . + "2018-08-16T13:14:45.590384"^^ . + "2018-08-16T00:00:00"^^ . + "Marchés hebdomadaires"@fr . + "Wekelijkse markten "@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4A0511EE8516A81E267E0670BFEF106A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4A0511EE8516A81E267E0670BFEF106A . + . + "icr - csv - 2016-01-01" . + . + "Lijsten van de Gemeenteraadsleden"@nl . + "Listes des conseillers communaux"@fr . + "5ed1cc65-e73f-4b23-a903-227ce1142780" . + "2017-08-07T07:15:27.422130"^^ . + "2018-12-14T14:12:27.975815"^^ . + "Conseillers communaux et échevins"@fr . + "Gemeenteraadsleden en Schepenen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF78FD2369E1D412FD3537AD7A087FF93 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF78FD2369E1D412FD3537AD7A087FF93 . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "WFS" . + . + . + "artistic_heritage - geojson - 2016-01-01" . + . + "Espace de Développement Renforcé du Logement et de la Rénovation"@fr . + "Ruimte voor Versterkte Ontwikkeling van de Huisvesting en de Renovatie"@nl . + "9d6fa307-2eed-4015-a7e1-4a16f8c438e1" . + "2018-08-23T10:37:50.884136"^^ . + "2018-08-24T07:53:37.326213"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCA6F9AAE556630DBEF5AB1FE71402409 . + "EDRLR"@fr . + "RVOHR"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC92AC9F4F75EC24B6544AC216A4E9458 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC92AC9F4F75EC24B6544AC216A4E9458 . + . + . + "Brussels Hoofdstedelijk Gewest: Informatie over het aantal vermogensbegrenzers (actief, ingeschakeld, uitgeschakeld en in vervanging), uitgesplitst volgens het vermogen van de begrenzer en het type bescherming. \n\nDe vermogensbegrenzer maakt het mogelijk het elektriciteitsverbruik te beperken volgens 3 vermogensniveaus: \n\n-\t1380 watt (6 ampère): Dit zijn de oude vermogensbegrenzers die werden geplaatst voor de goedkeuring van de wijzigingsordonnantie van 2011 betreffende de organisatie van de elektriciteitsmarkt. De gezinnen kunnen een verhoging vragen tot 10 ampère.\n-\t2300 watt (10 ampère): Dit zijn de vermogensbegrenzers die werden geplaatst in geval van onbetaalde facturen en op aanvraag van de commerciële leverancier. \n-\t4600 watt: Dit is een voorbeeld van een aanvraag tot verhoging van het vermogen van een begrenzer via het OCMW. De wijzigingsordonnantie van 23 juli 2018 voorziet deze maatregel niet meer. Hij werd vervangen door de verwijdering van de begrenzer..\n\nBij drie categorieën huishoudelijke klanten kan een vermogensbegrenzer worden geplaatst: \n\n-\tDe __beschermde klanten__: Het statuut van beschermde klant is een systeem dat werd ingevoerd voor de huishoudelijke klanten die het moeilijk hebben om hun energiefactuur te betalen. Dankzij deze bescherming kunnen gezinnen met betalingsmoeilijkheden de door hun leverancier gevraagde afsluiting vermijden.\nBovendien genieten ze tijdens de duur van de bescherming van een goedkopere energieprijs, die het specifiek sociaal tarief wordt genoemd. Dit tarief is lager dan de tarieven die door de commerciële leveranciers worden toegepast.\n\n-\tDe __winterklanten__: De afsluiting van een huishoudelijke klant, met toelating van de vrederechter, mag niet worden uitgevoerd tussen 1 oktober en 31 maart. Als een commerciële leverancier of de noodleverancier tijdens deze periode de gerechtelijke ontbinding van het contract verkrijgt, moet Sibelga dus de continuïteit van de levering tegen het specifiek sociaal tarief garanderen tot 31 maart, het einde van de winterstop.\nDeze klanten worden bij de “beschermde klanten” gerekend. \n \n-\tDe __niet-beschermde klanten__: In tegenstelling tot de twee voornoemde categorieën, zijn ze niet beschermd tegen afsluiting.\n\nEr zijn 4 soorten labels: \n-\tHet label \"__actief__\" betreft de leveringspunten die over een vermogensbegrenzer in werking beschikken op de laatste dag van de maand. \n\n-\tDe labels \"__ingeschakeld__\" en \"__uitgeschakeld__\": Een vermogensbegrenzer wordt als \"ingeschakeld\" beschouwd als hij in werking (actief) is op het leveringspunt terwijl er voor de voorgaande periode geen begrenzer in werking (actief) was op datzelfde leveringspunt. Omgekeerd wordt een vermogensbegrenzer als \"uitgeschakeld\" beschouwd als hij niet meer in werking (actief) is op het leveringspunt terwijl er voor de voorgaande periode een begrenzer in werking (actief) was op datzelfde leveringspunt.. \n\n-\tHet label “__vervanging van…__\" moet tegelijk met het veld “vermogensbegrenzer” worden gelezen. Zo is een begrenzer met een vermogen van 1,38 kVA in het veld \"vervanging van …\" vervangen door een vermogensbegrenzer van 2,3 kVA of 4,6 kVA zoals aangegeven in het veld \"vermogensbegrenzer”. \n\nVoor het label \"actief\" wordt een momentopname genomen op de laatste dag van de maand. Voor de labels \"ingeschakeld\", \"uitgeschakeld\" en \"vervanging van…\" worden de bewegingen over een periode van de eerste tot de laatste dag van de betrokken maand in aanmerking genomen. \n\nDe gegevens worden geleverd door Sibelga. De meegedeelde gegevens kunnen van rapport tot rapport verschillen. Dat is het gevolg van het rectificatieproces uitgevoerd door de actoren van de energiesector om de kwaliteit van de gegevens te verbeteren.\n"@nl . + "Brussels-Capital Region: Information on the number of power limiters (active, connected, disconnected and being replaced) broken down according to the power of the limiter and the type of protection. \n\nThe power limiter makes it possible to restrict the electricity consumption according to 3 levels of power: \n\n- \t1380 Watts (6 amps): These are the old power limiters installed before the adoption of the modifying decree of 2011 relating to the organisation of the electricity market. Households can request an increase to 10 amps.\n- \t2300 Watts (10 amps): These are the limiters installed in case of unpaid bills and at the request of the commercial supplier. \n- 4600 Watts: This situation used to correspond to a request to increase the power of a limiter issued by the C.P.A.S. (Public Social Services Centre). The amending order of 23 July 2018 no longer provides for this measure. It is in fact replaced by the simple removal of the limiter. \n\nThree categories of residential customers struggling to pay their bills are affected by the installation of a power limiter: \n\n-\t__Protected customers__: Protected customer status is a protection system set up for residential customers struggling to pay their energy bills. This protection allows households experiencing payment difficulties to avoid the power cut that is requested by their supplier.\nMoreover, during the term of protection, they benefit from a lower energy price called the special social tariff. This tariff is lower than the tariffs charged by commercial suppliers.\n\n\n-\t__Winter truce customers__: A residential customer cannot be disconnected between 1 October and 31 March, even if the disconnection is authorised by a judge of the peace. This means that, if a commercial supplier or the supplier of last resort obtains the legal termination of the contract binding it to its customer during this period, Sibelga must ensure continuity of the supply at the special social rate until 31 March, the end of the winter truce.\nThese customers are accounted for together with \"protected customers\". \n \n-\t__Unprotected customers__: They do not benefit from any protection against the disconnection of their energy supply, unlike the two above-mentioned categories.\n\nThere are 4 types of labels: \n-\tThe \"__active__\" label refers to supply points with a power limiter that is in operation on the last day of the month. \n\n-\tThe \"__connected__\" and \"__disconnected__” labels: A power limiter is considered to be \"connected\" if it is in operation (active) at the point of supply, whereas for the previous period no limiter was active at this point of supply. Conversely, a limiter is considered to be \"disconnected\" if a limiter is no longer in operation (no longer active) at the point of supply, whereas for the previous period a limiter was active at this same point of supply. \n\n-\tThe \"__replacement ...__” label should be read in conjunction with the \"power limiter\" field. Thus a limiter with a power of 1.38 kVA under the \"replacement ...\" field is replaced by a 2.3 kVA or 4.6 kVA power limiter as indicated under the \"power limiter\" field. \n\nFor the \"active\" label an image is taken on the last day of the month. For the \"connected”, \"disconnected\" and \"replacement ...” labels, movement accounting is carried out over a period running from the 1st to the last day of the month in question. \n\nData are provided by Sibelga. The data reported may differ from one report to another. This is the result of the process of rectification used by players in the energy sector in order to improve the quality of the data."@en . + "Région de Bruxelles-Capitale : Information sur le nombre de limiteurs de puissance (actifs, branchés, débranchés et en remplacement) ventilée selon la puissance du limiteur et le type de protection. \n\nLe limiteur de puissance permet de restreindre la consommation d’électricité selon 3 niveaux de puissance : \n\n-\t1380 Watts (6 ampères) : Ce sont les anciens limiteurs de puissance posés avant l’adoption de l’ordonnance modificatrice de 2011 relative à l’organisation du marché de l’électricité. Les ménages peuvent demander le rehaussement à 10 ampères.\n-\t2300 Watts (10 ampères) : Ce sont les limiteurs placés en cas d’impayés et à la demande du fournisseur commercial. \n-\t4600 Watts : Ce cas de figure correspondait à une demande de rehaussement de la puissance d’un limiteur de la part du C.P.A.S. L’ordonnance modificatrice du 23 juillet 2018 ne prévoit plus cette mesure. En effet, elle est remplacée par un enlèvement pure et simple du limiteur.\n\n\nTrois catégories de clients résidentiels en difficulté de paiement sont concernées par la pose d’un limiteur de puissance : \n\n-\tLes __clients protégés__ : Le statut de client protégé est un système de protection mis en place pour les clients résidentiels qui ont des difficultés de paiement de leur facture d’énergie. Cette protection permet aux ménages en difficulté de paiement d’éviter une coupure d’énergie demandée par leur fournisseur.\nDe plus, pendant la durée de la protection, ils bénéficient du prix de l’énergie plus avantageux qu’on appelle le tarif social spécifique. Ce tarif est moins élevé que les tarifs appliqués par les fournisseurs commerciaux.\n\n\n-\tLes __clients hivernaux__: La coupure d’un client résidentiel autorisée par le juge de paix ne peut être mise à exécution entre le 1er octobre et le 31 mars. Ainsi, si un fournisseur commercial ou le fournisseur de dernier ressort obtient pendant cette période la résiliation judiciaire du contrat le liant à son client, Sibelga doit assurer la continuité de la fourniture au tarif social spécifique jusqu’au 31 mars, fin de la trêve hivernale.\nCes clients sont comptabilisés avec les « clients protégés ». \n \n-\tLes __clients non protégés__ : Ils ne bénéficient d’aucune protection contre la coupure d’énergie contrairement aux deux catégories précitées.\n\n\nIl y a 4 types de labels : \n-\tLe label \"__actif__\" concerne les points de fourniture disposant d’un limiteur de puissance en fonction au dernier jour du mois. \n\n-\tLes Labels \"__branché__\" et \"__débranché__\" : Un limiteur de puissance est considéré comme \"branché\" s’il est en fonction (actif) sur le point de fourniture alors que pour la période précédente aucun limiteur n’était en fonction (actif) sur ce même point de fourniture. A l'inverse, un limiteur est considéré comme \"débranché\" si un limiteur n’est plus en fonction (n’est plus actif) sur le point de fourniture alors que pour la période précédente, un limiteur était en fonction (actif) pour ce même point de fourniture. \n\n-\tLe label « __remplacement de…__» doit être lu parallèlement au champ « puissance limiteur ». Ainsi, un limiteur d’une puissance de 1.38 kVA sous le champ « remplacement de … » est remplacé par un limiteur de puissance de 2.3 kVA ou de 4.6 kVA tel qu’indiqué sous le champ « puissance limiteur». \n\nPour le label « actif » une image est prise au dernier jour du mois. Pour les labels « branché », « débranché » et en « remplacement de… » une comptabilisation des mouvements est réalisée sur une période allant du 1er au dernier jour du mois considéré. \n\nLes données sont fournies par Sibelga. Les données communiquées peuvent différer d'un rapport à l'autre. Ceci résulte du processus de rectification établi par les acteurs du secteur de l'énergie afin d'améliorer la qualité des données.\n"@fr . + "d6616f03-4f49-4718-9b50-bafbbfbddd28" . + "2017-11-29T00:00:00"^^ . + "2018-04-20T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4E36B081E1F68290399CD29F821A4DD3 . + "Limiteurs de puissance"@fr . + "Power limiters"@en . + "Vermogensbegrenzers"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9D6ECEEC68F4256A0276CC7D01AE8A5D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d9D6ECEEC68F4256A0276CC7D01AE8A5D . + . + "Combien de travailleurs compte la Région ? Dans quels secteurs d'activité sont-ils actifs et pour quel salaire ? Où se retrouve la population active occupée bruxelloise ? Qu'en est-il des chiffres du chômage ?" . + "Marché du travail" . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Brussels Jazz Marathon Wheelchair Users" . + . + . + . + "Cette liste est non exhaustive. Certaines installations de collecte et/ou de traitement de déchets ont un accès limité, nous vous conseillons de contacter l’installation avant de vous y rendre afin de vérifier que vos déchets y seront bien acceptés. "@fr . + "Deze lijst is niet-exhaustief. Sommige inzamel- en verwerkingsinrichtingen hebben een beperkte toegang, dus we raden u aan vooraf de inrichting te contacteren zodat u weet of uw afval geaccepteerd zal worden."@nl . + "a8f330c9-0426-49a1-b9c3-04ef3bc3679c" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de vergunde inzamel- en verwerkingsinrichtingen van afvalstoffen in BHG "@nl . + "Liste des installations de collecte et de traitement de déchets (autorisées en RBC)"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d66C8CC10117D818ED30650176B6E1DBC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d66C8CC10117D818ED30650176B6E1DBC . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "bassin hydrographique"@fr . + "bassin versant"@fr . + "eau de surface"@fr . + . + . + "Ce fichier comprend tous les projets d'innovation acceptés par Innoviris et situés dans la période 2015-2017. Les variables suivantes ont été ajoutées: année, programme, titre du projet, nom du bénéficiaire, et montant."@fr . + "Deze dataset omvat alle door Innoviris geaccepteerde innovatieprojecten gesitueerd in de tijdsperiode 2015-2017. De volgende variabelen zijn toegevoegd: jaar, programma, titel project, naam begunstigde, en bedrag."@nl . + "This dataset includes all Innoviris' accepted innovation projects situated in the time period 2015-2017. The following variables are added: year, program, title project, name beneficiary, and amount. "@en . + "98ec97dc-7e6f-43aa-90b2-3519ae5a58b7" . + "2018-12-20T00:00:00"^^ . + "2018-12-20T09:43:54.475046"^^ . + . + "Innoviris - Accepted projects 2015-2017"@en . + "Innoviris - Geaccepteerde projecten 2015-2017"@nl . + "Innoviris - Projets acceptés 2015-2017"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB5D2A066B2D1B497ED32707F7660583B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB5D2A066B2D1B497ED32707F7660583B . + "CSV NL" . + . + "Lijst van gemeentelijke bibliotheken (jaarlijkse bijwerking)"@nl . + "Liste des bibliothèques communales (mise à jour annuelle)"@fr . + "5ce66361-5745-4b6a-a20a-d5feb443e6c5" . + "2018-08-16T12:45:54.906345"^^ . + "2018-08-16T00:00:00"^^ . + "Bibliothèques communales"@fr . + "Gemeentelijke bibliotheken"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d21F0720EEB06598DCCDBD33C490D5CE9 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d21F0720EEB06598DCCDBD33C490D5CE9 . + . + "regional_roads - csv - 2016-01-01" . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "environnement"@fr . + "hydrographie"@fr . + . + "rer_velo - csv - 2016-01-01" . + . + "De gasleveranciers in het Brussels Hoofdstedelijk Gewest "@nl . + "Liste des fournisseurs de gaz en Région de Bruxelles-Capitale "@fr . + "2cc25d50-5029-4ad7-bd06-6d0252195d09" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de gasleveranciers in het Brussels Hoofdstedelijk Gewest "@nl . + "Liste des fournisseurs de gaz en Région de Bruxelles-Capitale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d65C84745985BCE9A84593A7C8D9312AD . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d65C84745985BCE9A84593A7C8D9312AD . + "déchet"@fr . + "environnement"@fr . + . + "Projets des Contrats de Quartiers Durables."@fr . + "119df370-a159-46c8-a627-ffdcbab68e9e" . + "2018-08-23T08:23:23.176587"^^ . + "2018-08-23T11:24:33.311874"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dECF89F39C3C9CE4DA4C603C8367716D8 . + "Contrats de Quartiers Durables (Projets)"@fr . + "Duurzame Wijkcontracten (Projecten)"@nl . + "Sustainable Neighbourhood Contracts (Projects)"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7D9312F11CE8E15B0B4126CE264FE0BF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7D9312F11CE8E15B0B4126CE264FE0BF . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + . + . + "Parkeren voor inrij van eigendom in het Brussels Hoofdstedelijk Gewest"@nl . + "Parking in front of access road in Brussels-Capital Region"@en . + "Stationnement devant accès carrossable en Région de Bruxelles-Capitale"@fr . + "3602f144-3119-4241-b9ad-d21223541321" . + "2018-10-23T07:31:00.107756"^^ . + "2018-10-23T07:34:25.922507"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d07084B5259D1F169BC0067EA4B3878FC . + "Parkeren voor inrij van eigendom"@nl . + "Parking in front of access road"@en . + "Stationnement devant accès carrossable"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7B4451505D7D8168810BFA33783C81E4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B4451505D7D8168810BFA33783C81E4 . + . + "Lijst van de Kinderopvangplaatsen in Oudergem"@nl . + "Liste des crèches d'Auderghem"@fr . + "f4f60a03-b60d-444e-b69e-452ab966b3ac" . + "2019-02-06T07:58:13.829117"^^ . + "2019-02-12T13:07:30.221799"^^ . + "Crèches d'Auderghem"@fr . + "Kinderopvangplaatsen in Oudergem"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBFB2BD9481D9A7A050F3C9C32DEBE83B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBFB2BD9481D9A7A050F3C9C32DEBE83B . + . + "Bibliographic references on gender equality in Brussels. \nFrom the library catalogue of Amazone, Crossroads for Gender Equality" . + "JSON File" . + . + "La commune de Saint-gilles vit au rythme de divers marchés hebdomadaires répartis sur le territoire de la commune. Les marchés du Midi, du Parvis, de la Place Van Meenen et le marché de la Place Horta présentent chacun leurs propres produits, horaires et coûts d’installation pour les exposants. "@fr . + "2572013f-ee37-4f3b-9342-1ad8f0c2394f" . + "2018-11-13T00:00:00"^^ . + "2018-11-13T00:00:00"^^ . + "Marchés"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d84547D4D31DB4E4BBEAA4707EA2813E2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d84547D4D31DB4E4BBEAA4707EA2813E2 . + . + "Belgique"@fr . + "Harvested"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "air"@fr . + "environnement"@fr . + "qualité de l'air"@fr . + "station de surveillance"@fr . + . + "Het aantal fietsers wordt geteld op verschillende plaatsen in het Brussels Hoofdstedelijk Gewest. De komende jaren zullen er nog verschillende extra tellers geplaatst worden. Om toegang te hebben tot de data kunt u gebruik maken van de API en de geowebservices. Er is zowel Real Time data als historische data beschikbaar.\n\n__API: https://data-mobility.brussels/bike/api/counts/__\n\nLink naar de tellingen op kaart: [Mobigis](http://data-mobility.brussels/mobigis/fr/?x=485352.9798586729&y=6593040.226464508&zoom=12&baselayer=mapquest&layers=rt_counting%3B&opacity=1%3B&filter=#)\n\n[![img mobigis extract](http://data-mobility.brussels/static/bike/bike_counting_pole.jpg)] (http://data-mobility.brussels/mobigis/fr/?x=485352.9798586729&y=6593040.226464508&zoom=12&baselayer=mapquest&layers=rt_counting%3B&opacity=1%3B&filter=#)"@nl . + "Le nombre de cyclistes est compté à différents endroits dans la Région de Bruxelles Capitale. D'autres compteurs seront ajoutés durant les prochaines années. Vous pouvez utiliser l’API ou les services cartographiques pour accéder aux données. Des données en Real Time et des données historiques sont disponibles.\n\n__API: https://data-mobility.brussels/bike/api/counts/__\n\nLien vers les comptages en carte: [Mobigis](http://data-mobility.brussels/mobigis/fr/?x=485352.9798586729&y=6593040.226464508&zoom=12&baselayer=mapquest&layers=rt_counting%3B&opacity=1%3B&filter=#)\n\n[![img mobigis extract](http://data-mobility.brussels/static/bike/bike_counting_pole.jpg)] (http://data-mobility.brussels/mobigis/fr/?x=485352.9798586729&y=6593040.226464508&zoom=12&baselayer=mapquest&layers=rt_counting%3B&opacity=1%3B&filter=#)"@fr . + "The number of bikers are counted on several locations in the Brussels Region. The number of counters will be extended in the next years. To access the data, you can use the API or the geowebservices. Real time and historical data are available.\n\n__API: https://data-mobility.brussels/bike/api/counts/__\n\nDirect link to the countings on map: [Mobigis](http://data-mobility.brussels/mobigis/fr/?x=485352.9798586729&y=6593040.226464508&zoom=12&baselayer=mapquest&layers=rt_counting%3B&opacity=1%3B&filter=#)\n\n[![img mobigis extract](http://data-mobility.brussels/static/bike/bike_counting_pole.jpg)] (http://data-mobility.brussels/mobigis/fr/?x=485352.9798586729&y=6593040.226464508&zoom=12&baselayer=mapquest&layers=rt_counting%3B&opacity=1%3B&filter=#)"@en . + "d4961387-23f4-44f8-b2e7-c7e9a260a83e" . + "2017-09-12T12:37:07.725054"^^ . + "2019-03-05T12:22:53.745624"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0B1692FA5D6921FED89D9C2256FA71F8 . + "Bike counting poles"@en . + "Compteurs Vélo"@fr . + "Telpaal fietsen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d04E7157DE94B76D39C7A8E68066591C4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d04E7157DE94B76D39C7A8E68066591C4 . + "signalisation"@fr . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + "Car Sharing"@fr . + "Reporting Inspire"@fr . + "Région de Bruxelles-Capitale"@fr . + "environnement"@fr . + "installation à haut risque"@fr . + "législation en matière d'environnement"@fr . + "Belgique"@fr . + "site industriel"@fr . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_seveso_area&SRSNAME=EPSG:31370" . + . + . + "De elektriciteitsleveranciers in het Brussels Hoofdstedelijk Gewest"@nl . + "Fournisseurs d'électricité en Région de Bruxelles-Capitale"@fr . + "4c819c9b-5a16-489e-82b2-8a6685366f92" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de elektriciteitsleveranciers in het Brussels Hoofdstedelijk Gewest"@nl . + "Liste des fournisseurs d'électricité en Région de Bruxelles-Capitale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4786B3B2A524A1841D7D277A4B5B787F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4786B3B2A524A1841D7D277A4B5B787F . + . + "You will find an overview of public finance in the Brussels Region: the revenue and expenditure of the Brussels Regional Public Service, the local authorities and para-regional bodies, and the statistics on the outstanding debt of the Region.\n" . + "Public finance " . + . + "5a1d8902-91dd-4add-b1a9-1398f1a00ce7" . + "2018-11-10T00:00:00"^^ . + "2018-11-10T00:00:00"^^ . + "Masterplan"@fr . + "Masterplan"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7CB0731BC5A077DD6F04D041B3EA0953 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7CB0731BC5A077DD6F04D041B3EA0953 . + . + "De lijst van de erkende sociale economieprojecten die werknemers aanvaarden \"artikel 60 §7 - sociale economie\"."@nl . + "La liste des projets d'économie sociale reconnus susceptibles d'accueillir des travailleurs « article 60 §7 – économie sociale »."@fr . + "2e07119c-d061-40db-a535-3b88efe6c1ff" . + "2018-12-13T00:00:00"^^ . + "2018-12-13T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE373CAE178BD99E5056F38D325A666E1 . + "Projecten \"artikel 60 §7 - sociale economie\""@nl . + "Projects « article 60 §7 – social economy »"@en . + "Projets « article 60 §7 – économie sociale »"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d930F0717549000DF4BD2F30F75558791 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d930F0717549000DF4BD2F30F75558791 . + . + "La commune de Saint-Gilles accueille 2 bibliothèques sur son territoire ; une francophone et une néerlandophone présentant chacune divers services et activités aux citoyens.\nCes bibliothèques ainsi que leurs adresses, prix, heures d'ouverture et données statistiques sont disponibles dans ce jeu de données.\n\nHistorique :\nLéguée en 1970 par la famille Hoguet à la Commune de Saint-Gilles à la seule condition de servir à des fins culturelles, la maison Hoguet est occupée dans un premier temps par l’Académie de Musique. En 1997, le bâtiment est consacré à une politique visant notamment à une amélioration de la maîtrise du français. Il accueille donc désormais en ses lieux l’association la « Maison du Livre » et l’actuelle bibliothèque communale de Saint-Gilles.\nDepuis sa construction en 1928, la maison Hoguet a été restaurée à 2 reprises et conserve encore aujourd’hui sa façade d’origine de style art déco classée en 1938.\nDepuis janvier 2009, la commune de Saint-Gilles dispose d’une bibliothèque communale néerlandophone au n°173 de la rue Emile Féron. Rénovée en 2017, celle-ci occupe au total 750 m² et dispose, en plus du grand espace réservé aux livres, d’une salle d’exposition ou polyvalente. \n"@fr . + "f4049757-f13c-4c8f-9675-e599d3218ba7" . + "2018-11-06T00:00:00"^^ . + "2018-11-06T00:00:00"^^ . + "Bibliothèques"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4B578D98233B7CD59C58F982A70EDA06 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4B578D98233B7CD59C58F982A70EDA06 . + . + "(Données limitées à 1000 pour des raisons de performance. Voir &maxFeatures=1000)" . + "WFS" . + "Harvested"@fr . + "Belgique"@fr . + "Belgium"@fr . + "Brussel"@fr . + "Caractéristiques emploi"@fr . + "België"@fr . + "Chômage"@fr . + "Domestic employment"@fr . + "Employment characteristics"@fr . + "Kenmerken van de tewerkstelling"@fr . + "Regionale economische vooruitzichten"@fr . + "Sexe"@fr . + "Unemployment"@fr . + "Working-age population"@fr . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Bureaux d'étude agréés dans la discipline « installations de stockage » (stations‑service) en Région de Bruxelles-Capitale"@fr . + "De erkende studiebureaus op het vlak van “opslaginstallaties” (benzinestations) in het Brussels Hoofdstedelijk Gewest"@nl . + "30a0587a-c2cc-4556-ab3e-2f2f24095f38" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de erkende studiebureaus op het vlak van “opslaginstallaties” (benzinestations) in het Brussels Hoofdstedelijk Gewest"@nl . + "Liste des bureaux d'étude agréés dans la discipline « installations de stockage » (stations‑service) en Région de Bruxelles-Capitale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAE358BB0B4FC69794FB3C2C9BD3069E4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE358BB0B4FC69794FB3C2C9BD3069E4 . + . + . + "Ce rapport reprend les objectifs du plan bruxellois de simplification administrative 2015-2020 et l’état d’avancement de celui-ci."@fr . + "Dit verslag vervat de doelstellingen van het Brusselse plan voor administratieve vereenvoudiging 2015-2020 en de staat van voortgang ter zake."@nl . + "This report contains the objectives of the Brussels administrative simplification plan 2015-2020 and the state of progress in this area."@en . + "0706ba52-1867-4255-926f-3a5f3790f33d" . + "2018-05-22T00:00:00"^^ . + "2018-08-21T00:00:00"^^ . + . + "Annual report Easybrussels"@en . + "Jaarverslag Easybrussels"@nl . + "Rapport annuel Easybrussels"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d2915FE56A67014F452831C4FEA83BAC2 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d2915FE56A67014F452831C4FEA83BAC2 . + . + "Dit thema biedt zeer uitgebreide informatie over de bodembezetting, de kenmerken van de bestaande bebouwde oppervlakte en de verkoop van vastgoed." . + "Ruimtelijke ordening en vastgoed" . + . + . + "Infrastructures sportives (mise à jour annuelle)"@fr . + "Sportinfrastructuur (jaarlijkse bijwerking)"@nl . + "698f80dc-dfa9-40a7-95f4-461bdd6c287f" . + "2018-08-16T06:57:20.848053"^^ . + "2018-08-16T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC9940906CA143F5014C12574324BDC68 . + "Infrastructures sportives"@fr . + "Sportinfrastructuur"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC66892A1896E5A1E39D95582132ECAED . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC66892A1896E5A1E39D95582132ECAED . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_noise_monitoring_stations" . + "Population active occupée au lieu de résidence"@fr . + "Population en âge de travailler"@fr . + "Werkloosheid"@fr . + "Working population at place of residence"@fr . + . + "The enclosed file shows the data from a panel of 20 guided tours organisations. See the full interactive barometer via the following link: https://visit.brussels/en/article/tourism-barometer-of-the-brussels-capital-region\n\nPlease note that some organisations don't provide all of their data. Therefore, there are several panels according to the figures at our disposal."@en . + "27614a38-4700-4cb5-8d0a-a22d5e03ace9" . + "2017-10-10T12:33:13.824455"^^ . + "2018-09-12T13:41:35.547346"^^ . + . + "Guided Tours"@en . + "Rondleidingen"@nl . + "Visites Guidées"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3D63891F27CA83732EC6F76E0C532FA0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3D63891F27CA83732EC6F76E0C532FA0 . + . + "Brussels Hoofdstedelijk Gewest: Informatie over het aantal deactiveringen uitgevoerd door de distributienetbeheerder (“DNB” of “Sibelga”). \n\nBlad “__deactiveringen__” - De deactiveringen worden gekenmerkt door de overgang van de status van een leveringspunt van actief naar inactief. Ze omvatten vijf procedures:\n\n-\tDe “afsluitingen op bevel van de vrederechter” (waarvan het aantal is vermeld op blad 2, in tegenstelling tot de andere soorten deactiveringen);\n-\tDe \"Moza\" die eindigen met een afsluiting: dit is een procedure die wordt opgestart door de leverancier om een leveringspunt af te sluiten wanneer zijn klant het leveringspunt verlaat en hij geen aanvraag voor een leveringscontract heeft ontvangen van een eventuele nieuwe bewoner;\n-\tDe \"End of contract\": dit is een deactivering uitgevoerd op vraag van de leverancier wanneer het energiecontract dat hem aan de klant bindt, afloopt;\n-\tDe \"Move out\" : dit is een deactivering van de elektriciteits-/gasaansluiting op aanvraag van de marktactoren;\n-\tDe “drop” scenario’s: een afsluiting na betalingsachterstand van een professionele klant.\n\nBlad \"__Vrederechter__” - De deactiveringen die op bevel van de vrederechter worden uitgevoerd na een aanvraag tot ontbinding van het energiecontract bij de vrederechter. De aanvraag wordt ingediend door de leverancier wanneer hij vaststel dat de klant de facturen niet betaalt. Bij de afsluitingen wordt vermeld of de klant al of niet van de winterstop heeft genoten. We onderscheiden bijgevolg: \n\n-\tDe \"afsluitingen op bevel van de vrederechter\" die worden uitgevoerd buiten de periode van de winterstop, tussen 1 april en 30 september. \n-\tDe \"afsluitingen op bevel van de vrederechter\" die betrekking hebben op de klanten die van de periode van de winterstop hebben genoten. Aangezien de afsluitingen, hoewel ze werden bevolen, niet kunnen worden uitgevoerd tijdens de winterperiode, worden deze klanten tijdens die periode bevoorraad door Sibelga en worden ze “winterklanten” genoemd. Hun energie wordt dus effectief afgesloten vanaf 1 april;\n\nDe afsluitingen op bevel van de vrederechter zijn vermeld in het vorige blad. \n\nBlad \"__VZC__\" - Het verbruik buiten contract heeft betrekking op de leveringspunten die een verbruik registreren hoewel ze als inactief zijn geregistreerd bij Sibelga. De DNB verzegelt dan het toegangspunt als het verbruik niet is gedekt door een energiecontract. Het leveringspunt werd als inactief beschouwd en wordt, na tussenkomst van de DNB, effectief inactief. \n\nHet meegedeelde aantal is een momentopname van de laatste dag van de maand. \n\nDe gegevens worden geleverd door Sibelga. De meegedeelde gegevens kunnen van rapport tot rapport verschillen. Dat is het gevolg van het rectificatieproces uitgevoerd door de actoren van de energiesector om de kwaliteit van de gegevens te verbeteren.\n"@nl . + "Brussels-Capital Region: Information on the number of deactivations performed by the distribution network operator (\"DNO\" or \"Sibelga\"). \n\n« __désactivations__ » of “__deactiveringen__” sheet - Deactivations are characterised by the transition from an active to an inactive point of supply. They cover five procedures:\n\n-\t\"Disconnections by order of the justice of the peace\" (the number of which is detailed in sheet 2, unlike other types of deactivation);\n-\t\"Moza\" which end with a cut-off: This is a procedure initiated by the supplier in order to close a supply point as soon as its customer leaves the supply point and has not received any request for a supply contract from a potential new occupant;\n-\t“End of contract”: This is a deactivation carried out at the request of the supplier when the energy contract that binds it to the customer comes to an end;\n-\t“Move out “: This is a deactivation of the electrical/gas connection at the request of the market players;\n-\t“Drops” scenarios: This is a cut-off following non-payment by a professional customer.\n\n \"__Juge de paix__ \" of \"__Vrederechter__” sheet - Deactivations by order of the justice of the peace come after a request for termination of the energy contract to the justice of the peace. This request is submitted by the supplier in the wake of the customer's failure to pay the bills. The cut-offs are detailed according to whether the customer has been granted winter truce status or not. This makes it possible to make a distinction between: \n\n-\t\"Cut-offs by order of the justice of the peace” that occur outside the winter truce period between 1 April and 30 September. \n-\t\"Cut-offs by order of the justice of the peace - winter\" for customers who have been granted winter truce status. Since cut-offs, although decided, cannot be executed during the winter period, these customers are supplied by Sibelga during this period and are referred to as \"winter truce customers\". The effective cut-off of their energy therefore takes place from 1 April;\n\nCut-offs by order of the justice of the peace are described in the previous sheet.\n\n\"__CHC__” of “__VZC__\" sheet - Non-contractual consumption refers to points of supply that record consumption even though they are listed as inactive with Sibelga. Once this is found, the DNO seals the access point if the recorded consumption is not covered by an energy contract. The point of supply was deemed to be inactive and, after the intervention of the DNO, becomes inactive in practice. \n\nThe number communicated is an image taken on the last day of the month. \n\nData are provided by Sibelga. The data reported may differ from one report to another. This is the result of the process of rectification used by players in the energy sector in order to improve the quality of the data."@en . + "Région de Bruxelles-Capitale : Information sur le nombre de désactivations exécutées par le gestionnaire du réseau de distribution (« GRD » ou « Sibelga »). \n\nFeuille « __désactivations__» - Les désactivations sont caractérisées par le passage du statut d’un point de fourniture de l’actif vers l’inactif. Elles englobent cinq procédures :\n\n-\tLes « coupures sur ordre du juge de paix» (dont le nombre est détaillé dans la feuille 2, contrairement aux autres types de désactivations) ;\n-\tLes « Moza » qui se concluent par une coupure : Il s’agit d’une procédure lancée par le fournisseur afin de fermer un point de fourniture dès lors que son client quitte le point de fourniture et qu'il n'a reçu aucune demande de contrat de fourniture de la part d'un éventuel nouvel occupant ;\n-\tLes « End of contract » : Il s’agit d’une désactivation réalisée à la demande du fournisseur lorsque le contrat d’énergie qui le lie au client arrive à terme ;\n-\tLes « Move out » : Il s’agit d’une désactivation du raccordement électrique/gaz à la demande des acteurs du marché;\n-\tLes scénarios « drops » : Il s’agit d’une coupure suite à un défaut de paiement qui concerne un client professionnel.\n\nFeuille « __Juge de paix__» - Les désactivations sur ordre du juge de paix interviennent à la suite d’une demande de résiliation du contrat d’énergie auprès du juge de paix. Cette demande est introduite par le fournisseur suite au constat de non-paiement des factures par le client. Les coupures sont détaillées selon que le client ait bénéficié de la trêve hivernale ou pas. Dès lors, on distingue : \n\n-\tLes « coupures sur ordre du juge de paix » qui sont effectuées en dehors de la période de trêve hivernale, entre le 1er avril et le 30 septembre. \n-\tLes « coupures sur ordre du juge de paix – hivernal» qui concernent les clients ayant bénéficié de la période de trêve hivernale. Etant donné que les coupures, bien que décidées, ne peuvent être exécutées pendant la période hivernale, ces clients sont alimentés par Sibelga pendant cette période et ils sont appelés «clients hivernaux». La coupure effective de leur énergie intervient donc à partir du 1er avril ;\n\nLes coupures sur ordre du juge sont reprises dans la feuille précédente.\n\nFeuille « __CHC__ » - Les consommations hors contrat concernent des points de fourniture qui enregistrent une consommation bien qu’ils soient répertoriés comme inactifs auprès de Sibelga. Dès lors, le GRD scelle le point d’accès si la consommation enregistrée n’est pas couverte par un contrat d’énergie. Le point de fourniture était réputé inactif et, après l’intervention du GRD, devient réellement inactif. \n\nLe nombre communiqué est une image prise au dernier jour du mois. \n\nLes données sont fournies par Sibelga. Les données communiquées peuvent différer d'un rapport à l'autre. Ceci résulte du processus de rectification établi par les acteurs du secteur de l'énergie afin d'améliorer la qualité des données.\n"@fr . + "40318c4a-a7c7-481b-b416-5d35636c4cc4" . + "2017-11-29T00:00:00"^^ . + "2018-05-08T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9A7A7A669CEF5DB6A33CF50B16EF4C53 . + "Deactivations"@en . + "Deactiveringen"@nl . + "Désactivations"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d813AF87EE3691B319FF77DD20808DC93 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d813AF87EE3691B319FF77DD20808DC93 . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_monitoring_groundwater_level" . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Preview" . + . + . + "wifi.brussels est un réseau internet wifi gratuit, couvrant certaines zones du territoire de la Région de Bruxelles-Capitale. Info : www.wifi.brussels"@en . + "wifi.brussels est un réseau internet wifi gratuit, couvrant certaines zones du territoire de la Région de Bruxelles-Capitale. Info : www.wifi.brussels"@fr . + "wifi.brussels is een gratis wifinetwerk dat in bepaalde zones van het Brussels Hoofdstedelijk Gewest beschikbaar is. Info : www.wifi.brussels"@nl . + "56345c96-d458-41b2-950f-13f5849cbea9" . + "2016-01-27T09:37:32.906918"^^ . + "2017-10-05T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d362D1DE2134A3B705FD485B05348DBEE . + " Hotspots wifi.brussels"@en . + " Hotspots wifi.brussels"@fr . + " Hotspots wifi.brussels"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCB8C9776E516BCC67F03250592356723 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCB8C9776E516BCC67F03250592356723 . + . + "The BISA compiles data on the results of the different elections held in the Brussels-Capital Region.\nYou will find the results of Regional, local, European and legislative elections held in the Brussels-Capital Region.\n" . + "Elections " . + "Bevolking op beroepsactieve leeftijd"@fr . + "Binnenlandse werkgelegenheid"@fr . + "Brussels"@fr . + "Bruxelles"@fr . + "Emploi intérieur"@fr . + "Geslacht"@fr . + "Regional economic prospects"@fr . + "Sex"@fr . + "Statistics"@fr . + "Statistieken"@fr . + "Statistiques"@fr . + "Werkende beroepsbevolking per woonplaats"@fr . + . + "Ouverte en 1905, la piscine communale Victor Boin présente dispose d’un bassin de 33m/13 entouré de cabines individuelles, d’installations d’hydrothérapie unique en Belgique et d’une cafétéria. Après 1 an de fermeture pour rénovation, la piscine rouvre ses portes au public en début 2018.\n\nAncienne distillerie datant de 1881, l’actuel Centre sportif communal situé au 41 Rue de Russie met plusieurs salles à disposition de ses membres pour la location. Celles-ci incluent un grand hall, un petit hall et une salle d’arts martiaux. Le bâtiment dispose également d’une salle de fitness, une salle d’escalade et une salle de psychomotricité. Des activités pour adultes sont également dispensées par l’asbl Saint-Gilles Sport.\n\nAu n°40 de la Rue du Métal, le gymnase « Métal » est un endroit incontournable de la pratique gymnique. Cette salle dispose de nombreux instruments de gymnastique pour gymnastes débutants et confirmés. \n\nÀ proximité de la station de métro Erasme, la plaine des sports Corneille Barca dispose de 3 terrains de football, 6 terrains de tennis (dont 3 couverts) et 12 vestiaires. Un club house et des jeux pour enfants font également partie de ces installations.\n"@fr . + "515a939c-ceef-4d25-8403-ca9c37d0caa9" . + "2018-10-01T00:00:00"^^ . + "2018-10-01T00:00:00"^^ . + "Infrastructures sportives"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dA0CC1F2B7FDF3770403B89540D78DC94 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dA0CC1F2B7FDF3770403B89540D78DC94 . + . + "Lijst van culturele verenigingen (gesubsidieerd door de gemeente) op 30 september 2017 (jaarlijkse bijwerking)"@nl . + "Liste des associations culturelles (subsidiées par la commune) au 30 septembre 2017 (mise à jour annuelle)"@fr . + "6546248f-1411-4b2c-ac01-9db8fcf3d6f8" . + "2018-08-16T13:07:51.284436"^^ . + "2018-08-16T00:00:00"^^ . + "Associations culturelles (subsidiées)"@fr . + "Culturele verenigingen (gesubsidieerd)"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF844520D4A221D034F83C2BA8DAEC97C . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF844520D4A221D034F83C2BA8DAEC97C . + . + "Bibliographic references on gender equality in Brussels. \nFrom the library catalogue of Amazone, Crossroads for Gender Equality" . + "XML File" . + . + . + . + . + "Het activiteitenverslag is een verslag die de Archiefdienst jaarlijks bezorgt aan het Brussels Hoofdstedelijk Parlement. Overeenkomstig aan artikel 9 van de Ordonnantie betreffende de archieven van het Brussels Hoofdstedelijk Gewest bevat het verslag ten minste een uiteenzetting over de organisatie en het administratief beheer van de archiefbescheiden, de staat van de stukken en van de infrastructuur en de opsomming van de aanwinsten."@nl . + "Le rapport d'activités est un rapport que le Service des Archives transmet annuellement au Parlement de la Région de Bruxelles-Capitale. Conformément à l'article 9 de l'Ordonnance relative aux archives de la Région de Bruxelles-Capitale ce rapport comporte au moins un exposé relatif à l'organisation et à la gestion administrative des archives, à l'état des documents et des infrastructures ainsi qu'à l'énumération des acquisitions."@fr . + "bdc434f2-a21c-4006-aab7-6b1c14fa4da5" . + "2019-02-12T00:00:00"^^ . + "2019-02-12T00:00:00"^^ . + "Activiteitenverslag"@nl . + "Rapport d'activités"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d203888C329EFCDA286B0CCAEEFD862EA . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d203888C329EFCDA286B0CCAEEFD862EA . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Liste des défibrillateurs XLS" . + . + . + "traffic lights - MOBIGIS" . + . + "bikeservice - csv - 2016-01-01" . + . + "Het Studieregister is een administratief document dat alle door de administratieve eenheden van de GOB bestelde en bewaarde studies bevat.\nEen studie wordt uitgevoerd op vraag van een administratieve eenheid door een externe natuurlijke of rechtspersoon (consultant, wetenschapper, onderzoeker, onderzoeksinstelling, studiebureau, universiteit...).\nDe bedoeling van een studie is het verbeteren en updaten van de kennis van een administratieve eenheid en van de GOB, dan wel het voeren van een bepaald beleid of actie."@nl . + "Le Registre des études est un document administratif qui reprend l’ensemble des études commandées et conservées par les unités administratives du SPRB.\nUne étude est réalisée à la demande d’une unité administrative par une personne physique ou morale externe (consultant, scientifique, chercheur, institut de recherche, bureau d’étude, université…).\nUne étude a pour but soit d’améliorer et de mettre à jour les connaissances de l’unité administrative et du SPRB, soit de permettre la mise en place d’une politique ou d’une action."@fr . + "d21bc941-cc8f-43d2-adf6-21c398aeef12" . + "2018-02-20T00:00:00"^^ . + "2018-02-20T00:00:00"^^ . + "Registre des études"@fr . + "Studieregister"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d34D10808BF30DFE1A6234634284FA575 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d34D10808BF30DFE1A6234634284FA575 . + . + . + . + . + . + . + . + . + . + . + . + . + . + "c183f4b0-01ac-45b6-903d-e774dd064ab6" . + "2018-12-14T00:00:00"^^ . + "2018-12-14T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5DC62EEA39912626288A135B3526035A . + "Lijst erkende SINE-werkgevers in het Brussels Hoofdstedelijk Gewest."@nl . + "List \"SINE\" employers recognised in the BCR"@en . + "Liste des employeurs reconnus SINE en Région de Bruxelles-Capitale."@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC6046DEDFB0E49DE1AD8C1707AA3EDCB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC6046DEDFB0E49DE1AD8C1707AA3EDCB . + . + . + . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "Périmètres des Contrats de Quartiers Durables."@fr . + "bb5a01db-e025-4177-8153-3ff6e82948a2" . + "2018-08-23T05:56:01.989686"^^ . + "2018-08-23T11:01:18.055479"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD4D7FFA12F6262B33BCEA8F0D163C1D7 . + "Contrats de Quartiers Durables (Programmes)"@fr . + "Duurzame Wijkcontracten (Programma's)"@nl . + "Sustainable Neighbourhood Contracts"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d27AF0D2E61FE64B61AB9A09D3F235C61 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d27AF0D2E61FE64B61AB9A09D3F235C61 . + . + "De toeristische statistieken betreffen overnachtingen van personen die in het Brussels Hoofdstedelijk Gewest verbleven hebben.\nVoor het culturele aspect vindt u statistieken over de bioscoopzalen en de voorstellingen. " . + "Toerisme en cultuur " . + . + "0ee25be6-274e-4eea-94ae-733d86984ce3" . + "2018-08-24T06:27:00.472377"^^ . + "2018-08-24T06:33:42.955555"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD9B6BD2314CE1A58F051691203DFF411 . + "Lotissements"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8B0CB7C05AB135583138AA768106BEDC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8B0CB7C05AB135583138AA768106BEDC . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_natura_2000_station" . + . + "Gestandaardiseerde en geolokaliseerbare informatie verzameld door de dienst Sociaal Brussel over alle publieke en private organisaties die algemene sociale dienstverlening bieden in het Brussels Hoofdstedelijk Gewest. Zie ook: https://sociaal.brussels/sector/53. Licentie: CC-By"@nl . + "Informations standardisées et géolocalisées rassemblées par le service Bruxelles Social, relatives aux organisations publiques et privées qui offrent de l'aide sociale générale en Région de Bruxelles-Capitale. Voir aussi: https://social.brussels/sector/53. Licence: CC-By"@fr . + "ba53cc1b-789a-44f5-9ff4-becbdd310d10" . + "2018-11-27T00:00:00"^^ . + "2018-11-27T00:00:00"^^ . + "Aide sociale générale"@fr . + "Algemene sociale dienstverlening"@nl . + "General social aid"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCED86C842A76384DDF882B65DFACB591 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCED86C842A76384DDF882B65DFACB591 . + . + . + . + . + . + . + . + . + "Export du registre de population. Sexe des personnes enregistrées à la commune d'Auderghem" . + "10/04/2018 - Population - sexe" . + . + . + . + . + "De aardgas- en elektriciteitsmarkten in het Brussels Hoofdstedelijk gewest in cijfers"@nl . + "Les marchés du gaz et de l'électricité en Région de Bruxelles-Capitale en chiffres"@fr . + "dccf3f7e-1abb-4067-b8f0-0a278f3ca5d9" . + "2017-11-29T00:00:00"^^ . + "2018-03-16T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0F29FFFBEBBFC8606D143D87BCF08DF0 . + "Marktstatistieken"@nl . + "Statistiques de marché"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d20746414F0BBF064417566E1E9889C5A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d20746414F0BBF064417566E1E9889C5A . + . + . + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_european_protected_areas" . + . + "taxi_stops - csv - 2016-01-01" . + . + . + "Because there is something for everyone, ideals and budgets, U Talk Freelance has attempted to list coworking spaces in Brussels.\nThey are differentiated by their atypical design, their location, their audience or by their formulas …" . + "http://www.utalkfreelance.be/" . + . + . + "artistic_heritage - csv - 2016-01-01" . + . + "text/xml" . + . + "Hoewel gezondheid onder de bevoegdheid van de gemeenschappen valt, maakt het BISA hier toch een analyse van (aanbod van gezondheidsdiensten, levensverwachting enz.) door gegevens van de Vlaamse Gemeenschap, van de Franse Gemeenschap en van de Gemeenschappelijke Gemeenschapscommissie te combineren." . + "Gezondheid" . + . + "Liste des exploitants d'un centre de destruction et de recyclage de véhicules hors d'usage habilités à délivrer un certificat de destruction (type C) et enregistrés en Région de Bruxelles-Capitale"@fr . + "Registratie als exploitant van een centrum voor de vernietiging en recycling van afgedankte voertuigen dat ertoe gemachtigd is een vernietigingsattest af te geven (type C) "@nl . + "fc7c62f9-c5f1-4b5d-abab-f704306c0769" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Liste des exploitants d'un centre de destruction et de recyclage de véhicules hors d'usage habilités à délivrer un certificat de destruction (type C) et enregistrés en Région de Bruxelles-Capitale"@fr . + "Registratie als exploitant van een centrum voor de vernietiging en recycling van afgedankte voertuigen dat ertoe gemachtigd is een vernietigingsattest af te geven (type C) "@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB59437485B0B68D2073D428F2D6D2961 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dB59437485B0B68D2073D428F2D6D2961 . + . + . + . + . + . + . + . + . + "De opslagcentra van dierlijk afval"@nl . + "Les établissements d'entreposage de déchets animaux"@fr . + "d1374e9d-d6e2-42ae-bf4a-43905b47b25d" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de opslagcentra van dierlijk afval"@nl . + "Liste des établissements d'entreposage de déchets animaux"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d274B4ADCA66AB8397241B609AEA2A9D7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d274B4ADCA66AB8397241B609AEA2A9D7 . + . + "Preview" . + . + . + . + . + . + "Beveiligd fietsparkeren in Brussels Hoofdstedelijk Gewest"@nl . + "Secure bicycle parking in Brussels-Capital Region"@en . + "Stationnement vélo sécurisé en Région de Bruxelles-Capitale"@fr . + "731baa15-9378-4458-8755-78a4664885dd" . + "2018-10-23T06:51:52.153798"^^ . + "2018-10-23T07:34:05.340672"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d55C6A1C0C99A775344142EFAB20012AA . + "Bicycle box"@en . + "Fiets box"@nl . + "Vélo box"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC82FD5C3B36583D6637C3EE942CDF995 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC82FD5C3B36583D6637C3EE942CDF995 . + . + "The statistics gathered by the BISA relate to road traffic, soft mobility, collective transport and shared mobility, freight transport, road safety and mobility practices." . + "Mobility and Transport " . + . + "Lors de sa fondation en 1216, la paroisse de Saint-Gilles disposa d’une église construite sur le site de l’actuel Parvis, et d’un cimetière entourant celle-ci. D’autres cimetières ont également vu le jour afin de pallier à l’exiguïté de la ville de Bruxelles mais ils furent désaffectés à la fin du 18ème siècle.\nAprès plus de 600 ans, le Cimetière du parvis fut transféré dans l’actuelle rue Gisbert Combaz. Toutefois, l’accroissement de la population amena rapidement les autorités communales à acquérir un terrain de près de 2 hectares sur le territoire de la commune d’Uccle où le cimetière emménagea à nouveau. Celui-ci ne tarda pas à fermer au vu de l’instabilité du terrain.\nLe 28 janvier 1895, l’actuel cimetière de Saint-Gilles fut inauguré dans l’avenue du Silence sur un terrain à flanc de colline de 12 hectares.\n"@fr . + "f6e949f5-dedf-4eee-a362-7b0295385fe7" . + "2018-10-01T00:00:00"^^ . + "2018-10-01T00:00:00"^^ . + "Cimetières"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7B992CA34F29349865C57BCD06B0DED0 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7B992CA34F29349865C57BCD06B0DED0 . + . + . + . + . + "Centres de traitement de déchets animaux"@fr . + "De verwerkingscentra van dierlijk afval"@nl . + "4fc41862-ce9c-4b25-b116-7c21d65f6fee" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + " Liste des centres de traitement de déchets animaux "@fr . + "Lijst van de verwerkingscentra van dierlijk afval"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3E395C4EAF7FFBDFA9916363BF88B3FE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3E395C4EAF7FFBDFA9916363BF88B3FE . + . + . + . + . + "Les statistiques présentées portent notamment sur le nombre de milieux d’accueil et la capacité de ceux-ci." . + "Petite enfance" . + . + . + "De installaties die einde afvalfase verkregen hebben volgens gewestelijke Brusselse criteria."@nl . + "Installations ayant obtenu la fin de statut de déchets selon les critères régionaux bruxellois."@fr . + "b6d26aef-55d5-4226-a8f0-feb9274006b5" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de installaties die einde afvalfase verkregen hebben volgens gewestelijke Brusselse criteria "@nl . + "Liste des installations ayant obtenu la fin de statut de déchets selon les critères régionaux bruxellois "@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF176D3E6AB2E51480AF1EC9583D49A91 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF176D3E6AB2E51480AF1EC9583D49A91 . + . + "Preview" . + . + . + . + . + "Lijst van gemeentelijke kinderdagverblijven (jaarlijkse bijwerking)"@nl . + "Liste des crèches communales (mise à jour annuelle)"@fr . + "361f8229-ad8c-4ff8-bdd3-c22df4155300" . + "2018-08-16T12:49:58.342009"^^ . + "2018-08-16T00:00:00"^^ . + "Crèches communales "@fr . + "Gemeentelijke kinderdagverblijven "@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d1A04E759091E88CD34D17FAF87544EEE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1A04E759091E88CD34D17FAF87544EEE . + . + . + . + . + . + . + . + "Collecteurs des déchets animaux"@fr . + "De ophalers van dierlijk afval "@nl . + "267a93be-980e-4026-99f5-8a0da28b1168" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de ophalers van dierlijk afval"@nl . + "Liste des collecteurs de déchets animaux"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d71C100175350EC6CB2AEFDCC89B4A868 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d71C100175350EC6CB2AEFDCC89B4A868 . + . + . + "Zone de police 5340 (Molenbeek-Saint-Jean, Koekelberg, Jette, Ganshoren et Berchem-Sainte-Agathe)" . + . + "Brussels Hoofdstedelijk Gewest : de entiteit « Rail Type » stelt het tracé voor van de spoor- of tramlijnen. Het onderscheid tussen spoor- en tramlijn wordt weergegeven in de naam zelf van de entiteit (RW = Railway en TW = Tramway).\nOok het niveau wordt gepreciseerd in de naam van de entiteit (0 = grondniveau, M = onderliggend niveau en P = bovenliggend niveau)"@nl . + "Brussels-Capital Region : entity \"rail line\" is the track layout railway or tramway. The distinction between rail railway and tramway track is included in the same name of the entity (RW = TW = Railway and Tramway).\nA concept level is also specified in the name of the entity (ground level = 0, M = P = lower level and upper level)"@en . + "Région de Bruxelles-Capitale : l'entité « Ligne de rail » représente le tracé des voies de chemin de fer ou de tramway. La distinction entre rail de chemin de fer et rail de tramway est reprise dans le nom même de l'entité (RW = Railway et TW = Tramway).\nUne notion de niveau est aussi précisée dans le nom de l'entité (0 = niveau du sol, M = niveau inférieur et P = niveau supérieur)"@fr . + "9b7549b1-b463-4a8d-851b-7c5fb2451c49" . + "2015-03-30T00:00:00"^^ . + "2019-01-29T10:53:33.292521"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0AD7A730011EA64106C15BBE33FFFC33 . + "Ligne de rail"@fr . + "Straatknooppunt"@nl . + "Street node"@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d601B1BB85C8CBDE07F17FD30746642C4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d601B1BB85C8CBDE07F17FD30746642C4 . + . + . + . + . + . + "text/xml" . + . + "Inventaire scientifique des arbres remarquables de la Région de Bruxelles-Capitale."@fr . + "Wetenschappelijke inventaris van de merkwaardige bomen van het Brussels Hoofdstedelijk Gewest."@nl . + "28f48440-ce95-4b67-a54d-dfd9cc7c0182" . + "2018-08-23T12:28:55.671682"^^ . + "2018-08-24T06:06:20.678661"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d16A640AD7A30FA99BE9AB15566B7AD0B . + "Arbres remarquables"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dC4E8C685D3B77B989104CAA288F7BA99 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dC4E8C685D3B77B989104CAA288F7BA99 . + . + "The statistics on tourism relate to the accommodation of visitors to the Brussels-Capital Region.\nAs regards culture, you will find statistics on cinemas and screenings.\n" . + "Tourism and Culture " . + . + "Centres de formation agréés en systèmes de climatisation pour véhicules automobiles"@fr . + "De erkende opleidingscentra op het vlak van klimaatregelingssystemen in motorvoertuigen "@nl . + "735c5b7a-237f-420e-b66d-3b12b035757c" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de erkende opleidingscentra op het vlak van klimaatregelingssystemen in motorvoertuigen"@nl . + "Liste des centres de formation agréés en systèmes de climatisation pour véhicules automobiles"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d780DB9AE7F1EAB3F75CD6FA7B8656C63 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d780DB9AE7F1EAB3F75CD6FA7B8656C63 . + . + . + . + . + "Liste non exhaustive de noms de rue féminins en Région de Bruxelles-Capitale | Niet-exhaustieve lijst van vrouwelijke straatnamen in het Brussels Gewest" . + "Noms de rue féminins en Région bruxelloise - vrouwelijke straatnamen in het Brussels Gewst" . + . + "Le Code bruxellois de l'Aménagement du Territoire (CoBAT) permet de créer un droit de préemption au profit de divers pouvoirs publics. Il est exercé dans l'intérêt général, notamment en vue de lutter contre l'existence d'immeubles abandonnés et insalubres et de réaliser des logements de type social."@fr . + "3fded591-0cda-485f-97e7-3b982c7ff34b" . + "2018-08-23T09:36:13.951080"^^ . + "2018-08-23T11:57:20.332796"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3AB75DE4BF8A787BD56D082D043C9EF7 . + "Zones de préemption"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d85112E4C1AFD42DA2821C3015879CF4A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d85112E4C1AFD42DA2821C3015879CF4A . + . + . + "Brussels Hoofstedelijk Gewest: Een huizenblok staat voor een deel van het gemeentelijk grondgebied waarvan de zijden\nbegrensd worden door straatzijden en/of gemeentegrenzen.\n\nDe omtrekken van de voetpaden (stoepen) zijn niet in de huizenblokken inbegrepen. Rotondes en middenbermen zijn geen huizenblokken. De huizenblokken komen niet alleen overeen met de bebouwde stadszones, maar ook met\nbraakliggende zones, bouwgronden, zones voorbehouden aan spoorweg en metro, groene ruimten en waterpartijen, galerijen."@nl . + "Brussels-Capital Region: The island means a portion of the communal territory whose faces are bounded by the sides of streets and / or boundaries of municipalities.\n\nThe contours of the sidewalks are not included in the islets. Roundabouts and central berms are not islands. Blocks correspond not only to urban areas but also areas of wasteland, the Land, area railway and subway, green spaces and water bodies, galleries."@en . + "Région de Bruxelles-Capitale : L’îlot désigne une portion de territoire communal dont les faces sont délimitées par les côtés de rue et/ou des limites de communes.\n\nLes contours des trottoirs ne sont pas inclus dans les îlots. Les ronds-points et les bermes centrales ne sont pas des îlots. Les îlots correspondent non seulement aux zones urbanisées mais également aux zones de friche, aux terrains à bâtir, aux zones de chemin de fer et de métro, aux espaces verts et aux plans d'eau, aux galeries."@fr . + "68204ea1-819d-433c-81bb-4a1776fd56bc" . + "2015-03-30T00:00:00"^^ . + "2019-01-29T10:58:28.743722"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0386BC6B2D9D1261CE53E69092663780 . + "Block"@en . + "Huizenblok"@nl . + "Ilot"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d56213AB5E3C2ED629E055D687319D1D6 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d56213AB5E3C2ED629E055D687319D1D6 . + . + . + . + . + "Static Data with all information about the parkings.\nFormat Datex II (http://www.datex2.eu)." . + "Parking Occupancy - Static Data" . + . + . + . + . + "Ecoles communales - année scolaire 2017/2018 (mise à jour annuelle)"@fr . + "Gemeentelijke school - Schooljaar 2017/2018 (jaarlijkse bijwerking)"@nl . + "67b8548c-6360-4cba-aa45-31d4c2861256" . + "2018-11-12T14:57:08.974872"^^ . + "2018-11-12T00:00:00"^^ . + "2017/2018 - Ecoles communales"@fr . + "2017/2018 - Gemeentelijke school"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d4409DB53549CE3901C6F13728A2FB331 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d4409DB53549CE3901C6F13728A2FB331 . + . + "text/xml" . + . + . + . + . + . + . + . + . + . + . + "La Commune de Saint-Gilles organise un enseignement fondamental ordinaire, un enseignement primaire individualisé de type 8, un enseignement secondaire, ainsi qu'un enseignement de promotion sociale. Tous ces établissements sont liés par une culture commune construite autour de valeurs humanistes, neutres et respectueuses de toutes les conceptions philosophiques. Elles développent la liberté de conscience.\n\n Il y a aujourd'hui sept écoles fondamentales (maternel et primaire) : l'école 1-2, l'école Ulenspiegel, l'école Les 4 Saisons (immersion en néerlandais à partir de la 3ème maternelle), l'école Peter Pan, l'école J.J. Michel et l'école Nouvelle, ainsi qu'une école primaire spécialisée de type 8, l'école du Parvis.\n\n Depuis septembre 2017, la Commune de Saint-Gilles organise un enseignement secondaire général à pédagogie active, le Lycée Intégral Roger Lallemand.\n\n La Commune organise également un enseignement secondaire artistique à horaire réduit dans les domaines des arts plastiques, de la musique et des arts de la parole au sein des deux académies : l'Académie des Beaux-Arts et l'Académie de Musique Arthur Degreef.\n"@fr . + "e4c4c3c1-6099-41d9-ab3d-74410d308f3a" . + "2018-10-17T13:59:10.364035"^^ . + "2019-02-20T13:27:27.128871"^^ . + "Ecoles"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d852D7DF5713DC210E684C1414DAC6F84 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d852D7DF5713DC210E684C1414DAC6F84 . + . + . + . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_water_watershed" . + . + "Offre de stationnement en voirie en Région de Bruxelles-Capitale"@fr . + "On-street parking supply in the Brussels-Capital Region"@en . + "Parkeeraanbod op de openbare weg in het Brussels Hoodstedelijk Gewest"@nl . + "d8d348b6-2289-417e-8321-92769f7d6705" . + "2017-06-23T11:16:06.189659"^^ . + "2018-10-23T07:35:16.215055"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8ABEB1AB5CA2A24AF103AD140EB3C62E . + "Offre de stationnement en voirie"@fr . + "On-street parking supply"@en . + "Parkeeraanbod op de openbare weg"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6BDE8001DF2942F1AEF6CC60BDF527F4 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6BDE8001DF2942F1AEF6CC60BDF527F4 . + . + "Lijst van speelpleinen (halfjaarlijkse bijwerking)"@nl . + "Liste des plaines de jeux (mise à jour semestrielle)"@fr . + "4ee154a2-e9e0-45a3-8e45-f19f86c543c0" . + "2018-04-10T09:45:56.777731"^^ . + "2018-06-30T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3E17AF425BC148916597B80F2720C364 . + "Plaines de jeux"@fr . + "Speelpleinen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF0312FCB86A5E16D9E03EFB4720EBB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAAF0312FCB86A5E16D9E03EFB4720EBB . + . + . + . + . + "bicyclestore - Mobigis" . + . + . + . + . + . + . + . + . + "59447b03-1f87-4876-9571-91f49bb1afb7" . + "2019-02-20T00:00:00"^^ . + "2019-03-18T00:00:00"^^ . + "Espace public numérique"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5AA78B1425BE930523E3A6B870C4F1AF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5AA78B1425BE930523E3A6B870C4F1AF . + . + "Nombre d'affichage des pages du site internet communal d'Auderghem en 2017." . + "2017 - Affichage des pages du site internet" . + . + "5a8839c7-14b0-488a-871f-24a393640f07" . + "2019-02-20T00:00:00"^^ . + "2019-02-20T00:00:00"^^ . + "Infrastructures communales"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d44B94FAEB48CCB3C88E5D18D2AA11EFD . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d44B94FAEB48CCB3C88E5D18D2AA11EFD . + . + . + . + . + "De lijst van de door een van de drie erkende controleorganen gecertificeerde biologische bedrijven."@nl . + "La liste des entreprises certifiées bio par l'un des trois organismes agréés."@fr . + "838a8812-0b20-451d-a64c-966660479398" . + "2018-12-13T00:00:00"^^ . + "2018-12-13T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d03EFE23A906885D31F5D2FF3F547B61A . + "Lijst van de gecertificeerde biologische bedrijven in het Brussels Hoofdstedelijk Gewest"@nl . + "Liste des opérateurs certifiés bio situés en Région de Bruxelles-Capitale "@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCC8C70A633BD3117C9416C76C17FFF5B . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dCC8C70A633BD3117C9416C76C17FFF5B . + . + "text/xml" . + . + . + . + . + "-De ordonnantie van 22 april 1999 tot vaststelling van de lijst der ingedeelde inrichtingen van klasse 1A (Staatsblad van 05/08/99); \n\n -Het besluit van de Brusselse Hoofdstedelijke Regering van 4 maart 1999 tot vaststelling vand de lijst der ingedeelde inrichtingen van klasse 1B, 2 en 3 (Staatsblad van 07/08/99); \n\n -Het besluit van de Brusselse Hoofdstedelijke Regering van 20 mei 1999 tot verplichting van het inwinnen van het advies van de Brusselses Hoofdstedelijke Dienst voor Brandweer en Dringende Medische Hulp (Staatsblad van 18/08/99); \n\n -Andere besluiten van de Brusselse Hoofdstedelijke Regering die verschillende rubrieke wijzingen of er nieuwe toevegen; \n\n -Het besluit van de Brusselse Hoofdstedelijke Regering tot vaststelling van de lijst van de risicoactiviteiten van 17 december 2009 (B.S. 08/01/2010). "@nl . + "-Ordonnance du 22 avril 1999 fixant la liste des installations de classe 1A (M.B.du 05/08/99); \n\n-Arrêté du Gouvernement de la Région de Bruxelles-Capitale du 4 mars 1999 fixant la liste des installations de classe 1B, 2 et 3 (M.B du 07/08/99); \n\n-Arrêté du Gouvernement de la Région de Bruxelles-Capitale du 20 mai 1999 imposant l'avis du Service Incendie et d'Aide médicale urgente en Région de Bruxelles-Capitale (M.B. du 18/05/99); \n\n-Autres arrêtés du Gouvernement de la Région de Bruxelles-Capital modifiant certaines rubriques ou en incluant des nouvelles; \n\n-Arrêté du Gouvernement de la Région de Bruxelles-Capitale fixant la liste des activités à risque du 17 décembre 2009 (M.B. du 08/01/2010). "@fr . + "b9760bbf-b6d8-4d45-af99-3d968f200e31" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de ingedeelde inrichtingen"@nl . + "Liste coordonnée des installations classées"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d790C843BED6D428E5A6EADE571F8C123 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d790C843BED6D428E5A6EADE571F8C123 . + . + . + "Si la santé relève des compétences communautaires, l’IBSA présente une sélection d’indicateurs (offre de soins de santé, espérance de vie, etc.), en combinant des données provenant de la Communauté flamande, de la Communauté française et de la Commission communautaire commune." . + "Santé" . + . + "Liste des exploitants d'un centre de démontage de véhicules hors d'usage habilités à délivrer un certificat de destruction (type B) et enregistrés en Région de Bruxelles-Capitale"@fr . + "Registratie als exploitant van een demonteercentrum voor afgedankte voertuigen dat ertoe gemachtigd is een vernietigingsattest af te geven (type B) "@nl . + "9c8901d8-5f4a-41cc-80f8-b639e3996ed8" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Liste des exploitants d'un centre de démontage de véhicules hors d'usage habilités à délivrer un certificat de destruction (type B) et enregistrés en Région de Bruxelles-Capitale"@fr . + "Registratie als exploitant van een demonteercentrum voor afgedankte voertuigen dat ertoe gemachtigd is een vernietigingsattest af te geven (type B) "@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d07A839B5CB898E28C4AEF4C9D3A1DB6F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d07A839B5CB898E28C4AEF4C9D3A1DB6F . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : de statistische sectoren worden gegroepeerd om de monitoringwijken te vormen. De wijkmonitoring is een observatiemiddel dat het mogelijk maakt om de evolutie van de Brusselse wijken onder verschillende aspecten (demografisch, sociaal, gezondheid, economie, huisvesting, mobiliteit, leefklimaat, deelname...) te volgen en te begrijpen teneinde de efficiëntie van de stadsbeleidsmaatregelen te verbeteren en de openbare en particuliere investeringen inzake stadsvernieuwing beter te oriënteren. De Monitoring moet beheerd en bijgewerkt worden door het Brusselse Instituut voor de Statistiek en de Analyse (BISA)."@nl . + "Brussels-Capital Region : Brussels-Capital statistical areas are grouped to form the districts of monitoring. Monitoring the neighborhood is an urban observation tool to help track and understand the evolution of Brussels districts in different aspects (demographic, social, health, economy, housing, mobility, quality of life, participation, ... ) to improve the efficiency of urban policy and help guide public and private investment in urban renewal ..."@en . + "Région de Bruxelles-Capitale : les secteurs statistiques sont regroupés pour former les quartiers du monitoring. Le Monitoring des quartiers est un outil d'observation urbain qui doit permettre de suivre et de comprendre l'évolution des quartiers bruxellois sous différentes facettes (démographiques, social, santé, économie, logement, mobilité, cadre de vie, participation,...) afin d'améliorer l'efficience des politiques urbaines et de mieux orienter les investissements publics et privés en matière de rénovation urbaine,..."@fr . + "07e83a81-0c0c-458f-8732-a8f1313ae710" . + "2015-09-28T00:00:00"^^ . + "2019-01-29T10:54:10.432340"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dDB0CB2D98F00E32DFFBE2A2E5CED934B . + "Monitoring District"@en . + "Monitoringwijk"@nl . + "Quartiers du monitoring"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d81ACDBAABB90150F7C85E0DE1D6C74C7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d81ACDBAABB90150F7C85E0DE1D6C74C7 . + . + "Avantages de toute nature et frais de représentation" . + . + . + . + . + . + . + . + . + "Calendrier des séances du conseil communal"@fr . + "Kalender van de zittingen van de Gemeenteraad"@nl . + "a013d97c-42aa-4d92-a714-56a3d19d0325" . + "2017-07-27T08:14:06.387047"^^ . + "2018-08-16T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d0675422B5FF221F19ADF6782B3C43C8A . + "Conseil communal"@fr . + "Gemeenteraad"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7756E19FDDF6ED0DC6AFC24023F6B0E5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d7756E19FDDF6ED0DC6AFC24023F6B0E5 . + . + . + . + . + "artistic_heritage - Mobigis" . + . + "text/xml" . + . + "Brussels Hoofdstedelijk Gewest: Informatie over de marktaandelen van de leveranciers die houder zijn van een leveringsvergunning voor energie die geldig is in het Brussels Gewest, in aantal leveringspunten en in volume. De informatie over de verliesleverancier wordt eveneens meegedeeld, hij dekt de verliezen op het elektriciteitsnet van de DNB. De gegevens worden uitgesplitst per klanttype, \"professioneel\" of \"huishoudelijk\", en per frequentie van de meteropname.\n\nEr zijn 3 meteropnamefrequenties, afhankelijk van het type meter: \n\n-\tDe zogenoemde \"continue\" frequentie van de meteropname voor de \"AMR\"-meters, _automatic meter reading_ of \"meter met automatische opname\". De informatie over de meterstanden wordt elke 15 minuten geregistreerd voor elektriciteit en elk uur voor gas. Daarna wordt ze een keer per dag op afstand doorgestuurd naar de DNB voor de twee energietypes. \n-\tDe zogenoemde \"maandelijkse\" frequentie van de meteropname voor de meters van het \"MMR\"-type, _monthly meter reading_ of \"meter met maandelijkse opname\". De informatie over de meterstanden wordt een keer per maand geregistreerd en op afstand doorgestuurd naar de DNB. \n-\tDe zogenoemde \"jaarlijkse\" frequentie van de meteropname voor de \"YMR\"-meters, _yearly meter reading_ of \"meter met jaarlijkse opname\". De meterstanden worden een keer per jaar fysiek (ter plaatse) opgenomen door het personeel van de DNB.\n\nDe gegevens worden geleverd door Sibelga en Elia. \n\nDe meegedeelde gegevens kunnen van rapport tot rapport verschillen. Dat is het gevolg van het rectificatieproces uitgevoerd door de actoren van de energiesector om de kwaliteit van de gegevens te verbeteren.\n"@nl . + "Brussels-Capital Region: Information on the market shares of suppliers holding an energy supply licence, valid in the Brussels Region, number of points of supply and volume. Information on the loss supplier is also provided, the supplier that covers losses on the DNO's electricity grid. The data are broken down according to \"business\" or \"residential\" customer and by meter reading frequency.\n\nThere are 3 meter reading frequencies depending on the type of meter: \n\n-\t\"Continuous\" reading frequency for “AMR” meters, automatic meter reading meters. Information on indexes is recorded every 15 minutes for electricity and every hour for gas. Then, it is transmitted remotely to the DNO once a day for both energies. \n-\t\"Monthly\" meter reading frequency for “MMR” meters, or monthly meter readings. Index information is recorded and transmitted to the DNO remotely once a month. \n-\t\"Annual\" meter reading frequency for \"YMR\" meters, or yearly meter readings. Index information is physically recorded (on-site) by DNO staff once a year.\n\nData are provided by Sibelga and Elia. \n\nThe data reported may differ from one report to another. This is the result of the rectification process used by players in the energy sector in order to improve the quality of the data.\n"@en . + "Région de Bruxelles-Capitale : Information sur les parts de marché des fournisseurs détenteurs d'une licence de fourniture d'énergie, valable en Région bruxelloise, en nombre de points de fourniture et en volume. L’information sur le fournisseur de perte est également communiquée, celui-ci couvre les pertes sur le réseau électrique du GRD. Les données sont ventilées par type de client « professionnel » ou « résidentiel » et par fréquence de relevé.\n\nIl existe 3 fréquences de relevé selon le type de compteur : \n\n-\tFréquence de relevé dite « continue » pour les compteurs « AMR », _automatic meter reading_ ou \"compteur à relevé automatique\". L’information sur les index est enregistrée toutes les 15 minutes pour l’électricité et toutes les heures pour le gaz. Puis, elle est transmise à distance au GRD une fois par jour pour les deux énergies. \n-\tFréquence de relevé dite « mensuelle » pour les compteurs de type « MMR », _monthly meter reading_ ou \"compteur à relevé mensuel\". L’information sur les index est enregistrée et transmise à distance au GRD une fois par mois. \n-\tFréquence de relevé dite « annuelle » pour les compteurs « YMR », _yearly meter reading_ ou \"compteur à relevé annuel\". L’information sur les index est relevée physiquement (sur place) par le personnel du GRD une fois par an.\n\nLes données sont fournies par Sibelga et Elia. \n\nLes données communiquées peuvent différer d'un rapport à l'autre. Ceci résulte du processus de rectification établis par les acteurs du secteur de l'énergie afin d'améliorer la qualité des données.\n"@fr . + "5454c8ac-5696-4a70-b529-27d43cedacd8" . + "2017-11-29T00:00:00"^^ . + "2018-05-08T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d62A3947A3A310C88D911C8D1D39DAA42 . + "Market shares "@en . + "Marktaandeel "@nl . + "Parts de marché"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d322B10B6203A86CA015B9F44FD12B8DB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d322B10B6203A86CA015B9F44FD12B8DB . + . + . + "Preview" . + . + . + "hierarchy - wfs" . + . + "De stedenbouwkundige zoneverordeningen hebben betrekking op specifieke aspecten van bepaalde gebieden van het grondgebied. Zij dragen bijvoorbeeld bij tot het behoud en de ontwikkeling van een wijk. Zowel het gewest als de gemeenten kunnen ze opstellen."@nl . + "Les règlements d’urbanisme zonés portent sur des aspects spécifiques relatifs à certaines zones du territoire. Ils peuvent être élaborés par la Région ou par les communes. Ils contribuent par exemple à la conservation et au développement d’un quartier."@fr . + "b9023e9e-5986-47ae-bc3f-d68c7540533d" . + "2018-08-24T06:14:30.953423"^^ . + "2018-08-24T06:23:21.915189"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7B0A83B6462DEBA8741B6FA7600E0A36 . + "Gezoneerde verordeningen"@nl . + "Règlements d'urbanisme zonés"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3B419BB42B9323D97DECCF73C9A8E522 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3B419BB42B9323D97DECCF73C9A8E522 . + . + "Brussels Hoofdstedelijk Gewest : een gebouw staat voor elke fysieke constructie bestaande uit vaste buitenmuren, bedekt met een dak, voor altijd bevestigd op een terrein, en bedoeld voor het onderbrengen van een menselijke activiteit en/of dieren, goederen en machines."@nl . + "Brussels-Capital Region : building means any physical construction formed of rigid exterior walls, roofed, permanently attached to land, and designed to accommodate human activity and / or shelter for animals, goods and machinery."@en . + "Région de Bruxelles-Capitale : le bâtiment désigne toute construction physique formée de murs extérieurs rigides, couverte d'un toit, fixée à demeure sur un terrain, et destinée à accueillir une activité humaine et/ou à abriter des animaux, des marchandises et des machines."@fr . + "fdd6a43f-fdbc-4181-9c3d-1efb5abc61f2" . + "2015-09-28T00:00:00"^^ . + "2019-01-29T10:57:58.669533"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dD903FB770CF839C911725C9A81852268 . + "Buildings"@en . + "Bâtiments"@fr . + "Gebouw"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d5890E9EA4B22C403AD5E9E87ACE41839 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d5890E9EA4B22C403AD5E9E87ACE41839 . + . + "taxi_stops - wfs" . + . + . + . + . + "text/xml" . + . + . + . + . + "Information from https://social.brussels"@en . + "L'ensemble des informations hébergées sur Bruxelles Social, la carte sociale des organisations actives dans le domaine social-santé en région de Bruxelles-Capitale.\n\nLes données sont gratuitement mises à disposition sous la licence CC BY.\n\nUn projet pilote de webservices est en cours de création; veuillez adresser toute demande d'information et d'accès aux webservices à la Coördinatrice du service Bruxelles Social : Valérie Wispenninckx (vwispenninckx@cmdc-cdcs.be | 02/639.60.24)"@fr . + "Sociaal Brussel, de tweetalige sociale kaart van organisaties en diensten actief binnen de welzijns- en gezondheidssector in het Brussels Gewest.\nGegevens worden gratis ter beschikking gesteld onder de licentie CC BY.\n\nEr is momenteel een pilootproject gaande dat webservices aanbiedt; gelieve elke informatieaanvraag hier rond voor te leggen aan de coördinator van Sociaal Brussel : Valérie Wispenninckx (vwispenninckx@cmdc-cdcs.be | 02/639.60.24)"@nl . + "ea0deec5-f810-447c-a3b8-759a303d66bf" . + "2018-10-25T00:00:00"^^ . + "2018-10-27T00:00:00"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d839AEB4D9933D25447A9793486A08931 . + "Sociaal.brussels"@nl . + "Social.brussels"@en . + "Social.brussels"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d904C2429CA620CB91CE7C139CDC36DDB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d904C2429CA620CB91CE7C139CDC36DDB . + . + . + "WFS" . + . + "The purpose of the barometer of overnights is to give a monthly view on the bednights spent by tourists in Brussels. It gives a total view of the bednights by month and splits them by purpose of the trip (leisure / professional), by type of accomodation and by country of origin of tourists. The data comes from the National Statistics Office who collects them from hotels in Brussels"@en . + "f03544a1-a01c-4374-b19d-e93697f1ac73" . + "2017-07-28T08:59:58.368114"^^ . + "2018-09-26T13:26:28.206067"^^ . + . + "Aankomsten en Overnachtingen - Gegeven Historiek"@nl . + "Arrivals & Overnights - Data History"@en . + "Arrivées et nuitées - Historique des Données"@fr . + . + "Erkenning van examencentra in koeltechniek"@nl . + "Liste des centres d’examen en technique du froid agréés en Région de Bruxelles-Capitale"@fr . + "57e48d57-2721-4675-99df-9c007dd0835a" . + "2018-10-05T00:00:00"^^ . + "2018-10-06T00:00:00"^^ . + "Erkenning van examencentra in koeltechniek"@nl . + "Liste des centres d’examen en technique du froid agréés en Région de Bruxelles-Capitale"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF4AA0E7103612041E59554867E4A2306 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF4AA0E7103612041E59554867E4A2306 . + . + . + . + . + . + . + . + . + "De personen die erkend zijn als inzamelaar, handelaar en makelaar van gevaarlijk afval mogen alleen de afvalstoffen opgenomen in de onderstaande lijst.\nDe beschrijving van de afvalstoffen die horen bij de codes bevindt zich in het Beschikking 2000/532/EG van de Europese commissie van 3 mei 2000 tot vaststelling van een lijst van afvalstoffen.\n"@nl . + "Les entreprises agréées comme collecteur, négociant et courtier de déchets dangereux ne peuvent reprendre que les déchets indiqués dans la liste ci-dessous.\nLa description des déchets liés aux codes repris se trouve dans la décision de la Commission Européenne 2000/532/CE du 3 mai 2000 établissant une liste de déchets.\n"@fr . + "a5eab045-9c1e-4cc2-bfde-9f40a90bf262" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Aanwijzende lijst van erkende inzamelaars, handelaars en makelaars van gevaarlijk afval"@nl . + "Liste des collecteurs, négociants et courtiers de déchets dangereux "@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dAE3907014DAEF28ABE232BB3B7840BAB . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dAE3907014DAEF28ABE232BB3B7840BAB . + . + "rer_velo - geojson - 2016-01-01" . + . + . + . + . + "25 novembre 1915, en pleine Première guerre mondiale, le Conseil communal de Saint-Gilles prend la décision de créer un espace réservé, au sein du cimetière communal, aux tombes des soldats saint-gillois morts au champ d’honneur. Cette décision aboutit à la création de la crypte militaire une fois la paix rétablie.\nLa pelouse d’honneur fut quant à elle créée par décision du Conseil du 28 avril 1927 afin de rendre hommage aux anciens combattants et aux soldats décédés des suites de maladies contractées au front qui n’étaient dès lors pas admissibles au sein de la crypte militaire. Elle accueille également les dépouilles des soldats alliés, russes et des civils belges. Pour y être enterré, les soldats belges devaient être décédés à Saint-Gilles alors que les combattants alliés et russes devaient y résider, avec obligation notamment pour les soldats russes de posséder une attestation d’invalidité. Les civils belges, hommes ou femmes, quant à eux, devaient être porteurs des médailles commémoratives de la Guerre 14-18 et de la Victoire, ou assimilés aux militaires par ordonnance du Ministère de la Défense Nationale.\nLa Seconde guerre mondiale entraina la création d’une seconde pelouse d’honneur réservée aux dépouilles des soldats saint-gillois morts au front ou des suites de leurs blessures et ce dans un délai de un an à partir du 28 mai 1940, c’est-à-dire un an après la fin de la campagne des 18 jours.\nLa pelouse d’honneur accueille également les restes des résistants saint-gillois morts en service commandé ou des suites de leurs blessures et ce endéans deux ans, à partir du 3 septembre 1945, date de la libération, à l’instar des prisonniers politiques."@fr . + "e5c59623-e1d5-4a97-ae3b-aafb212ad486" . + "2017-10-16T12:58:54.068334"^^ . + "2018-12-05T14:46:14.237247"^^ . + "Sépultures militaires"@fr . + . + "traffic_events - html" . + . + "Données retranscrivant la performance mensuelle du secteur hôtelier en région bruxelloise"@fr . + "e083a504-7ce9-4f76-b0b7-0e64b8469c3b" . + "2017-04-28T13:20:37.969124"^^ . + "2018-10-31T13:59:00.025637"^^ . + . + "Hotels Performance"@en . + "Hotels prestaties"@nl . + "Performance Hôtelière"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d36E341601DFF7A5B5E862A235F79C9CC . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d36E341601DFF7A5B5E862A235F79C9CC . + . + "text/xml" . + . + "0a50e990-2a37-4c25-9156-c9c26b8eed48" . + "2018-12-13T00:00:00"^^ . + "2018-12-13T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d92B17BB0E4314D1C0B539DE5DF349601 . + "Agences d'emploi privées reconnues en RBC"@fr . + "Erkende particuliere bureaus voor arbeidsbemiddeling in het BHG"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d3B6081C4073B928C40D03B078E319E50 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d3B6081C4073B928C40D03B078E319E50 . + . + . + . + . + "tunnelsections - Mobigis" . + . + "Brussels Hoofdstedelijk Gewest: Informatie over het aantal klanten van wie het verbruik wordt gefactureerd door de distributienetbeheerder (“DNB” of “Sibelga”). Het maandelijks aantal klanten wordt uitgesplitst volgens postcode en energietype. \n\nDrie klantencategorieën worden door de DNB bevoorraad in zijn rol van “noodleverancier”:\n\nDe __beschermde klanten__: Het statuut van beschermde klant is een systeem dat werd ingevoerd voor de huishoudelijke klanten die het moeilijk hebben om hun energiefactuur te betalen. Dankzij deze tijdelijke bescherming kunnen gezinnen met betalingsmoeilijkheden de door hun leverancier gevraagde afsluiting vermijden.\nBovendien genieten ze tijdens de duur van de bescherming van het specifiek sociaal tarief, dat voordeliger is dan de prijs die door de commerciële leveranciers wordt aangerekend.\n \nDe __winterklanten__: De afsluiting van een huishoudelijke klant, met toelating van de vrederechter, mag niet worden uitgevoerd tussen 1 oktober en 31 maart. Als een commerciële leverancier of de noodleverancier tijdens deze periode de gerechtelijke ontbinding van het contract verkrijgt, moet Sibelga dus de continuïteit van de levering tegen het specifiek sociaal tarief garanderen tot 31 maart, het einde van de winterstop.\n\nDe __EOC klanten__ : Voor de klanten wier contract afloopt en niet verlengd wordt door hun leverancier en die geen contract afsluiten bij een andere leverancier, zal de energiebevoorrading worden afgesloten. Het gaat hier om zogenaamde EOC-afsluitingen (End of Contract). Tijdens de winterperiode worden deze klanten bevoorraad door Sibelga, totdat het leveringspunt wordt overgenomen door een commerciële leverancier.\n\nHet meegedeelde aantal is een momentopname van de laatste dag van de maand. \n\nDe gegevens worden geleverd door Sibelga. De meegedeelde gegevens kunnen van rapport tot rapport verschillen. Dit is het gevolg van de rectificatieprocessen uitgevoerd door de actoren van de energiesector om de kwaliteit van de gegevens te verbeteren."@nl . + "Brussels-Capital Region: Information on the number of customers whose consumption is billed by the distribution network operator (\"DNO\" or \"Sibelga\"). The monthly number of these customers is broken down by postcode and energy vector. \n\nTwo categories of customers are supplied by the DNO in its role as \"supplier of last resort\":\n\n__Protected customers__: Protected customer status is a protection system set up for residential customers who are struggling to pay their energy bills. This temporary protection allows households experiencing payment difficulties to avoid having their power cut off by their supplier.\nMoreover, during the term of the protection, they benefit from the special social tariff, a lower tariff than the price charged by the commercial suppliers.\n \n__Winter truce customers__: A residential customer cannot have their power cut off between 1 October and 31 March, even if authorised by a justice of the peace . This means that, if a commercial supplier or the supplier of last resort obtains the legal termination of the contract binding it to its customer during this period, Sibelga must ensure the continuity of the supply at the special social rate until 31 March, the end of the winter truce.\n\n__EOC customers__: Customers with an energy contract that is about to expire and that is not being extended by their supplier, and who have not signed a contract with another supplier, have their energy supply cut. These are \"End of Contract (EOC)\" cuts. During the winter break, these customers are supplied by Sibelga until their supply is resumed by a commercial supplier.\n\nThe number provided is an image taken on the last day of the month. \n\nData are provided by Sibelga. The data reported may differ from one report to another. This is the result of the rectification processes used by players in the energy sector in order to improve the quality of the data.\n"@en . + "Région de Bruxelles-Capitale : Information sur le nombre de clients dont la consommation est facturée par le gestionnaire du réseau de distribution (« GRD » ou « Sibelga »). Le nombre mensuel de ces clients est ventilé par code postal et par vecteur d'énergie. \n\nTrois catégories de clients sont fournies par le GRD dans son rôle de \"fournisseur de dernier ressort\":\n\nLes __clients protégés__ : Le statut de client protégé est un système de protection mis en place pour les clients résidentiels qui ont des difficultés à payer leur facture d’énergie. Cette protection temporaire permet aux ménages en difficulté de paiement d’éviter une coupure d’énergie demandée par leur fournisseur.\nDe plus, pendant la durée de la protection, ils bénéficient du tarif social spécifique, tarif plus avantageux que le prix appliqué par les fournisseurs commerciaux.\n \nLes __clients hivernaux__ : La coupure d’un client résidentiel, sur autorisation du juge de paix, ne peut être mise à exécution entre le 1er octobre et le 31 mars. Ainsi, si un fournisseur commercial ou le fournisseur de dernier ressort obtient pendant cette période la résiliation judiciaire du contrat le liant à son client, Sibelga doit assurer la continuité de la fourniture au tarif social spécifique jusqu’au 31 mars, fin de la trêve hivernale.\n\nLes __clients EOC__ : Les clients dont le contrat d’énergie, arrivant à terme, n’est pas prolongé par leur fournisseur et qui n’ont pas signé un contrat auprès d’un autre fournisseur, subissent une coupure de leur fourniture d’énergie. Il s’agit de coupures « End of contract (EOC) ». Pendant la trêve hivernale, ces clients sont alimentés par Sibelga jusqu’à la reprise du point de fourniture par un fournisseur commercial.\n\nLe nombre communiqué est une image prise au dernier jour du mois. \n\nLes données sont fournies par Sibelga. Les données communiquées peuvent différer d'un rapport à l'autre. Ceci résulte des processus de rectification établis par les acteurs du secteur de l'énergie afin d'améliorer la qualité des données.\n"@fr . + "c427b5e7-deaf-4d95-b8fe-74f86c913894" . + "2017-11-29T00:00:00"^^ . + "2018-05-08T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d964B0DAF9CF755F6E5FD8F555D459350 . + "Clientèle sociale régionale"@fr . + "Regional social customers"@en . + "Sociaal, regionaal cliënteel"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d567EEC4D7A008F925B3D177DE80889B5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d567EEC4D7A008F925B3D177DE80889B5 . + . + "Cahier 1 : L'offre de transport à Bruxelles" . + . + . + . + "application/zip" . + . + "Zone de police 5342 (Uccle, Watermael-Boitsfort et Auderghem)" . + . + "Brussels Hoofdstedelijk Gewest: Informatie over de actieve, inactieve en geactiveerde leveringspunten gas en elektriciteit, uitgesplitst volgens het professioneel of huishoudelijk cliënteel en volgens “laag-” of “hoogspanning\" (alleen voor elektriciteit). Voor elektriciteit is een elektrische laagspanning, \"B\", lager dan of gelijk aan 1 kilovolt (1 kV) , terwijl een elektrische hoogspanning, \"H\", hoger is dan een kilovolt (1 kV).\n\nEen leveringspunt is __inactief__ als er voor dit leveringspunt geen enkele leverancier is geregistreerd. Er is geen uitsplitsing per type professioneel of huishoudelijk cliënteel, de informatie is dus niet van toepassing (n.v.t.).\nDe __activeringen__ hebben betrekking op de leveringspunten die naar de status “actief” zijn overgegaan tussen de eerste en de laatste dag van de betrokken maand, terwijl ze de maand voor de genoemde periode een andere status hadden. \n\nDe gegevens worden geleverd door Sibelga en Elia.\n\nDe meegedeelde gegevens kunnen van rapport tot rapport verschillen. Dat is het gevolg van het rectificatieproces uitgevoerd door de actoren van de energiesector om de kwaliteit van de gegevens te verbeteren.\n"@nl . + "Brussels-Capital Region: Information on active, inactive and activated supply points for gas and electricity broken down according to business or residential customers and according to \"low\" or \"high\" voltage (only in electricity). For electricity, a low voltage \"B\" is less than or equal to 1 kilovolt (1 kV), while a high voltage \"H\" is greater than one kilovolt (1 kV).\n\nA point of supply is __inactive__ if no supplier is registered for that access point. There is no breakdown according to business or residential clientele, so the information is not applicable (n/a).\nThe __activations__ relate to points of supply which have switched to \"active\" status between the 1st and last days of the month in question when they were under a different status during the month preceding that period. \n\nData are provided by Sibelga and Elia.\n\nThe data reported may differ from one report to another. This is the result of the process of rectification used by players in the energy sector in order to improve the quality of the data."@en . + "Région de Bruxelles - Capitale : Information sur les points de fourniture actifs, inactifs et activés en gaz et en électricité ventilée selon la clientèle professionnelle ou résidentielle et selon la tension \"basse\" ou \"haute\" (uniquement en électricité). Pour l’électricité, une tension électrique basse, « B », est inférieure ou égale à 1 kilovolt (1 kV) tandis qu’une tension électrique haute, « H », est supérieure à un kilovolt (1 kV).\n\nUn point de fourniture est __inactif__ si aucun fournisseur n'est enregistré, pour ce point d'accès. Il n'y pas de ventilation par type de clientèle professionnel ou résidentiel, l'information est donc non applicable (n/a).\nLes __activations__ concernent les points de fournitures qui sont passés au statut « actif » entre le 1er et le dernier jour du mois considéré alors qu’ils étaient sous un autre statut pendant le mois précédant ladite période. \n\nLes données sont fournies par Sibelga et Elia.\n\nLes données communiquées peuvent différer d'un rapport à l'autre. Ceci résulte du processus de rectification établi par les acteurs du secteur de l'énergie afin d'améliorer la qualité des données.\n"@fr . + "b941643b-8c1f-4a8e-a938-e2b62a8155b1" . + "2017-11-29T00:00:00"^^ . + "2018-05-08T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d23B8BB7B9ADBD9419C195E57E4CFE089 . + "Leveringspunten "@nl . + "Points de fournitures"@fr . + "Points of supply "@en . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d94EDE7F54CE24412D64FFBB70323540A . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d94EDE7F54CE24412D64FFBB70323540A . + . + . + "Katern 2: De verplaatsingsgewoonten in Brussel" . + . + . + "Lijst van de overheidsopdrachten 2017 (jaarelijkse bijwerking)"@nl . + "Liste des marchés publics 2017 (mise à jour annuelle)"@fr . + "e764365b-aed2-4b0c-a627-058b241b0a89" . + "2018-08-16T13:20:01.949783"^^ . + "2018-08-16T00:00:00"^^ . + "Marchés publics"@fr . + "Overheidsopdrachten"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d1145EF867677852CE553D812ACFA63A5 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d1145EF867677852CE553D812ACFA63A5 . + . + "Preview" . + . + . + . + . + . + . + . + . + . + . + . + "On-street parking regulation zones in Brussels-Capital Region"@en . + "Op de openbare weg parkeerreglementering in het Brussels Hoofdstedelijk Gewest"@nl . + "Réglementation du stationnement en voirie en Région de Bruxelles-Capitale"@fr . + "c43d7443-3e5a-4c6a-ae07-743153fa35e6" . + "2017-06-23T11:22:11.560401"^^ . + "2018-10-23T07:33:48.134768"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d14378FFF0C0BD3C80F90963B3FA4F7A6 . + "On-street parking regulation zones"@en . + "Op de openbare weg parkeerreglementering"@nl . + "Réglementation du stationnement en voirie"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dF59082FE2CE120EF836EB6373C323D8D . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dF59082FE2CE120EF836EB6373C323D8D . + . + . + "Le trafic routier est mesuré en plusieurs endroits de la Région Bruxelloise, au moyen de caméras ou de boucles électromagnétiques. Vous pouvez utiliser l’API ou les services cartographiques pour accéder aux données. Pour le moment, les données sont uniquement diffusées en temps réel ; la diffusion de données historiques est envisagée dans le futur.\n\n__API: https://data-mobility.brussels/traffic/api/counts__\n\nLien vers la carte des points de comptages: [Mobigis](https://data-mobility.brussels/mobigis/fr/?x=486153.44345781335&y=6592969.096448178&zoom=13&baselayer=urbis_grey&layers=traffic_live_geom%3BTunnels%3B&opacity=1%3B1%3B&filter=)\n\n[![img mobigis extract] (https://data-mobility.brussels/media/img/traffic_count.jpg)](https://data-mobility.brussels/mobigis/fr/?x=486153.44345781335&y=6592969.096448178&zoom=13&baselayer=urbis_grey&layers=traffic_live_geom%3BTunnels%3B&opacity=1%3B1%3B&filter=)"@fr . + "On several locations in the Brussels Region, traffic is measured using magnetic loops or cameras. To access this data, you can use the API or the geowebservices. For the moment, only real-time data is available, historical data is coming.\n\n__API: https://data-mobility.brussels/traffic/api/counts__\n\nDirect link to the countings on map: [Mobigis](https://data-mobility.brussels/mobigis/fr/?x=486153.44345781335&y=6592969.096448178&zoom=13&baselayer=urbis_grey&layers=traffic_live_geom%3BTunnels%3B&opacity=1%3B1%3B&filter=)\n\n[![img mobigis extract] (https://data-mobility.brussels/media/img/traffic_count.jpg)](https://data-mobility.brussels/mobigis/fr/?x=486153.44345781335&y=6592969.096448178&zoom=13&baselayer=urbis_grey&layers=traffic_live_geom%3BTunnels%3B&opacity=1%3B1%3B&filter=)"@en . + "Op verschillende plaatsen in het Brussels Gewest wordt het verkeer geteld door gebruik te maken van elektromagnetische tellussen of camera’s. De data is toegankelijk via een API en via geowebservices. Op dit ogenblik is er enkel Real Time data beschikbaar, in de toekomst zal eveneens historische data beschikbaar worden gemaakt.\n\n__API: https://data-mobility.brussels/traffic/api/counts__\n\nLink naar de telpunten op kaart: [Mobigis](https://data-mobility.brussels/mobigis/nl/?x=486153.44345781335&y=6592969.096448178&zoom=13&baselayer=urbis_grey&layers=traffic_live_geom%3BTunnels%3B&opacity=1%3B1%3B&filter=)\n\n[![img mobigis extract] (https://data-mobility.brussels/media/img/traffic_count.jpg)](https://data-mobility.brussels/mobigis/fr/?x=486153.44345781335&y=6592969.096448178&zoom=13&baselayer=urbis_grey&layers=traffic_live_geom%3BTunnels%3B&opacity=1%3B1%3B&filter=)"@nl . + "9a047e86-3947-424a-84e8-a192f18a735a" . + "2019-03-05T12:49:45.760963"^^ . + "2019-03-07T14:11:59.901354"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dCDA61420E8AB9F9A1CEDB119FF9A03C2 . + "Comptages traffic"@fr . + "Traffic counting"@en . + "Verkeerstellingen"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d8849B003B708E7DD7746598C1F546BB3 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d8849B003B708E7DD7746598C1F546BB3 . + . + "taxi_stops - geojson - 2016-01-01" . + . + . + . + . + "application/zip" . + . + . + . + . + . + . + . + "De opleidingen erkend door de erkenningscommissie in het kader van het betaald educatief verlof."@nl . + "Les formations reconnues par la Commission d'agrément dans le cadre du congé-éducation payé."@fr . + "7cd74931-c879-467f-adf2-cfbb59a2c424" . + "2018-12-13T00:00:00"^^ . + "2018-12-13T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d9C270F8D208124A178E6DB5D4083AA5D . + "Betaald educatief verlof - Erkenningscommissie"@nl . + "Congé-éducation payé - Commission d'agrément"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE95269EBDCE3E26F69BC9706ED810A56 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE95269EBDCE3E26F69BC9706ED810A56 . + . + "3D modeling of buildings"@en . + "3D-modellen van gebouwen"@nl . + "Modélisation 3D des bâtiments"@fr . + "e35fb9a8-619e-4e68-9063-a808d75aa13e" . + "2016-02-29T00:00:00"^^ . + "2019-03-18T16:12:25.101281"^^ . + "Buildings 3D "@en . + "Bâtiments 3D"@fr . + "Gebouwen 3D"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE7EE0B40716784694B036509823F2A5E . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE7EE0B40716784694B036509823F2A5E . + . + "icr - gpx - 2017-01-01" . + . + "Lijst van sportverenigingen (gesubsidieerd door de gemeente) - (jaarlijkse bijwerking)"@nl . + "Liste de associations sportives (subsidiées par la commune) - (mise à jour annuelle)"@fr . + "ef01e4cb-57ff-4b5e-9def-bdb31f199054" . + "2018-08-16T12:59:53.585128"^^ . + "2018-08-16T00:00:00"^^ . + "Associations sportives (subsidiées)"@fr . + "Sportverenigingen (gesubsidieerd)"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6B3D9739545C9364D3DF204541FDA29F . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6B3D9739545C9364D3DF204541FDA29F . + . + . + . + . + . + "Liste des rues d'Evere"@fr . + "Stratenlijst van Evere"@nl . + "2985d203-a374-4016-a0f5-9bd5e398f724" . + "2019-02-11T00:00:00"^^ . + "2019-02-11T00:00:00"^^ . + "Nom des rues d'Evere"@fr . + "Straatnamen van Evere"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dE767622D4D208738EA4C0C970C431D89 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dE767622D4D208738EA4C0C970C431D89 . + . + . + . + . + . + . + . + . + . + . + . + . + . + . + "ef763e97-af1a-4dd7-b1f2-da06d7a30c59" . + "2018-12-14T00:00:00"^^ . + "2018-12-14T00:00:00"^^ . + . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d362E839840D38165D85EAE6FCE2B9D71 . + "Authorised trainings in the context of the training fund service vouchers"@en . + "Formations admises dans le cadre du fonds de formation titres-services"@fr . + "Toegelaten opleidingen in het kader van opleidingsfonds dienstencheques"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d635F101DA107D18104171D3AE85E95BF . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d635F101DA107D18104171D3AE85E95BF . + . + "application/zip" . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : de entiteit « Green Block » (of groenzone) identificeert de delen van het grondgebied ingenomen door groenstroken die dienen tot ontspanning (beplant met gras, bomen, eventueel bloemen, sierbomen en sierstruiken, en vaak versierd met waterpartijen en paden).\n\nDe groenzones werden getekend op basis van diverse plannen (fotogrammetrische opmetingen, NGI-kaarten, kaarten van de gemeenten...).\n \nZij hebben een uiteenlopende nauwkeurigheid omdat ze uit verschillende bronnen afkomstig zijn.\n\nDe entiteit « Green Block » (of groenzone) wordt voorgesteld door drie verschillende types:\n\n- GB-A: grasstroken (bepaalde met gras of bomen beplante middenbermen);\n- GB-B: de parken (de waterpartijen in deze parken zijn niet uitgesloten);\n- GB-F: bossen of wouden (delen van het Zoniënwoud die in het Brussels Gewest gelegen zijn)."@nl . + "Brussels-Capital Region : entity \"Green Block\" (or green area) identifies ROW floor of approval vegetated areas (lawn, trees, possibly planted with flowers and ornamental trees and bushes, and often lined ponds and paths).\n\nGreen areas have been designed on the basis of various plans (photogrammetric surveys, detailed maps, maps of towns, ...). They have a heterogeneous precision because of the diversity of origin.\n\nThe entity \"Green Block\" (or green area) is represented using three different types:\n- GB-A: grassed (some grassy berm plants or trees) bands;\n- GB-B: parks (water parks located in these areas are not excluded);\n- GB-F: forests or wood (islets of the Sonian Forest located in the Brussels Region).\n"@en . + "Région de Bruxelles-Capitale : l'entité « Green Block » (ou zone verte) identifie les emprises au sol des espaces d'agrément végétalisés (engazonné, arboré, éventuellement planté de fleurs et d'arbres et buissons d'ornement, et souvent garni de pièces d'eau et cheminements).\n\nLes zones vertes ont été dessinées sur base de plans divers (levés photogrammétriques, cartes IGN, cartes des communes, …). Elles ont une précision hétérogène du fait de la diversité de provenance.\n\nL'entité « Green Block » (ou zone verte) est représentée via trois types différents :\n- GB-A : les bandes engazonnées (certaines bermes centrales herbeuses ou arborées) ;\n- GB-B : les parcs (les zones d’eau situés dans ces parcs ne sont pas exclues) ;\n- GB-F : les forêts ou bois (îlots de la Forêt de Soignes situés en Région Bruxelloise)."@fr . + "d3911d5d-bac0-4fa0-8d3a-5b8f87a06e84" . + "2015-03-30T00:00:00"^^ . + "2019-01-29T10:52:56.826401"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dB8BECFF6BAF31300DF9D22C2B43500F9 . + "Espaces verts"@fr . + "Green block"@en . + "Groenzone"@nl . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d118C647FBE0DB107B93D2D6D8526D1FE . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d118C647FBE0DB107B93D2D6D8526D1FE . + . + . + . + . + . + . + . + . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : de entiteit BH03 vertegenwoordigt punten van hoogte die aan de bodem door luchtfotogrammetrie worden opgemerkt, grootschalig. Deze punten bevinden zich voornamelijk op openbaar gebied."@nl . + "Brussels-Capital Region : BH03 entity represents the points elevation ground surveys by aerial photogrammetry scale. These points are essentially in the public domain."@en . + "Région de Bruxelles-Capitale : l'entité BH03 représente des points d'altitude relevés au sol par photogrammétrie aérienne à grande échelle. Ces points se trouvent essentiellement dans le domaine public."@fr . + "9cd66984-6da6-47b1-aa09-b43e9ded3353" . + "2015-03-30T00:00:00"^^ . + "2019-01-29T10:59:59.432979"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d208362642605765D41E97A5F8191E91B . + "Altitude points"@en . + "Hoogtepunten"@nl . + "Points d'altitude"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122dBEE33AE874DC0320C0075AFC23F94BF7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122dBEE33AE874DC0320C0075AFC23F94BF7 . + . + . + . + . + . + "Lijst van de erkende EPB-adviseurs (rechtspersonen)"@nl . + "Liste des agréments en matière de réglementation PEB (chauffage, climatisation, travaux et cirtification)."@fr . + "4395c9fb-ffab-403b-9ffc-e982b11ffbfb" . + "2018-08-20T00:00:00"^^ . + "2018-10-10T00:00:00"^^ . + "Lijst van de erkende EPB-adviseurs (rechtspersonen)"@nl . + "Liste des conseillers PEB agréés (personnes morales)"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d6DD8823B931E079AA77D967405159BE7 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d6DD8823B931E079AA77D967405159BE7 . + . + . + . + . + "application/zip" . + . + . + . + . + . + "http://wfs.ibgebim.be/cgi-bin/tinyows.fcgi?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=IBGE:wsl_bruenvi_protection_area_sonian_forest&SRSNAME=EPSG:31370" . + . + . + . + . + "Brussels Hoofdstedelijk Gewest : de PW-entiteit stemt met de officiële database van verwijzing van benaming en codificatie van de openbare wegen gelegen overeen op het grondgebied van de Regio van Brussel-Hoofdstad"@nl . + "Brussels-Capital Region : entity PW is the database reference name and official codification of public roads within the territory of the Brussels-Capital"@en . + "Région de Bruxelles-Capitale : l'entité PW correspond à la base de données de référence de dénomination et de codification officielles des voies publiques situées sur le territoire de la Région de Bruxelles-Capitale"@fr . + "a2dd324e-5bfb-41a8-b17e-09d6ec2cc356" . + "2015-03-30T00:00:00"^^ . + "2019-01-29T10:52:22.938868"^^ . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d7A86E59D559480E5063C00374C120C60 . + "Openbare Weg"@nl . + "Public way"@en . + "Voies publiques"@fr . + _:genid2d1c0fdc56bb274053a43550120a7fab0122d52C0E83B4FADCB042AF748DA915CB072 . +_:genid2d1c0fdc56bb274053a43550120a7fab0122d52C0E83B4FADCB042AF748DA915CB072 . + . + . + . + . + . From af823907fe2c3b5a23a04b2354fea0549c6260e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 13:25:16 +0200 Subject: [PATCH 16/79] wip --- ...ctPredicateIndexDistributionDebugTest.java | 196 ++++++++++++++++++ ...dicateIndexDistributionRegressionTest.java | 34 +-- 2 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java new file mode 100644 index 0000000000..6859d11b76 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.DCTERMS; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class SubjectPredicateIndexDistributionDebugTest { + + private static final String DATASET_RESOURCE = "temp.nquad"; + private static final String DATASET_IRI = "http://data.gov.be/dataset/brussels/3fded591-0cda-485f-97e7-3b982c7ff34b"; + + @Test + void debugListLanguages(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + List lmdbLangs = getLanguages(lmdbConn); + List memLangs = getLanguages(memoryConn); + + System.out.println("LMDB languages: " + lmdbLangs); + System.out.println("MEM languages: " + memLangs); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void debugFindFirstMismatch(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository referenceRepository = new SailRepository(new MemoryStore()); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + referenceRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection memoryConn = referenceRepository.getConnection()) { + loadDataset(memoryConn); + } + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection(); + SailRepositoryConnection referenceConn = referenceRepository.getConnection()) { + + try (var statements = referenceConn.getStatements(null, null, null, true)) { + int count = 0; + for (Statement stmt : statements) { + count++; + lmdbConn.begin(IsolationLevels.NONE); + memoryConn.begin(IsolationLevels.NONE); + try { + lmdbConn.add(stmt); + memoryConn.add(stmt); + } finally { + lmdbConn.commit(); + memoryConn.commit(); + } + + long countLmdb = org.eclipse.rdf4j.query.QueryResults + .count(lmdbConn.getStatements(SimpleValueFactory.getInstance().createIRI(DATASET_IRI), + DCTERMS.LANGUAGE, null, true)); + long countMem = org.eclipse.rdf4j.query.QueryResults + .count(memoryConn.getStatements(SimpleValueFactory.getInstance().createIRI(DATASET_IRI), + DCTERMS.LANGUAGE, null, true)); + if (countLmdb != countMem) { + System.out.println("Mismatch after adding statement #" + count + ": " + stmt); + System.out.println("LMDB count=" + countLmdb + ", MEM count=" + countMem); + break; + } + } + } + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + referenceRepository.shutDown(); + } + } + + @Test + void debugListLanguagesPsocOnly(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("psoc"); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + List lmdbLangs = getLanguages(lmdbConn); + List memLangs = getLanguages(memoryConn); + + System.out.println("[psoc] LMDB languages: " + lmdbLangs); + System.out.println("[psoc] MEM languages: " + memLangs); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + @Test + void debugListLanguagesNoDupsortRead(@TempDir Path tempDir) throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + config.setDupsortRead(false); + SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.resolve("lmdb").toFile(), config)); + SailRepository memoryRepository = new SailRepository(new MemoryStore()); + + lmdbRepository.init(); + memoryRepository.init(); + + try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + loadDataset(lmdbConn); + loadDataset(memoryConn); + + List lmdbLangs = getLanguages(lmdbConn); + List memLangs = getLanguages(memoryConn); + + System.out.println("[no-dupsort] LMDB languages: " + lmdbLangs); + System.out.println("[no-dupsort] MEM languages: " + memLangs); + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + private static void loadDataset(SailRepositoryConnection connection) throws IOException { + connection.begin(IsolationLevels.NONE); + try (InputStream data = getResource(DATASET_RESOURCE)) { + connection.add(data, "", RDFFormat.TURTLE); + } + connection.commit(); + } + + private static InputStream getResource(String name) { + InputStream stream = SubjectPredicateIndexDistributionDebugTest.class + .getClassLoader() + .getResourceAsStream(name); + assertNotNull(stream, "Missing resource: " + name); + return stream; + } + + private static List getLanguages(SailRepositoryConnection connection) { + var vf = SimpleValueFactory.getInstance(); + var dataset = vf.createIRI(DATASET_IRI); + try (var iter = connection.getStatements(dataset, DCTERMS.LANGUAGE, null, true)) { + return Iterations.stream(iter) + .map(Statement::getObject) + .map(Value::stringValue) + .sorted() + .collect(Collectors.toList()); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java index 8e92b0caf2..9d234a61d3 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java @@ -10,6 +10,17 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + import org.eclipse.rdf4j.common.iteration.Iterations; import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; @@ -31,17 +42,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertEquals; - class SubjectPredicateIndexDistributionRegressionTest { private static final String DATASET_RESOURCE = "temp.nquad"; @@ -95,7 +95,7 @@ void countLanguage(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -311,7 +311,7 @@ void countLanguageDifferentIndexes1(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -336,7 +336,7 @@ void countLanguageDifferentIndexes2(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -361,7 +361,7 @@ void countLanguageDifferentIndexes3(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -386,7 +386,7 @@ void countLanguageDifferentIndexes4(@TempDir Path tempDir) throws Exception { memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadDataset(lmdbConn); loadDataset(memoryConn); @@ -412,7 +412,7 @@ void lmdbExposesAllLanguagesForSimpleDataset(@TempDir Path tempDir) throws Excep memoryRepository.init(); try (SailRepositoryConnection lmdbConn = lmdbRepository.getConnection(); - SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { + SailRepositoryConnection memoryConn = memoryRepository.getConnection()) { loadSimpleDataset(lmdbConn); loadSimpleDataset(memoryConn); From 2b295e62725fb95523c93bbdaa3c4b10f7180d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 21:46:58 +0200 Subject: [PATCH 17/79] wip --- ...ectPredicateIndexDistributionDebugTest.java | 18 ++++++++++++++++-- ...edicateIndexDistributionRegressionTest.java | 14 +++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java index 6859d11b76..d246ac7815 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionDebugTest.java @@ -52,10 +52,12 @@ void debugListLanguages(@TempDir Path tempDir) throws Exception { loadDataset(memoryConn); List lmdbLangs = getLanguages(lmdbConn); + List lmdbExplicit = getLanguagesExplicitOnly(lmdbConn); List memLangs = getLanguages(memoryConn); - System.out.println("LMDB languages: " + lmdbLangs); - System.out.println("MEM languages: " + memLangs); + System.out.println("LMDB languages (incl inf): " + lmdbLangs); + System.out.println("LMDB languages (explicit): " + lmdbExplicit); + System.out.println("MEM languages: " + memLangs); } finally { lmdbRepository.shutDown(); memoryRepository.shutDown(); @@ -193,4 +195,16 @@ private static List getLanguages(SailRepositoryConnection connection) { .collect(Collectors.toList()); } } + + private static List getLanguagesExplicitOnly(SailRepositoryConnection connection) { + var vf = SimpleValueFactory.getInstance(); + var dataset = vf.createIRI(DATASET_IRI); + try (var iter = connection.getStatements(dataset, DCTERMS.LANGUAGE, null, false)) { + return Iterations.stream(iter) + .map(Statement::getObject) + .map(Value::stringValue) + .sorted() + .collect(Collectors.toList()); + } + } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java index 9d234a61d3..9c17ebdabd 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java @@ -99,10 +99,22 @@ void countLanguage(@TempDir Path tempDir) throws Exception { loadDataset(lmdbConn); loadDataset(memoryConn); + System.out.println("LMDB's count of languages for dataset: "); + String lmdbRes = QueryResults.toString(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true), + "\n"); + System.out.println(lmdbRes); + + System.out.println(); + System.out.println("MemoryStore's count of languages for dataset: "); + String memRes = QueryResults.toString(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true), + "\n"); + System.out.println(memRes); + long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); - assertEquals(countl, countm); + assertEquals(3, countl); + assertEquals(3, countm); } finally { lmdbRepository.shutDown(); From 3eac7e995ea74bad8a8cd9905c3349c0580387ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 21:49:30 +0200 Subject: [PATCH 18/79] wip --- .../lmdb/SubjectPredicateIndexDistributionRegressionTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java index 9c17ebdabd..3bdccaef46 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java @@ -113,8 +113,8 @@ void countLanguage(@TempDir Path tempDir) throws Exception { long countl = QueryResults.count(lmdbConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); long countm = QueryResults.count(memoryConn.getStatements(DATASET_IRI, DCTERMS.LANGUAGE, null, true)); - assertEquals(3, countl); - assertEquals(3, countm); + assertEquals(3, countm, "Expected 3 languages for dataset in Memory store"); + assertEquals(3, countl, "Expected 3 languages for dataset in LMDB store"); } finally { lmdbRepository.shutDown(); From 419e62c791ee687397178236c9eb080b3b59c289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 22:27:13 +0200 Subject: [PATCH 19/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 7 +- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 78 ++++++++++++++++++- .../sail/lmdb/SubjectPredicateIndexTest.java | 25 ++++++ 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index f4ea5da13c..3fa07ca1d6 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.lmdb; import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; +import static org.lwjgl.util.lmdb.LMDB.MDB_FIRST_DUP; import static org.lwjgl.util.lmdb.LMDB.MDB_GET_CURRENT; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT_DUP; @@ -120,7 +121,8 @@ interface FallbackSupplier { boolean positioned = positionOnPrefix(); if (positioned) { - positioned = loadDuplicate(MDB_GET_CURRENT); + // Ensure we start at the first duplicate for the (s,p) key + positioned = loadDuplicate(MDB_FIRST_DUP); } if (!positioned) { closeInternal(false); @@ -153,7 +155,7 @@ public long[] next() { if (txnRefVersion != txnRef.version()) { E(mdb_cursor_renew(txn, cursor)); txnRefVersion = txnRef.version(); - if (!positionOnPrefix() || !loadDuplicate(MDB_GET_CURRENT)) { + if (!positionOnPrefix() || !loadDuplicate(MDB_FIRST_DUP)) { closeInternal(false); return null; } @@ -249,6 +251,7 @@ private boolean loadDuplicate(int op) throws IOException { private void readCurrentDuplicate(ByteBuffer buffer) { ByteBuffer duplicate = buffer.duplicate(); int offset = duplicate.position(); + // values are stored as two 8-byte little-endian longs currentObj = readLittleEndianLong(duplicate, offset); currentContext = readLittleEndianLong(duplicate, offset + Long.BYTES); } 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 34a4e52134..34e9c7f2ee 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 @@ -137,6 +137,10 @@ class TripleStore implements Closeable { * The key used to store the triple indexes specification that specifies which triple indexes exist. */ private static final String INDEXES_KEY = "triple-indexes"; + /** + * The key used to store whether dupsort indices are enabled. + */ + private static final String DUPSORT_INDICES_KEY = "dupsort-indices"; /** * The version number for the current triple store. *
    @@ -239,11 +243,11 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in File propFile = new File(this.dir, PROPERTIES_FILE); boolean isNewStore = !propFile.exists(); - this.dupsortIndices = isNewStore && config.isDupsortIndices(); this.dupsortRead = config.isDupsortRead(); String indexSpecStr = config.getTripleIndexes(); if (isNewStore) { // newly created lmdb store + this.dupsortIndices = config.isDupsortIndices(); properties = new Properties(); Set indexSpecs = parseIndexSpecList(indexSpecStr); @@ -260,6 +264,8 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in properties = loadProperties(propFile); checkVersion(); + this.dupsortIndices = determineExistingDupsortSetting(config.isDupsortIndices()); + // Initialize existing indexes Set indexSpecs = getIndexSpecs(); initIndexes(indexSpecs, config.getTripleDBSize()); @@ -277,11 +283,22 @@ public int compareRegion(ByteBuffer array1, int startIdx1, ByteBuffer array2, in subjectPredicateIndex = dupsortIndices ? new SubjectPredicateIndex() : null; - if (!String.valueOf(SCHEME_VERSION).equals(properties.getProperty(VERSION_KEY)) - || !indexSpecStr.equals(properties.getProperty(INDEXES_KEY))) { - // Store up-to-date properties + boolean propertiesDirty = false; + if (!String.valueOf(SCHEME_VERSION).equals(properties.getProperty(VERSION_KEY))) { properties.setProperty(VERSION_KEY, String.valueOf(SCHEME_VERSION)); + propertiesDirty = true; + } + if (!indexSpecStr.equals(properties.getProperty(INDEXES_KEY))) { properties.setProperty(INDEXES_KEY, indexSpecStr); + propertiesDirty = true; + } + String dupsortProperty = properties.getProperty(DUPSORT_INDICES_KEY); + String dupsortValue = Boolean.toString(dupsortIndices); + if (!dupsortValue.equals(dupsortProperty)) { + properties.setProperty(DUPSORT_INDICES_KEY, dupsortValue); + propertiesDirty = true; + } + if (propertiesDirty) { storeProperties(propFile); } } @@ -303,6 +320,45 @@ private void checkVersion() throws SailException { } } + private boolean determineExistingDupsortSetting(boolean requestedDupsort) throws IOException { + String storedValue = properties.getProperty(DUPSORT_INDICES_KEY); + if (storedValue != null) { + return Boolean.parseBoolean(storedValue); + } + if (hasSubjectPredicateDupIndex()) { + return true; + } + if (requestedDupsort) { + logger.debug("Dupsort indices requested but not present on disk for store at {}", dir); + } + return false; + } + + private boolean hasSubjectPredicateDupIndex() throws IOException { + return readTransaction(env, (stack, txn) -> { + IntBuffer ip = stack.mallocInt(1); + + int rc = mdb_dbi_open(txn, "sp-dup", 0, ip); + if (rc == MDB_NOTFOUND) { + return false; + } + E(rc); + int explicitDbi = ip.get(0); + try { + rc = mdb_dbi_open(txn, "sp-dup-inf", 0, ip); + if (rc == MDB_NOTFOUND) { + return false; + } + E(rc); + int inferredDbi = ip.get(0); + mdb_dbi_close(env, inferredDbi); + return true; + } finally { + mdb_dbi_close(env, explicitDbi); + } + }); + } + private Set getIndexSpecs() throws SailException { String indexesStr = properties.getProperty(INDEXES_KEY); @@ -542,6 +598,8 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c subj, pred, obj, context, explicit, txn); if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj == -1 && context == -1) { assert context == -1 && obj == -1 : "subject-predicate index can only be used for (s,p,?,?) patterns"; + // Use SP dup iterator, but union with the standard iterator to guard against any edge cases + // in SP storage/retrieval; de-duplicate at the record level. return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, fallbackSupplier); } @@ -1375,6 +1433,17 @@ protected void updateFromCache() throws IOException { } } + // Ensure sufficient space before writing to the subject-predicate dup index + if (requiresResize()) { + // resize map if required + E(mdb_txn_commit(writeTxn)); + mapSize = LmdbUtil.autoGrowMapSize(mapSize, pageSize, 0); + E(mdb_env_set_mapsize(env, mapSize)); + logger.debug("resized map to {}", mapSize); + E(mdb_txn_begin(env, NULL, 0, pp)); + writeTxn = pp.get(0); + } + if (subjectPredicateIndex != null && r != null) { if (r.add) { subjectPredicateIndex.put(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, @@ -2344,6 +2413,7 @@ void put(long txn, long subj, long pred, long obj, long context, boolean explici toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); dupKeyBuf.flip(); dupValBuf.clear(); + // store as two 8-byte little-endian longs writeLongLittleEndian(dupValBuf, obj); writeLongLittleEndian(dupValBuf, context); dupValBuf.flip(); diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java index 689081ce66..12eb25adc5 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java @@ -63,6 +63,31 @@ void subjectPredicatePatternUsesDupIterator() throws Exception { } } + @Test + void subjectPredicateDupsortPersistsAcrossRestart() throws Exception { + tripleStore.close(); + + LmdbStoreConfig config = new LmdbStoreConfig("posc"); + config.setDupsortIndices(true); + config.setDupsortRead(true); + tripleStore = new TripleStore(dataDir, config, null); + + tripleStore.startTransaction(); + tripleStore.storeTriple(1, 2, 5, 0, true); + tripleStore.commit(); + + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + RecordIterator iter = tripleStore.getTriples(txn, 1, 2, -1, -1, true)) { + assertThat(iter).isInstanceOf(LmdbDupRecordIterator.class); + + int count = 0; + while (iter.next() != null) { + count++; + } + assertEquals(3, count); + } + } + @Test void predicateObjectPatternFallsBackToStandardIterator() throws Exception { try (Txn txn = tripleStore.getTxnManager().createReadTxn(); From 4f4518f185b7ad7c3a2e20fc8b3873b692c6700c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 23:01:41 +0200 Subject: [PATCH 20/79] all tests pass --- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 35 +++++++++---------- .../sail/lmdb/SubjectPredicateIndexTest.java | 31 ++++++++++++++++ 2 files changed, 48 insertions(+), 18 deletions(-) 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 34e9c7f2ee..40568370a8 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 @@ -1431,26 +1431,25 @@ protected void updateFromCache() throws IOException { } } } - } - // Ensure sufficient space before writing to the subject-predicate dup index - if (requiresResize()) { - // resize map if required - E(mdb_txn_commit(writeTxn)); - mapSize = LmdbUtil.autoGrowMapSize(mapSize, pageSize, 0); - E(mdb_env_set_mapsize(env, mapSize)); - logger.debug("resized map to {}", mapSize); - E(mdb_txn_begin(env, NULL, 0, pp)); - writeTxn = pp.get(0); - } + if (subjectPredicateIndex != null) { + if (requiresResize()) { + // resize map if required before touching the dup index + E(mdb_txn_commit(writeTxn)); + mapSize = LmdbUtil.autoGrowMapSize(mapSize, pageSize, 0); + E(mdb_env_set_mapsize(env, mapSize)); + logger.debug("resized map to {}", mapSize); + E(mdb_txn_begin(env, NULL, 0, pp)); + writeTxn = pp.get(0); + } - if (subjectPredicateIndex != null && r != null) { - if (r.add) { - subjectPredicateIndex.put(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, - stack); - } else { - subjectPredicateIndex.delete(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, - stack); + if (r.add) { + subjectPredicateIndex.put(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, + stack); + } else { + subjectPredicateIndex.delete(writeTxn, r.quad[0], r.quad[1], r.quad[2], r.quad[3], explicit, + stack); + } } } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java index 12eb25adc5..1e8fe187ea 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; +import java.lang.reflect.Field; import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; @@ -88,6 +89,36 @@ void subjectPredicateDupsortPersistsAcrossRestart() throws Exception { } } + @Test + void subjectPredicateDupsortCacheFlushMaintainsIndex() throws Exception { + tripleStore.startTransaction(); + + TxnRecordCache cache = new TxnRecordCache(dataDir); + try { + cache.storeRecord(new long[] { 1, 2, 5, 0 }, true); + Field recordCacheField = TripleStore.class.getDeclaredField("recordCache"); + recordCacheField.setAccessible(true); + recordCacheField.set(tripleStore, cache); + + tripleStore.updateFromCache(); + recordCacheField.set(tripleStore, null); + } finally { + // updateFromCache closes the cache, nothing to do here + } + tripleStore.commit(); + + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + RecordIterator iter = tripleStore.getTriples(txn, 1, 2, -1, -1, true)) { + assertThat(iter).isInstanceOf(LmdbDupRecordIterator.class); + + int count = 0; + while (iter.next() != null) { + count++; + } + assertEquals(3, count); + } + } + @Test void predicateObjectPatternFallsBackToStandardIterator() throws Exception { try (Txn txn = tripleStore.getTxnManager().createReadTxn(); From a28cc2359643ac9c05af75b9ab1e911dfa62e2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 23:05:59 +0200 Subject: [PATCH 21/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 102 ++++++++++++------ 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 3fa07ca1d6..e8c116284f 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -11,10 +11,8 @@ package org.eclipse.rdf4j.sail.lmdb; import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; -import static org.lwjgl.util.lmdb.LMDB.MDB_FIRST_DUP; -import static org.lwjgl.util.lmdb.LMDB.MDB_GET_CURRENT; +import static org.lwjgl.util.lmdb.LMDB.MDB_GET_MULTIPLE; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; -import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT_DUP; import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_KEY; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; @@ -26,6 +24,7 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; import org.eclipse.rdf4j.sail.SailException; @@ -37,9 +36,6 @@ /** * A dupsort/dupfixed-optimized record iterator using MDB_GET_MULTIPLE/NEXT_MULTIPLE to reduce JNI calls. - * - * This iterator is hard coded to only work with the SP index and assumes that the object and context keys are free - * (-1), */ class LmdbDupRecordIterator implements RecordIterator { @@ -70,9 +66,9 @@ interface FallbackSupplier { private ByteBuffer prefixKeyBuf; private long[] prefixValues; - private boolean hasCurrentDuplicate = false; - private long currentObj; - private long currentContext; + private ByteBuffer dupBuf; + private int dupPos; + private int dupLimit; private int lastResult; private boolean closed = false; @@ -121,8 +117,7 @@ interface FallbackSupplier { boolean positioned = positionOnPrefix(); if (positioned) { - // Ensure we start at the first duplicate for the (s,p) key - positioned = loadDuplicate(MDB_FIRST_DUP); + positioned = primeDuplicateBlock(); } if (!positioned) { closeInternal(false); @@ -155,25 +150,53 @@ public long[] next() { if (txnRefVersion != txnRef.version()) { E(mdb_cursor_renew(txn, cursor)); txnRefVersion = txnRef.version(); - if (!positionOnPrefix() || !loadDuplicate(MDB_FIRST_DUP)) { + if (!positionOnPrefix() || !primeDuplicateBlock()) { closeInternal(false); return null; } } while (true) { - if (!hasCurrentDuplicate) { - closeInternal(false); - return null; + if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { + long v3; + long v4; + if (true) { + v3 = readLittleEndianLong(dupBuf, dupPos); + v4 = readLittleEndianLong(dupBuf, dupPos + Long.BYTES); + dupPos += Long.BYTES * 2; + } else { + v3 = dupBuf.getLong(dupPos); + v4 = dupBuf.getLong(dupPos + Long.BYTES); + dupPos += Long.BYTES * 2; + } + fillQuadFromPrefixAndValue(v3, v4); + return quad; } - long v3 = currentObj; - long v4 = currentContext; - if (!loadDuplicate(MDB_NEXT_DUP)) { - hasCurrentDuplicate = false; - } - fillQuadFromPrefixAndValue(v3, v4); - return quad; + closeInternal(false); + return null; + +// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); +// if (lastResult == MDB_SUCCESS) { +// resetDuplicateBuffer(valueData.mv_data()); +// continue; +// } +// +// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); +// if (lastResult != MDB_SUCCESS) { +// closeInternal(false); +// return null; +// } +// if (!currentKeyHasPrefix()) { +// if (!adjustCursorToPrefix()) { +// closeInternal(false); +// return null; +// } +// } +// if (!primeDuplicateBlock()) { +// closeInternal(false); +// return null; +// } } } catch (IOException e) { throw new SailException(e); @@ -234,26 +257,35 @@ private int comparePrefix() { return 0; } - private boolean loadDuplicate(int op) throws IOException { - lastResult = mdb_cursor_get(cursor, keyData, valueData, op); + private boolean primeDuplicateBlock() throws IOException { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_GET_MULTIPLE); if (lastResult == MDB_SUCCESS) { - readCurrentDuplicate(valueData.mv_data()); - hasCurrentDuplicate = true; - return true; + resetDuplicateBuffer(valueData.mv_data()); + return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; } else if (lastResult == MDB_NOTFOUND) { - hasCurrentDuplicate = false; return false; } else { - throw new IOException("Failed to load duplicate, lmdb error code: " + lastResult); + resetDuplicateBuffer(valueData.mv_data()); + return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; } } - private void readCurrentDuplicate(ByteBuffer buffer) { - ByteBuffer duplicate = buffer.duplicate(); - int offset = duplicate.position(); - // values are stored as two 8-byte little-endian longs - currentObj = readLittleEndianLong(duplicate, offset); - currentContext = readLittleEndianLong(duplicate, offset + Long.BYTES); + private void resetDuplicateBuffer(ByteBuffer buffer) { + if (buffer == null) { + dupBuf = null; + dupPos = dupLimit = 0; + } else { + ByteBuffer source = buffer.duplicate(); + source.position(buffer.position()); + source.limit(buffer.limit()); + ByteBuffer copy = ByteBuffer.allocate(source.remaining()); + copy.put(source); + copy.flip(); + copy.order(ByteOrder.nativeOrder()); + dupBuf = copy; + dupPos = dupBuf.position(); + dupLimit = dupBuf.limit(); + } } private void fillQuadFromPrefixAndValue(long v3, long v4) { From f8b4f9c3cd447d4334771aa376806523d0250957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 18 Oct 2025 23:39:23 +0200 Subject: [PATCH 22/79] wip --- .../sail/lmdb/LmdbDupRecordIterator.java | 67 +++++++++----- .../LmdbDupRecordIteratorMultiBlockTest.java | 90 +++++++++++++++++++ 2 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorMultiBlockTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index e8c116284f..bc0ee65c08 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -13,11 +13,13 @@ import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; import static org.lwjgl.util.lmdb.LMDB.MDB_GET_MULTIPLE; import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; +import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT_MULTIPLE; import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_KEY; import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; import static org.lwjgl.util.lmdb.LMDB.MDB_SUCCESS; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; +import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_count; 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_cursor_renew; @@ -73,6 +75,10 @@ interface FallbackSupplier { private int lastResult; private boolean closed = false; + // Duplicate counting for the current key + private long dupTotalCount; + private long dupEmittedCount; + private final RecordIterator fallback; private final FallbackSupplier fallbackSupplier; @@ -157,6 +163,7 @@ public long[] next() { } while (true) { + // Emit from current duplicate block if available if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { long v3; long v4; @@ -170,33 +177,35 @@ public long[] next() { dupPos += Long.BYTES * 2; } fillQuadFromPrefixAndValue(v3, v4); + dupEmittedCount++; return quad; } - closeInternal(false); - return null; + // Current block exhausted; try next duplicate block if this key still has more + if (dupEmittedCount < dupTotalCount) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); + if (lastResult == MDB_SUCCESS) { + resetDuplicateBuffer(valueData.mv_data()); + continue; + } + } -// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); -// if (lastResult == MDB_SUCCESS) { -// resetDuplicateBuffer(valueData.mv_data()); -// continue; -// } -// -// lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); -// if (lastResult != MDB_SUCCESS) { -// closeInternal(false); -// return null; -// } -// if (!currentKeyHasPrefix()) { -// if (!adjustCursorToPrefix()) { -// closeInternal(false); -// return null; -// } -// } -// if (!primeDuplicateBlock()) { -// closeInternal(false); -// return null; -// } + // Advance to next key and re-prime if still within the requested prefix + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + if (lastResult != MDB_SUCCESS) { + closeInternal(false); + return null; + } + if (!currentKeyHasPrefix()) { + if (!adjustCursorToPrefix()) { + closeInternal(false); + return null; + } + } + if (!primeDuplicateBlock()) { + closeInternal(false); + return null; + } } } catch (IOException e) { throw new SailException(e); @@ -261,15 +270,27 @@ private boolean primeDuplicateBlock() throws IOException { lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_GET_MULTIPLE); if (lastResult == MDB_SUCCESS) { resetDuplicateBuffer(valueData.mv_data()); + refreshDuplicateCount(); + dupEmittedCount = 0L; return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; } else if (lastResult == MDB_NOTFOUND) { return false; } else { resetDuplicateBuffer(valueData.mv_data()); + refreshDuplicateCount(); + dupEmittedCount = 0L; return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; } } + private void refreshDuplicateCount() throws IOException { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pb = stack.mallocPointer(1); + E(mdb_cursor_count(cursor, pb)); + dupTotalCount = pb.get(0); + } + } + private void resetDuplicateBuffer(ByteBuffer buffer) { if (buffer == null) { dupBuf = null; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorMultiBlockTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorMultiBlockTest.java new file mode 100644 index 0000000000..7f746349e9 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIteratorMultiBlockTest.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies that LmdbDupRecordIterator returns all duplicates across multiple MDB_GET_MULTIPLE blocks by inserting more + * duplicate values than can fit in a single page-chunk for one (subject,predicate) key. + */ +class LmdbDupRecordIteratorMultiBlockTest { + + @TempDir + File dataDir; + + private LmdbStore store; + private NotifyingSailConnection con; + private final ValueFactory vf = SimpleValueFactory.getInstance(); + + @BeforeEach + void setUp() throws Exception { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,posc"); + config.setDupsortIndices(true); + config.setDupsortRead(true); + store = new LmdbStore(dataDir, config); + store.init(); + con = store.getConnection(); + } + + @AfterEach + void tearDown() throws Exception { + if (con != null) { + con.close(); + con = null; + } + if (store != null) { + store.shutDown(); + store = null; + } + } + + @Test + @Timeout(20) + void returnsAllDuplicatesAcrossMultipleBlocks() throws Exception { + // Choose a single subject/predicate and generate many distinct objects so that + // duplicates exceed a single MDB_GET_MULTIPLE chunk. + Resource s = vf.createIRI("urn:dup:subject"); + IRI p = vf.createIRI("urn:dup:predicate"); + + int duplicates = 1300; // > 1 page of 16-byte dup values on common 4K/16K pages + + con.begin(); + for (int i = 0; i < duplicates; i++) { + con.addStatement(s, p, vf.createIRI("urn:dup:obj:" + i)); + } + con.commit(); + + int count = 0; + try (var iter = con.getStatements(s, p, null, true)) { + while (iter.hasNext()) { + iter.next(); + count++; + } + } + + assertEquals(duplicates, count, "Iterator must return all duplicates, across multiple LMDB blocks"); + } +} From 5ac155556b4b67cd4188d1739524b021e368f48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 19 Oct 2025 12:43:09 +0200 Subject: [PATCH 23/79] wip --- .../rdf4j/sail/base/UnionSailDataset.java | 4 +- .../base/UnionSailDatasetComparatorTest.java | 107 +++++++++ .../rdf4j/sail/lmdb/LmdbSailStore.java | 213 +++++++++++++++++- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 5 + .../rdf4j/sail/lmdb/LmdbOrderedTest.java | 99 ++++++++ .../sail/lmdb/LmdbSupportedOrdersTest.java | 109 +++++++++ 6 files changed, 533 insertions(+), 4 deletions(-) create mode 100644 core/sail/base/src/test/java/org/eclipse/rdf4j/sail/base/UnionSailDatasetComparatorTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbOrderedTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSupportedOrdersTest.java 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 b7a9ac90dc..01849199f0 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 @@ -223,7 +223,9 @@ public Comparator getComparator() { Comparator comparator1 = dataset1.getComparator(); Comparator comparator2 = dataset2.getComparator(); - assert (comparator1 == null && comparator2 == null) || (comparator1 != null && comparator2 != null); + if (comparator1 == null || comparator2 == null) { + return null; + } return comparator1; } diff --git a/core/sail/base/src/test/java/org/eclipse/rdf4j/sail/base/UnionSailDatasetComparatorTest.java b/core/sail/base/src/test/java/org/eclipse/rdf4j/sail/base/UnionSailDatasetComparatorTest.java new file mode 100644 index 0000000000..5d679025c8 --- /dev/null +++ b/core/sail/base/src/test/java/org/eclipse/rdf4j/sail/base/UnionSailDatasetComparatorTest.java @@ -0,0 +1,107 @@ +/* + * ****************************************************************************** + * Copyright (c) 2025 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.sail.base; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.util.Comparator; +import java.util.Set; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Namespace; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Triple; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.util.ValueComparator; +import org.eclipse.rdf4j.sail.SailException; +import org.junit.jupiter.api.Test; + +/** + * Reproduces a mismatch where one dataset reports a value comparator and the other doesn't. The union should not + * assert; it should degrade by reporting null and avoid ordered merging. + */ +public class UnionSailDatasetComparatorTest { + + private static class StubDataset implements SailDataset { + private final Comparator cmp; + + StubDataset(Comparator cmp) { + this.cmp = cmp; + } + + @Override + public void close() { + } + + @Override + public CloseableIteration getNamespaces() { + return new EmptyIteration<>(); + } + + @Override + public String getNamespace(String prefix) { + return null; + } + + @Override + public CloseableIteration getContextIDs() { + return new EmptyIteration<>(); + } + + @Override + public CloseableIteration getStatements(Resource s, IRI p, Value o, Resource... c) { + return new EmptyIteration<>(); + } + + @Override + public CloseableIteration getStatements(StatementOrder order, Resource s, IRI p, Value o, + Resource... c) { + return new EmptyIteration<>(); + } + + @Override + public CloseableIteration getTriples(Resource s, IRI p, Value o) { + return new EmptyIteration<>(); + } + + @Override + public Set getSupportedOrders(Resource subj, IRI pred, Value obj, Resource... contexts) { + return Set.of(); + } + + @Override + public Comparator getComparator() { + return cmp; + } + } + + @Test + public void mismatchedComparators_returnsNullAndNoAssert() throws QueryEvaluationException { + SailDataset withComparator = new StubDataset(new ValueComparator()); + SailDataset withoutComparator = new StubDataset(null); + + SailDataset union = UnionSailDataset.getInstance(withComparator, withoutComparator); + + // Expect graceful degradation + assertThat(union.getComparator()).isNull(); + + // And ordered getStatements should not attempt to use a comparator in this case + assertDoesNotThrow(() -> union.getStatements(StatementOrder.S, null, null, null)); + } +} 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 421cb98dc5..af1b7849ae 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 @@ -16,6 +16,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Comparator; +import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -29,6 +30,7 @@ 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.DualUnionIteration; import org.eclipse.rdf4j.common.iteration.FilterIteration; import org.eclipse.rdf4j.common.iteration.UnionIteration; import org.eclipse.rdf4j.common.order.StatementOrder; @@ -40,6 +42,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.util.ValueComparator; import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.BackingSailSource; @@ -955,17 +958,221 @@ public CloseableIteration getStatements(Resource subj, IRI @Override public CloseableIteration getStatements(StatementOrder statementOrder, Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException { - throw new UnsupportedOperationException("Not implemented yet"); + try { + // Fast reject: inferred-only dataset but store has no inferred + if (!explicit && !mayHaveInferred) { + return CloseableIteration.EMPTY_STATEMENT_ITERATION; + } + + // Resolve value ids + long subjID = LmdbValue.UNKNOWN_ID; + if (subj != null) { + subjID = valueStore.getId(subj); + if (subjID == LmdbValue.UNKNOWN_ID) { + return CloseableIteration.EMPTY_STATEMENT_ITERATION; + } + } + + long predID = LmdbValue.UNKNOWN_ID; + if (pred != null) { + predID = valueStore.getId(pred); + if (predID == LmdbValue.UNKNOWN_ID) { + return CloseableIteration.EMPTY_STATEMENT_ITERATION; + } + } + + long objID = LmdbValue.UNKNOWN_ID; + if (obj != null) { + objID = valueStore.getId(obj); + if (objID == LmdbValue.UNKNOWN_ID) { + return CloseableIteration.EMPTY_STATEMENT_ITERATION; + } + } + + // Context handling: if more than one context is requested, we cannot efficiently guarantee a global + // order + // without a k-way merge. In that case, fall back to default behavior (unordered union). + if (contexts != null && contexts.length > 1) { + throw new IllegalArgumentException("LMDB SailStore does not support ordered scans over multiple contexts"); + } + + long contextID; + if (contexts == null || contexts.length == 0) { + contextID = LmdbValue.UNKNOWN_ID; // wildcard over all contexts + } else { + Resource ctx = contexts[0]; + if (ctx == null) { + contextID = 0L; // default graph + } else if (!ctx.isTriple()) { + contextID = valueStore.getId(ctx); + if (contextID == LmdbValue.UNKNOWN_ID) { + return CloseableIteration.EMPTY_STATEMENT_ITERATION; + } + } else { + // RDF* triple as context not supported by LMDB index order; fall back to default behavior + return createStatementIterator(txn, subj, pred, obj, explicit, contexts); + } + } + +// System.out.println("HERE"); + + // Pick an index that can provide the requested order given current bindings + TripleStore.TripleIndex chosen = chooseIndexForOrder(statementOrder, subjID, predID, objID, contextID); + if (chosen == null) { + // No compatible index for ordered scan; fall back to default iterator + return createStatementIterator(txn, subj, pred, obj, explicit, contexts); + } + + boolean rangeSearch = chosen.getPatternScore(subjID, predID, objID, contextID) > 0; + RecordIterator records = new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, + explicit, txn); + return new LmdbStatementIterator(records, valueStore); + } catch (IOException e) { + throw new SailException("Unable to get ordered statements", e); + } } @Override public Set getSupportedOrders(Resource subj, IRI pred, Value obj, Resource... contexts) { - return Set.of(); + // If multiple contexts are specified, LMDB currently returns a union without a global ordering guarantee + if (contexts != null && contexts.length > 1) { + return Set.of(); + } + + boolean sBound = subj != null; + boolean pBound = pred != null; + boolean oBound = obj != null; + boolean cBound = false; + if (contexts != null && contexts.length == 1) { + Resource ctx = contexts[0]; + // null context is a concrete value (default graph) + cBound = ctx == null || (ctx != null && !ctx.isTriple()); + } + + EnumSet supported = EnumSet.noneOf(StatementOrder.class); + // Scan available indexes and collect orders supported by compatible ones + for (TripleStore.TripleIndex index : tripleStore.getAllIndexes()) { + char[] seq = index.getFieldSeq(); + if (!isIndexCompatible(seq, sBound, pBound, oBound, cBound)) { + continue; + } + + // Add bound dimensions: they are trivially ordered (single value) + if (sBound) + supported.add(StatementOrder.S); + if (pBound) + supported.add(StatementOrder.P); + if (oBound) + supported.add(StatementOrder.O); + if (cBound) + supported.add(StatementOrder.C); + + // Add the first free variable in this index sequence + for (char f : seq) { + if (!isBound(f, sBound, pBound, oBound, cBound)) { + supported.add(toStatementOrder(f)); + break; + } + } + } + + return supported; + } + + private boolean isIndexCompatible(char[] seq, boolean sBound, boolean pBound, boolean oBound, boolean cBound) { + boolean seenUnbound = false; + for (char f : seq) { + boolean bound = isBound(f, sBound, pBound, oBound, cBound); + if (!bound) { + seenUnbound = true; + } else if (seenUnbound) { + // bound after an unbound earlier field -> cannot use index prefix, not compatible + return false; + } + } + return true; + } + + private boolean isBound(char f, boolean sBound, boolean pBound, boolean oBound, boolean cBound) { + switch (f) { + case 's': + return sBound; + case 'p': + return pBound; + case 'o': + return oBound; + case 'c': + return cBound; + default: + return false; + } + } + + private StatementOrder toStatementOrder(char f) { + switch (f) { + case 's': + return StatementOrder.S; + case 'p': + return StatementOrder.P; + case 'o': + return StatementOrder.O; + case 'c': + return StatementOrder.C; + default: + throw new IllegalArgumentException("Unknown field: " + f); + } + } + + private TripleStore.TripleIndex chooseIndexForOrder(StatementOrder order, long s, long p, long o, long c) + throws IOException { + boolean sBound = s >= 0; + boolean pBound = p >= 0; + boolean oBound = o >= 0; + boolean cBound = c >= 0; // 0 is a concrete (null) context; unknown is -1 + + for (TripleStore.TripleIndex index : tripleStore.getAllIndexes()) { + char[] seq = index.getFieldSeq(); + if (!isIndexCompatible(seq, sBound, pBound, oBound, cBound)) { + continue; + } + + // If requested order variable is bound, any compatible index will do + if ((order == StatementOrder.S && sBound) || (order == StatementOrder.P && pBound) + || (order == StatementOrder.O && oBound) || (order == StatementOrder.C && cBound)) { + return index; + } + + // Otherwise, ensure the requested variable is the first unbound dimension in this index + for (char f : seq) { + boolean bound = isBound(f, sBound, pBound, oBound, cBound); + if (!bound) { + if (toStatementOrder(f) == order) { + return index; + } else { + break; // first unbound is different -> this index can't provide the requested order + } + } + } + } + + return null; } @Override public Comparator getComparator() { - return null; + return (v1, v2) -> { + try { + long id1 = valueStore.getId(v1); + long id2 = valueStore.getId(v2); + if (id1 != LmdbValue.UNKNOWN_ID && id2 != LmdbValue.UNKNOWN_ID) { + return Long.compare(id1, id2); + } + } catch (IOException ignore) { + // fall through to lexical comparator + } + // Fallback to standard SPARQL value comparator when IDs are not available + return new ValueComparator().compare(v1, v2); + }; } } } 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 40568370a8..a1345df4b5 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 @@ -1002,6 +1002,11 @@ private void rebuildBestIndexLookup() { bestIndexLookup = newLookup; } + // Package-private: allow LMDB dataset to inspect available indexes for order support + List getAllIndexes() { + return indexes; + } + private enum IndexPattern { NONE(-1, -1, -1, -1), S(0, -1, -1, -1), diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbOrderedTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbOrderedTest.java new file mode 100644 index 0000000000..f2e9a5a6d0 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbOrderedTest.java @@ -0,0 +1,99 @@ +/** + * ****************************************************************************** + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.util.Values; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Verifies that LMDB store exposes statement ordering capabilities needed for merge-join optimization. + */ +public class LmdbOrderedTest { + + private File dataDir; + private LmdbStore store; + + @BeforeEach + public void setup() throws Exception { + dataDir = Files.createTempDirectory("rdf4j-lmdb-ordered-test").toFile(); + // Ensure both S and P primary indexes are available (default is spoc,posc, but set explicitly here) + store = new LmdbStore(dataDir, new LmdbStoreConfig("spoc,posc")); + } + + @AfterEach + public void tearDown() { + if (store != null) { + store.shutDown(); + } + if (dataDir != null) { + deleteRecursively(dataDir); + } + } + + private static void deleteRecursively(File f) { + if (f.isDirectory()) { + File[] files = f.listFiles(); + if (files != null) { + for (File c : files) { + deleteRecursively(c); + } + } + } + // ignore result; best-effort cleanup + f.delete(); + } + + @Test + public void getStatements_orderBySubject_returnsSorted() { + String NS = "http://example.org/"; + try (NotifyingSailConnection conn = store.getConnection()) { + conn.begin(); + conn.addStatement(Values.iri(NS, "d"), RDFS.LABEL, Values.literal("b")); + conn.addStatement(Values.iri(NS, "e"), RDFS.LABEL, Values.literal("a")); + conn.addStatement(Values.iri(NS, "a"), RDFS.LABEL, Values.literal("e")); + conn.addStatement(Values.iri(NS, "c"), RDFS.LABEL, Values.literal("c")); + conn.addStatement(Values.iri(NS, "b"), RDFS.LABEL, Values.literal("d")); + conn.commit(); + + conn.begin(IsolationLevels.NONE); + try (CloseableIteration it = conn.getStatements(StatementOrder.S, null, null, null, + true)) { + List subjects = it.stream() + .map(Statement::getSubject) + .map(v -> (IRI) v) + .map(IRI::getLocalName) + .collect(Collectors.toList()); + // LMDB orders by internal value-id (insertion order), not lexical + assertThat(subjects).isEqualTo(List.of("d", "e", "a", "c", "b")); + } + conn.commit(); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSupportedOrdersTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSupportedOrdersTest.java new file mode 100644 index 0000000000..22f08e79d0 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSupportedOrdersTest.java @@ -0,0 +1,109 @@ +/** + * ****************************************************************************** + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.nio.file.Files; +import java.util.EnumSet; +import java.util.Set; + +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.util.Values; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for LMDB supported statement orders, used by merge-join optimization. + */ +public class LmdbSupportedOrdersTest { + + private File dataDir; + private LmdbStore store; + + @BeforeEach + public void setup() throws Exception { + dataDir = Files.createTempDirectory("rdf4j-lmdb-supported-orders-test").toFile(); + store = new LmdbStore(dataDir, new LmdbStoreConfig("spoc,posc")); + // Force initialization so that backing store is available + try (NotifyingSailConnection ignored = store.getConnection()) { + // no-op + } + } + + @AfterEach + public void tearDown() { + if (store != null) { + store.shutDown(); + } + if (dataDir != null) { + deleteRecursively(dataDir); + } + } + + private static void deleteRecursively(File f) { + if (f.isDirectory()) { + File[] files = f.listFiles(); + if (files != null) { + for (File c : files) { + deleteRecursively(c); + } + } + } + // ignore result; best-effort cleanup + f.delete(); + } + + @Test + public void noBindings_defaultIndexes_supports_S_and_P() throws Exception { + LmdbSailStore internal = getBackingStore(store); + try (var dataset = internal.getExplicitSailSource().dataset(IsolationLevels.NONE)) { + Set supported = dataset.getSupportedOrders(null, null, null); + assertThat(supported).isEqualTo(EnumSet.of(StatementOrder.S, StatementOrder.P)); + } + } + + @Test + public void predicateBound_supports_P_and_O() throws Exception { + LmdbSailStore internal = getBackingStore(store); + try (var dataset = internal.getExplicitSailSource().dataset(IsolationLevels.NONE)) { + Set supported = dataset.getSupportedOrders(null, RDFS.LABEL, null); + assertThat(supported).isEqualTo(EnumSet.of(StatementOrder.P, StatementOrder.O)); + } + } + + @Test + public void subjectAndPredicateBound_supports_S_P_O() throws Exception { + IRI subj = Values.iri("urn:s"); + LmdbSailStore internal = getBackingStore(store); + try (var dataset = internal.getExplicitSailSource().dataset(IsolationLevels.NONE)) { + Set supported = dataset.getSupportedOrders((Resource) subj, RDFS.LABEL, (Value) null); + assertThat(supported).isEqualTo(EnumSet.of(StatementOrder.S, StatementOrder.P, StatementOrder.O)); + } + } + + private static LmdbSailStore getBackingStore(LmdbStore store) throws Exception { + var field = LmdbStore.class.getDeclaredField("backingStore"); + field.setAccessible(true); + return (LmdbSailStore) field.get(store); + } +} From e95ae2a864d2f353c63346bf3a2f80962e2c0add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 19 Oct 2025 13:13:38 +0200 Subject: [PATCH 24/79] wip --- .../rdf4j/sail/lmdb/LmdbSailStore.java | 146 ++++++++++++++---- .../sail/lmdb/config/LmdbStoreConfig.java | 9 +- .../rdf4j/sail/lmdb/model/LmdbBNode.java | 14 +- .../rdf4j/sail/lmdb/model/LmdbLiteral.java | 8 +- .../sail/lmdb/config/LmdbStoreConfigTest.java | 26 +++- 5 files changed, 163 insertions(+), 40 deletions(-) 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 af1b7849ae..26b64aff45 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 @@ -67,6 +67,11 @@ class LmdbSailStore implements SailStore { private final ValueStore valueStore; + // Precomputed lookup: for each bound-mask (bits for S=1,P=2,O=4,C=8), the union of + // supported StatementOrder across all configured indexes that are compatible with that mask. + @SuppressWarnings("unchecked") + private volatile EnumSet[] supportedOrdersLookup = (EnumSet[]) new EnumSet[16]; + private final ExecutorService tripleStoreExecutor = Executors.newCachedThreadPool(); private final CircularBuffer opQueue = new CircularBuffer<>(1024); private volatile Throwable tripleStoreException; @@ -209,6 +214,103 @@ public LmdbSailStore(File dataDir, LmdbStoreConfig config) throws IOException, S } } + private static int bitFor(char f) { + switch (f) { + case 's': + return 1; + case 'p': + return 1 << 1; + case 'o': + return 1 << 2; + case 'c': + return 1 << 3; + default: + return 0; + } + } + + private static StatementOrder orderFor(char f) { + switch (f) { + case 's': + return StatementOrder.S; + case 'p': + return StatementOrder.P; + case 'o': + return StatementOrder.O; + case 'c': + return StatementOrder.C; + default: + throw new IllegalArgumentException("Unknown field: " + f); + } + } + + private static boolean isIndexCompatible(char[] seq, int mask) { + boolean seenUnbound = false; + for (char f : seq) { + boolean bound = (mask & bitFor(f)) != 0; + if (!bound) { + seenUnbound = true; + } else if (seenUnbound) { + return false; + } + } + return true; + } + + private EnumSet[] getSupportedOrdersLookup() { + EnumSet[] local = supportedOrdersLookup; + if (local[0] == null) { + synchronized (this) { + local = supportedOrdersLookup; + if (local[0] == null) { + buildSupportedOrdersLookup(local); + } + } + } + return local; + } + + private void buildSupportedOrdersLookup(EnumSet[] table) { + for (int i = 0; i < table.length; i++) { + table[i] = EnumSet.noneOf(StatementOrder.class); + } + List indexes = tripleStore.getAllIndexes(); + char[][] seqs = new char[indexes.size()][]; + for (int i = 0; i < indexes.size(); i++) { + seqs[i] = indexes.get(i).getFieldSeq(); + } + for (int mask = 0; mask < 16; mask++) { + EnumSet set = table[mask]; + boolean anyCompatible = false; + for (char[] seq : seqs) { + if (!isIndexCompatible(seq, mask)) { + continue; + } + anyCompatible = true; + // add bound dimensions once per compatible index; set deduplicates + if ((mask & 1) != 0) + set.add(StatementOrder.S); + if ((mask & (1 << 1)) != 0) + set.add(StatementOrder.P); + if ((mask & (1 << 2)) != 0) + set.add(StatementOrder.O); + if ((mask & (1 << 3)) != 0) + set.add(StatementOrder.C); + + // add first free variable per index + for (char f : seq) { + if ((mask & bitFor(f)) == 0) { + set.add(orderFor(f)); + break; + } + } + } + if (!anyCompatible) { + set.clear(); + } + } + } + @Override public ValueFactory getValueFactory() { return valueStore; @@ -993,7 +1095,8 @@ public CloseableIteration getStatements(StatementOrder stat // order // without a k-way merge. In that case, fall back to default behavior (unordered union). if (contexts != null && contexts.length > 1) { - throw new IllegalArgumentException("LMDB SailStore does not support ordered scans over multiple contexts"); + throw new IllegalArgumentException( + "LMDB SailStore does not support ordered scans over multiple contexts"); } long contextID; @@ -1014,8 +1117,6 @@ public CloseableIteration getStatements(StatementOrder stat } } -// System.out.println("HERE"); - // Pick an index that can provide the requested order given current bindings TripleStore.TripleIndex chosen = chooseIndexForOrder(statementOrder, subjID, predID, objID, contextID); if (chosen == null) { @@ -1045,38 +1146,19 @@ public Set getSupportedOrders(Resource subj, IRI pred, Value obj boolean cBound = false; if (contexts != null && contexts.length == 1) { Resource ctx = contexts[0]; - // null context is a concrete value (default graph) - cBound = ctx == null || (ctx != null && !ctx.isTriple()); - } - - EnumSet supported = EnumSet.noneOf(StatementOrder.class); - // Scan available indexes and collect orders supported by compatible ones - for (TripleStore.TripleIndex index : tripleStore.getAllIndexes()) { - char[] seq = index.getFieldSeq(); - if (!isIndexCompatible(seq, sBound, pBound, oBound, cBound)) { - continue; - } - - // Add bound dimensions: they are trivially ordered (single value) - if (sBound) - supported.add(StatementOrder.S); - if (pBound) - supported.add(StatementOrder.P); - if (oBound) - supported.add(StatementOrder.O); - if (cBound) - supported.add(StatementOrder.C); - - // Add the first free variable in this index sequence - for (char f : seq) { - if (!isBound(f, sBound, pBound, oBound, cBound)) { - supported.add(toStatementOrder(f)); - break; - } + if (ctx == null) { + cBound = true; + } else if (!ctx.isTriple()) { + cBound = true; + } else { + // triple context not supported for ordered scans + return Set.of(); } } - return supported; + int mask = (sBound ? 1 : 0) | (pBound ? (1 << 1) : 0) | (oBound ? (1 << 2) : 0) | (cBound ? (1 << 3) : 0); + EnumSet res = getSupportedOrdersLookup()[mask]; + return res.isEmpty() ? Set.of() : EnumSet.copyOf(res); } private boolean isIndexCompatible(char[] seq, boolean sBound, boolean pBound, boolean oBound, boolean cBound) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java index 235badd7a2..882cb9fe51 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfig.java @@ -247,11 +247,12 @@ public Resource export(Model m) { if (valueEvictionInterval != Duration.ofSeconds(60).toMillis()) { m.add(implNode, LmdbStoreSchema.VALUE_EVICTION_INTERVAL, vf.createLiteral(valueEvictionInterval)); } - if (dupsortIndices) { - m.add(implNode, LmdbStoreSchema.DUPSORT_INDICES, vf.createLiteral(true)); + // Persist only when deviating from defaults (defaults: true) + if (!dupsortIndices) { + m.add(implNode, LmdbStoreSchema.DUPSORT_INDICES, vf.createLiteral(false)); } - if (dupsortRead) { - m.add(implNode, LmdbStoreSchema.DUPSORT_READ, vf.createLiteral(true)); + if (!dupsortRead) { + m.add(implNode, LmdbStoreSchema.DUPSORT_READ, vf.createLiteral(false)); } return implNode; } 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 eeab5e7479..178927b9bf 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 @@ -12,6 +12,7 @@ import java.io.ObjectStreamException; +import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.impl.SimpleBNode; import org.eclipse.rdf4j.sail.lmdb.ValueStoreRevision; @@ -110,7 +111,18 @@ public boolean equals(Object o) { } } - return super.equals(o); + if (o instanceof BNode) { + init(); + return super.equals(o); + } + + return false; + } + + @Override + public int hashCode() { + init(); + return super.hashCode(); } protected Object writeReplace() throws ObjectStreamException { 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 84e16136bc..9181e07e55 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 @@ -14,6 +14,7 @@ import java.util.Optional; import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.base.AbstractLiteral; import org.eclipse.rdf4j.model.base.CoreDatatype; import org.eclipse.rdf4j.sail.lmdb.ValueStoreRevision; @@ -226,8 +227,11 @@ public boolean equals(Object o) { } } - init(); - return super.equals(o); + if (o instanceof Literal) { + init(); + return super.equals(o); + } + return false; } @Override diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfigTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfigTest.java index 62dffba9ff..f6c94d30b0 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfigTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/config/LmdbStoreConfigTest.java @@ -41,6 +41,30 @@ void testThatLmdbStoreConfigParseAndExportValueEvictionInterval(final long value ); } + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void testThatLmdbStoreConfigParseAndExportDupsortIndices(final boolean dupsortIndices) { + testParseAndExport( + LmdbStoreSchema.DUPSORT_INDICES, + Values.literal(dupsortIndices), + LmdbStoreConfig::isDupsortIndices, + dupsortIndices, + !dupsortIndices + ); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void testThatLmdbStoreConfigParseAndExportDupsortRead(final boolean dupsortRead) { + testParseAndExport( + LmdbStoreSchema.DUPSORT_READ, + Values.literal(dupsortRead), + LmdbStoreConfig::isDupsortRead, + dupsortRead, + !dupsortRead + ); + } + @ParameterizedTest @ValueSource(booleans = { true, false }) void testThatLmdbStoreConfigParseAndExportAutoGrow(final boolean autoGrow) { @@ -113,4 +137,4 @@ private void testParseAndExport( assertThat(exportedModel.contains(exportImplNode, property, value)) .isEqualTo(expectedContains); } -} \ No newline at end of file +} From 6694d3b62db67bf19edf7c412b868ea2ff141cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 19 Oct 2025 13:27:07 +0200 Subject: [PATCH 25/79] wip --- .../algebra/evaluation/iterator/InnerMergeJoinIterator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 63448ba96a..725d059298 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 @@ -185,7 +185,7 @@ private void lessThan() { leftPeekValue = null; currentLeftValueAndPeekEquals = -1; - if (oldLeftValue.equals(currentLeftValue)) { + if (oldLeftValue.getType() == currentLeftValue.getType() && oldLeftValue.equals(currentLeftValue)) { // we have duplicate keys on the leftIterator and need to reset the rightIterator (if it // is resettable) if (rightIterator.isResettable()) { @@ -219,7 +219,7 @@ private void doLeftPeek() { } if (currentLeftValueAndPeekEquals == -1) { - boolean equals = currentLeftValue.equals(leftPeekValue); + boolean equals =leftPeekValue != null && currentLeftValue.getType() == leftPeekValue.getType() && currentLeftValue.equals(leftPeekValue); if (equals) { currentLeftValue = leftPeekValue; currentLeftValueAndPeekEquals = 0; From e202ba7d72b0c71c48c2a249dd1f427e6c725aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 19 Oct 2025 14:18:15 +0200 Subject: [PATCH 26/79] wip --- .../rdf4j/sail/lmdb/LmdbSailStore.java | 91 ++++++++++++------- 1 file changed, 60 insertions(+), 31 deletions(-) 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 26b64aff45..797de647ea 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 @@ -18,6 +18,7 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -72,6 +73,15 @@ class LmdbSailStore implements SailStore { @SuppressWarnings("unchecked") private volatile EnumSet[] supportedOrdersLookup = (EnumSet[]) new EnumSet[16]; + @SuppressWarnings("unchecked") + private volatile java.util.List[] compatibleIndexesByMask = (java.util.List[]) new java.util.List[16]; + + // firstFreeOrderByIndexAndMask[indexPos][mask] -> first free StatementOrder in that index for that mask, or null + private volatile StatementOrder[][] firstFreeOrderByIndexAndMask; + + // Map index instance -> its stable position used in the lookup arrays + private volatile Map indexPositionMap; + private final ExecutorService tripleStoreExecutor = Executors.newCachedThreadPool(); private final CircularBuffer opQueue = new CircularBuffer<>(1024); private volatile Throwable tripleStoreException; @@ -273,21 +283,43 @@ private EnumSet[] getSupportedOrdersLookup() { private void buildSupportedOrdersLookup(EnumSet[] table) { for (int i = 0; i < table.length; i++) { table[i] = EnumSet.noneOf(StatementOrder.class); + compatibleIndexesByMask[i] = new ArrayList<>(); } List indexes = tripleStore.getAllIndexes(); char[][] seqs = new char[indexes.size()][]; for (int i = 0; i < indexes.size(); i++) { seqs[i] = indexes.get(i).getFieldSeq(); } + // Build index position map + Map posMap = new java.util.IdentityHashMap<>(); + for (int i = 0; i < indexes.size(); i++) { + posMap.put(indexes.get(i), i); + } + StatementOrder[][] firstFree = new StatementOrder[indexes.size()][16]; + for (int i = 0; i < indexes.size(); i++) { + char[] seq = seqs[i]; + for (int mask = 0; mask < 16; mask++) { + StatementOrder first = null; + for (char f : seq) { + if ((mask & bitFor(f)) == 0) { + first = orderFor(f); + break; + } + } + firstFree[i][mask] = first; + } + } for (int mask = 0; mask < 16; mask++) { EnumSet set = table[mask]; boolean anyCompatible = false; - for (char[] seq : seqs) { + for (int i = 0; i < indexes.size(); i++) { + char[] seq = seqs[i]; if (!isIndexCompatible(seq, mask)) { continue; } anyCompatible = true; - // add bound dimensions once per compatible index; set deduplicates + compatibleIndexesByMask[mask].add(indexes.get(i)); + // add bound dimensions (trivial order) if ((mask & 1) != 0) set.add(StatementOrder.S); if ((mask & (1 << 1)) != 0) @@ -296,19 +328,19 @@ private void buildSupportedOrdersLookup(EnumSet[] table) { set.add(StatementOrder.O); if ((mask & (1 << 3)) != 0) set.add(StatementOrder.C); - - // add first free variable per index - for (char f : seq) { - if ((mask & bitFor(f)) == 0) { - set.add(orderFor(f)); - break; - } + // add first free variable for this index & mask if present + StatementOrder first = firstFree[i][mask]; + if (first != null) { + set.add(first); } } if (!anyCompatible) { set.clear(); } } + // publish + this.firstFreeOrderByIndexAndMask = firstFree; + this.indexPositionMap = posMap; } @Override @@ -1207,36 +1239,33 @@ private StatementOrder toStatementOrder(char f) { private TripleStore.TripleIndex chooseIndexForOrder(StatementOrder order, long s, long p, long o, long c) throws IOException { + // ensure metadata initialized + getSupportedOrdersLookup(); boolean sBound = s >= 0; boolean pBound = p >= 0; boolean oBound = o >= 0; boolean cBound = c >= 0; // 0 is a concrete (null) context; unknown is -1 - for (TripleStore.TripleIndex index : tripleStore.getAllIndexes()) { - char[] seq = index.getFieldSeq(); - if (!isIndexCompatible(seq, sBound, pBound, oBound, cBound)) { - continue; - } - - // If requested order variable is bound, any compatible index will do - if ((order == StatementOrder.S && sBound) || (order == StatementOrder.P && pBound) - || (order == StatementOrder.O && oBound) || (order == StatementOrder.C && cBound)) { - return index; - } - - // Otherwise, ensure the requested variable is the first unbound dimension in this index - for (char f : seq) { - boolean bound = isBound(f, sBound, pBound, oBound, cBound); - if (!bound) { - if (toStatementOrder(f) == order) { - return index; - } else { - break; // first unbound is different -> this index can't provide the requested order - } + int mask = (sBound ? 1 : 0) | (pBound ? (1 << 1) : 0) | (oBound ? (1 << 2) : 0) | (cBound ? (1 << 3) : 0); + java.util.List compat = compatibleIndexesByMask[mask]; + if (compat == null || compat.isEmpty()) { + return null; + } + // If requested order var is bound, any compatible index will do + if ((order == StatementOrder.S && sBound) || (order == StatementOrder.P && pBound) + || (order == StatementOrder.O && oBound) || (order == StatementOrder.C && cBound)) { + return compat.get(0); + } + // Else pick one whose first free variable matches + for (TripleStore.TripleIndex index : compat) { + Integer pos = indexPositionMap.get(index); + if (pos != null) { + StatementOrder first = firstFreeOrderByIndexAndMask[pos][mask]; + if (first == order) { + return index; } } } - return null; } From 748d024c009fec11a7c9fe23ab4f3738cb00809b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 19 Oct 2025 21:20:06 +0200 Subject: [PATCH 27/79] wip --- .../iterator/InnerMergeJoinIterator.java | 3 +- .../rdf4j/sail/base/UnionSailDataset.java | 2 + .../rdf4j/sail/lmdb/LmdbSailStore.java | 107 +++++++++++++++- .../sail/lmdb/LmdbStatementIterator.java | 120 +++++++++++++++--- 4 files changed, 211 insertions(+), 21 deletions(-) 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 725d059298..60404d0e31 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 @@ -219,7 +219,8 @@ private void doLeftPeek() { } if (currentLeftValueAndPeekEquals == -1) { - boolean equals =leftPeekValue != null && currentLeftValue.getType() == leftPeekValue.getType() && currentLeftValue.equals(leftPeekValue); + boolean equals = leftPeekValue != null && currentLeftValue.getType() == leftPeekValue.getType() + && currentLeftValue.equals(leftPeekValue); if (equals) { currentLeftValue = leftPeekValue; currentLeftValueAndPeekEquals = 0; 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 01849199f0..28fbd6b5ce 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 @@ -180,6 +180,8 @@ public CloseableIteration getStatements(StatementOrder stat iteration1 = dataset1.getStatements(statementOrder, subj, pred, obj, contexts); iteration2 = dataset2.getStatements(statementOrder, subj, pred, obj, contexts); Comparator cmp = statementOrder.getComparator(dataset1.getComparator()); + assert dataset1.getComparator() != null && dataset2.getComparator() != null + : "At this point the code assumes both datasets have a comparator, since otherwise getStatements without order would have been used"; return DualUnionIteration.getWildcardInstance(cmp, iteration1, iteration2); } catch (Throwable t) { try { 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 797de647ea..388ec42b68 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 @@ -522,7 +522,30 @@ CloseableIteration createStatementIterator( List contextIDList; if (contexts.length == 0) { RecordIterator records = tripleStore.getTriples(txn, subjID, predID, objID, LmdbValue.UNKNOWN_ID, explicit); - return new LmdbStatementIterator(records, valueStore); + boolean sBound = subj != null; + boolean pBound = pred != null; + boolean oBound = obj != null; + Resource cachedS = null; + IRI cachedP = null; + Value cachedO = null; + if (sBound && subj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) subj).getValueStoreRevision())) { + cachedS = subj; + } + if (pBound && pred instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) pred).getValueStoreRevision())) { + cachedP = pred; + } + if (oBound && obj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) obj).getValueStoreRevision())) { + cachedO = obj; + } + LmdbStatementIterator.StatementCreator creator = new LmdbStatementIterator.StatementCreator(valueStore, + cachedS, cachedP, cachedO, null, sBound, pBound, oBound, false); + return new LmdbStatementIterator(records, creator); } else { contextIDList = new ArrayList<>(contexts.length); for (Resource context : contexts) { @@ -542,7 +565,40 @@ CloseableIteration createStatementIterator( for (long contextID : contextIDList) { RecordIterator records = tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); - perContextIterList.add(new LmdbStatementIterator(records, valueStore)); + boolean sBound = subj != null; + boolean pBound = pred != null; + boolean oBound = obj != null; + Resource cachedS = null; + IRI cachedP = null; + Value cachedO = null; + Resource cachedC = null; + if (sBound && subj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) subj).getValueStoreRevision())) { + cachedS = subj; + } + if (pBound && pred instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) pred).getValueStoreRevision())) { + cachedP = pred; + } + if (oBound && obj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) obj).getValueStoreRevision())) { + cachedO = obj; + } + // If exactly one context was provided and is revision-compatible LmdbValue, pass it + if (contexts.length == 1) { + Resource ctx = contexts[0]; + if (ctx != null && !ctx.isTriple() && ctx instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) ctx).getValueStoreRevision())) { + cachedC = ctx; + } + } + LmdbStatementIterator.StatementCreator creator = new LmdbStatementIterator.StatementCreator(valueStore, + cachedS, cachedP, cachedO, cachedC, sBound, pBound, oBound, true); + perContextIterList.add(new LmdbStatementIterator(records, creator)); } if (perContextIterList.size() == 1) { @@ -1159,7 +1215,52 @@ public CloseableIteration getStatements(StatementOrder stat boolean rangeSearch = chosen.getPatternScore(subjID, predID, objID, contextID) > 0; RecordIterator records = new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, explicit, txn); - return new LmdbStatementIterator(records, valueStore); + + boolean sBound = subj != null; + boolean pBound = pred != null; + boolean oBound = obj != null; + boolean cBound; + if (contexts == null || contexts.length == 0) { + cBound = false; + } else { + cBound = true; // exactly one context allowed at this point + } + + Resource cachedS = null; + IRI cachedP = null; + Value cachedO = null; + Resource cachedC = null; + + if (sBound && subj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) subj).getValueStoreRevision())) { + cachedS = subj; + } + if (pBound && pred instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) pred).getValueStoreRevision())) { + cachedP = pred; + } + if (oBound && obj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) obj).getValueStoreRevision())) { + cachedO = obj; + } + + if (cBound && contexts != null && contexts.length == 1) { + Resource ctx = contexts[0]; + if (ctx != null && !ctx.isTriple() + && ctx instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && valueStore.getRevision() + .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) ctx) + .getValueStoreRevision())) { + cachedC = ctx; + } + } + + LmdbStatementIterator.StatementCreator creator = new LmdbStatementIterator.StatementCreator(valueStore, + cachedS, cachedP, cachedO, cachedC, sBound, pBound, oBound, cBound); + return new LmdbStatementIterator(records, creator); } catch (IOException e) { throw new SailException("Unable to get ordered statements", e); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java index d9df2f8dbb..fc3164f4cb 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java @@ -33,6 +33,7 @@ class LmdbStatementIterator implements CloseableIteration { private final RecordIterator recordIt; private final ValueStore valueStore; + private final StatementCreator statementCreator; private Statement nextElement; /** * Flag indicating whether this iteration has been closed. @@ -44,11 +45,23 @@ class LmdbStatementIterator implements CloseableIteration { *--------------*/ /** - * Creates a new LmdbStatementIterator. + * Creates a new LmdbStatementIterator with the default statement creation logic. */ public LmdbStatementIterator(RecordIterator recordIt, ValueStore valueStore) { this.recordIt = recordIt; this.valueStore = valueStore; + this.statementCreator = new StatementCreator(valueStore, false, false, false, false); + } + + /** + * Creates a new LmdbStatementIterator using a custom statement creator. The provided {@link StatementCreator} is + * responsible for creating the Values and Statement for each record and can cache bound values so the iterator + * always returns LmdbValue-backed instances. + */ + public LmdbStatementIterator(RecordIterator recordIt, StatementCreator statementCreator) { + this.recordIt = recordIt; + this.valueStore = null; + this.statementCreator = statementCreator; } /*---------* @@ -68,22 +81,7 @@ public Statement getNextElement() throws SailException { return null; } - long subjID = quad[TripleStore.SUBJ_IDX]; - Resource subj = (Resource) valueStore.getLazyValue(subjID); - - long predID = quad[TripleStore.PRED_IDX]; - IRI pred = (IRI) valueStore.getLazyValue(predID); - - long objID = quad[TripleStore.OBJ_IDX]; - Value obj = valueStore.getLazyValue(objID); - - Resource context = null; - long contextID = quad[TripleStore.CONTEXT_IDX]; - if (contextID != 0) { - context = (Resource) valueStore.getLazyValue(contextID); - } - - return valueStore.createStatement(subj, pred, obj, context); + return statementCreator.create(quad); } catch (IOException e) { throw causeIOException(e); } @@ -164,4 +162,92 @@ public final void close() { handleClose(); } } + + /** + * Statement creator that can cache bound values (S, P, O, and/or C) the first time they are seen via + * {@link ValueStore#getLazyValue(long)} and reuse them for subsequent records. This ensures bound values are always + * returned as LmdbValue-backed instances. + */ + static final class StatementCreator { + private final ValueStore valueStore; + private final boolean sBound; + private final boolean pBound; + private final boolean oBound; + private final boolean cBound; + + private Resource cachedS; + private IRI cachedP; + private Value cachedO; + private Resource cachedC; + + StatementCreator(ValueStore valueStore, boolean sBound, boolean pBound, boolean oBound, boolean cBound) { + this.valueStore = valueStore; + this.sBound = sBound; + this.pBound = pBound; + this.oBound = oBound; + this.cBound = cBound; + } + + StatementCreator(ValueStore valueStore, Resource cachedS, IRI cachedP, Value cachedO, Resource cachedC, + boolean sBound, boolean pBound, boolean oBound, boolean cBound) { + this.valueStore = valueStore; + this.cachedS = cachedS; + this.cachedP = cachedP; + this.cachedO = cachedO; + this.cachedC = cachedC; + this.sBound = sBound; + this.pBound = pBound; + this.oBound = oBound; + this.cBound = cBound; + } + + Statement create(long[] quad) throws IOException { + Resource s; + if (sBound) { + if (cachedS == null) { + cachedS = (Resource) valueStore.getLazyValue(quad[TripleStore.SUBJ_IDX]); + } + s = cachedS; + } else { + s = (Resource) valueStore.getLazyValue(quad[TripleStore.SUBJ_IDX]); + } + + IRI p; + if (pBound) { + if (cachedP == null) { + cachedP = (IRI) valueStore.getLazyValue(quad[TripleStore.PRED_IDX]); + } + p = cachedP; + } else { + p = (IRI) valueStore.getLazyValue(quad[TripleStore.PRED_IDX]); + } + + Value o; + if (oBound) { + if (cachedO == null) { + cachedO = valueStore.getLazyValue(quad[TripleStore.OBJ_IDX]); + } + o = cachedO; + } else { + o = valueStore.getLazyValue(quad[TripleStore.OBJ_IDX]); + } + + Resource c = null; + long contextID = quad[TripleStore.CONTEXT_IDX]; + if (cBound) { + if (cachedC == null) { + if (contextID != 0) { + cachedC = (Resource) valueStore.getLazyValue(contextID); + } else { + cachedC = null; // default graph + } + } + c = cachedC; + } else if (contextID != 0) { + c = (Resource) valueStore.getLazyValue(contextID); + } + + return valueStore.createStatement(s, p, o, c); + } + } } From 4b9e2c8d41b7ab794df6c1a99e7baff3087f16d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 19 Oct 2025 22:09:45 +0200 Subject: [PATCH 28/79] wip --- .../sail/lmdb/LmdbStatementIterator.java | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java index fc3164f4cb..43b2f472cf 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStatementIterator.java @@ -202,52 +202,51 @@ static final class StatementCreator { } Statement create(long[] quad) throws IOException { - Resource s; + final ValueStore vs = this.valueStore; + + // subject + Resource s = this.cachedS; if (sBound) { - if (cachedS == null) { - cachedS = (Resource) valueStore.getLazyValue(quad[TripleStore.SUBJ_IDX]); + if (s == null) { + s = this.cachedS = (Resource) vs.getLazyValue(quad[TripleStore.SUBJ_IDX]); } - s = cachedS; } else { - s = (Resource) valueStore.getLazyValue(quad[TripleStore.SUBJ_IDX]); + s = (Resource) vs.getLazyValue(quad[TripleStore.SUBJ_IDX]); } - IRI p; + // predicate + IRI p = this.cachedP; if (pBound) { - if (cachedP == null) { - cachedP = (IRI) valueStore.getLazyValue(quad[TripleStore.PRED_IDX]); + if (p == null) { + p = this.cachedP = (IRI) vs.getLazyValue(quad[TripleStore.PRED_IDX]); } - p = cachedP; } else { - p = (IRI) valueStore.getLazyValue(quad[TripleStore.PRED_IDX]); + p = (IRI) vs.getLazyValue(quad[TripleStore.PRED_IDX]); } - Value o; + // object + Value o = this.cachedO; if (oBound) { - if (cachedO == null) { - cachedO = valueStore.getLazyValue(quad[TripleStore.OBJ_IDX]); + if (o == null) { + o = this.cachedO = vs.getLazyValue(quad[TripleStore.OBJ_IDX]); } - o = cachedO; } else { - o = valueStore.getLazyValue(quad[TripleStore.OBJ_IDX]); + o = vs.getLazyValue(quad[TripleStore.OBJ_IDX]); } - Resource c = null; - long contextID = quad[TripleStore.CONTEXT_IDX]; + // context + final long contextID = quad[TripleStore.CONTEXT_IDX]; + Resource c = this.cachedC; if (cBound) { - if (cachedC == null) { - if (contextID != 0) { - cachedC = (Resource) valueStore.getLazyValue(contextID); - } else { - cachedC = null; // default graph - } + if (c == null) { + c = this.cachedC = (contextID != 0) ? (Resource) vs.getLazyValue(contextID) : null; } - c = cachedC; - } else if (contextID != 0) { - c = (Resource) valueStore.getLazyValue(contextID); + // if cBound and cachedC already set, ignore contextID (preserves original semantics) + } else { + c = (contextID != 0) ? (Resource) vs.getLazyValue(contextID) : null; } - return valueStore.createStatement(s, p, o, c); + return vs.createStatement(s, p, o, c); } } } From b0c9f797ead49cd2d7b8e08dd00138258ba579aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 20 Oct 2025 07:54:39 +0200 Subject: [PATCH 29/79] endianness changes --- .../sail/lmdb/LmdbDupRecordIterator.java | 19 ++--- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 26 +++--- .../LmdbSubjectPredicateDupOrderTest.java | 82 +++++++++++++++++++ 3 files changed, 100 insertions(+), 27 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSubjectPredicateDupOrderTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index bc0ee65c08..68279d85c0 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -165,17 +165,9 @@ public long[] next() { while (true) { // Emit from current duplicate block if available if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { - long v3; - long v4; - if (true) { - v3 = readLittleEndianLong(dupBuf, dupPos); - v4 = readLittleEndianLong(dupBuf, dupPos + Long.BYTES); - dupPos += Long.BYTES * 2; - } else { - v3 = dupBuf.getLong(dupPos); - v4 = dupBuf.getLong(dupPos + Long.BYTES); - dupPos += Long.BYTES * 2; - } + long v3 = readBigEndianLong(dupBuf, dupPos); + long v4 = readBigEndianLong(dupBuf, dupPos + Long.BYTES); + dupPos += Long.BYTES * 2; fillQuadFromPrefixAndValue(v3, v4); dupEmittedCount++; return quad; @@ -302,7 +294,6 @@ private void resetDuplicateBuffer(ByteBuffer buffer) { ByteBuffer copy = ByteBuffer.allocate(source.remaining()); copy.put(source); copy.flip(); - copy.order(ByteOrder.nativeOrder()); dupBuf = copy; dupPos = dupBuf.position(); dupLimit = dupBuf.limit(); @@ -394,10 +385,10 @@ private RecordIterator createFallback() throws IOException { return fallbackSupplier.get(); } - private long readLittleEndianLong(ByteBuffer buffer, int offset) { + private long readBigEndianLong(ByteBuffer buffer, int offset) { long value = 0L; for (int i = 0; i < Long.BYTES; i++) { - value |= (buffer.get(offset + i) & 0xFFL) << (i * 8); + value = (value << 8) | (buffer.get(offset + i) & 0xFFL); } return value; } 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 a1345df4b5..3bfeedd5e8 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 @@ -2417,9 +2417,9 @@ void put(long txn, long subj, long pred, long obj, long context, boolean explici toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); dupKeyBuf.flip(); dupValBuf.clear(); - // store as two 8-byte little-endian longs - writeLongLittleEndian(dupValBuf, obj); - writeLongLittleEndian(dupValBuf, context); + // store as two 8-byte big-endian longs to preserve LMDB's lexicographic ordering + writeLongBigEndian(dupValBuf, obj); + writeLongBigEndian(dupValBuf, context); dupValBuf.flip(); MDBVal dupKeyVal = MDBVal.malloc(stack); MDBVal dupDataVal = MDBVal.malloc(stack); @@ -2441,8 +2441,8 @@ void delete(long txn, long subj, long pred, long obj, long context, boolean expl toDupKeyPrefix(dupKeyBuf, subj, pred, obj, context); dupKeyBuf.flip(); dupValBuf.clear(); - writeLongLittleEndian(dupValBuf, obj); - writeLongLittleEndian(dupValBuf, context); + writeLongBigEndian(dupValBuf, obj); + writeLongBigEndian(dupValBuf, context); dupValBuf.flip(); MDBVal dupKeyVal = MDBVal.malloc(stack); MDBVal dupDataVal = MDBVal.malloc(stack); @@ -2459,15 +2459,15 @@ void close() { mdb_dbi_close(env, dbiDupInferred); } - private void writeLongLittleEndian(ByteBuffer buffer, long value) { - buffer.put((byte) ((value >> (0)) & 0xFF)); - buffer.put((byte) ((value >> (8)) & 0xFF)); - buffer.put((byte) ((value >> (2 * 8)) & 0xFF)); - buffer.put((byte) ((value >> (3 * 8)) & 0xFF)); - buffer.put((byte) ((value >> (4 * 8)) & 0xFF)); - buffer.put((byte) ((value >> (5 * 8)) & 0xFF)); - buffer.put((byte) ((value >> (6 * 8)) & 0xFF)); + private void writeLongBigEndian(ByteBuffer buffer, long value) { buffer.put((byte) ((value >> (7 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (6 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (5 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (4 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (3 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (2 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (1 * 8)) & 0xFF)); + buffer.put((byte) ((value >> (0)) & 0xFF)); } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSubjectPredicateDupOrderTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSubjectPredicateDupOrderTest.java new file mode 100644 index 0000000000..54afd3329a --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSubjectPredicateDupOrderTest.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies that the subject–predicate dup index returns duplicate values in ascending numeric order for object/context + * ids. This guards against little-endian encoding of dup values, which breaks LMDB's lexicographic ordering and thus + * ORDER BY semantics. + */ +public class LmdbSubjectPredicateDupOrderTest { + + private TripleStore tripleStore; + + @BeforeEach + public void setup(@TempDir File dataDir) throws Exception { + // Ensure dupsort indices are enabled (default true) and pick a simple index set + tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"), null); + } + + @AfterEach + public void teardown() throws Exception { + if (tripleStore != null) { + tripleStore.close(); + } + } + + @Test + public void subjectPredicateDuplicateValuesOrderedByObjectAscending() throws Exception { + long s = 100L; + long p = 200L; + long c = 1L; + + // Object ids specifically chosen to expose little-endian vs big-endian lexicographic ordering + long[] objects = new long[] { 1L, 256L, 3L, 255L, 2L }; + + tripleStore.startTransaction(); + for (long o : objects) { + tripleStore.storeTriple(s, p, o, c, true); + } + tripleStore.commit(); + + List observed = new ArrayList<>(); + + try (TxnManager.Txn txn = tripleStore.getTxnManager().createReadTxn(); + RecordIterator it = tripleStore.getTriples(txn, s, p, -1, -1, true)) { + long[] quad; + while ((quad = it.next()) != null) { + // Ensure we only consider the (s,p,?,?) pattern we inserted + if (quad[TripleStore.SUBJ_IDX] == s && quad[TripleStore.PRED_IDX] == p) { + observed.add(quad[TripleStore.OBJ_IDX]); + } + } + } + + long[] expectedOrder = new long[] { 1L, 2L, 3L, 255L, 256L }; + long[] actualOrder = observed.stream().mapToLong(Long::longValue).toArray(); + + // If dup values are little-endian, LMDB will sort lexicographically and return 256 before 1. + // We assert strictly ascending numeric order by object id. + assertArrayEquals(expectedOrder, actualOrder, "Objects must be returned in ascending order by id"); + } +} From 8e8a417c5de7cdfee61f1fa48e9c02ff2d308d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 20 Oct 2025 07:59:52 +0200 Subject: [PATCH 30/79] endianness changes --- .../sail/lmdb/LmdbDupRecordIterator.java | 217 +++++++++--------- 1 file changed, 108 insertions(+), 109 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 68279d85c0..d64f7796d3 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -19,7 +19,6 @@ import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; import static org.lwjgl.util.lmdb.LMDB.MDB_SUCCESS; import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; -import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_count; 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_cursor_renew; @@ -46,13 +45,19 @@ interface FallbackSupplier { RecordIterator get() throws IOException; } + /** Toggle copying of duplicate blocks for extra safety (defaults to zero-copy views). */ + private static final boolean COPY_DUP_BLOCKS = Boolean.getBoolean("rdf4j.lmdb.copyDupBlocks"); + + /** Size in bytes of one (v3,v4) tuple. */ + private static final int DUP_PAIR_BYTES = Long.BYTES * 2; + private final Pool pool; private final DupIndex index; private final int dupDbi; private final long cursor; private final Txn txnRef; - private final long txn; + private long txn; // refreshed on txn version changes private long txnRefVersion; private final StampedLongAdderLockManager txnLockManager; private final Thread ownerThread = Thread.currentThread(); @@ -60,14 +65,16 @@ interface FallbackSupplier { private final MDBVal keyData; private final MDBVal valueData; + /** Reused output buffer required by the RecordIterator API. */ private final long[] quad; - private final long[] originalQuad; - private final boolean matchValues; + /** Scalars defining the prefix to scan (subject, predicate). */ + private final long prefixSubj; + private final long prefixPred; private ByteBuffer prefixKeyBuf; - private long[] prefixValues; + /** Current duplicate block view and read indices. */ private ByteBuffer dupBuf; private int dupPos; private int dupLimit; @@ -75,10 +82,6 @@ interface FallbackSupplier { private int lastResult; private boolean closed = false; - // Duplicate counting for the current key - private long dupTotalCount; - private long dupEmittedCount; - private final RecordIterator fallback; private final FallbackSupplier fallbackSupplier; @@ -86,9 +89,16 @@ interface FallbackSupplier { boolean explicit, Txn txnRef, FallbackSupplier fallbackSupplier) throws IOException { this.index = index; - this.quad = new long[] { subj, pred, -1, -1 }; - this.originalQuad = new long[] { subj, pred, -1, -1 }; - this.matchValues = subj > 0 || pred > 0; + // Output buffer (s,p are constant for the life of this iterator) + this.quad = new long[4]; + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = -1L; + this.quad[3] = -1L; + + this.prefixSubj = subj; + this.prefixPred = pred; + this.fallbackSupplier = fallbackSupplier; this.pool = Pool.get(); @@ -113,12 +123,10 @@ interface FallbackSupplier { cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); prefixKeyBuf = pool.getKeyBuffer(); - prefixValues = new long[] { subj, pred }; prefixKeyBuf.clear(); - Varint.writeUnsigned(prefixKeyBuf, subj); - Varint.writeUnsigned(prefixKeyBuf, pred); - -// index.toDupKeyPrefix(prefixKeyBuf, subj, pred, 0, 0); + Varint.writeUnsigned(prefixKeyBuf, prefixSubj); + Varint.writeUnsigned(prefixKeyBuf, prefixPred); + // index.toDupKeyPrefix(prefixKeyBuf, subj, pred, 0, 0); prefixKeyBuf.flip(); boolean positioned = positionOnPrefix(); @@ -153,7 +161,9 @@ public long[] next() { return null; } + // Txn renewal if the TxnManager rotated its underlying LMDB txn if (txnRefVersion != txnRef.version()) { + this.txn = txnRef.get(); E(mdb_cursor_renew(txn, cursor)); txnRefVersion = txnRef.version(); if (!positionOnPrefix() || !primeDuplicateBlock()) { @@ -163,41 +173,41 @@ public long[] next() { } while (true) { - // Emit from current duplicate block if available - if (dupBuf != null && dupPos + (Long.BYTES * 2) <= dupLimit) { - long v3 = readBigEndianLong(dupBuf, dupPos); - long v4 = readBigEndianLong(dupBuf, dupPos + Long.BYTES); - dupPos += Long.BYTES * 2; - fillQuadFromPrefixAndValue(v3, v4); - dupEmittedCount++; + // Fast-path: emit from current duplicate block if at least one pair remains + if (dupBuf != null && dupLimit - dupPos >= DUP_PAIR_BYTES) { + long v3 = dupBuf.getLong(dupPos); + long v4 = dupBuf.getLong(dupPos + Long.BYTES); + dupPos += DUP_PAIR_BYTES; + + // s,p are constant; update only the tail + quad[2] = v3; + quad[3] = v4; return quad; } - // Current block exhausted; try next duplicate block if this key still has more - if (dupEmittedCount < dupTotalCount) { - lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); - if (lastResult == MDB_SUCCESS) { - resetDuplicateBuffer(valueData.mv_data()); - continue; - } + // Ask LMDB for the next duplicate block under the same key + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT_MULTIPLE); + if (lastResult == MDB_SUCCESS) { + resetDuplicateBuffer(valueData.mv_data()); + continue; } - - // Advance to next key and re-prime if still within the requested prefix - lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); - if (lastResult != MDB_SUCCESS) { - closeInternal(false); - return null; + if (lastResult != MDB_NOTFOUND) { + E(lastResult); } - if (!currentKeyHasPrefix()) { - if (!adjustCursorToPrefix()) { + + // No more duplicate blocks for this key; advance to next key in range + do { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + if (lastResult != MDB_SUCCESS) { closeInternal(false); return null; } - } - if (!primeDuplicateBlock()) { - closeInternal(false); - return null; - } + // Ensure we're still within the requested (subj,pred) prefix + if (!currentKeyHasPrefix() && !adjustCursorToPrefix()) { + closeInternal(false); + return null; + } + } while (!primeDuplicateBlock()); // skip any keys without a duplicate block (defensive) } } catch (IOException e) { throw new SailException(e); @@ -206,8 +216,11 @@ public long[] next() { } } + /* ---------- Positioning & Prefix handling ---------- */ + private boolean positionOnPrefix() throws IOException { keyData.mv_data(prefixKeyBuf); + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_KEY); if (lastResult == MDB_NOTFOUND) { lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); @@ -233,88 +246,82 @@ private boolean adjustCursorToPrefix() throws IOException { return cmp == 0; } + /** + * Compare current cursor key with (prefixSubj, prefixPred) without allocating a duplicate buffer. + */ private int comparePrefix() { - ByteBuffer key = keyData.mv_data().duplicate(); - { - long actual = Varint.readUnsigned(key); - long expected = prefixValues[0]; - if (actual < expected) { - return -1; - } - if (actual > expected) { - return 1; - } - } - { - long actual = Varint.readUnsigned(key); - long expected = prefixValues[1]; - if (actual < expected) { - return -1; - } - if (actual > expected) { - return 1; + ByteBuffer key = keyData.mv_data(); + final int pos = key.position(); + try { + long a0 = Varint.readUnsigned(key); + int c0 = Long.compare(a0, prefixSubj); + if (c0 != 0) { + return c0; } + long a1 = Varint.readUnsigned(key); + return Long.compare(a1, prefixPred); + } finally { + key.position(pos); // restore buffer position } - return 0; } + private boolean currentKeyHasPrefix() { + return comparePrefix() == 0; + } + + /* ---------- Duplicate block priming ---------- */ + + /** + * Prime the duplicate buffer for the current key using MDB_GET_MULTIPLE. + */ private boolean primeDuplicateBlock() throws IOException { lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_GET_MULTIPLE); if (lastResult == MDB_SUCCESS) { resetDuplicateBuffer(valueData.mv_data()); - refreshDuplicateCount(); - dupEmittedCount = 0L; - return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; - } else if (lastResult == MDB_NOTFOUND) { - return false; - } else { - resetDuplicateBuffer(valueData.mv_data()); - refreshDuplicateCount(); - dupEmittedCount = 0L; - return dupBuf != null && dupLimit - dupPos >= Long.BYTES * 2; + return dupBuf != null && dupLimit - dupPos >= DUP_PAIR_BYTES; } - } - - private void refreshDuplicateCount() throws IOException { - try (MemoryStack stack = MemoryStack.stackPush()) { - PointerBuffer pb = stack.mallocPointer(1); - E(mdb_cursor_count(cursor, pb)); - dupTotalCount = pb.get(0); + if (lastResult == MDB_NOTFOUND) { + resetDuplicateBuffer(null); + return false; } + E(lastResult); + return false; // unreachable } + /** + * Prepare a readable view over the LMDB-provided value buffer. Default: zero-copy {@code slice()}. If + * COPY_DUP_BLOCKS is true, heap-copy for extra safety. + */ private void resetDuplicateBuffer(ByteBuffer buffer) { if (buffer == null) { dupBuf = null; dupPos = dupLimit = 0; + return; + } + + if (!COPY_DUP_BLOCKS) { + // Zero-copy: view over [position, limit) of the native buffer + ByteBuffer view = buffer.slice(); + view.order(ByteOrder.BIG_ENDIAN); + dupBuf = view; + dupPos = view.position(); // 0 + dupLimit = view.limit(); } else { - ByteBuffer source = buffer.duplicate(); - source.position(buffer.position()); - source.limit(buffer.limit()); - ByteBuffer copy = ByteBuffer.allocate(source.remaining()); - copy.put(source); + // Conservative path: copy to Java heap to decouple lifetime from cursor operations + ByteBuffer src = buffer.duplicate(); + src.position(buffer.position()); + src.limit(buffer.limit()); + ByteBuffer copy = ByteBuffer.allocate(src.remaining()); + copy.put(src); copy.flip(); + copy.order(ByteOrder.BIG_ENDIAN); dupBuf = copy; dupPos = dupBuf.position(); dupLimit = dupBuf.limit(); } } - private void fillQuadFromPrefixAndValue(long v3, long v4) { - quad[0] = prefixValues[0]; - quad[1] = prefixValues[1]; - quad[2] = v3; - quad[3] = v4; - } - - private boolean currentKeyHasPrefix() { - return comparePrefix() == 0; - } - - private boolean matchesQuad() { - return (originalQuad[0] < 0 || quad[0] == originalQuad[0]) - && (originalQuad[1] < 0 || quad[1] == originalQuad[1]); - } + /* ---------- Lifecycle ---------- */ private void closeInternal(boolean maybeCalledAsync) { if (!closed) { @@ -384,12 +391,4 @@ private RecordIterator createFallback() throws IOException { } return fallbackSupplier.get(); } - - private long readBigEndianLong(ByteBuffer buffer, int offset) { - long value = 0L; - for (int i = 0; i < Long.BYTES; i++) { - value = (value << 8) | (buffer.get(offset + i) & 0xFFL); - } - return value; - } } From b2b2989034945966ee8c5669bf0907408fbcf4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 20 Oct 2025 08:27:23 +0200 Subject: [PATCH 31/79] endianness changes --- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 8 ++--- .../rdf4j/sail/lmdb/TxnRecordCache.java | 4 +-- .../org/eclipse/rdf4j/sail/lmdb/Varint.java | 30 +++++++++---------- .../eclipse/rdf4j/sail/lmdb/VarintTest.java | 4 +-- 4 files changed, 22 insertions(+), 24 deletions(-) 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 3bfeedd5e8..d44d6f3171 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 @@ -852,7 +852,7 @@ protected double cardinality(long subj, long pred, long obj, long context) throw if (rc != MDB_SUCCESS || mdb_cmp(txn, dbi, keyData, maxKey) >= 0) { break; } else { - Varint.readListUnsigned(keyData.mv_data(), s.minValues); + Varint.readQuadUnsigned(keyData.mv_data(), s.minValues); } // set cursor to max key @@ -866,7 +866,7 @@ protected double cardinality(long subj, long pred, long obj, long context) throw rc = mdb_cursor_get(cursor, keyData, valueData, MDB_PREV); } if (rc == MDB_SUCCESS) { - Varint.readListUnsigned(keyData.mv_data(), s.maxValues); + Varint.readQuadUnsigned(keyData.mv_data(), s.maxValues); // this is required to correctly estimate the range size at a later point s.startValues[s.MAX_BUCKETS] = s.maxValues; } else { @@ -880,7 +880,7 @@ protected double cardinality(long subj, long pred, long obj, long context) throw if (bucket != 0) { bucketStart((double) bucket / s.MAX_BUCKETS, s.minValues, s.maxValues, s.values); keyBuf.clear(); - Varint.writeListUnsigned(keyBuf, s.values); + Varint.writeQuadUnsigned(keyBuf, s.values); keyBuf.flip(); } // this is the min key for the first iteration @@ -897,7 +897,7 @@ protected double cardinality(long subj, long pred, long obj, long context) throw currentSamplesCount++; System.arraycopy(s.values, 0, s.lastValues[bucket], 0, s.values.length); - Varint.readListUnsigned(keyData.mv_data(), s.values); + Varint.readQuadUnsigned(keyData.mv_data(), s.values); if (currentSamplesCount == 1) { Arrays.fill(s.counts, 1); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnRecordCache.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnRecordCache.java index 9974f6a2df..6b1f59069f 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnRecordCache.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/TxnRecordCache.java @@ -130,7 +130,7 @@ protected boolean update(long[] quad, boolean explicit, boolean add) throws IOEx // use calloc to get an empty data value MDBVal dataVal = MDBVal.calloc(stack); ByteBuffer keyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); - Varint.writeListUnsigned(keyBuf, quad); + Varint.writeQuadUnsigned(keyBuf, quad); keyBuf.flip(); keyVal.mv_data(keyBuf); @@ -197,7 +197,7 @@ protected RecordCacheIterator(int dbi) throws IOException { public Record next() { if (mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT) == MDB_SUCCESS) { - Varint.readListUnsigned(keyData.mv_data(), quad); + Varint.readQuadUnsigned(keyData.mv_data(), quad); byte op = valueData.mv_data().get(0); Record r = new Record(); r.quad = quad; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java index af6632804d..8ee5494f8d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java @@ -446,26 +446,24 @@ public static long readListElementUnsigned(ByteBuffer bb, int index) { * @param values array with values to write */ public static void writeListUnsigned(final ByteBuffer bb, final long[] values) { - // TODO: Optimise for quads and also call writeUnsigned for (int i = 0; i < values.length; i++) { - final long value = values[i]; - if (value <= 240) { - bb.put((byte) value); - } else if (value <= 2287) { - bb.put((byte) ((value - 240) / 256 + 241)); - bb.put((byte) ((value - 240) % 256)); - } else if (value <= 67823) { - bb.put((byte) 249); - bb.put((byte) ((value - 2288) / 256)); - bb.put((byte) ((value - 2288) % 256)); - } else { - int bytes = descriptor(value) + 1; - bb.put((byte) (250 + (bytes - 3))); - writeSignificantBits(bb, value, bytes); - } + writeUnsigned(bb, values[i]); } } + /** + * Encodes multiple values using variable-length encoding into the given buffer. + * + * @param bb buffer for writing bytes + * @param values array with values to write + */ + public static void writeQuadUnsigned(final ByteBuffer bb, final long[] values) { + writeUnsigned(bb, values[0]); + writeUnsigned(bb, values[1]); + writeUnsigned(bb, values[2]); + writeUnsigned(bb, values[3]); + } + /** * Decodes multiple values using variable-length encoding from the given buffer. * diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/VarintTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/VarintTest.java index fef3bd6731..221b58843f 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/VarintTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/VarintTest.java @@ -129,10 +129,10 @@ public void testVarintList() { long[] expected = new long[4]; System.arraycopy(values, 0, expected, 0, 4); bb.clear(); - Varint.writeListUnsigned(bb, expected); + Varint.writeQuadUnsigned(bb, expected); bb.flip(); long[] actual = new long[4]; - Varint.readListUnsigned(bb, actual); + Varint.readQuadUnsigned(bb, actual); assertArrayEquals("Encoded and decoded value should be equal", expected, actual); } } From 642df3d72656da6bae3b18852ad2d2fb9a3f0cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 20 Oct 2025 21:45:13 +0200 Subject: [PATCH 32/79] wip --- .../eclipse/rdf4j/model/base/AbstractIRI.java | 16 +++++ .../eclipse/rdf4j/model/impl/SimpleIRI.java | 16 +++++ .../impl/SimpleIRICrossTypeEqualityTest.java | 68 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRICrossTypeEqualityTest.java diff --git a/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java b/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java index 8cf6dfb525..ab252919e9 100644 --- a/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java +++ b/core/model-api/src/main/java/org/eclipse/rdf4j/model/base/AbstractIRI.java @@ -124,6 +124,22 @@ private int split() { } } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof GenericIRI) { + // Fast-path for same concrete type: compare internal string directly + return this.iri.equals(((GenericIRI) o).iri); + } + if (o instanceof IRI) { + // Cross-type equality by lexical form + return stringValue().equals(((IRI) o).stringValue()); + } + return false; + } + } } diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java b/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java index e40b15e714..9092db42f9 100644 --- a/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java @@ -123,4 +123,20 @@ public String getLocalName() { return iriString.substring(localNameIdx); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof SimpleIRI) { + // Fast-path for same concrete type: compare internal string directly + return this.iriString.equals(((SimpleIRI) o).iriString); + } + if (o instanceof IRI) { + // Cross-type equality by lexical form + return stringValue().equals(((IRI) o).stringValue()); + } + return false; + } + } diff --git a/core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRICrossTypeEqualityTest.java b/core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRICrossTypeEqualityTest.java new file mode 100644 index 0000000000..5c043d8da3 --- /dev/null +++ b/core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRICrossTypeEqualityTest.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 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.model.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; + +import org.eclipse.rdf4j.model.IRI; +import org.junit.jupiter.api.Test; + +/** + * Verifies cross-type equality between {@link SimpleIRI} and a third-party {@link IRI} implementation that does not + * override {@link Object#equals(Object)} or {@link Object#hashCode()}. + */ +public class SimpleIRICrossTypeEqualityTest { + + @Test + public void simpleIriEqualsThirdPartyIriByStringValue() { + IRI simple = new SimpleIRI("http://example.org/x"); + IRI thirdParty = new ThirdPartyIri("http://example.org/", "x"); + + // Historical behavior: SimpleIRI compares by string value against any IRI implementation. + assertThat(simple).isEqualTo(thirdParty); + + // Note: The reverse direction is deliberately not asserted here. A third-party + // implementation may rely on Object.equals (identity) and thus not be symmetric. + } + + /** + * Minimal third-party IRI implementation: relies on Object.equals/hashCode (identity), only implements the methods + * required by the {@link IRI} contract for namespace/localname. + */ + private static final class ThirdPartyIri implements IRI, Serializable { + private static final long serialVersionUID = 1L; + + private final String namespace; + private final String localname; + + ThirdPartyIri(String namespace, String localname) { + this.namespace = namespace; + this.localname = localname; + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public String getLocalName() { + return localname; + } + + @Override + public String stringValue() { + return namespace + localname; + } + } +} From e21e7fffb6204ea222b7d96733dee8c3123e198d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 21 Oct 2025 13:26:51 +0200 Subject: [PATCH 33/79] wip --- .../base/GenericIRISymmetryContractTest.java | 64 +++++++++++++++++++ .../impl/SimpleIRISymmetryContractTest.java | 64 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 core/model-api/src/test/java/org/eclipse/rdf4j/model/base/GenericIRISymmetryContractTest.java create mode 100644 core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRISymmetryContractTest.java diff --git a/core/model-api/src/test/java/org/eclipse/rdf4j/model/base/GenericIRISymmetryContractTest.java b/core/model-api/src/test/java/org/eclipse/rdf4j/model/base/GenericIRISymmetryContractTest.java new file mode 100644 index 0000000000..6683464813 --- /dev/null +++ b/core/model-api/src/test/java/org/eclipse/rdf4j/model/base/GenericIRISymmetryContractTest.java @@ -0,0 +1,64 @@ +/** + * ****************************************************************************** + * Copyright (c) 2025 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.model.base; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; + +import org.eclipse.rdf4j.model.IRI; +import org.junit.jupiter.api.Test; + +/** + * Reproduces equals() symmetry issue for AbstractIRI.GenericIRI with third-party IRI implementations that rely on + * Object.equals. + */ +public class GenericIRISymmetryContractTest { + + private static final class ThirdPartyIri implements IRI, Serializable { + private static final long serialVersionUID = 1L; + private final String ns; + private final String ln; + + ThirdPartyIri(String ns, String ln) { + this.ns = ns; + this.ln = ln; + } + + @Override + public String getNamespace() { + return ns; + } + + @Override + public String getLocalName() { + return ln; + } + + @Override + public String stringValue() { + return ns + ln; + } + } + + @Test + public void equalsMustBeSymmetric() { + IRI generic = new AbstractIRI.GenericIRI("http://example.org/", "x"); + IRI third = new ThirdPartyIri("http://example.org/", "x"); + + boolean a = generic.equals(third); + boolean b = third.equals(generic); + + assertThat(a).as("GenericIRI.equals(third) must equal third.equals(GenericIRI)").isEqualTo(b); + } +} diff --git a/core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRISymmetryContractTest.java b/core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRISymmetryContractTest.java new file mode 100644 index 0000000000..aa6a5bd3d7 --- /dev/null +++ b/core/model/src/test/java/org/eclipse/rdf4j/model/impl/SimpleIRISymmetryContractTest.java @@ -0,0 +1,64 @@ +/** + * ****************************************************************************** + * Copyright (c) 2025 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.model.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; + +import org.eclipse.rdf4j.model.IRI; +import org.junit.jupiter.api.Test; + +/** + * Reproduces equals() symmetry issue for SimpleIRI with third-party IRI implementations that rely on Object.equals. + */ +public class SimpleIRISymmetryContractTest { + + private static final class ThirdPartyIri implements IRI, Serializable { + private static final long serialVersionUID = 1L; + private final String ns; + private final String ln; + + ThirdPartyIri(String ns, String ln) { + this.ns = ns; + this.ln = ln; + } + + @Override + public String getNamespace() { + return ns; + } + + @Override + public String getLocalName() { + return ln; + } + + @Override + public String stringValue() { + return ns + ln; + } + } + + @Test + public void equalsMustBeSymmetric() { + IRI simple = new SimpleIRI("http://example.org/x"); + IRI third = new ThirdPartyIri("http://example.org/", "x"); + + boolean a = simple.equals(third); + boolean b = third.equals(simple); + + // Contract: equals must be symmetric + assertThat(a).as("SimpleIRI.equals(third) must equal third.equals(SimpleIRI)").isEqualTo(b); + } +} From 1df624783ed49a4d2ac2d1cb099ea03f51512885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 21 Oct 2025 13:27:14 +0200 Subject: [PATCH 34/79] wip --- .../eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 3f8b3068fd..97782c8c26 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -298,13 +298,11 @@ public void simpleUpdateQueryIsolationNone() { @Test public void ordered_union_limit() { - for (int i = 0; i < 100; i++) { - try (SailRepositoryConnection connection = repository.getConnection()) { - long count = count(connection - .prepareTupleQuery(ordered_union_limit) - .evaluate()); - assertEquals(250L, count); - } + try (SailRepositoryConnection connection = repository.getConnection()) { + long count = count(connection + .prepareTupleQuery(ordered_union_limit) + .evaluate()); + assertEquals(250L, count); } } From 4ad69b7c8ff71d2f77ede87eaf2061a5333a8e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 27 Oct 2025 06:39:33 +0900 Subject: [PATCH 35/79] working on new ID based join iterator --- AGENTS.md | 88 ++++-- PLANS.md | 70 ++--- .../eclipse/rdf4j/sail/lmdb/IdAccessor.java | 31 ++ .../rdf4j/sail/lmdb/IdBindingInfo.java | 170 +++++++++++ .../rdf4j/sail/lmdb/LmdbDatasetContext.java | 27 ++ .../LmdbDelegatingQueryEvaluationContext.java | 98 +++++++ .../sail/lmdb/LmdbEvaluationDataset.java | 72 +++++ .../sail/lmdb/LmdbEvaluationStrategy.java | 101 +++++++ .../lmdb/LmdbEvaluationStrategyFactory.java | 61 ++++ .../rdf4j/sail/lmdb/LmdbIdVarBinding.java | 27 ++ .../lmdb/LmdbOverlayEvaluationDataset.java | 202 +++++++++++++ .../sail/lmdb/LmdbQueryEvaluationContext.java | 45 +++ .../rdf4j/sail/lmdb/LmdbSailStore.java | 191 ++++++++++++- .../eclipse/rdf4j/sail/lmdb/LmdbStore.java | 2 +- .../rdf4j/sail/lmdb/RecordIterator.java | 5 +- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 12 +- .../eclipse/rdf4j/sail/lmdb/ValueStore.java | 4 +- .../sail/lmdb/join/IdJoinRecordIterator.java | 73 +++++ .../join/LmdbIdBGPQueryEvaluationStep.java | 232 +++++++++++++++ .../join/LmdbIdFinalBindingSetIteration.java | 58 ++++ .../sail/lmdb/join/LmdbIdJoinIterator.java | 269 ++++++++++++++++++ .../join/LmdbIdJoinQueryEvaluationStep.java | 108 +++++++ .../rdf4j/sail/lmdb/join/package-info.java | 20 ++ .../sail/lmdb/IdJoinRecordIteratorTest.java | 76 +++++ .../sail/lmdb/LmdbIdBGPEvaluationTest.java | 167 +++++++++++ .../rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java | 240 ++++++++++++++++ .../lmdb/LmdbIdJoinDisableOnChangesTest.java | 154 ++++++++++ .../sail/lmdb/LmdbIdJoinEvaluationTest.java | 198 +++++++++++++ .../sail/lmdb/LmdbIdJoinIsolationTest.java | 156 ++++++++++ finding1.md | 66 +++++ finding2.md | 69 +++++ 31 files changed, 3022 insertions(+), 70 deletions(-) create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDatasetContext.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingQueryEvaluationContext.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryEvaluationContext.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/IdJoinRecordIterator.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/package-info.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/IdJoinRecordIteratorTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java create mode 100644 finding1.md create mode 100644 finding2.md diff --git a/AGENTS.md b/AGENTS.md index 33fde3d827..961be562b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,6 @@ Welcome, AI Agent! Your persistence, curiosity, and craftsmanship make a differe You need to read the entire AGENTS.md file and follow all instructions exactly. Keep this fresh in your context as you work. -> **Timebox:** Aim to complete each autonomous run in **15–30 minutes**. - --- ## Read‑Me‑Now: Proportional Test‑First Rule (Default) @@ -27,31 +25,36 @@ It is illegal to `-am` when running tests! It is illegal to `-q` when running tests! > **Clarification:** For **strictly behavior‑neutral refactors** that are already **fully exercised by existing tests**, or for **bugfixes with an existing failing test**, you may use **Routine B — Change without new tests**. In that case you must capture **pre‑change passing evidence** at the smallest scope that hits the code you’re about to edit, prove **Hit Proof**, then show **post‑change passing evidence** from the **same selection**. -> **No exceptions for any behavior‑changing change** — for those, you must follow **Routine A — Full TDD**. +> **No exceptions for any behavior‑changing change** — for those, you must follow **Routine A — Full TDD** or **Routine D — ExecPlans**. --- -## Three Routines: Choose Your Path +## Four Routines: Choose Your Path **Routine A — Full TDD (Default)** **Routine B — Change without new tests (Proportional, gated)** **Routine C — Spike/Investigate (No production changes)** +**Routine D — ExecPlans: Complex features or significant refactors** ### Decision quickstart -1. **Is new externally observable behavior required?** +1. **Is ExecPlans required (complex feature, significant refactor or requested by the user)?** + → **Yes:** **Routine D (ExecPlans)**. Use an ExecPlan (as described in .agent/PLANS.md) from design to implementation. + → **No:** continue. + +2**Is new externally observable behavior required?** → **Yes:** **Routine A (Full TDD)**. Add the smallest failing test first. → **No:** continue. -2. **Does a failing test already exist in this repo that pinpoints the issue?** +3**Does a failing test already exist in this repo that pinpoints the issue?** → **Yes:** **Routine B (Bugfix using existing failing test).** → **No:** continue. -3. **Is the edit strictly behavior‑neutral, local in scope, and clearly hit by existing tests?** +4**Is the edit strictly behavior‑neutral, local in scope, and clearly hit by existing tests?** → **Yes:** **Routine B (Refactor/micro‑perf/documentation/build).** → **No or unsure:** continue. -4. **Is this purely an investigation/design spike with no production code changes?** +5**Is this purely an investigation/design spike with no production code changes?** → **Yes:** **Routine C (Spike/Investigate).** → **No or unsure:** **Routine A.** @@ -59,27 +62,55 @@ It is illegal to `-q` when running tests! --- +## ExecPlans + +When writing complex features or significant refactors, use an ExecPlan (as described in PLANS.md) from design to implementation. + ## PIOSEE Decision Model (Adopted) -Use PIOSEE on every task to structure thinking and execution. It complements the routines below and ties directly into the Traceability trio (Description, Evidence, Plan). +Use this as a compact, repeatable loop for anything from a one‑line bug fix to a multi‑quarter program. + +### P — **Problem** + +**Goal:** State the core problem and what “good” looks like. +**Ask:** Who’s affected? What outcome is required? What happens if we do nothing? +**Tip:** Include measurable target(s): error rate ↓, latency p95 ↓, revenue ↑, risk ↓. + +### I — **Information** + +**Goal:** Gather only the facts needed to move. +**Ask:** What do logs/metrics/user feedback say? What constraints (security, compliance, budget, SLA/SLO)? What assumptions must we test? + +### O — **Options** + +**Goal:** Generate viable ways forward, including “do nothing.” +**Ask:** What are 2–4 distinct approaches (patch, redesign, buy vs. build, defer)? What risks, costs, and second‑order effects? +**Tip:** Check guardrails: reliability, security/privacy, accessibility, performance, operability, unit economics. -- Problem: restate the task in one sentence, note constraints/timebox, and identify likely routine (A/B/C). -- Information: inspect modules and AGENTS.md, gather environment constraints, locate existing tests/reports, and search code to localize the work. -- Options: list 2–3 viable approaches (routine choice, test scope, fix location) and weigh them with the Proportionality Model. -- Select: choose one option and routine; update the Living Plan with exactly one `in_progress` step. -- Execute: follow the Working Loop and house rules; for Routine A add the smallest failing test first; capture an Evidence block after each grouped action. -- Evaluate: check against the Definition of Done; if gaps remain, adjust the plan or change routine; record final Evidence and a brief retrospective. +### S — **Select** -PIOSEE → Traceability trio mapping -- P/I/O → Description -- S → Plan (one `in_progress`) -- E/E → Evidence and Verification +**Goal:** Decide deliberately and document why. +**Ask:** Which option best meets the success criteria under constraints? Who is the decision owner? What’s the fallback/abort condition? +**Tip:** Use lightweight scoring (e.g., Impact×Confidence÷Effort) to avoid bike‑shedding. -For documentation‑only edits and other Routine B cases, still run PIOSEE briefly to confirm neutrality and reversibility. +### E — **Execute** + +**Goal:** Ship safely and visibly. +**Ask:** What is the smallest safe slice? How do we de‑risk (feature flag, canary, dark launch, rollback)? Who owns what? +**Checklist:** Traces/logs/alerts; security & privacy checks; docs & changelog; incident plan if relevant. + +### E — **Evaluate** + +**Goal:** Verify outcomes and learn. +**Ask:** Did metrics hit targets? Any regressions or side effects? What will we keep/change next loop? +**Output:** Post‑release review (or retro), decision log entry, follow‑ups (tickets), debt captured. +**Tip:** If outcomes miss, either **iterate** (new Options) or **reframe** (back to Problem). + +--- ## Proportionality Model (Think before you test) -Score the change on these lenses. If any are **High**, prefer **Routine A**. +Score the change on these lenses. If any are **High**, prefer **Routine A or D**. - **Behavioral surface:** affects outputs, serialization, parsing, APIs, error text, timing/order? - **Blast radius:** number of modules/classes touched; public vs internal. @@ -101,7 +132,7 @@ Score the change on these lenses. If any are **High**, prefer **Routine A**. * Relevant module tests pass; failures triaged or crisply explained. * Only necessary files changed; headers correct for new files. * Clear final summary: what changed, why, where, how verified, next steps. - * **Evidence present:** failing test output (pre‑fix) and passing output (post‑fix) are shown for Routine A; for Routine B show **pre/post green** from the **same selection** plus **Hit Proof**. + * **Evidence present:** failing test output (pre‑fix) and passing output (post‑fix) are shown for Routine A; for Routine B show **pre/post green** from the **same selection** plus **Hit Proof**; for Routine D NO EVIDENCE. ### No Monkey‑Patching or Band‑Aid Fixes (Non‑Negotiable) @@ -336,6 +367,14 @@ It is illegal to `-q` when running tests! --- +## Routine D — ExecPlans + +> Use for **complex features or significant refactors**. + +When writing complex features or significant refactors, use an ExecPlan (as described in .agent/PLANS.md) from design to implementation. + +--- + ## Where to Draw the Line — A Short Debate > **Purist:** “All changes must start with a failing test.” @@ -349,7 +388,7 @@ It is illegal to `-q` when running tests! * Logging/message tweaks **not** asserted by tests. * Build/CI config that doesn’t alter runtime behavior. -**Out‑of‑scope (use Routine A)** +**Out‑of‑scope (use Routine A/D)** * Changing query results, serialization, or parsing behavior. * Altering error messages that tests assert. * Anything touching concurrency, timeouts, IO, or ordering. @@ -361,7 +400,7 @@ It is illegal to `-q` when running tests! ## Working Loop * **PIOSEE first:** restate Problem, gather Information, list Options; then Select, Execute, Evaluate. -* **Plan:** small, verifiable steps; keep one `in_progress`. +* **Plan:** small, verifiable steps; keep one `in_progress`, or follow PLANS.md (ExecPlans) * **Change:** minimal, surgical edits; keep style/structure consistent. * **Format:** `mvn -o -Dmaven.repo.local=.m2_repo -q -T 2C formatter:format impsort:sort xml-format:xml-format` * **Compile (fast):** `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick install | tail -500` @@ -530,6 +569,7 @@ Do **not** modify existing headers’ years. *Routine A:* failing output (pre‑fix) and passing output (post‑fix). *Routine B:* pre‑ and post‑green snippets from the **same selection** + **Hit Proof**. *Routine C:* artifacts from investigation (logs/notes/measurements) and proposed next steps. + *Routine D:* NO EVIDENCE REQUIRED. * **Assumptions:** key assumptions and autonomous decisions. * **Limitations:** anything left or risky edge cases. * **Next steps:** optional follow‑ups. diff --git a/PLANS.md b/PLANS.md index 7d8044a9f7..6422d1771a 100644 --- a/PLANS.md +++ b/PLANS.md @@ -1,77 +1,77 @@ # Codex Execution Plans (ExecPlans): - + This document describes the requirements for an execution plan ("ExecPlan"), a design document that a coding agent can follow to deliver a working feature or system change. Treat the reader as a complete beginner to this repository: they have only the current working tree and the single ExecPlan file you provide. There is no memory of prior plans and no external context. - + ## How to use ExecPlans and PLANS.md - + When authoring an executable specification (ExecPlan), follow PLANS.md _to the letter_. If it is not in your context, refresh your memory by reading the entire PLANS.md file. Be thorough in reading (and re-reading) source material to produce an accurate specification. When creating a spec, start from the skeleton and flesh it out as you do your research. - + When implementing an executable specification (ExecPlan), do not prompt the user for "next steps"; simply proceed to the next milestone. Keep all sections up to date, add or split entries in the list at every stopping point to affirmatively state the progress made and next steps. Resolve ambiguities autonomously, and commit frequently. - + When discussing an executable specification (ExecPlan), record decisions in a log in the spec for posterity; it should be unambiguously clear why any change to the specification was made. ExecPlans are living documents, and it should always be possible to restart from _only_ the ExecPlan and no other work. - + When researching a design with challenging requirements or significant unknowns, use milestones to implement proof of concepts, "toy implementations", etc., that allow validating whether the user's proposal is feasible. Read the source code of libraries by finding or acquiring them, research deeply, and include prototypes to guide a fuller implementation. - + ## Requirements - + NON-NEGOTIABLE REQUIREMENTS: - + * Every ExecPlan must be fully self-contained. Self-contained means that in its current form it contains all knowledge and instructions needed for a novice to succeed. * Every ExecPlan is a living document. Contributors are required to revise it as progress is made, as discoveries occur, and as design decisions are finalized. Each revision must remain fully self-contained. * Every ExecPlan must enable a complete novice to implement the feature end-to-end without prior knowledge of this repo. * Every ExecPlan must produce a demonstrably working behavior, not merely code changes to "meet a definition". * Every ExecPlan must define every term of art in plain language or do not use it. - + Purpose and intent come first. Begin by explaining, in a few sentences, why the work matters from a user's perspective: what someone can do after this change that they could not do before, and how to see it working. Then guide the reader through the exact steps to achieve that outcome, including what to edit, what to run, and what they should observe. - + The agent executing your plan can list files, read files, search, run the project, and run tests. It does not know any prior context and cannot infer what you meant from earlier milestones. Repeat any assumption you rely on. Do not point to external blogs or docs; if knowledge is required, embed it in the plan itself in your own words. If an ExecPlan builds upon a prior ExecPlan and that file is checked in, incorporate it by reference. If it is not, you must include all relevant context from that plan. - + ## Formatting - + Format and envelope are simple and strict. Each ExecPlan must be one single fenced code block labeled as `md` that begins and ends with triple backticks. Do not nest additional triple-backtick code fences inside; when you need to show commands, transcripts, diffs, or code, present them as indented blocks within that single fence. Use indentation for clarity rather than code fences inside an ExecPlan to avoid prematurely closing the ExecPlan's code fence. Use two newlines after every heading, use # and ## and so on, and correct syntax for ordered and unordered lists. - + When writing an ExecPlan to a Markdown (.md) file where the content of the file *is only* the single ExecPlan, you should omit the triple backticks. - + Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and long enumerations unless brevity would obscure meaning. Checklists are permitted only in the `Progress` section, where they are mandatory. Narrative sections must remain prose-first. - + ## Guidelines - + Self-containment and plain language are paramount. If you introduce a phrase that is not ordinary English ("daemon", "middleware", "RPC gateway", "filter graph"), define it immediately and remind the reader how it manifests in this repository (for example, by naming the files or commands where it appears). Do not say "as defined previously" or "according to the architecture doc." Include the needed explanation here, even if you repeat yourself. - + Avoid common failure modes. Do not rely on undefined jargon. Do not describe "the letter of a feature" so narrowly that the resulting code compiles but does nothing meaningful. Do not outsource key decisions to the reader. When ambiguity exists, resolve it in the plan itself and explain why you chose that path. Err on the side of over-explaining user-visible effects and under-specifying incidental implementation details. - + Anchor the plan with observable outcomes. State what the user can do after implementation, the commands to run, and the outputs they should see. Acceptance should be phrased as behavior a human can verify ("after starting the server, navigating to [http://localhost:8080/health](http://localhost:8080/health) returns HTTP 200 with body OK") rather than internal attributes ("added a HealthCheck struct"). If a change is internal, explain how its impact can still be demonstrated (for example, by running tests that fail before and pass after, and by showing a scenario that uses the new behavior). - + Specify repository context explicitly. Name files with full repository-relative paths, name functions and modules precisely, and describe where new files should be created. If touching multiple areas, include a short orientation paragraph that explains how those parts fit together so a novice can navigate confidently. When running commands, show the working directory and exact command line. When outcomes depend on environment, state the assumptions and provide alternatives when reasonable. - + Be idempotent and safe. Write the steps so they can be run multiple times without causing damage or drift. If a step can fail halfway, include how to retry or adapt. If a migration or destructive operation is necessary, spell out backups or safe fallbacks. Prefer additive, testable changes that can be validated as you go. - + Validation is not optional. Include instructions to run tests, to start the system if applicable, and to observe it doing something useful. Describe comprehensive testing for any new features or capabilities. Include expected outputs and error messages so a novice can tell success from failure. Where possible, show how to prove that the change is effective beyond compilation (for example, through a small end-to-end scenario, a CLI invocation, or an HTTP request/response transcript). State the exact test commands appropriate to the project’s toolchain and how to interpret their results. - + Capture evidence. When your steps produce terminal output, short diffs, or logs, include them inside the single fenced block as indented examples. Keep them concise and focused on what proves success. If you need to include a patch, prefer file-scoped diffs or small excerpts that a reader can recreate by following your instructions rather than pasting large blobs. - + ## Milestones - + Milestones are narrative, not bureaucracy. If you break the work into milestones, introduce each with a brief paragraph that describes the scope, what will exist at the end of the milestone that did not exist before, the commands to run, and the acceptance you expect to observe. Keep it readable as a story: goal, work, result, proof. Progress and milestones are distinct: milestones tell the story, progress tracks granular work. Both must exist. Never abbreviate a milestone merely for the sake of brevity, do not leave out details that could be crucial to a future implementation. - + Each milestone must be independently verifiable and incrementally implement the overall goal of the execution plan. - + ## Living plans and design decisions - + * ExecPlans are living documents. As you make key design decisions, update the plan to record both the decision and the thinking behind it. Record all decisions in the `Decision Log` section. * ExecPlans must contain and maintain a `Progress` section, a `Surprises & Discoveries` section, a `Decision Log`, and an `Outcomes & Retrospective` section. These are not optional. * When you discover optimizer behavior, performance tradeoffs, unexpected bugs, or inverse/unapply semantics that shaped your approach, capture those observations in the `Surprises & Discoveries` section with short evidence snippets (test output is ideal). * If you change course mid-implementation, document why in the `Decision Log` and reflect the implications in `Progress`. Plans are guides for the next contributor as much as checklists for you. * At completion of a major task or the full plan, write an `Outcomes & Retrospective` entry summarizing what was achieved, what remains, and lessons learned. - + # Prototyping milestones and parallel implementations - + It is acceptable—-and often encouraged—-to include explicit prototyping milestones when they de-risk a larger change. Examples: adding a low-level operator to a dependency to validate feasibility, or exploring two composition orders while measuring optimizer effects. Keep prototypes additive and testable. Clearly label the scope as “prototyping”; describe how to run and observe results; and state the criteria for promoting or discarding the prototype. - + Prefer additive code changes followed by subtractions that keep tests passing. Parallel implementations (e.g., keeping an adapter alongside an older path during migration) are fine when they reduce risk or enable tests to continue passing during a large migration. Describe how to validate both paths and how to retire one safely with tests. When working with multiple new libraries or feature areas, consider creating spikes that evaluate the feasibility of these features _independently_ of one another, proving that the external library performs as expected and implements the features we need in isolation. - + ## Skeleton of a Good ExecPlan - + ```md # @@ -146,7 +146,7 @@ In crates/foo/planner.rs, define: fn plan(&self, observed: &Observed) -> Vec; } ``` - + If you follow the guidance above, a single, stateless agent -- or a human novice -- can read your ExecPlan from top to bottom and produce a working, observable result. That is the bar: SELF-CONTAINED, SELF-SUFFICIENT, NOVICE-GUIDING, OUTCOME-FOCUSED. - + When you revise a plan, you must ensure your changes are comprehensively reflected across all sections, including the living document sections, and you must write a note at the bottom of the plan describing the change and the reason why. ExecPlans must describe not just the what but the why for almost everything. diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java new file mode 100644 index 0000000000..fd24b168e6 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Set; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; + +/** + * Read-only accessor for variable ID lookups against a record long[] produced by a RecordIterator. + */ +@InternalUseOnly +public interface IdAccessor { + long getId(long[] record, String varName); + + Set getVariableNames(); + + /** + * Returns the index inside the {@code long[]} record where the given variable's ID is stored. Implementations + * should return {@code -1} when the variable is not part of the record. + */ + int getRecordIndex(String varName); +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java new file mode 100644 index 0000000000..35ffd8b1d3 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +/** + * Describes an ID-binding record shape (variables and their positions) and can materialize it to a BindingSet. + */ +@InternalUseOnly +public final class IdBindingInfo implements IdAccessor { + + private final Map indexByVar; // insertion order preserved + private final Map positionsMaskByVar; // aggregated from contributing patterns + + IdBindingInfo(LinkedHashMap indexByVar, Map positionsMaskByVar) { + this.indexByVar = Collections.unmodifiableMap(new LinkedHashMap<>(indexByVar)); + this.positionsMaskByVar = Collections.unmodifiableMap(positionsMaskByVar); + } + + @Override + public Set getVariableNames() { + return indexByVar.keySet(); + } + + public int size() { + return indexByVar.size(); + } + + public int getIndex(String name) { + Integer idx = indexByVar.get(name); + return idx == null ? -1 : idx; + } + + public int getPositionsMask(String name) { + Integer m = positionsMaskByVar.get(name); + return m == null ? 0 : m; + } + + @Override + public int getRecordIndex(String varName) { + return getIndex(varName); + } + + @Override + public long getId(long[] record, String varName) { + int i = getIndex(varName); + if (i < 0 || i >= record.length) { + return LmdbValue.UNKNOWN_ID; + } + return record[i]; + } + + public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) + throws QueryEvaluationException { + for (Map.Entry e : indexByVar.entrySet()) { + String name = e.getKey(); + int idx = e.getValue(); + long id = record[idx]; + if (id == LmdbValue.UNKNOWN_ID) { + continue; + } + int mask = getPositionsMask(name); + if (((mask >> TripleStore.CONTEXT_IDX) & 1) == 1 && id == 0L) { + // default graph treated as null (unbound) + continue; + } + Value existing = target.getValue(name); + Value candidate; + try { + candidate = valueStore.getLazyValue(id); + } catch (IOException ex) { + throw new QueryEvaluationException(ex); + } + if (existing != null && !existing.equals(candidate)) { + return false; + } + if (existing == null) { + target.setBinding(name, candidate); + } + } + return true; + } + + public static IdBindingInfo combine(IdBindingInfo left, LmdbIdJoinIterator.PatternInfo right) { + LinkedHashMap map = new LinkedHashMap<>(); + Map masks = new java.util.HashMap<>(); + // left variables first + if (left != null) { + for (String v : left.getVariableNames()) { + map.put(v, map.size()); + masks.put(v, left.getPositionsMask(v)); + } + } + // add right-only variables and merge masks for shared + for (String v : right.getVariableNames()) { + Integer mask = right.getPositionsMask(v); + if (!map.containsKey(v)) { + map.put(v, map.size()); + masks.put(v, mask); + } else { + masks.put(v, masks.getOrDefault(v, 0) | mask); + } + } + return new IdBindingInfo(map, masks); + } + + public static IdBindingInfo fromFirstPattern(LmdbIdJoinIterator.PatternInfo first) { + LinkedHashMap map = new LinkedHashMap<>(); + Map masks = new java.util.HashMap<>(); + // Use SPOC order for stability + String s = firstVarName(first, TripleStore.SUBJ_IDX); + if (s != null) { + map.put(s, map.size()); + masks.put(s, first.getPositionsMask(s)); + } + String p = firstVarName(first, TripleStore.PRED_IDX); + if (p != null && !map.containsKey(p)) { + map.put(p, map.size()); + masks.put(p, first.getPositionsMask(p)); + } + String o = firstVarName(first, TripleStore.OBJ_IDX); + if (o != null && !map.containsKey(o)) { + map.put(o, map.size()); + masks.put(o, first.getPositionsMask(o)); + } + String c = firstVarName(first, TripleStore.CONTEXT_IDX); + if (c != null && !map.containsKey(c)) { + map.put(c, map.size()); + masks.put(c, first.getPositionsMask(c)); + } + // include any remaining (e.g., repeated vars not in SPOC scan) + for (String v : first.getVariableNames()) { + if (!map.containsKey(v)) { + map.put(v, map.size()); + masks.put(v, first.getPositionsMask(v)); + } + } + return new IdBindingInfo(map, masks); + } + + private static String firstVarName(LmdbIdJoinIterator.PatternInfo info, int position) { + for (String v : info.getVariableNames()) { + int mask = info.getPositionsMask(v); + if (((mask >> position) & 1) == 1) { + return v; + } + } + return null; + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDatasetContext.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDatasetContext.java new file mode 100644 index 0000000000..345029cb37 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDatasetContext.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Optional; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; + +/** + * Exposes LMDB-specific resources from a + * {@link org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext}. + */ +@InternalUseOnly +public interface LmdbDatasetContext { + + Optional getLmdbDataset(); + + Optional getValueStore(); +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingQueryEvaluationContext.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingQueryEvaluationContext.java new file mode 100644 index 0000000000..51f744a405 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingQueryEvaluationContext.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Comparator; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.Binding; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; + +class LmdbDelegatingQueryEvaluationContext implements QueryEvaluationContext, LmdbDatasetContext { + + private final QueryEvaluationContext delegate; + private final LmdbEvaluationDataset dataset; + private final ValueStore valueStore; + + LmdbDelegatingQueryEvaluationContext(QueryEvaluationContext delegate, LmdbEvaluationDataset dataset, + ValueStore valueStore) { + this.delegate = delegate; + this.dataset = dataset; + this.valueStore = valueStore; + } + + @Override + public Comparator getComparator() { + return delegate.getComparator(); + } + + @Override + public org.eclipse.rdf4j.model.Literal getNow() { + return delegate.getNow(); + } + + @Override + public Dataset getDataset() { + return delegate.getDataset(); + } + + @Override + public MutableBindingSet createBindingSet() { + return delegate.createBindingSet(); + } + + @Override + public Predicate hasBinding(String variableName) { + return delegate.hasBinding(variableName); + } + + @Override + public Function getBinding(String variableName) { + return delegate.getBinding(variableName); + } + + @Override + public Function getValue(String variableName) { + return delegate.getValue(variableName); + } + + @Override + public BiConsumer setBinding(String variableName) { + return delegate.setBinding(variableName); + } + + @Override + public BiConsumer addBinding(String variableName) { + return delegate.addBinding(variableName); + } + + @Override + public MutableBindingSet createBindingSet(BindingSet bindings) { + return delegate.createBindingSet(bindings); + } + + @Override + public Optional getLmdbDataset() { + return Optional.ofNullable(dataset); + } + + @Override + public Optional getValueStore() { + return Optional.ofNullable(valueStore); + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java new file mode 100644 index 0000000000..68ea5a641b --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.StatementPattern; + +/** + * Provides LMDB-specific access needed during query evaluation. + */ +public interface LmdbEvaluationDataset { + + /** + * Create a {@link RecordIterator} for the supplied {@link StatementPattern}, taking into account any existing + * bindings. + * + * @param pattern the statement pattern to evaluate + * @param bindings the bindings that should be respected when creating the iterator + * @return a {@link RecordIterator} that yields matching statement records + * @throws QueryEvaluationException if the iterator could not be created + */ + RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException; + + /** + * Create a {@link RecordIterator} for the supplied {@link StatementPattern}, taking into account any existing + * variable bindings provided as LMDB internal IDs. + * + *

    + * This overload avoids materializing Values during join evaluation and should be used when chaining joins in an + * ID-only pipeline. + *

    + * + * @param pattern the statement pattern to evaluate + * @param idBinding a binding accessor that can provide internal IDs for variables present on the left side of the + * join + * @return a {@link RecordIterator} that yields matching statement records + * @throws QueryEvaluationException if the iterator could not be created + */ + @InternalUseOnly + public RecordIterator getRecordIterator(StatementPattern pattern, LmdbIdVarBinding idBinding) + throws QueryEvaluationException; + + /** + * @return the {@link ValueStore} backing this dataset. + */ + ValueStore getValueStore(); + + /** + * Indicates whether the current evaluation should consider the active transaction as containing uncommitted changes + * that require reading through an overlay rather than directly from the LMDB indexes. + * + *

    + * Implementations that expose a transaction overlay should override this to return {@code true}. The default is + * {@code false} for plain snapshot datasets. + *

    + * + * @return {@code true} if the evaluation is layered over uncommitted transaction changes; {@code false} otherwise. + */ + default boolean hasTransactionChanges() { + return false; + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java new file mode 100644 index 0000000000..8eb9b11797 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.transaction.QueryEvaluationMode; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.QueryRoot; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.ArrayBindingBasedQueryEvaluationContext; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategy; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdBGPQueryEvaluationStep; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinQueryEvaluationStep; + +/** + * Evaluation strategy that can use LMDB-specific join iterators. + */ +@InternalUseOnly +public class LmdbEvaluationStrategy extends StrictEvaluationStrategy { + + private static final ThreadLocal CURRENT_DATASET = new ThreadLocal<>(); + + LmdbEvaluationStrategy(TripleSource tripleSource, Dataset dataset, FederatedServiceResolver serviceResolver, + long iterationCacheSyncThreshold, EvaluationStatistics evaluationStatistics, boolean trackResultSize) { + super(tripleSource, dataset, serviceResolver, iterationCacheSyncThreshold, evaluationStatistics, + trackResultSize); + setQueryEvaluationMode(QueryEvaluationMode.STRICT); + } + + @Override + public QueryEvaluationStep precompile(TupleExpr expr) { + LmdbEvaluationDataset datasetRef = CURRENT_DATASET.get(); + ValueStore valueStore = datasetRef != null ? datasetRef.getValueStore() : null; + // Prefer the active LMDB dataset for ID-join evaluation to avoid premature materialization. + LmdbEvaluationDataset effectiveDataset = datasetRef != null ? datasetRef : null; + LmdbQueryEvaluationContext baseContext = new LmdbQueryEvaluationContext(dataset, tripleSource.getValueFactory(), + tripleSource.getComparator(), effectiveDataset, valueStore); + QueryEvaluationContext context = baseContext; + if (expr instanceof QueryRoot) { + String[] allVariables = ArrayBindingBasedQueryEvaluationContext + .findAllVariablesUsedInQuery((QueryRoot) expr); + QueryEvaluationContext arrayContext = new ArrayBindingBasedQueryEvaluationContext(baseContext, allVariables, + tripleSource.getComparator()); + context = new LmdbDelegatingQueryEvaluationContext(arrayContext, effectiveDataset, valueStore); + } + return precompile(expr, context); + } + + @Override + protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) { + if (context instanceof LmdbDatasetContext && ((LmdbDatasetContext) context).getLmdbDataset().isPresent()) { + LmdbEvaluationDataset ds = ((LmdbDatasetContext) context).getLmdbDataset().get(); + // If the active transaction has uncommitted changes, avoid ID-only join shortcuts. + if (ds.hasTransactionChanges()) { + return super.prepare(node, context); + } + // Try to flatten a full BGP of statement patterns + List patterns = new ArrayList<>(); + if (LmdbIdBGPQueryEvaluationStep.flattenBGP(node, patterns) + && !patterns.isEmpty()) { + return new LmdbIdBGPQueryEvaluationStep(node, patterns, context); + } + // Fallback to two-pattern ID join + if (node.getLeftArg() instanceof StatementPattern && node.getRightArg() instanceof StatementPattern) { + return new LmdbIdJoinQueryEvaluationStep(this, node, context); + } + } + return super.prepare(node, context); + } + + static void setCurrentDataset(LmdbEvaluationDataset dataset) { + CURRENT_DATASET.set(dataset); + } + + static void clearCurrentDataset() { + CURRENT_DATASET.remove(); + } + + public static Optional getCurrentDataset() { + return Optional.ofNullable(CURRENT_DATASET.get()); + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java new file mode 100644 index 0000000000..c31d79c389 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.function.Supplier; + +import org.eclipse.rdf4j.collection.factory.api.CollectionFactory; +import org.eclipse.rdf4j.collection.factory.impl.DefaultCollectionFactory; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; +import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; + +/** + * Evaluation strategy factory that installs LMDB-specific join behaviour. + */ +class LmdbEvaluationStrategyFactory extends StrictEvaluationStrategyFactory { + + private FederatedServiceResolver serviceResolver; + private Supplier collectionFactorySupplier = DefaultCollectionFactory::new; + + LmdbEvaluationStrategyFactory(FederatedServiceResolver resolver) { + this.serviceResolver = resolver; + } + + @Override + public void setFederatedServiceResolver(FederatedServiceResolver resolver) { + this.serviceResolver = resolver; + } + + @Override + public FederatedServiceResolver getFederatedServiceResolver() { + return serviceResolver; + } + + @Override + public void setCollectionFactory(Supplier collectionFactory) { + this.collectionFactorySupplier = collectionFactory; + } + + @Override + public EvaluationStrategy createEvaluationStrategy(Dataset dataset, TripleSource tripleSource, + EvaluationStatistics evaluationStatistics) { + LmdbEvaluationStrategy strategy = new LmdbEvaluationStrategy(tripleSource, dataset, serviceResolver, + getQuerySolutionCacheThreshold(), evaluationStatistics, isTrackResultSize()); + getOptimizerPipeline().ifPresent(strategy::setOptimizerPipeline); + strategy.setCollectionFactory(collectionFactorySupplier); + return strategy; + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java new file mode 100644 index 0000000000..78ff7f8d23 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; + +/** + * Provides variable bindings as LMDB internal IDs during ID-only join evaluation. + */ +@InternalUseOnly +public interface LmdbIdVarBinding { + + /** + * Return the internal ID for the given variable name or + * {@link org.eclipse.rdf4j.sail.lmdb.model.LmdbValue#UNKNOWN_ID} if the variable is unbound in the current left + * record. + */ + long getIdOrUnknown(String varName); +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java new file mode 100644 index 0000000000..3359cf2302 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -0,0 +1,202 @@ +/***/ +/******************************************************************************* +* Copyright (c) 2025 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.sail.lmdb; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +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.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; + +/** + * An {@link LmdbEvaluationDataset} implementation that reads via the delegated Sail layer (through the + * {@link TripleSource}) to ensure transaction overlays and isolation levels are honored, while exposing LMDB record + * iterators for the ID join. + */ +final class LmdbOverlayEvaluationDataset implements LmdbEvaluationDataset { + + private final TripleSource tripleSource; + private final ValueStore valueStore; + + LmdbOverlayEvaluationDataset(TripleSource tripleSource, ValueStore valueStore) { + this.tripleSource = tripleSource; + this.valueStore = valueStore; + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) + throws QueryEvaluationException { + + Value subj = resolveValue(pattern.getSubjectVar(), bindings); + if (subj != null && !(subj instanceof Resource)) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + Value pred = resolveValue(pattern.getPredicateVar(), bindings); + if (pred != null && !(pred instanceof IRI)) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + Value obj = resolveValue(pattern.getObjectVar(), bindings); + Value ctxVal = resolveValue(pattern.getContextVar(), bindings); + + Resource subjRes = (Resource) subj; // may be null + IRI predIri = (IRI) pred; // may be null + + if (ctxVal != null && !(ctxVal instanceof Resource)) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + if (ctxVal instanceof Resource && ((Resource) ctxVal).isTriple()) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + + Resource[] contexts; + if (ctxVal == null) { + contexts = new Resource[0]; // no restriction: all contexts + } else { + contexts = new Resource[] { (Resource) ctxVal }; + } + + final CloseableIteration stmts = contexts.length == 0 + ? tripleSource.getStatements(subjRes, predIri, obj) + : tripleSource.getStatements(subjRes, predIri, obj, contexts); + + return new RecordIterator() { + @Override + public long[] next() throws QueryEvaluationException { + try { + if (!stmts.hasNext()) { + stmts.close(); + return null; + } + Statement st = stmts.next(); + if (st == null) { + stmts.close(); + return null; + } + long s = resolveId(st.getSubject()); + long p = resolveId(st.getPredicate()); + long o = resolveId(st.getObject()); + long c = st.getContext() == null ? 0L : resolveId(st.getContext()); + return new long[] { s, p, o, c }; + } catch (Exception e) { + // ensure iterator is closed on error + try { + stmts.close(); + } catch (Exception ignore) { + } + if (e instanceof QueryEvaluationException) { + throw (QueryEvaluationException) e; + } + throw new QueryEvaluationException(e); + } + } + + @Override + public void close() { + try { + stmts.close(); + } catch (Exception ignore) { + } + } + }; + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, LmdbIdVarBinding idBinding) + throws QueryEvaluationException { + + // Translate known ID bindings to Values and delegate to the BindingSet-based variant + org.eclipse.rdf4j.query.impl.MapBindingSet bs = new org.eclipse.rdf4j.query.impl.MapBindingSet(); + try { + org.eclipse.rdf4j.query.algebra.Var s = pattern.getSubjectVar(); + if (s != null && !s.hasValue()) { + long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID + : idBinding.getIdOrUnknown(s.getName()); + if (id >= 0) { // allow 0 only if context; for subject 0 won't occur + bs.addBinding(s.getName(), valueStore.getLazyValue(id)); + } + } + org.eclipse.rdf4j.query.algebra.Var p = pattern.getPredicateVar(); + if (p != null && !p.hasValue()) { + long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID + : idBinding.getIdOrUnknown(p.getName()); + if (id >= 0) { + bs.addBinding(p.getName(), valueStore.getLazyValue(id)); + } + } + org.eclipse.rdf4j.query.algebra.Var o = pattern.getObjectVar(); + if (o != null && !o.hasValue()) { + long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID + : idBinding.getIdOrUnknown(o.getName()); + if (id >= 0) { + bs.addBinding(o.getName(), valueStore.getLazyValue(id)); + } + } + org.eclipse.rdf4j.query.algebra.Var c = pattern.getContextVar(); + if (c != null && !c.hasValue()) { + long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID + : idBinding.getIdOrUnknown(c.getName()); + if (id > 0) { // context id 0 means default graph (treated as unbound in this overlay) + bs.addBinding(c.getName(), valueStore.getLazyValue(id)); + } + } + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + + return getRecordIterator(pattern, bs); + } + + @Override + public ValueStore getValueStore() { + return valueStore; + } + + @Override + public boolean hasTransactionChanges() { + return true; + } + + private Value resolveValue(Var var, BindingSet bindings) { + if (var == null) { + return null; + } + if (var.hasValue()) { + return var.getValue(); + } + if (bindings != null) { + Value bound = bindings.getValue(var.getName()); + if (bound != null) { + return bound; + } + } + return null; + } + + private long resolveId(Value value) throws Exception { + if (value == null) { + return org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + } + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (valueStore.getRevision().equals(lmdbValue.getValueStoreRevision())) { + return lmdbValue.getInternalID(); + } + } + return valueStore.getId(value); + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryEvaluationContext.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryEvaluationContext.java new file mode 100644 index 0000000000..2c253f33cf --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryEvaluationContext.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Comparator; +import java.util.Optional; + +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; + +/** + * Extension of {@link QueryEvaluationContext} that exposes LMDB specific resources to evaluation steps. + */ +public class LmdbQueryEvaluationContext extends QueryEvaluationContext.Minimal implements LmdbDatasetContext { + + private final LmdbEvaluationDataset dataset; + private final ValueStore valueStore; + + public LmdbQueryEvaluationContext(Dataset dataset, ValueFactory valueFactory, Comparator comparator, + LmdbEvaluationDataset lmdbDataset, ValueStore valueStore) { + super(dataset, valueFactory, comparator); + this.dataset = lmdbDataset; + this.valueStore = valueStore; + } + + @Override + public Optional getLmdbDataset() { + return Optional.ofNullable(dataset); + } + + @Override + public Optional getValueStore() { + return Optional.ofNullable(valueStore); + } +} 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 388ec42b68..d799f740e1 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 @@ -42,6 +42,10 @@ import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; import org.eclipse.rdf4j.query.algebra.evaluation.util.ValueComparator; import org.eclipse.rdf4j.sail.InterruptedSailException; @@ -62,6 +66,18 @@ */ class LmdbSailStore implements SailStore { + private static final RecordIterator EMPTY_RECORD_ITERATOR = new RecordIterator() { + @Override + public long[] next() { + return null; + } + + @Override + public void close() { + // no-op + } + }; + final Logger logger = LoggerFactory.getLogger(LmdbSailStore.class); private final TripleStore tripleStore; @@ -1089,7 +1105,7 @@ public boolean supportsDeprecateByQuery() { } } - private final class LmdbSailDataset implements SailDataset { + private final class LmdbSailDataset implements SailDataset, LmdbEvaluationDataset { private final boolean explicit; private final Txn txn; @@ -1098,6 +1114,7 @@ public LmdbSailDataset(boolean explicit) throws SailException { this.explicit = explicit; try { this.txn = tripleStore.getTxnManager().createReadTxn(); + LmdbEvaluationStrategy.setCurrentDataset(this); } catch (IOException e) { throw new SailException(e); } @@ -1105,8 +1122,12 @@ public LmdbSailDataset(boolean explicit) throws SailException { @Override public void close() { - // close the associated txn - txn.close(); + try { + // close the associated txn + txn.close(); + } finally { + LmdbEvaluationStrategy.clearCurrentDataset(); + } } @Override @@ -1338,6 +1359,170 @@ private StatementOrder toStatementOrder(char f) { } } + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) + throws QueryEvaluationException { + try { + Value subj = resolveValue(pattern.getSubjectVar(), bindings); + if (subj != null && !(subj instanceof Resource)) { + return emptyRecordIterator(); + } + Value pred = resolveValue(pattern.getPredicateVar(), bindings); + if (pred != null && !(pred instanceof IRI)) { + return emptyRecordIterator(); + } + Value obj = resolveValue(pattern.getObjectVar(), bindings); + + long subjID = resolveId(subj); + if (subj != null && subjID == LmdbValue.UNKNOWN_ID) { + return emptyRecordIterator(); + } + + long predID = resolveId(pred); + if (pred != null && predID == LmdbValue.UNKNOWN_ID) { + return emptyRecordIterator(); + } + + long objID = resolveId(obj); + if (obj != null && objID == LmdbValue.UNKNOWN_ID) { + return emptyRecordIterator(); + } + + Value contextValue = resolveValue(pattern.getContextVar(), bindings); + long contextID; + if (contextValue == null) { + contextID = LmdbValue.UNKNOWN_ID; + } else if (contextValue instanceof Resource) { + Resource ctx = (Resource) contextValue; + if (ctx.isTriple()) { + return emptyRecordIterator(); + } + contextID = resolveId(ctx); + if (contextID == LmdbValue.UNKNOWN_ID) { + return emptyRecordIterator(); + } + } else { + return emptyRecordIterator(); + } + + return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); + } catch (IOException e) { + throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + } + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, LmdbIdVarBinding idBinding) + throws QueryEvaluationException { + try { + // Resolve subject + long subjID = resolveIdFromVarOrBinding(pattern.getSubjectVar(), idBinding, true); + if (subjID == Long.MIN_VALUE) { // incompatible type + return emptyRecordIterator(); + } + + // Resolve predicate + long predID = resolveIdFromVarOrBinding(pattern.getPredicateVar(), idBinding, false); + if (predID == Long.MIN_VALUE) { + return emptyRecordIterator(); + } + + // Resolve object + long objID = resolveIdFromVarOrBinding(pattern.getObjectVar(), idBinding, null); + if (objID == Long.MIN_VALUE) { + return emptyRecordIterator(); + } + + // Resolve context + org.eclipse.rdf4j.query.algebra.Var ctxVar = pattern.getContextVar(); + long contextID; + if (ctxVar == null) { + contextID = LmdbValue.UNKNOWN_ID; + } else if (ctxVar.hasValue()) { + Value ctxValue = ctxVar.getValue(); + if (ctxValue instanceof Resource) { + Resource ctx = (Resource) ctxValue; + if (ctx.isTriple()) { + return emptyRecordIterator(); + } + contextID = resolveId(ctx); + if (contextID == LmdbValue.UNKNOWN_ID) { + return emptyRecordIterator(); + } + } else { + return emptyRecordIterator(); + } + } else { + // variable context; if bound on left, use that; 0 represents default graph + long bound = idBinding == null ? LmdbValue.UNKNOWN_ID : idBinding.getIdOrUnknown(ctxVar.getName()); + contextID = bound; + } + + return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); + } catch (IOException e) { + throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + } + } + + private long resolveIdFromVarOrBinding(org.eclipse.rdf4j.query.algebra.Var var, LmdbIdVarBinding idBinding, + Boolean resourceType) throws IOException { + if (var == null) { + return LmdbValue.UNKNOWN_ID; + } + if (var.hasValue()) { + Value v = var.getValue(); + if (resourceType == Boolean.TRUE && !(v instanceof Resource)) { + return Long.MIN_VALUE; // incompatible type + } + if (resourceType == Boolean.FALSE && !(v instanceof IRI)) { + return Long.MIN_VALUE; // incompatible type + } + long id = resolveId(v); + return id == LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; + } + long bound = idBinding == null ? LmdbValue.UNKNOWN_ID : idBinding.getIdOrUnknown(var.getName()); + return bound; + } + + @Override + public ValueStore getValueStore() { + return valueStore; + } + + private RecordIterator emptyRecordIterator() { + return EMPTY_RECORD_ITERATOR; + } + + private Value resolveValue(Var var, BindingSet bindings) { + if (var == null) { + return null; + } + if (var.hasValue()) { + return var.getValue(); + } + if (bindings != null) { + Value bound = bindings.getValue(var.getName()); + if (bound != null) { + return bound; + } + } + return null; + } + + private long resolveId(Value value) throws IOException { + if (value == null) { + return LmdbValue.UNKNOWN_ID; + } + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (valueStore.getRevision().equals(lmdbValue.getValueStoreRevision())) { + return lmdbValue.getInternalID(); + } + } + long id = valueStore.getId(value); + return id; + } + private TripleStore.TripleIndex chooseIndexForOrder(StatementOrder order, long s, long p, long o, long c) throws IOException { // ensure metadata initialized diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java index 2dc7fd6e51..6c9791d4c9 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java @@ -169,7 +169,7 @@ public void setDataDir(File dataDir) { */ public synchronized EvaluationStrategyFactory getEvaluationStrategyFactory() { if (evalStratFactory == null) { - evalStratFactory = new StrictEvaluationStrategyFactory(getFederatedServiceResolver()); + evalStratFactory = new LmdbEvaluationStrategyFactory(getFederatedServiceResolver()); } evalStratFactory.setQuerySolutionCacheThreshold(getIterationCacheSyncThreshold()); evalStratFactory.setTrackResultSize(isTrackResultSize()); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/RecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/RecordIterator.java index c0a89c0628..1f29b9d68d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/RecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/RecordIterator.java @@ -12,10 +12,13 @@ import java.io.Closeable; +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; + /** * An iterator that iterates over records, for example those in a key-value database. */ -interface RecordIterator extends Closeable { +@InternalUseOnly +public interface RecordIterator extends Closeable { /** * Returns the next record. 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 d44d6f3171..86d5aacab4 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 @@ -78,6 +78,7 @@ import java.util.concurrent.atomic.LongAdder; import java.util.function.Consumer; +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.lmdb.TxnManager.Mode; @@ -102,7 +103,8 @@ * an actual RDF value. */ @SuppressWarnings("deprecation") -class TripleStore implements Closeable { +@InternalUseOnly +public class TripleStore implements Closeable { static ConcurrentHashMap stats = new ConcurrentHashMap<>(); static long hit = 0; @@ -114,10 +116,10 @@ class TripleStore implements Closeable { *-----------*/ // triples are represented by 4 varints for subject, predicate, object and context - static final int SUBJ_IDX = 0; - static final int PRED_IDX = 1; - static final int OBJ_IDX = 2; - static final int CONTEXT_IDX = 3; + public static final int SUBJ_IDX = 0; + public static final int PRED_IDX = 1; + public static final int OBJ_IDX = 2; + public static final int CONTEXT_IDX = 3; static final int MAX_KEY_LENGTH = 4 * 9; 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 2d8ac34f7e..54a5152487 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 @@ -67,6 +67,7 @@ import java.util.concurrent.locks.StampedLock; import java.util.zip.CRC32; +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.ConcurrentCleaner; import org.eclipse.rdf4j.common.io.ByteArrayUtil; import org.eclipse.rdf4j.model.BNode; @@ -96,7 +97,8 @@ /** * LMDB-based indexed storage and retrieval of RDF values. ValueStore maps RDF values to integer IDs and vice-versa. */ -class ValueStore extends AbstractValueFactory { +@InternalUseOnly +public class ValueStore extends AbstractValueFactory { private final static Logger logger = LoggerFactory.getLogger(ValueStore.class); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/IdJoinRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/IdJoinRecordIterator.java new file mode 100644 index 0000000000..4da0fd4ea7 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/IdJoinRecordIterator.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; + +/** + * Record iterator that interleaves left and right record iterators in the same way that + * {@link org.eclipse.rdf4j.query.algebra.evaluation.iterator.JoinIterator} does for binding sets, but operating purely + * on ID-based record arrays. + */ +public final class IdJoinRecordIterator implements RecordIterator { + + @FunctionalInterface + public interface RightFactory { + RecordIterator apply(long[] leftRecord) throws QueryEvaluationException; + } + + private final RecordIterator left; + private final RightFactory rightFactory; + private RecordIterator currentRight; + + public IdJoinRecordIterator(RecordIterator left, RightFactory rightFactory) { + this.left = left; + this.rightFactory = rightFactory; + } + + @Override + public long[] next() { + try { + while (true) { + if (currentRight != null) { + long[] rightRec = currentRight.next(); + if (rightRec != null) { + return rightRec; + } + currentRight.close(); + currentRight = null; + } + + long[] leftRec = left.next(); + if (leftRec == null) { + return null; + } + + currentRight = rightFactory.apply(leftRec); + if (currentRight == null) { + currentRight = LmdbIdJoinIterator.emptyRecordIterator(); + } + } + } catch (QueryEvaluationException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + left.close(); + if (currentRight != null) { + currentRight.close(); + currentRight = null; + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java new file mode 100644 index 0000000000..7a0b295880 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.IdAccessor; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; +import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdFinalBindingSetIteration; + +/** + * Builds a left-deep chain of ID-only join iterators for an entire BGP and materializes bindings only once. + */ +public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { + + private static final String ID_JOIN_ALGORITHM = LmdbIdJoinIterator.class.getSimpleName(); + + private final List patterns; + private final QueryEvaluationContext context; + private final LmdbDatasetContext datasetContext; + private final Join root; + + public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context) { + if (!(context instanceof LmdbDatasetContext)) { + throw new IllegalArgumentException("LMDB ID BGP join requires LMDB query evaluation context"); + } + this.root = root; + this.patterns = patterns; + this.context = context; + this.datasetContext = (LmdbDatasetContext) context; + markJoinTreeWithIdAlgorithm(root); + } + + @Override + public CloseableIteration evaluate(BindingSet bindings) { + try { + LmdbEvaluationDataset dataset = resolveDataset(); + ValueStore valueStore = dataset.getValueStore(); + + // Leftmost iterator uses initial bindings (outer input bindings may have Values) + StatementPattern first = patterns.get(0); + RecordIterator left = dataset.getRecordIterator(first, bindings); + LmdbIdJoinIterator.PatternInfo leftInfoPI = LmdbIdJoinIterator.PatternInfo.create(first); + IdBindingInfo leftInfo = IdBindingInfo.fromFirstPattern(leftInfoPI); + + for (int i = 1; i < patterns.size(); i++) { + StatementPattern rightPat = patterns.get(i); + LmdbIdJoinIterator.PatternInfo rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPat); + Set shared = sharedVars(leftInfo.getVariableNames(), rightInfo.getVariableNames()); + IdBindingInfo outInfo = IdBindingInfo.combine(leftInfo, rightInfo); + + IdAccessor leftAccessor = (i == 1) ? leftInfoPI : leftInfo; + final IdAccessor leftAccessorForLambda = leftAccessor; + final IdBindingInfo outputInfo = outInfo; + final LmdbIdJoinIterator.PatternInfo rightPatternInfo = rightInfo; + final CopyPlan leftCopyPlan = CopyPlan.forAccessor(leftAccessor, outInfo); + final CopyPlan rightCopyPlan = CopyPlan.forRightExclusive(rightPatternInfo, shared, outInfo); + + IdJoinRecordIterator.RightFactory rightFactory = leftRecord -> { + long[] leftSnapshot = Arrays.copyOf(leftRecord, leftRecord.length); + RecordIterator rightIter = dataset.getRecordIterator(rightPat, + varName -> leftAccessorForLambda.getId(leftSnapshot, varName)); + if (rightIter == null) { + rightIter = LmdbIdJoinIterator.emptyRecordIterator(); + } + long[] leftSeed = seedLeftContribution(leftSnapshot, leftCopyPlan, outputInfo.size()); + RecordIterator finalRightIter = rightIter; + return new RecordIterator() { + @Override + public long[] next() { + long[] rightRecord; + while ((rightRecord = finalRightIter.next()) != null) { + long[] out = Arrays.copyOf(leftSeed, leftSeed.length); + appendRightExclusive(out, rightRecord, rightCopyPlan); + return out; + } + return null; + } + + @Override + public void close() { + finalRightIter.close(); + } + }; + }; + + left = new IdJoinRecordIterator(left, rightFactory); + leftInfo = outInfo; + } + + return new LmdbIdFinalBindingSetIteration(left, leftInfo, context, bindings, valueStore); + } catch (QueryEvaluationException e) { + throw e; + } + } + + private Set sharedVars(Set a, Set b) { + LinkedHashSet s = new LinkedHashSet<>(a); + s.retainAll(new HashSet<>(b)); + return s; + } + + private static long[] seedLeftContribution(long[] leftRecord, CopyPlan copyPlan, int outputSize) { + long[] seed = new long[outputSize]; + copyPlan.copy(leftRecord, seed); + return seed; + } + + private static void appendRightExclusive(long[] target, long[] rightRecord, CopyPlan copyPlan) { + copyPlan.copy(rightRecord, target); + } + + private static final class CopyPlan { + private final int[] sourceIndexes; + private final int[] targetIndexes; + + private CopyPlan(int[] sourceIndexes, int[] targetIndexes) { + this.sourceIndexes = sourceIndexes; + this.targetIndexes = targetIndexes; + } + + static CopyPlan forAccessor(IdAccessor accessor, IdBindingInfo outputInfo) { + List sources = new ArrayList<>(); + List targets = new ArrayList<>(); + for (String name : accessor.getVariableNames()) { + int sourceIdx = accessor.getRecordIndex(name); + int targetIdx = outputInfo.getIndex(name); + if (sourceIdx >= 0 && targetIdx >= 0) { + sources.add(sourceIdx); + targets.add(targetIdx); + } + } + return new CopyPlan(toIntArray(sources), toIntArray(targets)); + } + + static CopyPlan forRightExclusive(LmdbIdJoinIterator.PatternInfo rightInfo, Set sharedVars, + IdBindingInfo outputInfo) { + List sources = new ArrayList<>(); + List targets = new ArrayList<>(); + for (String name : rightInfo.getVariableNames()) { + if (sharedVars.contains(name)) { + continue; + } + int sourceIdx = rightInfo.getRecordIndex(name); + int targetIdx = outputInfo.getIndex(name); + if (sourceIdx >= 0 && targetIdx >= 0) { + sources.add(sourceIdx); + targets.add(targetIdx); + } + } + return new CopyPlan(toIntArray(sources), toIntArray(targets)); + } + + void copy(long[] source, long[] target) { + for (int i = 0; i < sourceIndexes.length; i++) { + target[targetIndexes[i]] = source[sourceIndexes[i]]; + } + } + + private static int[] toIntArray(List values) { + int[] out = new int[values.size()]; + for (int i = 0; i < values.size(); i++) { + out[i] = values.get(i); + } + return out; + } + } + + private LmdbEvaluationDataset resolveDataset() { + // Prefer the dataset supplied by the evaluation context (may be an overlay honoring txn writes) + java.util.Optional fromContext = datasetContext.getLmdbDataset(); + if (fromContext.isPresent()) { + return fromContext.get(); + } + // Fall back to the thread-local dataset if the context did not provide one + return LmdbEvaluationStrategy.getCurrentDataset() + .orElseThrow(() -> new IllegalStateException("No active LMDB dataset available for join evaluation")); + } + + public static boolean flattenBGP(TupleExpr expr, List out) { + if (expr instanceof StatementPattern) { + out.add((StatementPattern) expr); + return true; + } + if (expr instanceof Join) { + Join j = (Join) expr; +// // merge joins only; we avoid mergeJoin or other special joins +// if (j.isMergeJoin()) { +// return false; +// } + return flattenBGP(j.getLeftArg(), out) && flattenBGP(j.getRightArg(), out); + } + return false; + } + + private static void markJoinTreeWithIdAlgorithm(TupleExpr expr) { + if (expr instanceof Join) { + Join join = (Join) expr; + join.setAlgorithm(ID_JOIN_ALGORITHM); + markJoinTreeWithIdAlgorithm(join.getLeftArg()); + markJoinTreeWithIdAlgorithm(join.getRightArg()); + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java new file mode 100644 index 0000000000..c3db04c3fa --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import org.eclipse.rdf4j.common.iteration.LookAheadIteration; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; + +/** + * Final adapter that materializes ID-binding records to BindingSets once, at the end of a join chain. + */ +final class LmdbIdFinalBindingSetIteration extends LookAheadIteration { + + private final RecordIterator input; + private final IdBindingInfo info; + private final QueryEvaluationContext context; + private final BindingSet initial; + private final ValueStore valueStore; + + LmdbIdFinalBindingSetIteration(RecordIterator input, IdBindingInfo info, QueryEvaluationContext context, + BindingSet initial, ValueStore valueStore) { + this.input = input; + this.info = info; + this.context = context; + this.initial = initial; + this.valueStore = valueStore; + } + + @Override + protected BindingSet getNextElement() throws QueryEvaluationException { + long[] rec; + while ((rec = input.next()) != null) { + MutableBindingSet bs = context.createBindingSet(initial); + if (info.applyRecord(rec, bs, valueStore)) { + return bs; + } + } + return null; + } + + @Override + protected void handleClose() throws QueryEvaluationException { + input.close(); + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java new file mode 100644 index 0000000000..c20844b04b --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -0,0 +1,269 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.iteration.LookAheadIteration; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.IdAccessor; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.TripleStore; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +/** + * Join iterator that operates on LMDB internal IDs (long arrays) instead of binding sets. + */ +public class LmdbIdJoinIterator extends LookAheadIteration { + + @FunctionalInterface + interface RecordIteratorFactory { + RecordIterator apply(long[] leftRecord) throws QueryEvaluationException; + } + + private static final RecordIterator EMPTY_RECORD_ITERATOR = new RecordIterator() { + @Override + public long[] next() { + return null; + } + + @Override + public void close() { + // no-op + } + }; + + public static RecordIterator emptyRecordIterator() { + return EMPTY_RECORD_ITERATOR; + } + + public static final class PatternInfo implements IdAccessor { + private final Map indexByVar; + private final Set variableNames; + private final boolean hasContextVar; + + private PatternInfo(Map indexByVar, boolean hasContextVar) { + this.indexByVar = indexByVar; + this.variableNames = Collections.unmodifiableSet(indexByVar.keySet()); + this.hasContextVar = hasContextVar; + } + + static PatternInfo create(StatementPattern pattern) { + Map map = new HashMap<>(); + registerVar(map, pattern.getSubjectVar(), TripleStore.SUBJ_IDX); + registerVar(map, pattern.getPredicateVar(), TripleStore.PRED_IDX); + registerVar(map, pattern.getObjectVar(), TripleStore.OBJ_IDX); + boolean hasContext = registerVar(map, pattern.getContextVar(), TripleStore.CONTEXT_IDX); + return new PatternInfo(map, hasContext); + } + + private static boolean registerVar(Map map, org.eclipse.rdf4j.query.algebra.Var var, int index) { + if (var == null || var.hasValue()) { + return false; + } + map.compute(var.getName(), (k, v) -> { + if (v == null) { + return new int[] { index }; + } + int[] expanded = Arrays.copyOf(v, v.length + 1); + expanded[v.length] = index; + return expanded; + }); + return index == TripleStore.CONTEXT_IDX; + } + + @Override + public Set getVariableNames() { + return variableNames; + } + + @Override + public int getRecordIndex(String varName) { + int[] indices = indexByVar.get(varName); + if (indices == null || indices.length == 0) { + return -1; + } + return indices[0]; + } + + boolean hasContextVar() { + return hasContextVar; + } + + public int getPositionsMask(String varName) { + int[] indices = indexByVar.get(varName); + if (indices == null) { + return 0; + } + int mask = 0; + for (int idx : indices) { + mask |= (1 << idx); + } + return mask; + } + + @Override + public long getId(long[] record, String varName) { + int[] indices = indexByVar.get(varName); + if (indices == null || indices.length == 0) { + return LmdbValue.UNKNOWN_ID; + } + return record[indices[0]]; + } + + boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) + throws QueryEvaluationException { + for (Map.Entry entry : indexByVar.entrySet()) { + String name = entry.getKey(); + int[] indices = entry.getValue(); + Value existing = target.getValue(name); + for (int index : indices) { + long id = record[index]; + Value candidate = resolveValue(id, index, valueStore); + if (candidate == null) { + if (existing != null) { + return false; + } + continue; + } + if (existing != null) { + if (!existing.equals(candidate)) { + return false; + } + } else { + target.setBinding(name, candidate); + existing = candidate; + } + } + } + return true; + } + + private Value resolveValue(long id, int position, ValueStore valueStore) throws QueryEvaluationException { + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + if (position == TripleStore.CONTEXT_IDX && id == 0L) { + return null; + } + try { + return valueStore.getLazyValue(id); + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } + } + + private final RecordIterator leftIterator; + private final RecordIteratorFactory rightFactory; + private final PatternInfo leftInfo; + private final PatternInfo rightInfo; + private final Set sharedVariables; + private final QueryEvaluationContext context; + private final BindingSet initialBindings; + private final ValueStore valueStore; + + private RecordIterator currentRightIterator; + private long[] currentLeftRecord; + private BindingSet currentLeftBinding; + + LmdbIdJoinIterator(RecordIterator leftIterator, RecordIteratorFactory rightFactory, PatternInfo leftInfo, + PatternInfo rightInfo, Set sharedVariables, QueryEvaluationContext context, + BindingSet initialBindings, ValueStore valueStore) { + this.leftIterator = leftIterator; + this.rightFactory = rightFactory; + this.leftInfo = leftInfo; + this.rightInfo = rightInfo; + this.sharedVariables = sharedVariables; + this.context = context; + this.initialBindings = initialBindings; + this.valueStore = valueStore; + } + + @Override + protected BindingSet getNextElement() throws QueryEvaluationException { + while (true) { + if (currentRightIterator != null) { + long[] rightRecord; + while ((rightRecord = nextRecord(currentRightIterator)) != null) { + if (!matchesJoin(currentLeftRecord, rightRecord)) { + continue; + } + MutableBindingSet result = context.createBindingSet(currentLeftBinding); + if (!rightInfo.applyRecord(rightRecord, result, valueStore)) { + continue; + } + return result; + } + currentRightIterator.close(); + currentRightIterator = null; + } + + long[] leftRecord = nextRecord(leftIterator); + if (leftRecord == null) { + return null; + } + + MutableBindingSet leftBinding = context.createBindingSet(initialBindings); + if (!leftInfo.applyRecord(leftRecord, leftBinding, valueStore)) { + continue; + } + + currentLeftRecord = leftRecord; + currentLeftBinding = leftBinding; + + currentRightIterator = rightFactory.apply(leftRecord); + if (currentRightIterator == null) { + currentRightIterator = emptyRecordIterator(); + } + } + } + + private boolean matchesJoin(long[] leftRecord, long[] rightRecord) { + for (String name : sharedVariables) { + long leftId = leftInfo.getId(leftRecord, name); + long rightId = rightInfo.getId(rightRecord, name); + if (leftId != LmdbValue.UNKNOWN_ID && rightId != LmdbValue.UNKNOWN_ID && leftId != rightId) { + return false; + } + } + return true; + } + + private long[] nextRecord(RecordIterator iterator) throws QueryEvaluationException { + long[] record = iterator.next(); + if (record == null) { + return null; + } + return Arrays.copyOf(record, record.length); + } + + @Override + protected void handleClose() throws QueryEvaluationException { + leftIterator.close(); + if (currentRightIterator != null) { + currentRightIterator.close(); + currentRightIterator = null; + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java new file mode 100644 index 0000000000..1911be85a7 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; + +/** + * Query evaluation step that wires up the LMDB ID join iterator. + */ +public class LmdbIdJoinQueryEvaluationStep implements QueryEvaluationStep { + + private final StatementPattern leftPattern; + private final StatementPattern rightPattern; + private final QueryEvaluationContext context; + private final LmdbIdJoinIterator.PatternInfo leftInfo; + private final LmdbIdJoinIterator.PatternInfo rightInfo; + private final Set sharedVariables; + private final LmdbDatasetContext datasetContext; + + public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, QueryEvaluationContext context) { + if (!(join.getLeftArg() instanceof StatementPattern) || !(join.getRightArg() instanceof StatementPattern)) { + throw new IllegalArgumentException("LMDB ID join requires StatementPattern operands"); + } + if (!(context instanceof LmdbDatasetContext)) { + throw new IllegalArgumentException("LMDB ID join requires LMDB query evaluation context"); + } + + this.datasetContext = (LmdbDatasetContext) context; + this.leftPattern = (StatementPattern) join.getLeftArg(); + this.rightPattern = (StatementPattern) join.getRightArg(); + this.context = context; + + this.leftInfo = LmdbIdJoinIterator.PatternInfo.create(leftPattern); + this.rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPattern); + this.sharedVariables = computeSharedVariables(leftInfo, rightInfo); + + join.setAlgorithm(LmdbIdJoinIterator.class.getSimpleName()); + + } + + private Set computeSharedVariables(LmdbIdJoinIterator.PatternInfo left, + LmdbIdJoinIterator.PatternInfo right) { + Set shared = new HashSet<>(left.getVariableNames()); + shared.retainAll(right.getVariableNames()); + return Collections.unmodifiableSet(shared); + } + + @Override + public CloseableIteration evaluate(BindingSet bindings) { + try { + LmdbEvaluationDataset dataset = resolveDataset(); + ValueStore valueStore = dataset.getValueStore(); + RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); + + LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { + MutableBindingSet bs = context.createBindingSet(); + bindings.forEach(binding -> bs.addBinding(binding.getName(), binding.getValue())); + if (!leftInfo.applyRecord(leftRecord, bs, valueStore)) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + return dataset.getRecordIterator(rightPattern, bs); + }; + + return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, rightInfo, sharedVariables, context, + bindings, valueStore); + } catch (QueryEvaluationException e) { + throw e; + } + } + + private LmdbEvaluationDataset resolveDataset() { + // Honor the delegated SailDataset provided via the evaluation context first. + // This preserves transaction-local visibility (e.g., SNAPSHOT/SERIALIZABLE overlays). + Optional fromContext = datasetContext.getLmdbDataset(); + if (fromContext.isPresent()) { + return fromContext.get(); + } + // Fall back to the thread-local only if the context did not carry a dataset reference. + return LmdbEvaluationStrategy.getCurrentDataset() + .orElseThrow(() -> new IllegalStateException("No active LMDB dataset available for join evaluation")); + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/package-info.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/package-info.java new file mode 100644 index 0000000000..c64114acf6 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/package-info.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2020 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 + *******************************************************************************/ + +/** + * @apiNote This feature is for internal use only: its existence, signature or behavior may change without warning from + * one release to the next. + */ + +@InternalUseOnly +package org.eclipse.rdf4j.sail.lmdb.join; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/IdJoinRecordIteratorTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/IdJoinRecordIteratorTest.java new file mode 100644 index 0000000000..0550469d54 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/IdJoinRecordIteratorTest.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.rdf4j.sail.lmdb.join.IdJoinRecordIterator; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; +import org.junit.jupiter.api.Test; + +class IdJoinRecordIteratorTest { + + @Test + void rightIteratorAlreadyIncludesLeftBindings() { + IdBindingInfo leftInfo = bindingInfo(Map.of("a", 0, "b", 2)); + + RecordIterator leftIterator = recordIterator( + new long[] { 1L, 0L, 2L }, + new long[] { 7L, 0L, 8L }); + + IdJoinRecordIterator.RightFactory rightFactory = leftRecord -> { + long a = leftInfo.getId(leftRecord, "a"); + long b = leftInfo.getId(leftRecord, "b"); + if (a == 1L) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + return recordIterator( + new long[] { a, b, 50L }, + new long[] { a, b, 60L }); + }; + + IdJoinRecordIterator iterator = new IdJoinRecordIterator(leftIterator, rightFactory); + + assertThat(iterator.next()).containsExactly(7L, 8L, 50L); + assertThat(iterator.next()).containsExactly(7L, 8L, 60L); + assertThat(iterator.next()).isNull(); + } + + private static RecordIterator recordIterator(long[]... records) { + return new RecordIterator() { + int index = 0; + + @Override + public long[] next() { + if (index >= records.length) { + return null; + } + return Arrays.copyOf(records[index], records[index++].length); + } + + @Override + public void close() { + // no-op + } + }; + } + + private static IdBindingInfo bindingInfo(Map indexByVar) { + LinkedHashMap indexMap = new LinkedHashMap<>(indexByVar); + Map maskMap = new java.util.HashMap<>(); + indexByVar.forEach((name, position) -> maskMap.put(name, 1 << position)); + return new IdBindingInfo(indexMap, maskMap); + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java new file mode 100644 index 0000000000..890c36e1db --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.iteration.SingletonIteration; +import org.eclipse.rdf4j.common.iteration.UnionIteration; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +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.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.sail.SailConnection; +import org.eclipse.rdf4j.sail.base.SailDataset; +import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; +import org.eclipse.rdf4j.sail.base.SailSource; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdBGPQueryEvaluationStep; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests for the LMDB ID BGP evaluation step. + */ +public class LmdbIdBGPEvaluationTest { + + private static final String NS = "http://example.com/"; + + @Test + public void bgpPrefersContextOverlayDataset(@TempDir java.nio.file.Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + // Baseline: only the 'knows' triple exists, not 'likes'. + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + + // unwrap to the top-level Join expression + TupleExpr current = tupleExpr; + if (current instanceof org.eclipse.rdf4j.query.algebra.QueryRoot) { + current = ((org.eclipse.rdf4j.query.algebra.QueryRoot) current).getArg(); + } + if (current instanceof org.eclipse.rdf4j.query.algebra.Projection) { + current = ((org.eclipse.rdf4j.query.algebra.Projection) current).getArg(); + } + if (!(current instanceof Join)) { + // conservative guard; if the test query changes shape, fail clearly + throw new AssertionError("expected Join at root of algebra"); + } + Join join = (Join) current; + + // Flatten the BGP patterns from the Join + List patterns = new ArrayList<>(); + boolean flattened = LmdbIdBGPQueryEvaluationStep.flattenBGP(join, patterns); + assertThat(flattened).isTrue(); + assertThat(patterns).hasSize(2); + + // Prepare a baseline dataset (thread-local) and its triple source + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset baselineDataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + + try { + SailDatasetTripleSource baseTs = new SailDatasetTripleSource(repository.getValueFactory(), baselineDataset); + + // Overlay triple source that returns the extra 'likes' statement in addition to baseline content + Statement overlayStmt = vf.createStatement(alice, likes, pizza); + TripleSource overlayTs = new TripleSource() { + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) { + boolean isLikes = pred != null && pred.equals(likes); + boolean subjOk = subj == null || subj.equals(alice); + boolean objOk = obj == null || obj.equals(pizza); + CloseableIteration base = baseTs.getStatements(subj, pred, obj, contexts); + if (isLikes && subjOk && objOk) { + // combine overlay with baseline + return new UnionIteration<>(new SingletonIteration<>(overlayStmt), base); + } + return base; + } + + @Override + public ValueFactory getValueFactory() { + return baseTs.getValueFactory(); + } + + @Override + public java.util.Comparator getComparator() { + // Narrow the wildcard comparator to the interface's return type + @SuppressWarnings("unchecked") + java.util.Comparator cmp = (java.util.Comparator) baseTs.getComparator(); + return cmp; + } + }; + + // Build the overlay dataset and a context carrying it + LmdbEvaluationDataset threadLocal = (LmdbEvaluationDataset) baselineDataset; + LmdbEvaluationDataset overlayDataset = new LmdbOverlayEvaluationDataset(overlayTs, + threadLocal.getValueStore()); + + QueryEvaluationContext ctx = new LmdbQueryEvaluationContext(null, overlayTs.getValueFactory(), + overlayTs.getComparator(), overlayDataset, threadLocal.getValueStore()); + + // Simulate a thread-local dataset reference that points to the baseline dataset + LmdbEvaluationStrategy.setCurrentDataset(threadLocal); + try { + LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(join, patterns, ctx); + try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { + List results = org.eclipse.rdf4j.common.iteration.Iterations.asList(iter); + // We expect 1 result because the overlay supplies the missing 'likes' triple. + assertThat(results).hasSize(1); + } + } finally { + LmdbEvaluationStrategy.clearCurrentDataset(); + } + } finally { + baselineDataset.close(); + branch.close(); + repository.shutDown(); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java new file mode 100644 index 0000000000..055c859566 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.Projection; +import org.eclipse.rdf4j.query.algebra.QueryRoot; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.query.explanation.GenericPlanNode; +import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.sail.SailConnection; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests for nested BGP joins using LMDB ID-only join iterators. + */ +public class LmdbIdBGPJoinTest { + + private static final String NS = "http://example.com/"; + + @Test + public void nestedThreePatternBGP_usesIdJoinChain(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + try (RepositoryConnection conn = repository.getConnection()) { + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI carol = vf.createIRI(NS, "carol"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + IRI pasta = vf.createIRI(NS, "pasta"); + + // Data to satisfy a 3-pattern BGP: ?p knows ?x . ?p likes ?i . ?x likes ?i + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + conn.add(bob, likes, pizza); + + // Some extra data + conn.add(carol, likes, pasta); + } + + String query = "SELECT ?p ?i WHERE {\n" + + " ?p <" + NS + "knows> ?x .\n" + + " ?p <" + NS + "likes> ?i .\n" + + " ?x <" + NS + "likes> ?i .\n" + + "}"; + + try (RepositoryConnection conn = repository.getConnection()) { + // Verify correctness + try (var result = conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate()) { + List list = Iterations.asList(result); + assertThat(list).hasSize(1); + assertThat(list.get(0).hasBinding("p")).isTrue(); + assertThat(list.get(0).hasBinding("i")).isTrue(); + } + + // Verify the top join algorithm is our LMDB ID join once implemented + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + SailRepositoryConnection sailRepoConn = (SailRepositoryConnection) conn; + SailConnection sailConnection = sailRepoConn.getSailConnection(); + sailConnection.explain(Explanation.Level.Optimized, tupleExpr, null, EmptyBindingSet.getInstance(), true, + 0); + + TupleExpr unwrapped = unwrap(tupleExpr); + assertThat(unwrapped).isInstanceOf(Join.class); + Join topJoin = (Join) unwrapped; + // Expect the top join to be marked with our ID join algorithm once nested support is implemented + assertThat(topJoin.getAlgorithmName()).isEqualTo("LmdbIdJoinIterator"); + } finally { + repository.shutDown(); + } + } + + @Test + public void twoPatternBGP_usesIdJoinChain(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + try (RepositoryConnection conn = repository.getConnection()) { + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + } + + String query = "SELECT ?p ?i WHERE {\n" + + " ?p <" + NS + "knows> ?x .\n" + + " ?p <" + NS + "likes> ?i .\n" + + "}"; + + try (RepositoryConnection conn = repository.getConnection()) { + try (var result = conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate()) { + List list = Iterations.asList(result); + assertThat(list).hasSize(1); + } + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + SailRepositoryConnection sailRepoConn = (SailRepositoryConnection) conn; + SailConnection sailConnection = sailRepoConn.getSailConnection(); + sailConnection.explain(Explanation.Level.Optimized, tupleExpr, null, EmptyBindingSet.getInstance(), true, + 0); + + TupleExpr unwrapped = unwrap(tupleExpr); + assertThat(unwrapped).isInstanceOf(Join.class); + Join topJoin = (Join) unwrapped; + assertThat(topJoin.getAlgorithmName()).isEqualTo("LmdbIdJoinIterator"); + } finally { + repository.shutDown(); + } + } + + @Test + public void queryPlanAnnotatesEveryIdJoin(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + try (RepositoryConnection conn = repository.getConnection()) { + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI carol = vf.createIRI(NS, "carol"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + IRI pasta = vf.createIRI(NS, "pasta"); + + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + conn.add(bob, likes, pizza); + conn.add(carol, likes, pasta); + } + + String query = "SELECT ?p ?i WHERE {\n" + + " ?p <" + NS + "knows> ?x .\n" + + " ?p <" + NS + "likes> ?i .\n" + + " ?x <" + NS + "likes> ?i .\n" + + "}"; + + try (RepositoryConnection conn = repository.getConnection()) { + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + + SailRepositoryConnection sailRepoConn = (SailRepositoryConnection) conn; + SailConnection sailConnection = sailRepoConn.getSailConnection(); + Explanation explanation = sailConnection.explain(Explanation.Level.Optimized, tupleExpr, null, + EmptyBindingSet.getInstance(), true, 0); + + GenericPlanNode plan = explanation.toGenericPlanNode(); + List joinNodes = collectJoinNodes(plan); + + assertThat(joinNodes) + .withFailMessage("Expected multi-join BGP to produce at least two join nodes in plan but saw %s", + joinNodes.size()) + .hasSizeGreaterThanOrEqualTo(2); + + joinNodes.forEach(node -> assertThat(node.getAlgorithm()) + .withFailMessage("Plan node %s should show LMDB ID join usage", node.getType()) + .isEqualTo("LmdbIdJoinIterator")); + } finally { + repository.shutDown(); + } + } + + private TupleExpr unwrap(TupleExpr tupleExpr) { + TupleExpr current = tupleExpr; + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + if (current instanceof Projection) { + current = ((Projection) current).getArg(); + } + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + return current; + } + + private static List collectJoinNodes(GenericPlanNode root) { + List nodes = new ArrayList<>(); + collectJoinNodes(root, nodes); + return nodes; + } + + private static void collectJoinNodes(GenericPlanNode node, List out) { + if (node == null) { + return; + } + String type = node.getType(); + if (type != null && type.startsWith("Join")) { + out.add(node); + } + List children = node.getPlans(); + if (children != null) { + for (GenericPlanNode child : children) { + collectJoinNodes(child, out); + } + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java new file mode 100644 index 0000000000..da973a91ec --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.iteration.SingletonIteration; +import org.eclipse.rdf4j.common.iteration.UnionIteration; +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.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.Projection; +import org.eclipse.rdf4j.query.algebra.QueryRoot; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.sail.SailConnection; +import org.eclipse.rdf4j.sail.base.SailDataset; +import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; +import org.eclipse.rdf4j.sail.base.SailSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies that the LMDB ID join optimization is disabled when the active transaction contains changes (represented via + * an overlay dataset). + */ +public class LmdbIdJoinDisableOnChangesTest { + + private static final String NS = "http://example.com/"; + + @Test + public void idJoinDisabledWhenOverlayPresent(@TempDir java.nio.file.Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + // Baseline: only the 'knows' triple exists + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr current = tupleExpr; + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + if (current instanceof Projection) { + current = ((Projection) current).getArg(); + } + if (!(current instanceof Join)) { + throw new AssertionError("expected Join at root of algebra"); + } + + // Build an overlay triple source that contributes the missing 'likes' triple + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset baselineDataset = branch + .dataset(org.eclipse.rdf4j.common.transaction.IsolationLevels.SNAPSHOT_READ); + try { + SailDatasetTripleSource baseTs = new SailDatasetTripleSource(repository.getValueFactory(), baselineDataset); + Statement overlayStmt = vf.createStatement(alice, likes, pizza); + TripleSource overlayTs = new TripleSource() { + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) { + boolean isLikes = pred != null && pred.equals(likes); + boolean subjOk = subj == null || subj.equals(alice); + boolean objOk = obj == null || obj.equals(pizza); + CloseableIteration base = baseTs.getStatements(subj, pred, obj, contexts); + if (isLikes && subjOk && objOk) { + return new UnionIteration<>(new SingletonIteration<>(overlayStmt), base); + } + return base; + } + + @Override + public ValueFactory getValueFactory() { + return baseTs.getValueFactory(); + } + + @Override + public java.util.Comparator getComparator() { + @SuppressWarnings("unchecked") + java.util.Comparator cmp = (java.util.Comparator) baseTs.getComparator(); + return cmp; + } + }; + + // Prepare evaluation strategy over the baseline triple source + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, baseTs, + store.getBackingStore().getEvaluationStatistics()); + + // Install overlay dataset into the evaluation strategy's thread-local so precompile can see it + LmdbEvaluationDataset overlayDataset = new LmdbOverlayEvaluationDataset(overlayTs, + ((LmdbEvaluationDataset) baselineDataset).getValueStore()); + LmdbEvaluationStrategy.setCurrentDataset(overlayDataset); + try { + // Precompile the Join; since the overlay carries transaction changes the ID join must be disabled + strategy.precompile(current); + + assertThat(current).isInstanceOf(Join.class); + Join join = (Join) current; + assertThat(join.getAlgorithmName()).isNotEqualTo("LmdbIdJoinIterator"); + } finally { + LmdbEvaluationStrategy.clearCurrentDataset(); + } + } finally { + baselineDataset.close(); + branch.close(); + repository.shutDown(); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java new file mode 100644 index 0000000000..670af3e720 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.List; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.Projection; +import org.eclipse.rdf4j.query.algebra.QueryRoot; +import org.eclipse.rdf4j.query.algebra.SingletonSet; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.sail.SailConnection; +import org.eclipse.rdf4j.sail.base.SailDataset; +import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; +import org.eclipse.rdf4j.sail.base.SailSource; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinQueryEvaluationStep; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LmdbIdJoinEvaluationTest { + + private static final String NS = "http://example.com/"; + + @Test + public void simpleJoinUsesIdIterator(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + assertThat(store.getEvaluationStrategyFactory().getClass().getSimpleName()) + .isEqualTo("LmdbEvaluationStrategyFactory"); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + conn.add(bob, likes, pizza); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + try (RepositoryConnection conn = repository.getConnection()) { + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + + SailRepositoryConnection sailRepoConn = (SailRepositoryConnection) conn; + SailConnection sailConnection = sailRepoConn.getSailConnection(); + try (CloseableIteration iter = sailConnection.evaluate(tupleExpr, null, + EmptyBindingSet.getInstance(), true)) { + List bindings = Iterations.asList(iter); + assertThat(bindings).hasSize(1); + } + + sailConnection.explain(Explanation.Level.Optimized, tupleExpr, null, EmptyBindingSet.getInstance(), true, + 0); + + TupleExpr joinExpr = unwrap(tupleExpr); + assertThat(joinExpr).isInstanceOf(Join.class); + Join join = (Join) joinExpr; + assertThat(join.getAlgorithmName()) + .withFailMessage("left=%s right=%s", join.getLeftArg().getClass(), join.getRightArg().getClass()) + .isEqualTo("LmdbIdJoinIterator"); + } finally { + repository.shutDown(); + } + } + + @Test + public void nonStatementPatternJoinRejected() { + Join join = new Join(new SingletonSet(), + new StatementPattern(new Var("s"), new Var("p"), new Var("o"))); + + EvaluationStrategy strategy = mock(EvaluationStrategy.class); + QueryEvaluationContext context = new QueryEvaluationContext.Minimal(null); + + assertThatThrownBy(() -> new LmdbIdJoinQueryEvaluationStep(strategy, join, context)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("StatementPattern"); + } + + @Test + public void joinUsesRecordIteratorsForLeftSide(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr joinExpr = unwrap(tupleExpr); + assertThat(joinExpr).isInstanceOf(Join.class); + Join join = (Join) joinExpr; + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + try { + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, tripleSource, + store.getBackingStore().getEvaluationStatistics()); + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + QueryEvaluationContext context = new LmdbQueryEvaluationContext(null, + tripleSource.getValueFactory(), tripleSource.getComparator(), lmdbDataset, + lmdbDataset.getValueStore()); + + LmdbIdJoinQueryEvaluationStep step = new LmdbIdJoinQueryEvaluationStep(strategy, join, context); + + try (CloseableIteration iteration = step.evaluate(EmptyBindingSet.getInstance())) { + Class iteratorClass = iteration.getClass(); + assertThat(iteratorClass.getSimpleName()).isEqualTo("LmdbIdJoinIterator"); + + Field leftIteratorField = iteratorClass.getDeclaredField("leftIterator"); + leftIteratorField.setAccessible(true); + Object leftIterValue = leftIteratorField.get(iteration); + assertThat(leftIterValue).isInstanceOf(RecordIterator.class); + } + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + + private TupleExpr unwrap(TupleExpr tupleExpr) { + TupleExpr current = tupleExpr; + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + if (current instanceof Projection) { + current = ((Projection) current).getArg(); + } + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + return current; + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java new file mode 100644 index 0000000000..57b2415a89 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.TupleQuery; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.Projection; +import org.eclipse.rdf4j.query.algebra.QueryRoot; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.sail.SailConnection; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class LmdbIdJoinIsolationTest { + + private static final String NS = "http://example.com/"; + private static final String KNOWS = NS + "knows"; + private static final String LIKES = NS + "likes"; + private static final String CTX1 = NS + "ctx1"; + private static final String CTX2 = NS + "ctx2"; + + private SailRepository repository; + + @BeforeEach + void setUp(@TempDir Path tempDir) { + repository = new SailRepository(new LmdbStore(tempDir.toFile())); + repository.init(); + } + + @AfterEach + void tearDown() { + if (repository != null) { + repository.shutDown(); + } + } + + private static Stream isolationLevels() { + return Stream.of(IsolationLevels.SNAPSHOT_READ, IsolationLevels.SNAPSHOT, IsolationLevels.SERIALIZABLE); + } + + @ParameterizedTest + @MethodSource("isolationLevels") + void joinReflectsCommittedChanges(IsolationLevel isolationLevel) throws Exception { + String query = "SELECT ?person ?liked WHERE {\n" + + " GRAPH <" + CTX1 + "> { ?person <" + KNOWS + "> ?friend . }\n" + + " GRAPH <" + CTX2 + "> { ?person <" + LIKES + "> ?liked . }\n" + + "}"; + + try (SailRepositoryConnection conn1 = repository.getConnection(); + SailRepositoryConnection conn2 = repository.getConnection()) { + conn1.setIsolationLevel(isolationLevel); + conn2.setIsolationLevel(isolationLevel); + + ValueFactory vf = conn1.getValueFactory(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI pizza = vf.createIRI(NS, "pizza"); + + conn1.begin(isolationLevel); + conn1.add(alice, vf.createIRI(KNOWS), bob, vf.createIRI(CTX1)); + conn1.add(alice, vf.createIRI(LIKES), pizza, vf.createIRI(CTX2)); + conn1.commit(); + + conn2.begin(isolationLevel); + assertThat(Iterations.asList(conn2.getStatements(null, vf.createIRI(KNOWS), null, false, + vf.createIRI(CTX1)))).hasSize(1); + + TupleQuery leftOnly = conn2.prepareTupleQuery(QueryLanguage.SPARQL, + "SELECT ?person WHERE { GRAPH <" + CTX1 + "> { ?person <" + KNOWS + "> ?friend . } }"); + assertThat(Iterations.asList(leftOnly.evaluate())).hasSize(1); + + TupleQuery rightOnly = conn2.prepareTupleQuery(QueryLanguage.SPARQL, + "SELECT ?person WHERE { GRAPH <" + CTX2 + "> { ?person <" + LIKES + "> ?liked . } }"); + assertThat(Iterations.asList(rightOnly.evaluate())).hasSize(1); + + TupleQuery tupleQuery = conn2.prepareTupleQuery(QueryLanguage.SPARQL, query); + List beforeClear; + try (TupleQueryResult result = tupleQuery.evaluate()) { + beforeClear = Iterations.asList(result); + } + assertThat(beforeClear).hasSize(1); + assertThat(beforeClear.get(0).getValue("person")).isEqualTo(alice); + assertThat(beforeClear.get(0).getValue("liked")).isEqualTo(pizza); + conn2.commit(); + + conn1.begin(isolationLevel); + conn1.clear(vf.createIRI(CTX1)); + conn1.commit(); + + conn2.begin(isolationLevel); + List afterClear; + try (TupleQueryResult result = tupleQuery.evaluate()) { + afterClear = Iterations.asList(result); + } + assertThat(afterClear).isEmpty(); + conn2.commit(); + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + SailConnection sailConnection = conn2.getSailConnection(); + sailConnection.explain(Explanation.Level.Optimized, tupleExpr, null, + EmptyBindingSet.getInstance(), true, 0); + Join join = unwrapJoin(tupleExpr); + assertThat(join.getAlgorithmName()) + .withFailMessage("left=%s right=%s", join.getLeftArg().getClass(), join.getRightArg().getClass()) + .isEqualTo("LmdbIdJoinIterator"); + } + } + + private Join unwrapJoin(TupleExpr tupleExpr) { + TupleExpr current = tupleExpr; + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + if (current instanceof Projection) { + current = ((Projection) current).getArg(); + } + if (current instanceof QueryRoot) { + current = ((QueryRoot) current).getArg(); + } + assertThat(current).isInstanceOf(Join.class); + return (Join) current; + } +} diff --git a/finding1.md b/finding1.md new file mode 100644 index 0000000000..0bf18da20b --- /dev/null +++ b/finding1.md @@ -0,0 +1,66 @@ +# Plan: Fix ID-only join to use ID-binding on RHS + +## Description + +- Problem: In the two-pattern ID join path, `LmdbIdJoinQueryEvaluationStep` materializes the left record back into a `MutableBindingSet` and uses the BindingSet-based iterator for the right-hand side, instead of staying in ID space. +- Evidence in code: + - `core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java:74` creates the left iterator via `dataset.getRecordIterator(leftPattern, bindings)`. + - `core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java:76-84` builds a `MutableBindingSet` from the left record and calls `dataset.getRecordIterator(rightPattern, bs)`. + - Contrast with BGP path: `core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java:70` uses the ID-binding overload: `dataset.getRecordIterator(rightPat, varName -> leftInfo.getId(leftRecord, varName))`. +- Impact: Allocates `Value` objects and performs value-store lookups on the right-hand side, negating expected performance gains and increasing GC pressure for ordinary binary joins. + +## Reproduce & Test Plan + +Goal: Prove the RHS iterator is created via the BindingSet overload today and enforce the change to the ID-binding overload with focused tests. + +1) Unit test (overload call verification) +- Add `LmdbIdJoinRightUsesIdBindingTest` under `core/sail/lmdb/src/test/java/.../lmdb/`. +- Approach A (Mockito): + - Spy/mock `LmdbEvaluationDataset` to record invocations of `getRecordIterator(StatementPattern, BindingSet)` vs. `getRecordIterator(StatementPattern, LmdbIdVarBinding)`. + - Build a simple join `?s p1 ?x . ?s p2 ?y` with shared var `?s` and create `LmdbIdJoinQueryEvaluationStep` using a valid `LmdbQueryEvaluationContext`. + - Evaluate with `EmptyBindingSet` and assert the RHS overload used is the ID-binding variant. +- Approach B (test double): + - Implement a small `RecordingDataset` test double implementing `LmdbEvaluationDataset` that throws if `getRecordIterator(…, BindingSet)` is called for RHS and flips a flag if `getRecordIterator(…, LmdbIdVarBinding)` is called. + - Assert the flag after evaluation. + +Suggested targeted command during iteration: +`mvn -o -Dmaven.repo.local=.m2_repo -pl core/sail/lmdb -Dtest=LmdbIdJoinRightUsesIdBindingTest verify | tail -500` + +2) Behavioral sanity (algorithm remains ID join) +- Reuse `LmdbIdJoinEvaluationTest.simpleJoinUsesIdIterator` pattern to assert the join algorithm name remains `LmdbIdJoinIterator` after the change. + +3) Optional micro-observability (no perf assert) +- Wrap `ValueStore` in a counting decorator in a dedicated test to assert that the RHS path does not call `getValue(id)` or materialize `Value` objects during the join (optional and only if trivial to wire without touching production code). + +## Fix Plan + +- Change `LmdbIdJoinQueryEvaluationStep.evaluate` right-factory to use ID binding: + - Replace the construction of a `MutableBindingSet` for RHS with an `LmdbIdVarBinding` lambda that provides IDs from the left record. + - Model the code after the BGP path in `LmdbIdBGPQueryEvaluationStep`. +- Keep materialization to `BindingSet` in the final stage only (e.g., `LmdbIdFinalBindingSetIteration`), ensuring the pipeline stays in ID space end-to-end. +- Update/add tests from the Reproduce plan; ensure they fail before and pass after the change. + +High-level pseudo-diff for clarity (not exact code): +```java +// Before (simplified) +MutableBindingSet bs = context.createBindingSet(); +leftInfo.applyRecord(leftRecord, bs, valueStore); +return dataset.getRecordIterator(rightPattern, bs); + +// After (RHS stays in ID space) +return dataset.getRecordIterator(rightPattern, + varName -> leftInfo.getId(leftRecord, varName)); +``` + +## Why This Needs To Be Fixed + +- Performance: Avoids unnecessary `Value` creation and value-store ID resolution on the RHS, preserving the intended speedup of the ID-only join. +- Consistency: Aligns the 2-pattern join path with the BGP optimization, ensuring uniform behavior across join forms. +- Resource usage: Reduces GC pressure and heap churn for common joins. + +## Validation / Success Criteria + +- New unit test confirms the RHS path uses `getRecordIterator(…, LmdbIdVarBinding)`. +- Existing `LmdbIdJoinEvaluationTest` continues to pass and still reports `LmdbIdJoinIterator` as the chosen algorithm. +- No regressions in `core/sail/lmdb` module tests. + diff --git a/finding2.md b/finding2.md new file mode 100644 index 0000000000..a8a4eccc56 --- /dev/null +++ b/finding2.md @@ -0,0 +1,69 @@ +# Plan: Disable ID-only join when transaction changes are present + +## Description + +- Problem: The safeguard that disables the LMDB ID-only join in the presence of uncommitted writes relies on `LmdbEvaluationDataset.hasTransactionChanges()`. In production, the dataset installed by the store is `LmdbSailStore.LmdbSailDataset`, which does not override this method (default `false`), so the optimization remains enabled. +- Evidence in code: + - Strategy gate: `core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java:62-70` checks `ds.hasTransactionChanges()` to decide whether to disable the ID-only join. + - Production dataset: `core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java:1363-1467` implements both `getRecordIterator` overloads but does not override `hasTransactionChanges()`. + - Overlay dataset (tests only): `core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java:169-171` returns `true` for `hasTransactionChanges()`, but is only referenced from tests. +- Impact: With the optimization enabled, queries evaluated while a connection has uncommitted writes can read directly from the LMDB snapshot and ignore pending changes, leading to stale/missing results. At minimum, the current safeguard is dead code and gives a false sense of safety. + +## Reproduce & Test Plan + +Goal: Demonstrate the optimization stays enabled with pending changes in a write transaction, then prove it’s disabled and correctness is preserved after the fix. + +1) Integration test (current behavior exposes the gap) +- Add `LmdbIdJoinTxnVisibilityTest` in `core/sail/lmdb/src/test/java/.../lmdb/`. +- Scenario: + 1. Create a store and repository; add baseline triple `:alice :knows :bob`. + 2. Open a connection, begin a transaction (if needed by the harness), and add uncommitted triple `:alice :likes :pizza`. + 3. Execute `SELECT ?person ?item WHERE { ?person :knows ?other . ?person :likes ?item . }` before commit. + 4. Capture the chosen algorithm via `explain` or by precompiling the algebra (as in `LmdbIdJoinEvaluationTest`) and assert it is currently `LmdbIdJoinIterator`. + 5. Assert that results are missing/stale (0 rows) if the store does not expose the overlay to the evaluation path. If the store flushes writes on read in this setup, adapt the test by using a `SailDatasetTripleSource` overlay (see 2). + +2) Deterministic overlay test (mirrors existing test but without manual dataset injection) +- Build an overlay `TripleSource` that unions the baseline with the pending (uncommitted) triple on-the-fly (similar to `LmdbIdJoinDisableOnChangesTest`). +- Construct an evaluation strategy over the baseline triple source, but do NOT install `LmdbOverlayEvaluationDataset` into the thread-local. +- Precompile or evaluate the join and assert that the ID-only join is disabled by the strategy after the fix, purely by inspecting the active triple source (no thread-local overlay dataset needed). + +Targeted command examples: +- `mvn -o -Dmaven.repo.local=.m2_repo -pl core/sail/lmdb -Dtest=LmdbIdJoinTxnVisibilityTest verify | tail -500` +- `mvn -o -Dmaven.repo.local=.m2_repo -pl core/sail/lmdb -Dtest=LmdbIdJoinDisableOnChangesTest verify | tail -500` + +3) Unit-level indicator (dataset signal) +- Add a focused test to assert that the production dataset used during evaluation exposes pending writes appropriately after the fix (e.g., `((LmdbEvaluationDataset) dataset).hasTransactionChanges()` becomes `true` in the same connection during a write). + +## Fix Plan + +Two viable approaches (pick one; Option 2 preferred for correctness): + +1) Quick, conservative fix (global writer visibility) +- Override `hasTransactionChanges()` in `LmdbSailStore.LmdbSailDataset` to return `true` when the store has an active write transaction (e.g., `storeTxnStarted.get()`). +- Pros: Minimal code, immediately makes the safeguard effective. +- Cons: Conservative: disables ID-only join for all queries while any writer is active, even in other connections. + +2) Connection-local detection (preferred) +- Add a connection/thread-local flag in `LmdbSailStore` that is set in `LmdbSailSink.startTransaction(...)` and cleared on commit/rollback. +- Override `hasTransactionChanges()` in `LmdbSailStore.LmdbSailDataset` to consult this connection-local flag, so only queries in the same connection/transaction see `true`. +- Alternatively or additionally, update `LmdbEvaluationStrategy.precompile(...)` to detect that the active `TripleSource` is a `SailDatasetTripleSource` with overlays and install an `LmdbOverlayEvaluationDataset` as the effective dataset in the `QueryEvaluationContext` (which returns `true` for `hasTransactionChanges()`). +- Pros: Accurate semantics, disables ID-only only where necessary. +- Cons: Slightly more wiring; ensure no cross-thread leakage if evaluation occurs on different threads. + +3) Safety net (minimal surface change in strategy) +- If wiring connection-local flags is not feasible, in `LmdbEvaluationStrategy.precompile(...)`, when `tripleSource` is not the LMDB-native source (e.g., it’s a `SailDatasetTripleSource`), wrap it with `new LmdbOverlayEvaluationDataset(tripleSource, valueStore)` as the `effectiveDataset`. This makes `hasTransactionChanges()` return `true` and will disable ID-only joins in `prepare(...)`. +- Pros: No store changes; straightforward. +- Cons: May disable ID-only joins even when no writes are pending; acceptable as a stopgap to ensure correctness. + +## Why This Needs To Be Fixed + +- Correctness: Queries executed within a write transaction must see uncommitted changes from their own transaction. Leaving the optimization enabled can yield stale or missing results. +- Transparency: The current safeguard is effectively dead code unless tests manually install an overlay dataset; production behavior should not depend on test-only wiring. +- Predictability: Aligns optimization behavior with RDF4J’s transactional semantics and user expectations. + +## Validation / Success Criteria + +- The new integration test demonstrates the issue pre-fix and passes post-fix: ID-only join is disabled when pending writes exist in the evaluating connection. +- Existing `LmdbIdJoinDisableOnChangesTest` continues to pass. +- No regressions in `core/sail/lmdb` tests; ID-only join remains enabled when no transaction changes are present. + From 1343de13397ed8fbeff9fc4e1b2c5039886d8d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 27 Oct 2025 12:20:09 +0900 Subject: [PATCH 36/79] working on new ID based join iterator --- .../org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java index 4a561a5fe1..410474f8a2 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java @@ -49,7 +49,7 @@ * @author Håvard Ottestad */ @State(Scope.Benchmark) -@Warmup(iterations = 5) +@Warmup(iterations = 50, time = 1, timeUnit = TimeUnit.SECONDS) @BenchmarkMode({ Mode.AverageTime }) @Fork(value = 1, jvmArgs = { "-Xms1G", "-Xmx1G" }) //@Fork(value = 1, jvmArgs = {"-Xms1G", "-Xmx1G", "-XX:StartFlightRecording=jdk.CPUTimeSample#enabled=true,filename=profile.jfr,method-profiling=max","-XX:FlightRecorderOptions=stackdepth=1024", "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints"}) From 98d04f5f7d3b5c0cf88f8a354d90fb1d76e23ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 27 Oct 2025 21:06:31 +0900 Subject: [PATCH 37/79] working on new ID based join iterator --- .../sail/lmdb/LmdbEvaluationDataset.java | 57 ++- .../sail/lmdb/LmdbEvaluationStrategy.java | 63 ++- .../lmdb/LmdbEvaluationStrategyFactory.java | 1 - .../rdf4j/sail/lmdb/LmdbIdVarBinding.java | 27 -- .../lmdb/LmdbOverlayEvaluationDataset.java | 252 ++++++++-- .../rdf4j/sail/lmdb/LmdbSailStore.java | 453 +++++++++++++++--- .../eclipse/rdf4j/sail/lmdb/LmdbStore.java | 1 - .../rdf4j/sail/lmdb/LmdbStoreConnection.java | 103 +++- .../join/LmdbIdBGPQueryEvaluationStep.java | 434 ++++++++++++----- .../sail/lmdb/join/LmdbIdJoinIterator.java | 1 - .../join/LmdbIdJoinQueryEvaluationStep.java | 11 +- .../lmdb/join/LmdbIdMergeJoinIterator.java | 221 +++++++++ .../LmdbIdMergeJoinQueryEvaluationStep.java | 362 ++++++++++++++ .../lmdb/join/PeekMarkRecordIterator.java | 186 +++++++ .../sail/lmdb/LmdbIdBGPEvaluationTest.java | 121 ++++- .../rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java | 1 - .../lmdb/LmdbIdJoinDisableOnChangesTest.java | 8 - .../sail/lmdb/LmdbIdJoinEvaluationTest.java | 283 ++++++++++- .../sail/lmdb/LmdbIdJoinIsolationTest.java | 1 - .../LmdbStoreConnectionExceptionFlagTest.java | 118 +++++ ...dicateIndexDistributionRegressionTest.java | 5 - ...ansactionsPerSecondForceSyncBenchmark.java | 13 - 22 files changed, 2381 insertions(+), 341 deletions(-) delete mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/PeekMarkRecordIterator.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnectionExceptionFlagTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index 68ea5a641b..922ff09354 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.lmdb; import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.algebra.StatementPattern; @@ -32,23 +33,59 @@ public interface LmdbEvaluationDataset { RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException; /** - * Create a {@link RecordIterator} for the supplied {@link StatementPattern}, taking into account any existing - * variable bindings provided as LMDB internal IDs. + * Create a {@link RecordIterator} for the supplied pattern, expressed as internal IDs, using the current binding + * snapshot. * *

    - * This overload avoids materializing Values during join evaluation and should be used when chaining joins in an - * ID-only pipeline. + * The {@code binding} array represents the accumulated variable IDs for the join so far and must be treated as + * read-only. The {@code patternIds} array contains four entries (subject, predicate, object, context) where a value + * of {@link org.eclipse.rdf4j.sail.lmdb.model.LmdbValue#UNKNOWN_ID} indicates that the corresponding position is + * unbound in the pattern. The index arguments point to the slots in {@code binding} where the resolved IDs should + * be written (or {@code -1} if that pattern position does not correspond to a variable). *

    * - * @param pattern the statement pattern to evaluate - * @param idBinding a binding accessor that can provide internal IDs for variables present on the left side of the - * join - * @return a {@link RecordIterator} that yields matching statement records + * @param binding the current binding snapshot; implementations must copy before mutating + * @param subjIndex index in {@code binding} for the subject variable, or {@code -1} if none + * @param predIndex index in {@code binding} for the predicate variable, or {@code -1} if none + * @param objIndex index in {@code binding} for the object variable, or {@code -1} if none + * @param ctxIndex index in {@code binding} for the context variable, or {@code -1} if none + * @param patternIds pattern constants for subject/predicate/object/context + * @return a {@link RecordIterator} that yields binding snapshots with the pattern applied * @throws QueryEvaluationException if the iterator could not be created */ @InternalUseOnly - public RecordIterator getRecordIterator(StatementPattern pattern, LmdbIdVarBinding idBinding) - throws QueryEvaluationException; + RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws QueryEvaluationException; + + /** + * Create an ordered {@link RecordIterator} for the supplied pattern expressed via internal IDs and binding indexes. + * Implementations may fall back to the unordered iterator when the requested order is unsupported. + */ + @InternalUseOnly + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + return null; + } + + /** + * Create an ordered {@link RecordIterator} for the supplied pattern. Implementations may fall back to the unordered + * iterator when the requested order is unsupported. + */ + default RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order) + throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(pattern, bindings); + } + return null; + } + + default boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, StatementOrder order) { + return order == null; + } /** * @return the {@link ValueStore} backing this dataset. diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java index 8eb9b11797..bb552dc3da 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java @@ -30,6 +30,7 @@ import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategy; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdBGPQueryEvaluationStep; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinQueryEvaluationStep; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdMergeJoinQueryEvaluationStep; /** * Evaluation strategy that can use LMDB-specific join iterators. @@ -38,6 +39,7 @@ public class LmdbEvaluationStrategy extends StrictEvaluationStrategy { private static final ThreadLocal CURRENT_DATASET = new ThreadLocal<>(); + private static final ThreadLocal CONNECTION_CHANGES = new ThreadLocal<>(); LmdbEvaluationStrategy(TripleSource tripleSource, Dataset dataset, FederatedServiceResolver serviceResolver, long iterationCacheSyncThreshold, EvaluationStatistics evaluationStatistics, boolean trackResultSize) { @@ -50,8 +52,10 @@ public class LmdbEvaluationStrategy extends StrictEvaluationStrategy { public QueryEvaluationStep precompile(TupleExpr expr) { LmdbEvaluationDataset datasetRef = CURRENT_DATASET.get(); ValueStore valueStore = datasetRef != null ? datasetRef.getValueStore() : null; - // Prefer the active LMDB dataset for ID-join evaluation to avoid premature materialization. - LmdbEvaluationDataset effectiveDataset = datasetRef != null ? datasetRef : null; + LmdbEvaluationDataset effectiveDataset = datasetRef; + if (connectionHasChanges() && valueStore != null) { + effectiveDataset = new LmdbOverlayEvaluationDataset(tripleSource, valueStore); + } LmdbQueryEvaluationContext baseContext = new LmdbQueryEvaluationContext(dataset, tripleSource.getValueFactory(), tripleSource.getComparator(), effectiveDataset, valueStore); QueryEvaluationContext context = baseContext; @@ -67,24 +71,30 @@ public QueryEvaluationStep precompile(TupleExpr expr) { @Override protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) { + QueryEvaluationStep defaultStep = super.prepare(node, context); if (context instanceof LmdbDatasetContext && ((LmdbDatasetContext) context).getLmdbDataset().isPresent()) { LmdbEvaluationDataset ds = ((LmdbDatasetContext) context).getLmdbDataset().get(); // If the active transaction has uncommitted changes, avoid ID-only join shortcuts. - if (ds.hasTransactionChanges()) { - return super.prepare(node, context); + if (ds.hasTransactionChanges() || connectionHasChanges()) { + return defaultStep; + } + + if (node.isMergeJoin() && node.getOrder() != null && node.getLeftArg() instanceof StatementPattern + && node.getRightArg() instanceof StatementPattern) { + return new LmdbIdMergeJoinQueryEvaluationStep(node, context, defaultStep); } // Try to flatten a full BGP of statement patterns List patterns = new ArrayList<>(); if (LmdbIdBGPQueryEvaluationStep.flattenBGP(node, patterns) && !patterns.isEmpty()) { - return new LmdbIdBGPQueryEvaluationStep(node, patterns, context); + return new LmdbIdBGPQueryEvaluationStep(node, patterns, context, defaultStep); } // Fallback to two-pattern ID join if (node.getLeftArg() instanceof StatementPattern && node.getRightArg() instanceof StatementPattern) { - return new LmdbIdJoinQueryEvaluationStep(this, node, context); + return new LmdbIdJoinQueryEvaluationStep(this, node, context, defaultStep); } } - return super.prepare(node, context); + return defaultStep; } static void setCurrentDataset(LmdbEvaluationDataset dataset) { @@ -98,4 +108,43 @@ static void clearCurrentDataset() { public static Optional getCurrentDataset() { return Optional.ofNullable(CURRENT_DATASET.get()); } + + static void pushConnectionChangesFlag(boolean hasUncommittedChanges) { + if (!hasUncommittedChanges && CONNECTION_CHANGES.get() == null) { + return; + } + + ConnectionChangeState state = CONNECTION_CHANGES.get(); + if (state == null) { + state = new ConnectionChangeState(); + CONNECTION_CHANGES.set(state); + } + state.depth++; + state.hasChanges |= hasUncommittedChanges; + } + + static void popConnectionChangesFlag() { + ConnectionChangeState state = CONNECTION_CHANGES.get(); + if (state == null) { + return; + } + state.depth--; + if (state.depth <= 0) { + CONNECTION_CHANGES.remove(); + } + } + + private static boolean connectionHasChanges() { + ConnectionChangeState state = CONNECTION_CHANGES.get(); + return state != null && state.hasChanges; + } + + public static boolean hasActiveConnectionChanges() { + return connectionHasChanges(); + } + + private static final class ConnectionChangeState { + private int depth; + private boolean hasChanges; + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java index c31d79c389..e589e4d9fd 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategyFactory.java @@ -18,7 +18,6 @@ import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; -import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java deleted file mode 100644 index 78ff7f8d23..0000000000 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdVarBinding.java +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2025 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.sail.lmdb; - -import org.eclipse.rdf4j.common.annotation.InternalUseOnly; - -/** - * Provides variable bindings as LMDB internal IDs during ID-only join evaluation. - */ -@InternalUseOnly -public interface LmdbIdVarBinding { - - /** - * Return the internal ID for the given variable name or - * {@link org.eclipse.rdf4j.sail.lmdb.model.LmdbValue#UNKNOWN_ID} if the variable is unbound in the current left - * record. - */ - long getIdOrUnknown(String varName); -} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 3359cf2302..124cd6fd7d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -11,7 +11,10 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +import java.util.Arrays; + import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; @@ -116,49 +119,96 @@ public void close() { } @Override - public RecordIterator getRecordIterator(StatementPattern pattern, LmdbIdVarBinding idBinding) - throws QueryEvaluationException { - - // Translate known ID bindings to Values and delegate to the BindingSet-based variant - org.eclipse.rdf4j.query.impl.MapBindingSet bs = new org.eclipse.rdf4j.query.impl.MapBindingSet(); + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws QueryEvaluationException { try { - org.eclipse.rdf4j.query.algebra.Var s = pattern.getSubjectVar(); - if (s != null && !s.hasValue()) { - long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID - : idBinding.getIdOrUnknown(s.getName()); - if (id >= 0) { // allow 0 only if context; for subject 0 won't occur - bs.addBinding(s.getName(), valueStore.getLazyValue(id)); + Value subjValue = valueForQuery(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex, true, false); + Resource subjRes = subjValue == null ? null : (Resource) subjValue; + + Value predValue = valueForQuery(patternIds[TripleStore.PRED_IDX], binding, predIndex, false, true); + IRI predIri = predValue == null ? null : (IRI) predValue; + + Value objValue = valueForQuery(patternIds[TripleStore.OBJ_IDX], binding, objIndex, false, false); + + long ctxQueryId = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + boolean requireDefaultContext = ctxQueryId == 0; + Resource[] contexts; + if (ctxQueryId > 0) { + Value ctxValue = valueStore.getLazyValue(ctxQueryId); + if (!(ctxValue instanceof Resource)) { + return LmdbIdJoinIterator.emptyRecordIterator(); } - } - org.eclipse.rdf4j.query.algebra.Var p = pattern.getPredicateVar(); - if (p != null && !p.hasValue()) { - long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID - : idBinding.getIdOrUnknown(p.getName()); - if (id >= 0) { - bs.addBinding(p.getName(), valueStore.getLazyValue(id)); + Resource ctxRes = (Resource) ctxValue; + if (ctxRes.isTriple()) { + return LmdbIdJoinIterator.emptyRecordIterator(); } + contexts = new Resource[] { ctxRes }; + requireDefaultContext = false; + } else { + contexts = new Resource[0]; } - org.eclipse.rdf4j.query.algebra.Var o = pattern.getObjectVar(); - if (o != null && !o.hasValue()) { - long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID - : idBinding.getIdOrUnknown(o.getName()); - if (id >= 0) { - bs.addBinding(o.getName(), valueStore.getLazyValue(id)); + + CloseableIteration stmts = contexts.length == 0 + ? tripleSource.getStatements(subjRes, predIri, objValue) + : tripleSource.getStatements(subjRes, predIri, objValue, contexts); + + final boolean defaultOnly = requireDefaultContext; + + return new RecordIterator() { + @Override + public long[] next() throws QueryEvaluationException { + while (true) { + try { + if (!stmts.hasNext()) { + stmts.close(); + return null; + } + Statement st = stmts.next(); + if (defaultOnly && st.getContext() != null) { + continue; + } + long subjId = resolveId(st.getSubject()); + long predId = resolveId(st.getPredicate()); + long objId = resolveId(st.getObject()); + long ctxId = st.getContext() == null ? 0L : resolveId(st.getContext()); + + long[] merged = mergeBinding(binding, subjId, predId, objId, ctxId, subjIndex, predIndex, + objIndex, ctxIndex); + if (merged != null) { + return merged; + } + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + try { + stmts.close(); + } catch (Exception ignore) { + } + throw new QueryEvaluationException(e); + } + } } - } - org.eclipse.rdf4j.query.algebra.Var c = pattern.getContextVar(); - if (c != null && !c.hasValue()) { - long id = idBinding == null ? org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID - : idBinding.getIdOrUnknown(c.getName()); - if (id > 0) { // context id 0 means default graph (treated as unbound in this overlay) - bs.addBinding(c.getName(), valueStore.getLazyValue(id)); + + @Override + public void close() { + try { + stmts.close(); + } catch (Exception ignore) { + } } - } + }; + } catch (QueryEvaluationException e) { + throw e; } catch (Exception e) { throw new QueryEvaluationException(e); } + } - return getRecordIterator(pattern, bs); + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order); } @Override @@ -166,6 +216,73 @@ public ValueStore getValueStore() { return valueStore; } + private long selectQueryId(long patternId, long[] binding, int index) { + if (patternId != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return patternId; + } + if (index >= 0 && index < binding.length) { + long bound = binding[index]; + if (bound != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return bound; + } + } + return org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + } + + private Value valueForQuery(long patternId, long[] binding, int index, boolean requireResource, boolean requireIri) + throws QueryEvaluationException { + long id = selectQueryId(patternId, binding, index); + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return null; + } + try { + Value value = valueStore.getLazyValue(id); + if (requireResource && !(value instanceof Resource)) { + throw new QueryEvaluationException("Expected resource-bound value"); + } + if (requireIri && !(value instanceof IRI)) { + throw new QueryEvaluationException("Expected IRI-bound value"); + } + if (value instanceof Resource && ((Resource) value).isTriple()) { + throw new QueryEvaluationException("Triple-valued resources are not supported in LMDB joins"); + } + return value; + } catch (Exception e) { + throw e instanceof QueryEvaluationException ? (QueryEvaluationException) e + : new QueryEvaluationException(e); + } + } + + private long[] mergeBinding(long[] binding, long subjId, long predId, long objId, long ctxId, int subjIndex, + int predIndex, int objIndex, int ctxIndex) { + long[] out = Arrays.copyOf(binding, binding.length); + if (!applyValue(out, subjIndex, subjId)) { + return null; + } + if (!applyValue(out, predIndex, predId)) { + return null; + } + if (!applyValue(out, objIndex, objId)) { + return null; + } + if (!applyValue(out, ctxIndex, ctxId)) { + return null; + } + return out; + } + + private boolean applyValue(long[] target, int index, long value) { + if (index < 0) { + return true; + } + long existing = target[index]; + if (existing != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID && existing != value) { + return false; + } + target[index] = value; + return true; + } + @Override public boolean hasTransactionChanges() { return true; @@ -199,4 +316,73 @@ private long resolveId(Value value) throws Exception { } return valueStore.getId(value); } + + private StatementPattern toStatementPattern(long[] patternIds, String[] varNames) throws QueryEvaluationException { + if (patternIds == null || varNames == null || patternIds.length != 4 || varNames.length != 4) { + throw new IllegalArgumentException("Pattern arrays must have length 4"); + } + org.eclipse.rdf4j.query.algebra.Var subj = buildVar(patternIds[0], varNames[0], "s", true, false); + org.eclipse.rdf4j.query.algebra.Var pred = buildVar(patternIds[1], varNames[1], "p", false, true); + org.eclipse.rdf4j.query.algebra.Var obj = buildVar(patternIds[2], varNames[2], "o", false, false); + org.eclipse.rdf4j.query.algebra.Var ctx = buildContextVar(patternIds[3], varNames[3]); + return new StatementPattern(subj, pred, obj, ctx); + } + + private org.eclipse.rdf4j.query.algebra.Var buildVar(long id, String name, String placeholder, + boolean requireResource, + boolean requireIri) throws QueryEvaluationException { + if (name != null) { + return new org.eclipse.rdf4j.query.algebra.Var(name); + } + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + // Should not occur for subject/predicate/object when derived from StatementPattern + return new org.eclipse.rdf4j.query.algebra.Var(placeholder); + } + try { + org.eclipse.rdf4j.model.Value value = valueStore.getLazyValue(id); + if (value == null) { + throw new QueryEvaluationException("Unable to resolve value for ID " + id); + } + if (requireResource && !(value instanceof org.eclipse.rdf4j.model.Resource)) { + throw new QueryEvaluationException("Expected resource value for subject ID " + id); + } + if (requireIri && !(value instanceof org.eclipse.rdf4j.model.IRI)) { + throw new QueryEvaluationException("Expected IRI value for predicate ID " + id); + } + return new org.eclipse.rdf4j.query.algebra.Var(placeholder, value); + } catch (Exception e) { + if (e instanceof QueryEvaluationException) { + throw (QueryEvaluationException) e; + } + throw new QueryEvaluationException(e); + } + } + + private org.eclipse.rdf4j.query.algebra.Var buildContextVar(long id, String name) throws QueryEvaluationException { + if (name != null) { + return new org.eclipse.rdf4j.query.algebra.Var(name); + } + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return null; + } + try { + org.eclipse.rdf4j.model.Value value = valueStore.getLazyValue(id); + if (value == null) { + throw new QueryEvaluationException("Unable to resolve context value for ID " + id); + } + if (!(value instanceof org.eclipse.rdf4j.model.Resource)) { + throw new QueryEvaluationException("Context ID " + id + " does not map to a resource"); + } + org.eclipse.rdf4j.model.Resource ctx = (org.eclipse.rdf4j.model.Resource) value; + if (ctx.isTriple()) { + throw new QueryEvaluationException("Triple-valued contexts are not supported"); + } + return new org.eclipse.rdf4j.query.algebra.Var("ctx", ctx); + } catch (Exception e) { + if (e instanceof QueryEvaluationException) { + throw (QueryEvaluationException) e; + } + throw new QueryEvaluationException(e); + } + } } 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 d799f740e1..c63c447c1f 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 @@ -15,6 +15,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.EnumSet; import java.util.List; @@ -31,7 +32,6 @@ 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.DualUnionIteration; import org.eclipse.rdf4j.common.iteration.FilterIteration; import org.eclipse.rdf4j.common.iteration.UnionIteration; import org.eclipse.rdf4j.common.order.StatementOrder; @@ -1362,6 +1362,133 @@ private StatementOrder toStatementOrder(char f) { @Override public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException { + try { + PatternArrays arrays = describePattern(pattern); + if (!arrays.valid) { + return emptyRecordIterator(); + } + long subjID = resolveIdWithBindings(arrays.ids[TripleStore.SUBJ_IDX], + arrays.varNames[TripleStore.SUBJ_IDX], + bindings, true, false); + if (subjID == INVALID_ID) { + return emptyRecordIterator(); + } + + long predID = resolveIdWithBindings(arrays.ids[TripleStore.PRED_IDX], + arrays.varNames[TripleStore.PRED_IDX], + bindings, false, true); + if (predID == INVALID_ID) { + return emptyRecordIterator(); + } + + long objID = resolveIdWithBindings(arrays.ids[TripleStore.OBJ_IDX], + arrays.varNames[TripleStore.OBJ_IDX], + bindings, false, false); + if (objID == INVALID_ID) { + return emptyRecordIterator(); + } + + long contextID = resolveContextWithBindings(arrays.ids[TripleStore.CONTEXT_IDX], + arrays.varNames[TripleStore.CONTEXT_IDX], bindings); + if (contextID == INVALID_ID) { + return emptyRecordIterator(); + } + + return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); + } catch (IOException e) { + throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + } + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, + long[] patternIds) throws QueryEvaluationException { + try { + long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); + long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); + long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); + long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + + RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); + + return new RecordIterator() { + @Override + public long[] next() throws QueryEvaluationException { + try { + long[] quad; + while ((quad = base.next()) != null) { + long[] merged = mergeBinding(binding, quad[TripleStore.SUBJ_IDX], + quad[TripleStore.PRED_IDX], + quad[TripleStore.OBJ_IDX], quad[TripleStore.CONTEXT_IDX], subjIndex, predIndex, + objIndex, ctxIndex); + if (merged != null) { + return merged; + } + } + return null; + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public void close() { + base.close(); + } + }; + } catch (IOException e) { + throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + } + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + try { + long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); + long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); + long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); + long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + + RecordIterator orderedIter = orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery); + if (orderedIter != null) { + return orderedIter; + } + return null; + } catch (IOException e) { + throw new QueryEvaluationException("Unable to create ordered LMDB record iterator", e); + } + } + + @Override + public boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, StatementOrder order) { + if (order == null) { + return true; + } + long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); + long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); + long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); + long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + try { + return orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery) != null; + } catch (IOException e) { + return false; + } + } + + @Override + public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, + StatementOrder order) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(pattern, bindings); + } try { Value subj = resolveValue(pattern.getSubjectVar(), bindings); if (subj != null && !(subj instanceof Resource)) { @@ -1405,108 +1532,277 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin return emptyRecordIterator(); } - return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); + RecordIterator orderedIter = orderedRecordIterator(order, subjID, predID, objID, contextID); + if (orderedIter != null) { + return orderedIter; + } + return null; } catch (IOException e) { - throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + throw new QueryEvaluationException("Unable to create ordered LMDB record iterator", e); } } @Override - public RecordIterator getRecordIterator(StatementPattern pattern, LmdbIdVarBinding idBinding) - throws QueryEvaluationException { - try { - // Resolve subject - long subjID = resolveIdFromVarOrBinding(pattern.getSubjectVar(), idBinding, true); - if (subjID == Long.MIN_VALUE) { // incompatible type - return emptyRecordIterator(); - } + public ValueStore getValueStore() { + return valueStore; + } - // Resolve predicate - long predID = resolveIdFromVarOrBinding(pattern.getPredicateVar(), idBinding, false); - if (predID == Long.MIN_VALUE) { - return emptyRecordIterator(); - } + @Override + public boolean hasTransactionChanges() { + // storeTxnStarted is flipped to true when a writer begins and only cleared + // after commit/rollback, so a true value indicates pending uncommitted changes. + return storeTxnStarted.get(); + } - // Resolve object - long objID = resolveIdFromVarOrBinding(pattern.getObjectVar(), idBinding, null); - if (objID == Long.MIN_VALUE) { - return emptyRecordIterator(); - } + private RecordIterator emptyRecordIterator() { + return EMPTY_RECORD_ITERATOR; + } - // Resolve context - org.eclipse.rdf4j.query.algebra.Var ctxVar = pattern.getContextVar(); - long contextID; - if (ctxVar == null) { - contextID = LmdbValue.UNKNOWN_ID; - } else if (ctxVar.hasValue()) { - Value ctxValue = ctxVar.getValue(); - if (ctxValue instanceof Resource) { - Resource ctx = (Resource) ctxValue; - if (ctx.isTriple()) { - return emptyRecordIterator(); - } - contextID = resolveId(ctx); - if (contextID == LmdbValue.UNKNOWN_ID) { - return emptyRecordIterator(); - } - } else { - return emptyRecordIterator(); - } - } else { - // variable context; if bound on left, use that; 0 represents default graph - long bound = idBinding == null ? LmdbValue.UNKNOWN_ID : idBinding.getIdOrUnknown(ctxVar.getName()); - contextID = bound; + private Value resolveValue(Var var, BindingSet bindings) { + if (var == null) { + return null; + } + if (var.hasValue()) { + return var.getValue(); + } + if (bindings != null) { + Value bound = bindings.getValue(var.getName()); + if (bound != null) { + return bound; } - - return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); - } catch (IOException e) { - throw new QueryEvaluationException("Unable to create LMDB record iterator", e); } + return null; } - private long resolveIdFromVarOrBinding(org.eclipse.rdf4j.query.algebra.Var var, LmdbIdVarBinding idBinding, - Boolean resourceType) throws IOException { + private static final long INVALID_ID = Long.MIN_VALUE; + + private PatternArrays describePattern(StatementPattern pattern) throws IOException { + long[] ids = new long[4]; + String[] varNames = new String[4]; + boolean valid = true; + + valid &= populateSubject(pattern.getSubjectVar(), ids, varNames); + valid &= populatePredicate(pattern.getPredicateVar(), ids, varNames); + valid &= populateObject(pattern.getObjectVar(), ids, varNames); + valid &= populateContext(pattern.getContextVar(), ids, varNames); + + return new PatternArrays(ids, varNames, valid); + } + + private boolean populateSubject(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + throws IOException { if (var == null) { - return LmdbValue.UNKNOWN_ID; + ids[TripleStore.SUBJ_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.SUBJ_IDX] = null; + return true; } if (var.hasValue()) { - Value v = var.getValue(); - if (resourceType == Boolean.TRUE && !(v instanceof Resource)) { - return Long.MIN_VALUE; // incompatible type + Value value = var.getValue(); + if (!(value instanceof Resource)) { + return false; } - if (resourceType == Boolean.FALSE && !(v instanceof IRI)) { - return Long.MIN_VALUE; // incompatible type + long id = resolveId(value); + if (id == LmdbValue.UNKNOWN_ID) { + return false; } - long id = resolveId(v); - return id == LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; + ids[TripleStore.SUBJ_IDX] = id; + varNames[TripleStore.SUBJ_IDX] = null; + return true; } - long bound = idBinding == null ? LmdbValue.UNKNOWN_ID : idBinding.getIdOrUnknown(var.getName()); - return bound; + ids[TripleStore.SUBJ_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.SUBJ_IDX] = var.getName(); + return true; } - @Override - public ValueStore getValueStore() { - return valueStore; + private boolean populatePredicate(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + throws IOException { + if (var == null) { + ids[TripleStore.PRED_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.PRED_IDX] = null; + return true; + } + if (var.hasValue()) { + Value value = var.getValue(); + if (!(value instanceof IRI)) { + return false; + } + long id = resolveId(value); + if (id == LmdbValue.UNKNOWN_ID) { + return false; + } + ids[TripleStore.PRED_IDX] = id; + varNames[TripleStore.PRED_IDX] = null; + return true; + } + ids[TripleStore.PRED_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.PRED_IDX] = var.getName(); + return true; } - private RecordIterator emptyRecordIterator() { - return EMPTY_RECORD_ITERATOR; + private boolean populateObject(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + throws IOException { + if (var == null) { + ids[TripleStore.OBJ_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.OBJ_IDX] = null; + return true; + } + if (var.hasValue()) { + Value value = var.getValue(); + long id = resolveId(value); + if (id == LmdbValue.UNKNOWN_ID) { + return false; + } + ids[TripleStore.OBJ_IDX] = id; + varNames[TripleStore.OBJ_IDX] = null; + return true; + } + ids[TripleStore.OBJ_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.OBJ_IDX] = var.getName(); + return true; } - private Value resolveValue(Var var, BindingSet bindings) { + private boolean populateContext(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + throws IOException { if (var == null) { - return null; + ids[TripleStore.CONTEXT_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.CONTEXT_IDX] = null; + return true; } if (var.hasValue()) { - return var.getValue(); + Value value = var.getValue(); + if (!(value instanceof Resource)) { + return false; + } + Resource ctx = (Resource) value; + if (ctx.isTriple()) { + return false; + } + long id = resolveId(ctx); + if (id == LmdbValue.UNKNOWN_ID) { + return false; + } + ids[TripleStore.CONTEXT_IDX] = id; + varNames[TripleStore.CONTEXT_IDX] = null; + return true; } - if (bindings != null) { - Value bound = bindings.getValue(var.getName()); - if (bound != null) { + ids[TripleStore.CONTEXT_IDX] = LmdbValue.UNKNOWN_ID; + varNames[TripleStore.CONTEXT_IDX] = var.getName(); + return true; + } + + private long selectQueryId(long patternId, long[] binding, int index) { + if (patternId != LmdbValue.UNKNOWN_ID) { + return patternId; + } + if (index >= 0 && index < binding.length) { + long bound = binding[index]; + if (bound != LmdbValue.UNKNOWN_ID) { return bound; } } - return null; + return LmdbValue.UNKNOWN_ID; + } + + private long[] mergeBinding(long[] binding, long subjId, long predId, long objId, long ctxId, int subjIndex, + int predIndex, int objIndex, int ctxIndex) { + long[] out = Arrays.copyOf(binding, binding.length); + if (!applyValue(out, subjIndex, subjId)) { + return null; + } + if (!applyValue(out, predIndex, predId)) { + return null; + } + if (!applyValue(out, objIndex, objId)) { + return null; + } + if (!applyValue(out, ctxIndex, ctxId)) { + return null; + } + return out; + } + + private boolean applyValue(long[] target, int index, long value) { + if (index < 0) { + return true; + } + long existing = target[index]; + if (existing != LmdbValue.UNKNOWN_ID && existing != value) { + return false; + } + target[index] = value; + return true; + } + + private long resolveIdWithBindings(long patternId, String varName, BindingSet bindings, boolean requireResource, + boolean requireIri) throws IOException { + if (patternId == INVALID_ID) { + return INVALID_ID; + } + if (varName == null) { + return patternId; + } + if (bindings == null) { + return LmdbValue.UNKNOWN_ID; + } + Value bound = bindings.getValue(varName); + if (bound == null) { + return LmdbValue.UNKNOWN_ID; + } + if (requireResource && !(bound instanceof Resource)) { + return INVALID_ID; + } + if (requireIri && !(bound instanceof IRI)) { + return INVALID_ID; + } + if (bound instanceof Resource && ((Resource) bound).isTriple()) { + return INVALID_ID; + } + long id = resolveId(bound); + if (id == LmdbValue.UNKNOWN_ID) { + return INVALID_ID; + } + return id; + } + + private long resolveContextWithBindings(long patternId, String varName, BindingSet bindings) + throws IOException { + if (patternId == INVALID_ID) { + return INVALID_ID; + } + if (varName == null) { + return patternId; + } + if (bindings == null) { + return LmdbValue.UNKNOWN_ID; + } + Value bound = bindings.getValue(varName); + if (bound == null) { + return LmdbValue.UNKNOWN_ID; + } + if (!(bound instanceof Resource)) { + return INVALID_ID; + } + Resource ctx = (Resource) bound; + if (ctx.isTriple()) { + return INVALID_ID; + } + long id = resolveId(ctx); + if (id == LmdbValue.UNKNOWN_ID) { + return INVALID_ID; + } + return id; + } + + private final class PatternArrays { + private final long[] ids; + private final String[] varNames; + private final boolean valid; + + private PatternArrays(long[] ids, String[] varNames, boolean valid) { + this.ids = ids; + this.varNames = varNames; + this.valid = valid; + } } private long resolveId(Value value) throws IOException { @@ -1523,6 +1819,19 @@ private long resolveId(Value value) throws IOException { return id; } + private RecordIterator orderedRecordIterator(StatementOrder order, long subjID, long predID, long objID, + long contextID) throws IOException { + if (order == null) { + return null; + } + TripleStore.TripleIndex chosen = chooseIndexForOrder(order, subjID, predID, objID, contextID); + if (chosen == null) { + return null; + } + boolean rangeSearch = chosen.getPatternScore(subjID, predID, objID, contextID) > 0; + return new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, explicit, txn); + } + private TripleStore.TripleIndex chooseIndexForOrder(StatementOrder order, long s, long p, long o, long c) throws IOException { // ensure metadata initialized diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java index 6c9791d4c9..feabd5e741 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java @@ -34,7 +34,6 @@ import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; -import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; import org.eclipse.rdf4j.repository.sparql.federation.SPARQLServiceResolver; import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.NotifyingSailConnection; 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 9c0577e655..bead908f8b 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 @@ -48,6 +48,7 @@ public class LmdbStoreConnection extends SailSourceConnection { * The transaction lock held by this connection during transactions. */ private volatile Lock txnLock; + private volatile boolean hasPendingChanges; /*--------------* * Constructors * @@ -90,6 +91,7 @@ protected void commitInternal() throws SailException { try { super.commitInternal(); } finally { + hasPendingChanges = false; if (txnLock != null && txnLock.isActive()) { txnLock.release(); } @@ -106,6 +108,7 @@ protected void rollbackInternal() throws SailException { try { super.rollbackInternal(); } finally { + hasPendingChanges = false; if (txnLock != null && txnLock.isActive()) { txnLock.release(); } @@ -118,6 +121,7 @@ protected void rollbackInternal() throws SailException { protected void addStatementInternal(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException { // assume the triple is not yet present in the triple store sailChangedEvent.setStatementsAdded(true); + hasPendingChanges = true; } @Override @@ -125,6 +129,7 @@ public boolean addInferredStatement(Resource subj, IRI pred, Value obj, Resource boolean ret = super.addInferredStatement(subj, pred, obj, contexts); // assume the triple is not yet present in the triple store sailChangedEvent.setStatementsAdded(true); + hasPendingChanges = true; return ret; } @@ -132,35 +137,87 @@ public boolean addInferredStatement(Resource subj, IRI pred, Value obj, Resource 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; + boolean flagPushed = false; + boolean success = false; + if (hasPendingChanges) { + LmdbEvaluationStrategy.pushConnectionChangesFlag(true); + flagPushed = true; + } else { + LmdbEvaluationStrategy.pushConnectionChangesFlag(false); + flagPushed = true; + } + try { + CloseableIteration base = super.evaluateInternal(tupleExpr, dataset, bindings, + includeInferred); + success = true; + // ensure that all elements of the binding set are initialized (lazy values are resolved) + return new IterationWrapper(base) { + @Override + public BindingSet next() throws QueryEvaluationException { + BindingSet bs = super.next(); + bs.forEach(b -> initValue(b.getValue())); + return bs; + } + + @Override + protected void handleClose() throws QueryEvaluationException { + try { + super.handleClose(); + } finally { + LmdbEvaluationStrategy.popConnectionChangesFlag(); + } + } + }; + } catch (RuntimeException e) { + throw e; + } finally { + if (!success && flagPushed) { + LmdbEvaluationStrategy.popConnectionChangesFlag(); } - }; + } } @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; + boolean flagPushed = false; + if (hasPendingChanges) { + LmdbEvaluationStrategy.pushConnectionChangesFlag(true); + flagPushed = true; + } else { + LmdbEvaluationStrategy.pushConnectionChangesFlag(false); + flagPushed = true; + } + try { + 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; + } + + @Override + protected void handleClose() throws SailException { + try { + super.handleClose(); + } finally { + LmdbEvaluationStrategy.popConnectionChangesFlag(); + } + } + }; + } catch (RuntimeException e) { + if (flagPushed) { + LmdbEvaluationStrategy.popConnectionChangesFlag(); } - }; + throw e; + } } /** @@ -178,6 +235,7 @@ protected void initValue(Value value) { protected void removeStatementsInternal(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException { sailChangedEvent.setStatementsRemoved(true); + hasPendingChanges = true; } @Override @@ -185,6 +243,7 @@ public boolean removeInferredStatement(Resource subj, IRI pred, Value obj, Resou throws SailException { boolean ret = super.removeInferredStatement(subj, pred, obj, contexts); sailChangedEvent.setStatementsRemoved(true); + hasPendingChanges = true; return ret; } @@ -192,12 +251,14 @@ public boolean removeInferredStatement(Resource subj, IRI pred, Value obj, Resou protected void clearInternal(Resource... contexts) throws SailException { super.clearInternal(contexts); sailChangedEvent.setStatementsRemoved(true); + hasPendingChanges = true; } @Override public void clearInferred(Resource... contexts) throws SailException { super.clearInferred(contexts); sailChangedEvent.setStatementsRemoved(true); + hasPendingChanges = true; } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 7a0b295880..d20721fed7 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -10,31 +10,32 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.join; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; -import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.BindingSet; -import org.eclipse.rdf4j.query.MutableBindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.StatementPattern; import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; -import org.eclipse.rdf4j.sail.lmdb.IdAccessor; import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.TripleStore; import org.eclipse.rdf4j.sail.lmdb.ValueStore; -import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdFinalBindingSetIteration; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; /** * Builds a left-deep chain of ID-only join iterators for an entire BGP and materializes bindings only once. @@ -43,166 +44,145 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private static final String ID_JOIN_ALGORITHM = LmdbIdJoinIterator.class.getSimpleName(); - private final List patterns; + private final List plans; + private final IdBindingInfo finalInfo; private final QueryEvaluationContext context; private final LmdbDatasetContext datasetContext; private final Join root; + private final QueryEvaluationStep fallbackStep; + private final boolean hasInvalidPattern; - public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context) { + public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context, + QueryEvaluationStep fallbackStep) { if (!(context instanceof LmdbDatasetContext)) { throw new IllegalArgumentException("LMDB ID BGP join requires LMDB query evaluation context"); } this.root = root; - this.patterns = patterns; this.context = context; this.datasetContext = (LmdbDatasetContext) context; + this.fallbackStep = fallbackStep; + + ValueStore valueStore = this.datasetContext.getValueStore() + .orElseThrow(() -> new IllegalStateException("LMDB ID BGP join requires ValueStore access")); + + List rawPatterns = new ArrayList<>(patterns.size()); + boolean invalid = false; + for (StatementPattern pattern : patterns) { + RawPattern raw = RawPattern.create(pattern, valueStore); + rawPatterns.add(raw); + invalid |= raw.invalid; + } + this.hasInvalidPattern = invalid; + + if (rawPatterns.isEmpty()) { + throw new IllegalArgumentException("Basic graph pattern must contain at least one statement pattern"); + } + + IdBindingInfo info = null; + for (RawPattern raw : rawPatterns) { + if (info == null) { + info = IdBindingInfo.fromFirstPattern(raw.patternInfo); + } else { + info = IdBindingInfo.combine(info, raw.patternInfo); + } + } + this.finalInfo = info; + + List planList = new ArrayList<>(rawPatterns.size()); + for (RawPattern raw : rawPatterns) { + planList.add(raw.toPlan(finalInfo)); + } + this.plans = planList; + markJoinTreeWithIdAlgorithm(root); } @Override public CloseableIteration evaluate(BindingSet bindings) { + if (fallbackStep != null && LmdbEvaluationStrategy.hasActiveConnectionChanges()) { + return fallbackStep.evaluate(bindings); + } try { LmdbEvaluationDataset dataset = resolveDataset(); - ValueStore valueStore = dataset.getValueStore(); + if (fallbackStep != null && dataset.hasTransactionChanges()) { + return fallbackStep.evaluate(bindings); + } + if (hasInvalidPattern) { + return new EmptyIteration<>(); + } - // Leftmost iterator uses initial bindings (outer input bindings may have Values) - StatementPattern first = patterns.get(0); - RecordIterator left = dataset.getRecordIterator(first, bindings); - LmdbIdJoinIterator.PatternInfo leftInfoPI = LmdbIdJoinIterator.PatternInfo.create(first); - IdBindingInfo leftInfo = IdBindingInfo.fromFirstPattern(leftInfoPI); - - for (int i = 1; i < patterns.size(); i++) { - StatementPattern rightPat = patterns.get(i); - LmdbIdJoinIterator.PatternInfo rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPat); - Set shared = sharedVars(leftInfo.getVariableNames(), rightInfo.getVariableNames()); - IdBindingInfo outInfo = IdBindingInfo.combine(leftInfo, rightInfo); - - IdAccessor leftAccessor = (i == 1) ? leftInfoPI : leftInfo; - final IdAccessor leftAccessorForLambda = leftAccessor; - final IdBindingInfo outputInfo = outInfo; - final LmdbIdJoinIterator.PatternInfo rightPatternInfo = rightInfo; - final CopyPlan leftCopyPlan = CopyPlan.forAccessor(leftAccessor, outInfo); - final CopyPlan rightCopyPlan = CopyPlan.forRightExclusive(rightPatternInfo, shared, outInfo); - - IdJoinRecordIterator.RightFactory rightFactory = leftRecord -> { - long[] leftSnapshot = Arrays.copyOf(leftRecord, leftRecord.length); - RecordIterator rightIter = dataset.getRecordIterator(rightPat, - varName -> leftAccessorForLambda.getId(leftSnapshot, varName)); - if (rightIter == null) { - rightIter = LmdbIdJoinIterator.emptyRecordIterator(); - } - long[] leftSeed = seedLeftContribution(leftSnapshot, leftCopyPlan, outputInfo.size()); - RecordIterator finalRightIter = rightIter; - return new RecordIterator() { - @Override - public long[] next() { - long[] rightRecord; - while ((rightRecord = finalRightIter.next()) != null) { - long[] out = Arrays.copyOf(leftSeed, leftSeed.length); - appendRightExclusive(out, rightRecord, rightCopyPlan); - return out; - } - return null; - } + ValueStore valueStore = dataset.getValueStore(); + long[] initialBinding = createInitialBinding(finalInfo, bindings, valueStore); + if (initialBinding == null) { + return new EmptyIteration<>(); + } - @Override - public void close() { - finalRightIter.close(); - } - }; - }; + PatternPlan firstPlan = plans.get(0); + RecordIterator iter = dataset.getRecordIterator(initialBinding, firstPlan.subjIndex, firstPlan.predIndex, + firstPlan.objIndex, firstPlan.ctxIndex, firstPlan.patternIds); - left = new IdJoinRecordIterator(left, rightFactory); - leftInfo = outInfo; + for (int i = 1; i < plans.size(); i++) { + iter = new BindingJoinRecordIterator(iter, dataset, plans.get(i)); } - return new LmdbIdFinalBindingSetIteration(left, leftInfo, context, bindings, valueStore); + return new LmdbIdFinalBindingSetIteration(iter, finalInfo, context, bindings, valueStore); } catch (QueryEvaluationException e) { throw e; } } - private Set sharedVars(Set a, Set b) { - LinkedHashSet s = new LinkedHashSet<>(a); - s.retainAll(new HashSet<>(b)); - return s; - } - - private static long[] seedLeftContribution(long[] leftRecord, CopyPlan copyPlan, int outputSize) { - long[] seed = new long[outputSize]; - copyPlan.copy(leftRecord, seed); - return seed; - } - - private static void appendRightExclusive(long[] target, long[] rightRecord, CopyPlan copyPlan) { - copyPlan.copy(rightRecord, target); + private LmdbEvaluationDataset resolveDataset() { + java.util.Optional fromContext = datasetContext.getLmdbDataset(); + if (fromContext.isPresent()) { + return fromContext.get(); + } + return LmdbEvaluationStrategy.getCurrentDataset() + .orElseThrow(() -> new IllegalStateException("No active LMDB dataset available for join evaluation")); } - private static final class CopyPlan { - private final int[] sourceIndexes; - private final int[] targetIndexes; - - private CopyPlan(int[] sourceIndexes, int[] targetIndexes) { - this.sourceIndexes = sourceIndexes; - this.targetIndexes = targetIndexes; + private static long[] createInitialBinding(IdBindingInfo info, BindingSet bindings, ValueStore valueStore) + throws QueryEvaluationException { + long[] binding = new long[info.size()]; + Arrays.fill(binding, LmdbValue.UNKNOWN_ID); + if (bindings == null || bindings.isEmpty()) { + return binding; } - - static CopyPlan forAccessor(IdAccessor accessor, IdBindingInfo outputInfo) { - List sources = new ArrayList<>(); - List targets = new ArrayList<>(); - for (String name : accessor.getVariableNames()) { - int sourceIdx = accessor.getRecordIndex(name); - int targetIdx = outputInfo.getIndex(name); - if (sourceIdx >= 0 && targetIdx >= 0) { - sources.add(sourceIdx); - targets.add(targetIdx); - } + for (String name : info.getVariableNames()) { + Value value = bindings.getValue(name); + if (value == null) { + continue; } - return new CopyPlan(toIntArray(sources), toIntArray(targets)); - } - - static CopyPlan forRightExclusive(LmdbIdJoinIterator.PatternInfo rightInfo, Set sharedVars, - IdBindingInfo outputInfo) { - List sources = new ArrayList<>(); - List targets = new ArrayList<>(); - for (String name : rightInfo.getVariableNames()) { - if (sharedVars.contains(name)) { - continue; - } - int sourceIdx = rightInfo.getRecordIndex(name); - int targetIdx = outputInfo.getIndex(name); - if (sourceIdx >= 0 && targetIdx >= 0) { - sources.add(sourceIdx); - targets.add(targetIdx); - } + long id = resolveId(valueStore, value); + if (id == LmdbValue.UNKNOWN_ID) { + return null; } - return new CopyPlan(toIntArray(sources), toIntArray(targets)); - } - - void copy(long[] source, long[] target) { - for (int i = 0; i < sourceIndexes.length; i++) { - target[targetIndexes[i]] = source[sourceIndexes[i]]; + int index = info.getIndex(name); + if (index >= 0) { + binding[index] = id; } } + return binding; + } - private static int[] toIntArray(List values) { - int[] out = new int[values.size()]; - for (int i = 0; i < values.size(); i++) { - out[i] = values.get(i); + private static long resolveId(ValueStore valueStore, Value value) throws QueryEvaluationException { + if (value == null) { + return LmdbValue.UNKNOWN_ID; + } + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != LmdbValue.UNKNOWN_ID) { + return id; + } } - return out; } - } - - private LmdbEvaluationDataset resolveDataset() { - // Prefer the dataset supplied by the evaluation context (may be an overlay honoring txn writes) - java.util.Optional fromContext = datasetContext.getLmdbDataset(); - if (fromContext.isPresent()) { - return fromContext.get(); + try { + long id = valueStore.getId(value); + return id; + } catch (IOException e) { + throw new QueryEvaluationException(e); } - // Fall back to the thread-local dataset if the context did not provide one - return LmdbEvaluationStrategy.getCurrentDataset() - .orElseThrow(() -> new IllegalStateException("No active LMDB dataset available for join evaluation")); } public static boolean flattenBGP(TupleExpr expr, List out) { @@ -212,10 +192,10 @@ public static boolean flattenBGP(TupleExpr expr, List out) { } if (expr instanceof Join) { Join j = (Join) expr; -// // merge joins only; we avoid mergeJoin or other special joins -// if (j.isMergeJoin()) { -// return false; -// } + // merge joins only; we avoid mergeJoin or other special joins + if (j.isMergeJoin()) { + return false; + } return flattenBGP(j.getLeftArg(), out) && flattenBGP(j.getRightArg(), out); } return false; @@ -229,4 +209,192 @@ private static void markJoinTreeWithIdAlgorithm(TupleExpr expr) { markJoinTreeWithIdAlgorithm(join.getRightArg()); } } + + private static final class BindingJoinRecordIterator implements RecordIterator { + private final RecordIterator left; + private final LmdbEvaluationDataset dataset; + private final PatternPlan plan; + private RecordIterator currentRight; + + private BindingJoinRecordIterator(RecordIterator left, LmdbEvaluationDataset dataset, PatternPlan plan) { + this.left = left; + this.dataset = dataset; + this.plan = plan; + } + + @Override + public long[] next() throws QueryEvaluationException { + while (true) { + if (currentRight != null) { + long[] next = currentRight.next(); + if (next != null) { + return next; + } + currentRight.close(); + currentRight = null; + } + + long[] leftBinding = left.next(); + if (leftBinding == null) { + return null; + } + + currentRight = dataset.getRecordIterator(leftBinding, plan.subjIndex, plan.predIndex, plan.objIndex, + plan.ctxIndex, plan.patternIds); + } + } + + @Override + public void close() { + if (currentRight != null) { + currentRight.close(); + currentRight = null; + } + left.close(); + } + } + + private static final class PatternPlan { + private final long[] patternIds; + private final int subjIndex; + private final int predIndex; + private final int objIndex; + private final int ctxIndex; + + private PatternPlan(long[] patternIds, int subjIndex, int predIndex, int objIndex, int ctxIndex) { + this.patternIds = patternIds; + this.subjIndex = subjIndex; + this.predIndex = predIndex; + this.objIndex = objIndex; + this.ctxIndex = ctxIndex; + } + } + + private static final class RawPattern { + private final long[] patternIds; + private final String subjVar; + private final String predVar; + private final String objVar; + private final String ctxVar; + private final LmdbIdJoinIterator.PatternInfo patternInfo; + private final boolean invalid; + + private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, + LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid) { + this.patternIds = patternIds; + this.subjVar = subjVar; + this.predVar = predVar; + this.objVar = objVar; + this.ctxVar = ctxVar; + this.patternInfo = patternInfo; + this.invalid = invalid; + } + + static RawPattern create(StatementPattern pattern, ValueStore valueStore) { + long[] ids = new long[4]; + Arrays.fill(ids, LmdbValue.UNKNOWN_ID); + + boolean invalid = false; + + Var subj = pattern.getSubjectVar(); + String subjVar = null; + if (subj != null) { + if (subj.hasValue()) { + long id = constantId(valueStore, subj.getValue(), true, false); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.SUBJ_IDX] = id; + } + } else { + subjVar = subj.getName(); + } + } + + Var pred = pattern.getPredicateVar(); + String predVar = null; + if (pred != null) { + if (pred.hasValue()) { + long id = constantId(valueStore, pred.getValue(), false, true); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.PRED_IDX] = id; + } + } else { + predVar = pred.getName(); + } + } + + Var obj = pattern.getObjectVar(); + String objVar = null; + if (obj != null) { + if (obj.hasValue()) { + long id = constantId(valueStore, obj.getValue(), false, false); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.OBJ_IDX] = id; + } + } else { + objVar = obj.getName(); + } + } + + Var ctx = pattern.getContextVar(); + String ctxVar = null; + if (ctx != null) { + if (ctx.hasValue()) { + long id = constantId(valueStore, ctx.getValue(), true, false); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.CONTEXT_IDX] = id; + } + } else { + ctxVar = ctx.getName(); + } + } + + LmdbIdJoinIterator.PatternInfo info = LmdbIdJoinIterator.PatternInfo.create(pattern); + return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, info, invalid); + } + + PatternPlan toPlan(IdBindingInfo finalInfo) { + return new PatternPlan(patternIds.clone(), indexFor(subjVar, finalInfo), + indexFor(predVar, finalInfo), indexFor(objVar, finalInfo), indexFor(ctxVar, finalInfo)); + } + + private static int indexFor(String varName, IdBindingInfo info) { + return varName == null ? -1 : info.getIndex(varName); + } + + private static long constantId(ValueStore valueStore, Value value, boolean requireResource, + boolean requireIri) { + if (requireResource && !(value instanceof Resource)) { + return Long.MIN_VALUE; + } + if (requireIri && !(value instanceof IRI)) { + return Long.MIN_VALUE; + } + if (value instanceof Resource && ((Resource) value).isTriple()) { + return Long.MIN_VALUE; + } + try { + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != LmdbValue.UNKNOWN_ID) { + return id; + } + } + } + long id = valueStore.getId(value); + return id == LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; + } catch (IOException e) { + return Long.MIN_VALUE; + } + } + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index c20844b04b..8305abf90a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -17,7 +17,6 @@ import java.util.Map; import java.util.Set; -import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.iteration.LookAheadIteration; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.BindingSet; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 1911be85a7..b5c58dcdff 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -42,8 +42,10 @@ public class LmdbIdJoinQueryEvaluationStep implements QueryEvaluationStep { private final LmdbIdJoinIterator.PatternInfo rightInfo; private final Set sharedVariables; private final LmdbDatasetContext datasetContext; + private final QueryEvaluationStep fallbackStep; - public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, QueryEvaluationContext context) { + public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, QueryEvaluationContext context, + QueryEvaluationStep fallbackStep) { if (!(join.getLeftArg() instanceof StatementPattern) || !(join.getRightArg() instanceof StatementPattern)) { throw new IllegalArgumentException("LMDB ID join requires StatementPattern operands"); } @@ -59,6 +61,7 @@ public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, Que this.leftInfo = LmdbIdJoinIterator.PatternInfo.create(leftPattern); this.rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPattern); this.sharedVariables = computeSharedVariables(leftInfo, rightInfo); + this.fallbackStep = fallbackStep; join.setAlgorithm(LmdbIdJoinIterator.class.getSimpleName()); @@ -73,8 +76,14 @@ private Set computeSharedVariables(LmdbIdJoinIterator.PatternInfo left, @Override public CloseableIteration evaluate(BindingSet bindings) { + if (fallbackStep != null && LmdbEvaluationStrategy.hasActiveConnectionChanges()) { + return fallbackStep.evaluate(bindings); + } try { LmdbEvaluationDataset dataset = resolveDataset(); + if (fallbackStep != null && dataset.hasTransactionChanges()) { + return fallbackStep.evaluate(bindings); + } ValueStore valueStore = dataset.getValueStore(); RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java new file mode 100644 index 0000000000..4d10342514 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import org.eclipse.rdf4j.common.iteration.LookAheadIteration; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +public class LmdbIdMergeJoinIterator extends LookAheadIteration { + + private final PeekMarkRecordIterator leftIterator; + private final PeekMarkRecordIterator rightIterator; + private final LmdbIdJoinIterator.PatternInfo leftInfo; + private final LmdbIdJoinIterator.PatternInfo rightInfo; + private final String mergeVariable; + private final QueryEvaluationContext context; + private final BindingSet initialBindings; + private final ValueStore valueStore; + + private long[] currentLeftRecord; + private MutableBindingSet currentLeftBinding; + private long currentLeftKey; + private boolean hasCurrentLeftKey; + + private long[] leftPeekRecord; + private long leftPeekKey; + private boolean hasLeftPeekKey; + private int currentLeftValueAndPeekEquals = -1; + + public LmdbIdMergeJoinIterator(RecordIterator leftIterator, RecordIterator rightIterator, + LmdbIdJoinIterator.PatternInfo leftInfo, LmdbIdJoinIterator.PatternInfo rightInfo, String mergeVariable, + QueryEvaluationContext context, BindingSet initialBindings, ValueStore valueStore) { + this.leftIterator = new PeekMarkRecordIterator(leftIterator); + this.rightIterator = new PeekMarkRecordIterator(rightIterator); + this.leftInfo = leftInfo; + this.rightInfo = rightInfo; + this.mergeVariable = mergeVariable; + this.context = context; + this.initialBindings = initialBindings; + this.valueStore = valueStore; + + if (mergeVariable == null || mergeVariable.isEmpty()) { + throw new IllegalArgumentException("Merge variable must be provided for LMDB merge join"); + } + if (leftInfo.getRecordIndex(mergeVariable) < 0 || rightInfo.getRecordIndex(mergeVariable) < 0) { + throw new IllegalArgumentException("Merge variable " + mergeVariable + + " must be present in both join operands for LMDB merge join"); + } + } + + @Override + protected BindingSet getNextElement() throws QueryEvaluationException { + if (!ensureCurrentLeft()) { + return null; + } + + while (true) { + if (!rightIterator.hasNext()) { + if (rightIterator.isResettable() && leftIterator.hasNext()) { + rightIterator.reset(); + if (!advanceLeft()) { + return null; + } + continue; + } + return null; + } + + long[] rightPeek = rightIterator.peek(); + if (rightPeek == null) { + return null; + } + + int compare = compare(currentLeftKey, key(rightInfo, rightPeek)); + if (compare == 0) { + BindingSet result = equal(); + if (result != null) { + return result; + } + } else if (compare < 0) { + if (leftIterator.hasNext()) { + if (!lessThan()) { + return null; + } + } else { + return null; + } + } else { + rightIterator.next(); + } + } + } + + private boolean ensureCurrentLeft() throws QueryEvaluationException { + if (currentLeftRecord != null) { + return true; + } + return advanceLeft(); + } + + private boolean advanceLeft() throws QueryEvaluationException { + leftPeekRecord = null; + hasLeftPeekKey = false; + currentLeftValueAndPeekEquals = -1; + + while (leftIterator.hasNext()) { + long[] candidate = leftIterator.next(); + MutableBindingSet binding = context.createBindingSet(initialBindings); + if (!leftInfo.applyRecord(candidate, binding, valueStore)) { + continue; + } + currentLeftRecord = candidate; + currentLeftBinding = binding; + currentLeftKey = key(leftInfo, candidate); + hasCurrentLeftKey = true; + return true; + } + + currentLeftRecord = null; + currentLeftBinding = null; + hasCurrentLeftKey = false; + return false; + } + + private boolean lessThan() throws QueryEvaluationException { + long previous = hasCurrentLeftKey ? currentLeftKey : Long.MIN_VALUE; + if (!advanceLeft()) { + return false; + } + if (hasCurrentLeftKey && previous == currentLeftKey) { + if (rightIterator.isResettable()) { + rightIterator.reset(); + } + } else { + rightIterator.unmark(); + } + return true; + } + + private BindingSet equal() throws QueryEvaluationException { + while (rightIterator.hasNext()) { + if (rightIterator.isResettable()) { + BindingSet result = joinWithCurrentLeft(rightIterator.next()); + if (result != null) { + return result; + } + } else { + doLeftPeek(); + if (currentLeftValueAndPeekEquals == 0 && !rightIterator.isMarked()) { + rightIterator.mark(); + } + BindingSet result = joinWithCurrentLeft(rightIterator.next()); + if (result != null) { + return result; + } + } + } + return null; + } + + private void doLeftPeek() throws QueryEvaluationException { + if (leftPeekRecord == null) { + leftPeekRecord = leftIterator.peek(); + if (leftPeekRecord != null) { + leftPeekKey = key(leftInfo, leftPeekRecord); + hasLeftPeekKey = true; + } else { + hasLeftPeekKey = false; + } + currentLeftValueAndPeekEquals = -1; + } + + if (currentLeftValueAndPeekEquals == -1) { + boolean equals = hasLeftPeekKey && hasCurrentLeftKey && currentLeftKey == leftPeekKey; + currentLeftValueAndPeekEquals = equals ? 0 : 1; + } + } + + private BindingSet joinWithCurrentLeft(long[] rightRecord) throws QueryEvaluationException { + MutableBindingSet result = context.createBindingSet(currentLeftBinding); + if (rightInfo.applyRecord(rightRecord, result, valueStore)) { + return result; + } + return null; + } + + private int compare(long left, long right) { + return Long.compare(left, right); + } + + private long key(LmdbIdJoinIterator.PatternInfo info, long[] record) throws QueryEvaluationException { + long id = info.getId(record, mergeVariable); + if (id == LmdbValue.UNKNOWN_ID) { + throw new QueryEvaluationException( + "Merge variable " + mergeVariable + " is unbound in the current record; cannot perform merge join"); + } + return id; + } + + @Override + protected void handleClose() throws QueryEvaluationException { + try { + leftIterator.close(); + } finally { + rightIterator.close(); + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java new file mode 100644 index 0000000000..dbc1e98f1c --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -0,0 +1,362 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Optional; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; +import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.TripleStore; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +/** + * Query evaluation step that wires up the LMDB merge join iterator. + */ +public class LmdbIdMergeJoinQueryEvaluationStep implements QueryEvaluationStep { + + private final PatternPlan leftPlan; + private final PatternPlan rightPlan; + private final IdBindingInfo bindingInfo; + private final QueryEvaluationContext context; + private final LmdbDatasetContext datasetContext; + private final Join join; + private final LmdbIdJoinIterator.PatternInfo leftInfo; + private final LmdbIdJoinIterator.PatternInfo rightInfo; + private final String mergeVariable; + private final QueryEvaluationStep fallbackStep; + private final boolean hasInvalidPattern; + private final String fallbackAlgorithmName; + + public LmdbIdMergeJoinQueryEvaluationStep(Join join, QueryEvaluationContext context, + QueryEvaluationStep fallbackStep) { + if (!(join.getLeftArg() instanceof StatementPattern) || !(join.getRightArg() instanceof StatementPattern)) { + throw new IllegalArgumentException("LMDB merge join requires StatementPattern operands"); + } + if (!(context instanceof LmdbDatasetContext)) { + throw new IllegalArgumentException("LMDB merge join requires LMDB query evaluation context"); + } + if (!join.isMergeJoin()) { + throw new IllegalArgumentException("Merge join flag must be set on the Join node"); + } + Var orderVar = join.getOrder(); + if (orderVar == null) { + throw new IllegalArgumentException("Merge join requires join order variable to be set"); + } + + this.context = context; + this.datasetContext = (LmdbDatasetContext) context; + this.join = join; + + StatementPattern leftPattern = (StatementPattern) join.getLeftArg(); + StatementPattern rightPattern = (StatementPattern) join.getRightArg(); + + this.mergeVariable = orderVar.getName(); + this.leftInfo = LmdbIdJoinIterator.PatternInfo.create(leftPattern); + this.rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPattern); + this.fallbackStep = fallbackStep; + this.fallbackAlgorithmName = fallbackStep != null ? join.getAlgorithmName() : null; + + ValueStore valueStore = this.datasetContext.getValueStore() + .orElseThrow(() -> new IllegalStateException("LMDB merge join requires ValueStore access")); + + RawPattern leftRaw = RawPattern.create(leftPattern, valueStore); + RawPattern rightRaw = RawPattern.create(rightPattern, valueStore); + this.hasInvalidPattern = leftRaw.invalid || rightRaw.invalid; + + IdBindingInfo info = IdBindingInfo.fromFirstPattern(leftInfo); + info = IdBindingInfo.combine(info, rightInfo); + this.bindingInfo = info; + + this.leftPlan = leftRaw.toPlan(info, leftPattern.getStatementOrder()); + this.rightPlan = rightRaw.toPlan(info, rightPattern.getStatementOrder()); + } + + @Override + public CloseableIteration evaluate(BindingSet bindings) { + if (fallbackStep != null && LmdbEvaluationStrategy.hasActiveConnectionChanges()) { + return fallbackStep.evaluate(bindings); + } + try { + LmdbEvaluationDataset dataset = resolveDataset(); + if (fallbackStep != null && dataset.hasTransactionChanges()) { + return fallbackStep.evaluate(bindings); + } + if (hasInvalidPattern) { + return new EmptyIteration<>(); + } + if (leftPlan.order == null || rightPlan.order == null) { + return evaluateFallback(bindings); + } + + ValueStore valueStore = dataset.getValueStore(); + long[] initialBinding = createInitialBinding(bindingInfo, bindings, valueStore); + if (initialBinding == null) { + return new EmptyIteration<>(); + } + + if (!dataset.supportsOrder(initialBinding, leftPlan.subjIndex, leftPlan.predIndex, leftPlan.objIndex, + leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order) + || !dataset.supportsOrder(initialBinding, rightPlan.subjIndex, rightPlan.predIndex, + rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order)) { + return evaluateFallback(bindings); + } + + RecordIterator leftIterator = dataset.getOrderedRecordIterator(initialBinding, leftPlan.subjIndex, + leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order); + if (leftIterator == null) { + return evaluateFallback(bindings); + } + + RecordIterator rightIterator = dataset.getOrderedRecordIterator(initialBinding, rightPlan.subjIndex, + rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order); + if (rightIterator == null) { + try { + leftIterator.close(); + } catch (Exception ignore) { + } + return evaluateFallback(bindings); + } + + join.setAlgorithm(LmdbIdMergeJoinIterator.class.getSimpleName()); + return new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, mergeVariable, context, + bindings, valueStore); + } catch (QueryEvaluationException e) { + throw e; + } + } + + private CloseableIteration evaluateFallback(BindingSet bindings) { + if (fallbackAlgorithmName != null) { + join.setAlgorithm(fallbackAlgorithmName); + } + if (fallbackStep != null) { + return fallbackStep.evaluate(bindings); + } + return new EmptyIteration<>(); + } + + private LmdbEvaluationDataset resolveDataset() { + Optional fromContext = datasetContext.getLmdbDataset(); + if (fromContext.isPresent()) { + return fromContext.get(); + } + return LmdbEvaluationStrategy.getCurrentDataset() + .orElseThrow( + () -> new IllegalStateException("No active LMDB dataset available for merge join evaluation")); + } + + private static long[] createInitialBinding(IdBindingInfo info, BindingSet bindings, ValueStore valueStore) + throws QueryEvaluationException { + long[] binding = new long[info.size()]; + Arrays.fill(binding, LmdbValue.UNKNOWN_ID); + if (bindings == null || bindings.isEmpty()) { + return binding; + } + for (String name : info.getVariableNames()) { + Value value = bindings.getValue(name); + if (value == null) { + continue; + } + long id = resolveId(valueStore, value); + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + int index = info.getIndex(name); + if (index >= 0) { + binding[index] = id; + } + } + return binding; + } + + private static long resolveId(ValueStore valueStore, Value value) throws QueryEvaluationException { + if (value == null) { + return LmdbValue.UNKNOWN_ID; + } + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != LmdbValue.UNKNOWN_ID) { + return id; + } + } + } + try { + long id = valueStore.getId(value); + return id; + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } + + private static final class PatternPlan { + private final long[] patternIds; + private final int subjIndex; + private final int predIndex; + private final int objIndex; + private final int ctxIndex; + private final StatementOrder order; + + private PatternPlan(long[] patternIds, int subjIndex, int predIndex, int objIndex, int ctxIndex, + StatementOrder order) { + this.patternIds = patternIds; + this.subjIndex = subjIndex; + this.predIndex = predIndex; + this.objIndex = objIndex; + this.ctxIndex = ctxIndex; + this.order = order; + } + } + + private static final class RawPattern { + private final long[] patternIds; + private final String subjVar; + private final String predVar; + private final String objVar; + private final String ctxVar; + private final boolean invalid; + + private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, + boolean invalid) { + this.patternIds = patternIds; + this.subjVar = subjVar; + this.predVar = predVar; + this.objVar = objVar; + this.ctxVar = ctxVar; + this.invalid = invalid; + } + + static RawPattern create(StatementPattern pattern, ValueStore valueStore) { + long[] ids = new long[4]; + Arrays.fill(ids, LmdbValue.UNKNOWN_ID); + boolean invalid = false; + + Var subj = pattern.getSubjectVar(); + String subjVar = null; + if (subj != null) { + if (subj.hasValue()) { + long id = constantId(valueStore, subj.getValue(), true, false); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.SUBJ_IDX] = id; + } + } else { + subjVar = subj.getName(); + } + } + + Var pred = pattern.getPredicateVar(); + String predVar = null; + if (pred != null) { + if (pred.hasValue()) { + long id = constantId(valueStore, pred.getValue(), false, true); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.PRED_IDX] = id; + } + } else { + predVar = pred.getName(); + } + } + + Var obj = pattern.getObjectVar(); + String objVar = null; + if (obj != null) { + if (obj.hasValue()) { + long id = constantId(valueStore, obj.getValue(), false, false); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.OBJ_IDX] = id; + } + } else { + objVar = obj.getName(); + } + } + + Var ctx = pattern.getContextVar(); + String ctxVar = null; + if (ctx != null) { + if (ctx.hasValue()) { + long id = constantId(valueStore, ctx.getValue(), true, false); + if (id == Long.MIN_VALUE) { + invalid = true; + } else { + ids[TripleStore.CONTEXT_IDX] = id; + } + } else { + ctxVar = ctx.getName(); + } + } + + return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, invalid); + } + + PatternPlan toPlan(IdBindingInfo bindingInfo, StatementOrder order) { + return new PatternPlan(patternIds.clone(), indexFor(subjVar, bindingInfo), indexFor(predVar, bindingInfo), + indexFor(objVar, bindingInfo), indexFor(ctxVar, bindingInfo), order); + } + + private static int indexFor(String varName, IdBindingInfo info) { + return varName == null ? -1 : info.getIndex(varName); + } + + private static long constantId(ValueStore valueStore, Value value, boolean requireResource, + boolean requireIri) { + if (requireResource && !(value instanceof Resource)) { + return Long.MIN_VALUE; + } + if (requireIri && !(value instanceof IRI)) { + return Long.MIN_VALUE; + } + if (value instanceof Resource && ((Resource) value).isTriple()) { + return Long.MIN_VALUE; + } + try { + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != LmdbValue.UNKNOWN_ID) { + return id; + } + } + } + long id = valueStore.getId(value); + return id == LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; + } catch (IOException e) { + return Long.MIN_VALUE; + } + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/PeekMarkRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/PeekMarkRecordIterator.java new file mode 100644 index 0000000000..61d9f227d8 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/PeekMarkRecordIterator.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; + +/** + * Peek/mark/reset wrapper for {@link RecordIterator}. Mirrors + * {@link org.eclipse.rdf4j.query.algebra.evaluation.iterator.PeekMarkIterator} but operates on primitive long[] + * records. + */ +class PeekMarkRecordIterator { + + private final RecordIterator iterator; + private boolean mark; + private List buffer; + private Iterator bufferIterator = Collections.emptyIterator(); + private long[] next; + + // -1: reset impossible, 0: available once, >0: buffered and ready + private int resetPossible; + + private boolean closed; + + PeekMarkRecordIterator(RecordIterator iterator) { + this.iterator = iterator; + } + + boolean hasNext() { + if (closed) { + return false; + } + calculateNext(); + return next != null; + } + + long[] next() { + if (closed) { + throw new NoSuchElementException("The iteration has been closed."); + } + calculateNext(); + long[] result = next; + next = null; + if (!mark && resetPossible == 0) { + resetPossible--; + } + if (result == null) { + throw new NoSuchElementException(); + } + return result; + } + + long[] peek() { + if (closed) { + return null; + } + calculateNext(); + return next; + } + + void mark() { + if (closed) { + throw new IllegalStateException("The iteration has been closed."); + } + mark = true; + resetPossible = 1; + + if (buffer != null && !bufferIterator.hasNext()) { + buffer.clear(); + bufferIterator = Collections.emptyIterator(); + } else { + buffer = new ArrayList<>(); + } + + if (next != null) { + buffer.add(next); + } + } + + void reset() { + if (closed) { + throw new IllegalStateException("The iteration has been closed."); + } + 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()); + } + } + + if (resetPossible == 0) { + assert !mark; + if (next != null) { + buffer.add(next); + next = null; + } + bufferIterator = buffer.iterator(); + } else if (resetPossible > 0) { + next = null; + bufferIterator = buffer.iterator(); + } + + mark = false; + resetPossible = 1; + } + + boolean isMarked() { + return !closed && mark; + } + + boolean isResettable() { + return !closed && (mark || resetPossible >= 0); + } + + void unmark() { + mark = false; + resetPossible = -1; + if (bufferIterator.hasNext()) { + buffer = null; + bufferIterator = Collections.emptyIterator(); + } else if (buffer != null) { + buffer.clear(); + bufferIterator = Collections.emptyIterator(); + } + } + + void close() { + if (!closed) { + closed = true; + buffer = null; + bufferIterator = Collections.emptyIterator(); + next = null; + try { + iterator.close(); + } catch (Exception ignore) { + // RecordIterator#close does not declare checked exceptions + } + } + } + + private void calculateNext() { + if (next != null || closed) { + return; + } + + if (bufferIterator.hasNext()) { + next = bufferIterator.next(); + } else { + if (!mark && resetPossible > -1) { + resetPossible--; + } + if (iterator != null) { + long[] candidate = iterator.next(); + if (candidate != null) { + next = Arrays.copyOf(candidate, candidate.length); + } + } + } + + if (mark && next != null) { + assert resetPossible > 0; + buffer.add(next); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java index 890c36e1db..51d309d7b6 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java @@ -16,7 +16,6 @@ import java.util.List; import org.eclipse.rdf4j.common.iteration.CloseableIteration; -import org.eclipse.rdf4j.common.iteration.EmptyIteration; import org.eclipse.rdf4j.common.iteration.SingletonIteration; import org.eclipse.rdf4j.common.iteration.UnionIteration; import org.eclipse.rdf4j.common.transaction.IsolationLevels; @@ -27,6 +26,7 @@ import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.QueryLanguage; import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.TupleExpr; @@ -37,8 +37,6 @@ import org.eclipse.rdf4j.query.parser.QueryParserUtil; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; -import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; -import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.base.SailDataset; import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; import org.eclipse.rdf4j.sail.base.SailSource; @@ -149,7 +147,7 @@ public java.util.Comparator getComparator() { // Simulate a thread-local dataset reference that points to the baseline dataset LmdbEvaluationStrategy.setCurrentDataset(threadLocal); try { - LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(join, patterns, ctx); + LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(join, patterns, ctx, null); try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { List results = org.eclipse.rdf4j.common.iteration.Iterations.asList(iter); // We expect 1 result because the overlay supplies the missing 'likes' triple. @@ -164,4 +162,119 @@ public java.util.Comparator getComparator() { repository.shutDown(); } } + + @Test + public void bgpUsesIdArrayIterator(@TempDir java.nio.file.Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr current = tupleExpr; + if (current instanceof org.eclipse.rdf4j.query.algebra.QueryRoot) { + current = ((org.eclipse.rdf4j.query.algebra.QueryRoot) current).getArg(); + } + if (current instanceof org.eclipse.rdf4j.query.algebra.Projection) { + current = ((org.eclipse.rdf4j.query.algebra.Projection) current).getArg(); + } + if (!(current instanceof Join)) { + throw new AssertionError("expected Join at root of algebra"); + } + Join join = (Join) current; + + List patterns = new ArrayList<>(); + boolean flattened = LmdbIdBGPQueryEvaluationStep.flattenBGP(join, patterns); + assertThat(flattened).isTrue(); + assertThat(patterns).hasSize(2); + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + + try { + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + RecordingDataset recordingDataset = new RecordingDataset(lmdbDataset); + + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + + QueryEvaluationContext ctx = new LmdbQueryEvaluationContext(null, tripleSource.getValueFactory(), + tripleSource.getComparator(), recordingDataset, lmdbDataset.getValueStore()); + + LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(join, patterns, ctx, null); + + try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { + List results = org.eclipse.rdf4j.common.iteration.Iterations.asList(iter); + assertThat(results).hasSize(1); + } + + assertThat(recordingDataset.wasLegacyApiUsed()).isFalse(); + assertThat(recordingDataset.wasArrayApiUsed()).isTrue(); + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + + private static final class RecordingDataset implements LmdbEvaluationDataset { + private final LmdbEvaluationDataset delegate; + + RecordingDataset(LmdbEvaluationDataset delegate) { + this.delegate = delegate; + } + + @Override + public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.StatementPattern pattern, + BindingSet bindings) { + legacyApiUsed = true; + return delegate.getRecordIterator(pattern, bindings); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, + long[] patternIds) throws QueryEvaluationException { + arrayApiUsed = true; + return delegate.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + + boolean wasLegacyApiUsed() { + return legacyApiUsed; + } + + boolean wasArrayApiUsed() { + return arrayApiUsed; + } + + @Override + public ValueStore getValueStore() { + return delegate.getValueStore(); + } + + @Override + public boolean hasTransactionChanges() { + return delegate.hasTransactionChanges(); + } + + private boolean legacyApiUsed; + private boolean arrayApiUsed; + } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java index 055c859566..238b26767f 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPJoinTest.java @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.Iterations; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.ValueFactory; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java index da973a91ec..5e31fcdf76 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinDisableOnChangesTest.java @@ -12,11 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.ArrayList; -import java.util.List; - import org.eclipse.rdf4j.common.iteration.CloseableIteration; -import org.eclipse.rdf4j.common.iteration.EmptyIteration; import org.eclipse.rdf4j.common.iteration.SingletonIteration; import org.eclipse.rdf4j.common.iteration.UnionIteration; import org.eclipse.rdf4j.model.IRI; @@ -25,7 +21,6 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; -import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryLanguage; import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.Projection; @@ -34,13 +29,10 @@ import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; -import org.eclipse.rdf4j.query.impl.EmptyBindingSet; import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; import org.eclipse.rdf4j.query.parser.QueryParserUtil; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; -import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; -import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.base.SailDataset; import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; import org.eclipse.rdf4j.sail.base.SailSource; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java index 670af3e720..7d4da17527 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java @@ -15,16 +15,19 @@ import static org.mockito.Mockito.mock; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.nio.file.Path; import java.util.List; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.QueryLanguage; import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.Projection; @@ -35,6 +38,7 @@ import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; import org.eclipse.rdf4j.query.explanation.Explanation; import org.eclipse.rdf4j.query.impl.EmptyBindingSet; @@ -109,6 +113,226 @@ public void simpleJoinUsesIdIterator(@TempDir Path tempDir) throws Exception { } } + @Test + public void mergeJoinRequestsLmdbMergeIterator(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI carol = vf.createIRI(NS, "carol"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + IRI salad = vf.createIRI(NS, "salad"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + conn.add(alice, likes, salad); + conn.add(bob, knows, carol); + conn.add(bob, likes, salad); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + + TupleExpr joinExpr = unwrap(tupleExpr); + assertThat(joinExpr).isInstanceOf(Join.class); + Join join = (Join) joinExpr; + StatementPattern left = (StatementPattern) join.getLeftArg(); + join.setOrder(left.getSubjectVar()); + join.setMergeJoin(true); + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + try { + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, tripleSource, + store.getBackingStore().getEvaluationStatistics()); + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + QueryEvaluationContext context = new LmdbQueryEvaluationContext(null, + tripleSource.getValueFactory(), tripleSource.getComparator(), lmdbDataset, + lmdbDataset.getValueStore()); + + QueryEvaluationStep step = strategy.precompile(join, context); + try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { + List bindings = Iterations.asList(iter); + assertThat(bindings).hasSize(3); + } + + assertThat(join.isMergeJoin()).isTrue(); + assertThat(join.getAlgorithmName()) + .withFailMessage("left=%s right=%s", join.getLeftArg().getClass(), join.getRightArg().getClass()) + .isEqualTo("LmdbIdMergeJoinIterator"); + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + + @Test + public void mergeJoinUsesArrayDatasetApi(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI carol = vf.createIRI(NS, "carol"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + IRI salad = vf.createIRI(NS, "salad"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + conn.add(alice, likes, salad); + conn.add(bob, knows, carol); + conn.add(bob, likes, salad); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person ?other .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr joinExpr = unwrap(tupleExpr); + assertThat(joinExpr).isInstanceOf(Join.class); + Join join = (Join) joinExpr; + StatementPattern left = (StatementPattern) join.getLeftArg(); + join.setOrder(left.getSubjectVar()); + join.setMergeJoin(true); + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + + try { + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, tripleSource, + store.getBackingStore().getEvaluationStatistics()); + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + RecordingDataset recordingDataset = new RecordingDataset(lmdbDataset); + QueryEvaluationContext context = new LmdbQueryEvaluationContext(null, tripleSource.getValueFactory(), + tripleSource.getComparator(), recordingDataset, lmdbDataset.getValueStore()); + + QueryEvaluationStep step = strategy.precompile(join, context); + try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { + List bindings = Iterations.asList(iter); + assertThat(bindings).hasSize(3); + } + + assertThat(recordingDataset.wasLegacyOrderedApiUsed()).isFalse(); + assertThat(recordingDataset.wasArrayOrderedApiUsed()).isTrue(); + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + + @Test + public void mergeJoinFallsBackWhenOrderUnsupported(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI ben = vf.createIRI(NS, "ben"); + IRI carol = vf.createIRI(NS, "carol"); + IRI dana = vf.createIRI(NS, "dana"); + IRI erin = vf.createIRI(NS, "erin"); + IRI frank = vf.createIRI(NS, "frank"); + IRI george = vf.createIRI(NS, "george"); + IRI hannah = vf.createIRI(NS, "hannah"); + IRI knows = vf.createIRI(NS, "knows"); + IRI mentors = vf.createIRI(NS, "mentors"); + IRI admires = vf.createIRI(NS, "admires"); + IRI trusts = vf.createIRI(NS, "trusts"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, ben); + conn.add(alice, mentors, ben); + conn.add(carol, knows, dana); + conn.add(carol, mentors, dana); + conn.add(erin, admires, ben); + conn.add(george, trusts, ben); + conn.add(frank, admires, dana); + conn.add(hannah, trusts, dana); + } + + String query = "SELECT ?friend ?person ?fan\n" + + "WHERE {\n" + + " ?person ?predicate ?friend .\n" + + " ?fan ?fanPredicate ?friend .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr joinExpr = unwrap(tupleExpr); + assertThat(joinExpr).isInstanceOf(Join.class); + Join join = (Join) joinExpr; + + StatementPattern left = (StatementPattern) join.getLeftArg(); + StatementPattern right = (StatementPattern) join.getRightArg(); + Var joinVar = left.getObjectVar(); + join.setOrder(joinVar); + left.setOrder(joinVar); + right.setOrder(right.getObjectVar()); + join.setMergeJoin(true); + assertThat(left.getStatementOrder()).isEqualTo(StatementOrder.O); + assertThat(right.getStatementOrder()).isEqualTo(StatementOrder.O); + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + + try { + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, tripleSource, + store.getBackingStore().getEvaluationStatistics()); + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + Method chooser = lmdbDataset.getClass() + .getDeclaredMethod("chooseIndexForOrder", StatementOrder.class, + long.class, long.class, long.class, long.class); + chooser.setAccessible(true); + Object chosen = chooser.invoke(lmdbDataset, StatementOrder.O, -1L, -1L, -1L, -1L); + assertThat(chosen).isNull(); + QueryEvaluationContext context = new LmdbQueryEvaluationContext(null, tripleSource.getValueFactory(), + tripleSource.getComparator(), lmdbDataset, lmdbDataset.getValueStore()); + + QueryEvaluationStep step = strategy.precompile(join, context); + try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { + List bindings = Iterations.asList(iter); + assertThat(bindings).hasSize(20); + } + + assertThat(join.getAlgorithmName()).isNotEqualTo("LmdbIdMergeJoinIterator"); + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + @Test public void nonStatementPatternJoinRejected() { Join join = new Join(new SingletonSet(), @@ -117,7 +341,7 @@ public void nonStatementPatternJoinRejected() { EvaluationStrategy strategy = mock(EvaluationStrategy.class); QueryEvaluationContext context = new QueryEvaluationContext.Minimal(null); - assertThatThrownBy(() -> new LmdbIdJoinQueryEvaluationStep(strategy, join, context)) + assertThatThrownBy(() -> new LmdbIdJoinQueryEvaluationStep(strategy, join, context, null)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("StatementPattern"); } @@ -164,7 +388,7 @@ public void joinUsesRecordIteratorsForLeftSide(@TempDir Path tempDir) throws Exc tripleSource.getValueFactory(), tripleSource.getComparator(), lmdbDataset, lmdbDataset.getValueStore()); - LmdbIdJoinQueryEvaluationStep step = new LmdbIdJoinQueryEvaluationStep(strategy, join, context); + LmdbIdJoinQueryEvaluationStep step = new LmdbIdJoinQueryEvaluationStep(strategy, join, context, null); try (CloseableIteration iteration = step.evaluate(EmptyBindingSet.getInstance())) { Class iteratorClass = iteration.getClass(); @@ -195,4 +419,59 @@ private TupleExpr unwrap(TupleExpr tupleExpr) { } return current; } + + private static final class RecordingDataset implements LmdbEvaluationDataset { + private final LmdbEvaluationDataset delegate; + private boolean legacyOrderedApiUsed; + private boolean arrayOrderedApiUsed; + + private RecordingDataset(LmdbEvaluationDataset delegate) { + this.delegate = delegate; + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) + throws QueryEvaluationException { + return delegate.getRecordIterator(pattern, bindings); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return delegate.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + + @Override + public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, + StatementOrder order) throws QueryEvaluationException { + legacyOrderedApiUsed = true; + return delegate.getOrderedRecordIterator(pattern, bindings, order); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + arrayOrderedApiUsed = true; + return delegate.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, + order); + } + + boolean wasLegacyOrderedApiUsed() { + return legacyOrderedApiUsed; + } + + boolean wasArrayOrderedApiUsed() { + return arrayOrderedApiUsed; + } + + @Override + public ValueStore getValueStore() { + return delegate.getValueStore(); + } + + @Override + public boolean hasTransactionChanges() { + return delegate.hasTransactionChanges(); + } + } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java index 57b2415a89..37e60965c0 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java @@ -33,7 +33,6 @@ import org.eclipse.rdf4j.query.impl.EmptyBindingSet; import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; import org.eclipse.rdf4j.query.parser.QueryParserUtil; -import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.eclipse.rdf4j.sail.SailConnection; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnectionExceptionFlagTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnectionExceptionFlagTest.java new file mode 100644 index 0000000000..3dad6c0e62 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnectionExceptionFlagTest.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.file.Path; +import java.util.function.Supplier; + +import org.eclipse.rdf4j.collection.factory.api.CollectionFactory; +import org.eclipse.rdf4j.collection.factory.impl.DefaultCollectionFactory; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.sail.SailException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies that {@link LmdbStoreConnection#evaluateInternal(TupleExpr, Dataset, BindingSet, boolean)} clears the + * connection-change flag when the delegate throws a {@link QueryEvaluationException}. + */ +public class LmdbStoreConnectionExceptionFlagTest { + + private static final String NS = "http://example.com/"; + + @Test + public void popsFlagOnQueryEvaluationException(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + store.setEvaluationStrategyFactory(new AlwaysThrowingEvaluationStrategyFactory()); + store.init(); + + try (LmdbStoreConnection connection = (LmdbStoreConnection) store.getConnection()) { + connection.begin(); + + ValueFactory vf = store.getValueFactory(); + connection.addStatement(vf.createIRI(NS, "alice"), vf.createIRI(NS, "knows"), vf.createIRI(NS, "bob")); + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, + "SELECT ?person WHERE { ?person ?p ?o }", null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + + assertFalse(LmdbEvaluationStrategy.hasActiveConnectionChanges(), + "precondition: no active thread-local flag"); + + SailException thrown = assertThrows(SailException.class, + () -> connection.evaluateInternal(tupleExpr, null, EmptyBindingSet.getInstance(), false)); + assertInstanceOf(QueryEvaluationException.class, thrown.getCause()); + + assertFalse(LmdbEvaluationStrategy.hasActiveConnectionChanges(), + "thread-local flag must be cleared after exception"); + + connection.rollback(); + } finally { + store.shutDown(); + } + } + + private static final class AlwaysThrowingEvaluationStrategyFactory extends LmdbEvaluationStrategyFactory { + + private Supplier collectionFactory = DefaultCollectionFactory::new; + + AlwaysThrowingEvaluationStrategyFactory() { + super(null); + } + + @Override + public void setCollectionFactory(Supplier collectionFactory) { + super.setCollectionFactory(collectionFactory); + this.collectionFactory = collectionFactory; + } + + @Override + public EvaluationStrategy createEvaluationStrategy(Dataset dataset, TripleSource tripleSource, + EvaluationStatistics evaluationStatistics) { + ThrowingEvaluationStrategy strategy = new ThrowingEvaluationStrategy(tripleSource, dataset, + getFederatedServiceResolver(), getQuerySolutionCacheThreshold(), evaluationStatistics, + isTrackResultSize()); + getOptimizerPipeline().ifPresent(strategy::setOptimizerPipeline); + strategy.setCollectionFactory(collectionFactory); + return strategy; + } + } + + private static final class ThrowingEvaluationStrategy extends LmdbEvaluationStrategy { + + ThrowingEvaluationStrategy(TripleSource tripleSource, Dataset dataset, FederatedServiceResolver resolver, + long iterationCacheSyncThreshold, EvaluationStatistics evaluationStatistics, boolean trackResultSize) { + super(tripleSource, dataset, resolver, iterationCacheSyncThreshold, evaluationStatistics, trackResultSize); + } + + @Override + public QueryEvaluationStep precompile(TupleExpr expr) { + throw new QueryEvaluationException("forced failure"); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java index 3bdccaef46..ad57199e09 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/SubjectPredicateIndexDistributionRegressionTest.java @@ -12,13 +12,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; import org.eclipse.rdf4j.common.iteration.Iterations; @@ -31,12 +29,9 @@ import org.eclipse.rdf4j.model.vocabulary.DCTERMS; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryResults; -import org.eclipse.rdf4j.repository.RepositoryResult; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.eclipse.rdf4j.rio.RDFFormat; -import org.eclipse.rdf4j.rio.RDFWriter; -import org.eclipse.rdf4j.rio.Rio; import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; import org.eclipse.rdf4j.sail.memory.MemoryStore; import org.junit.jupiter.api.Test; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/TransactionsPerSecondForceSyncBenchmark.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/TransactionsPerSecondForceSyncBenchmark.java index 94b2c42e09..53632e8a03 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/TransactionsPerSecondForceSyncBenchmark.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/TransactionsPerSecondForceSyncBenchmark.java @@ -11,28 +11,15 @@ package org.eclipse.rdf4j.sail.lmdb.benchmark; -import java.io.File; -import java.io.IOException; import java.util.concurrent.TimeUnit; -import org.apache.commons.io.FileUtils; -import org.assertj.core.util.Files; -import org.eclipse.rdf4j.common.transaction.IsolationLevels; -import org.eclipse.rdf4j.model.vocabulary.RDFS; -import org.eclipse.rdf4j.repository.sail.SailRepository; -import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; -import org.eclipse.rdf4j.sail.lmdb.LmdbStore; -import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; From e10d01f3a67ba2a61e6d6f8272b1c3bc9d92b94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 27 Oct 2025 22:53:31 +0900 Subject: [PATCH 38/79] working on new ID based join iterator --- .../sail/lmdb/LmdbEvaluationStrategy.java | 40 +++++- .../rdf4j/sail/lmdb/LmdbSailStore.java | 64 ++++++++- .../eclipse/rdf4j/sail/lmdb/ValueStore.java | 6 + .../join/LmdbIdBGPQueryEvaluationStep.java | 123 ++++++++++++++---- .../join/LmdbIdJoinQueryEvaluationStep.java | 86 +++++++++++- .../LmdbIdMergeJoinQueryEvaluationStep.java | 50 +++++-- .../sail/lmdb/LmdbIdJoinIsolationTest.java | 2 + 7 files changed, 333 insertions(+), 38 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java index bb552dc3da..7e8e3f0a4d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java @@ -87,11 +87,47 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) List patterns = new ArrayList<>(); if (LmdbIdBGPQueryEvaluationStep.flattenBGP(node, patterns) && !patterns.isEmpty()) { - return new LmdbIdBGPQueryEvaluationStep(node, patterns, context, defaultStep); + LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(node, patterns, context, + defaultStep); + if (step.shouldUseFallbackImmediately()) { + Optional valueStoreOpt = ((LmdbDatasetContext) context).getValueStore(); + if (valueStoreOpt.isPresent()) { + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + valueStoreOpt.get()); + QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, + overlay, + valueStoreOpt.get()); + LmdbIdBGPQueryEvaluationStep overlayStep = new LmdbIdBGPQueryEvaluationStep(node, patterns, + overlayContext, defaultStep); + overlayStep.applyAlgorithmTag(); + return overlayStep; + } + return defaultStep; + } + step.applyAlgorithmTag(); + return step; } // Fallback to two-pattern ID join if (node.getLeftArg() instanceof StatementPattern && node.getRightArg() instanceof StatementPattern) { - return new LmdbIdJoinQueryEvaluationStep(this, node, context, defaultStep); + LmdbIdJoinQueryEvaluationStep step = new LmdbIdJoinQueryEvaluationStep(this, node, context, + defaultStep); + if (step.shouldUseFallbackImmediately()) { + Optional valueStoreOpt = ((LmdbDatasetContext) context).getValueStore(); + if (valueStoreOpt.isPresent()) { + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + valueStoreOpt.get()); + QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, + overlay, + valueStoreOpt.get()); + LmdbIdJoinQueryEvaluationStep overlayStep = new LmdbIdJoinQueryEvaluationStep(this, node, + overlayContext, defaultStep); + overlayStep.applyAlgorithmTag(node); + return overlayStep; + } + return defaultStep; + } + step.applyAlgorithmTag(node); + return step; } } return defaultStep; 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 c63c447c1f..5ab5dfb6c6 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 @@ -1409,6 +1409,9 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + System.out + .println("DEBUG getRecordIterator(long[]) s=" + subjQuery + " p=" + predQuery + " o=" + objQuery + + " c=" + ctxQuery); RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); @@ -1460,6 +1463,15 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in if (orderedIter != null) { return orderedIter; } + + if (order == StatementOrder.S) { + int sortIndex = indexForOrder(order, subjIndex, predIndex, objIndex, ctxIndex); + if (sortIndex >= 0) { + RecordIterator fallback = getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds); + return sortedRecordIterator(fallback, sortIndex); + } + } return null; } catch (IOException e) { throw new QueryEvaluationException("Unable to create ordered LMDB record iterator", e); @@ -1477,7 +1489,10 @@ public boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int o long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); try { - return orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery) != null; + if (orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery) != null) { + return true; + } + return order == StatementOrder.S && indexForOrder(order, subjIndex, predIndex, objIndex, ctxIndex) >= 0; } catch (IOException e) { return false; } @@ -1678,6 +1693,7 @@ private boolean populateContext(org.eclipse.rdf4j.query.algebra.Var var, long[] return false; } long id = resolveId(ctx); + System.out.println("DEBUG populateContext value=" + ctx + " id=" + id); if (id == LmdbValue.UNKNOWN_ID) { return false; } @@ -1787,6 +1803,7 @@ private long resolveContextWithBindings(long patternId, String varName, BindingS return INVALID_ID; } long id = resolveId(ctx); + System.out.println("DEBUG resolveContextWithBindings var=" + varName + " id=" + id + " value=" + ctx); if (id == LmdbValue.UNKNOWN_ID) { return INVALID_ID; } @@ -1832,6 +1849,51 @@ private RecordIterator orderedRecordIterator(StatementOrder order, long subjID, return new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, explicit, txn); } + private int indexForOrder(StatementOrder order, int subjIndex, int predIndex, int objIndex, int ctxIndex) { + switch (order) { + case S: + return subjIndex; + case P: + return predIndex; + case O: + return objIndex; + case C: + return ctxIndex; + default: + return -1; + } + } + + private RecordIterator sortedRecordIterator(RecordIterator base, int sortIndex) + throws QueryEvaluationException { + java.util.List rows = new java.util.ArrayList<>(); + try { + long[] next; + while ((next = base.next()) != null) { + rows.add(next); + } + } finally { + base.close(); + } + rows.sort(java.util.Comparator.comparingLong(a -> a[sortIndex])); + + java.util.Iterator iterator = rows.iterator(); + return new RecordIterator() { + @Override + public long[] next() { + if (!iterator.hasNext()) { + return null; + } + return iterator.next(); + } + + @Override + public void close() { + // nothing to close + } + }; + } + private TripleStore.TripleIndex chooseIndexForOrder(StatementOrder order, long s, long p, long o, long c) throws IOException { // ensure metadata initialized 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 54a5152487..c0cb431a49 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 @@ -1279,6 +1279,12 @@ public long storeValue(Value value) throws IOException { return getId(value, true); } + public void refreshReadTxn() { + ReadTxn txn = threadLocalReadTxn.get(); + txn.close(); + threadLocalReadTxn.remove(); + } + /** * Computes a hash code for the supplied data. * diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index d20721fed7..d8a201f166 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -51,6 +51,8 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private final Join root; private final QueryEvaluationStep fallbackStep; private final boolean hasInvalidPattern; + private final boolean createdDynamicIds; + private final boolean allowCreateConstantIds; public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -65,14 +67,22 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, ValueStore valueStore = this.datasetContext.getValueStore() .orElseThrow(() -> new IllegalStateException("LMDB ID BGP join requires ValueStore access")); + boolean allowCreate = this.datasetContext.getLmdbDataset() + .map(LmdbEvaluationDataset::hasTransactionChanges) + .orElseGet(LmdbEvaluationStrategy::hasActiveConnectionChanges); + this.allowCreateConstantIds = allowCreate; + List rawPatterns = new ArrayList<>(patterns.size()); boolean invalid = false; + boolean created = false; for (StatementPattern pattern : patterns) { - RawPattern raw = RawPattern.create(pattern, valueStore); + RawPattern raw = RawPattern.create(pattern, valueStore, allowCreate); rawPatterns.add(raw); invalid |= raw.invalid; + created |= raw.createdIds; } this.hasInvalidPattern = invalid; + this.createdDynamicIds = created; if (rawPatterns.isEmpty()) { throw new IllegalArgumentException("Basic graph pattern must contain at least one statement pattern"); @@ -93,7 +103,13 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, planList.add(raw.toPlan(finalInfo)); } this.plans = planList; + } + + public boolean shouldUseFallbackImmediately() { + return hasInvalidPattern && fallbackStep != null && !allowCreateConstantIds; + } + public void applyAlgorithmTag() { markJoinTreeWithIdAlgorithm(root); } @@ -108,7 +124,15 @@ public CloseableIteration evaluate(BindingSet bindings) { return fallbackStep.evaluate(bindings); } if (hasInvalidPattern) { - return new EmptyIteration<>(); + if (fallbackStep != null) { + System.out.println("DEBUG BGP fallback: invalid pattern"); + } + return fallbackStep != null ? fallbackStep.evaluate(bindings) : new EmptyIteration<>(); + } + if (!dataset.hasTransactionChanges() && createdDynamicIds && fallbackStep != null + && !allowCreateConstantIds) { + System.out.println("DEBUG BGP fallback: dynamic id without transaction changes"); + return fallbackStep.evaluate(bindings); } ValueStore valueStore = dataset.getValueStore(); @@ -278,9 +302,10 @@ private static final class RawPattern { private final String ctxVar; private final LmdbIdJoinIterator.PatternInfo patternInfo; private final boolean invalid; + private final boolean createdIds; private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, - LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid) { + LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid, boolean createdIds) { this.patternIds = patternIds; this.subjVar = subjVar; this.predVar = predVar; @@ -288,23 +313,26 @@ private RawPattern(long[] patternIds, String subjVar, String predVar, String obj this.ctxVar = ctxVar; this.patternInfo = patternInfo; this.invalid = invalid; + this.createdIds = createdIds; } - static RawPattern create(StatementPattern pattern, ValueStore valueStore) { + static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolean allowCreate) { long[] ids = new long[4]; Arrays.fill(ids, LmdbValue.UNKNOWN_ID); boolean invalid = false; + boolean createdAny = false; Var subj = pattern.getSubjectVar(); String subjVar = null; if (subj != null) { if (subj.hasValue()) { - long id = constantId(valueStore, subj.getValue(), true, false); - if (id == Long.MIN_VALUE) { + ConstantIdResult result = constantId(valueStore, subj.getValue(), true, false, allowCreate); + if (result.isInvalid()) { invalid = true; } else { - ids[TripleStore.SUBJ_IDX] = id; + ids[TripleStore.SUBJ_IDX] = result.id; + createdAny |= result.created; } } else { subjVar = subj.getName(); @@ -315,11 +343,12 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore) { String predVar = null; if (pred != null) { if (pred.hasValue()) { - long id = constantId(valueStore, pred.getValue(), false, true); - if (id == Long.MIN_VALUE) { + ConstantIdResult result = constantId(valueStore, pred.getValue(), false, true, allowCreate); + if (result.isInvalid()) { invalid = true; } else { - ids[TripleStore.PRED_IDX] = id; + ids[TripleStore.PRED_IDX] = result.id; + createdAny |= result.created; } } else { predVar = pred.getName(); @@ -330,11 +359,12 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore) { String objVar = null; if (obj != null) { if (obj.hasValue()) { - long id = constantId(valueStore, obj.getValue(), false, false); - if (id == Long.MIN_VALUE) { + ConstantIdResult result = constantId(valueStore, obj.getValue(), false, false, allowCreate); + if (result.isInvalid()) { invalid = true; } else { - ids[TripleStore.OBJ_IDX] = id; + ids[TripleStore.OBJ_IDX] = result.id; + createdAny |= result.created; } } else { objVar = obj.getName(); @@ -345,11 +375,12 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore) { String ctxVar = null; if (ctx != null) { if (ctx.hasValue()) { - long id = constantId(valueStore, ctx.getValue(), true, false); - if (id == Long.MIN_VALUE) { + ConstantIdResult result = constantId(valueStore, ctx.getValue(), true, false, allowCreate); + if (result.isInvalid()) { invalid = true; } else { - ids[TripleStore.CONTEXT_IDX] = id; + ids[TripleStore.CONTEXT_IDX] = result.id; + createdAny |= result.created; } } else { ctxVar = ctx.getName(); @@ -357,7 +388,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore) { } LmdbIdJoinIterator.PatternInfo info = LmdbIdJoinIterator.PatternInfo.create(pattern); - return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, info, invalid); + return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, info, invalid, createdAny); } PatternPlan toPlan(IdBindingInfo finalInfo) { @@ -369,16 +400,16 @@ private static int indexFor(String varName, IdBindingInfo info) { return varName == null ? -1 : info.getIndex(varName); } - private static long constantId(ValueStore valueStore, Value value, boolean requireResource, - boolean requireIri) { + private static ConstantIdResult constantId(ValueStore valueStore, Value value, boolean requireResource, + boolean requireIri, boolean allowCreate) { if (requireResource && !(value instanceof Resource)) { - return Long.MIN_VALUE; + return ConstantIdResult.invalid(); } if (requireIri && !(value instanceof IRI)) { - return Long.MIN_VALUE; + return ConstantIdResult.invalid(); } if (value instanceof Resource && ((Resource) value).isTriple()) { - return Long.MIN_VALUE; + return ConstantIdResult.invalid(); } try { if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { @@ -386,14 +417,56 @@ private static long constantId(ValueStore valueStore, Value value, boolean requi if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { long id = lmdbValue.getInternalID(); if (id != LmdbValue.UNKNOWN_ID) { - return id; + return ConstantIdResult.existing(id); } } } long id = valueStore.getId(value); - return id == LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; + if (id == LmdbValue.UNKNOWN_ID && !allowCreate) { + valueStore.refreshReadTxn(); + id = valueStore.getId(value); + } + if (id == LmdbValue.UNKNOWN_ID) { + if (!allowCreate) { + return ConstantIdResult.invalid(); + } + id = valueStore.getId(value, true); + if (id == LmdbValue.UNKNOWN_ID) { + return ConstantIdResult.invalid(); + } + return ConstantIdResult.created(id); + } + return ConstantIdResult.existing(id); } catch (IOException e) { - return Long.MIN_VALUE; + return ConstantIdResult.invalid(); + } + } + + private static final class ConstantIdResult { + private final long id; + private final boolean created; + private final boolean valid; + + private ConstantIdResult(long id, boolean created, boolean valid) { + this.id = id; + this.created = created; + this.valid = valid; + } + + static ConstantIdResult invalid() { + return new ConstantIdResult(Long.MIN_VALUE, false, false); + } + + static ConstantIdResult existing(long id) { + return new ConstantIdResult(id, false, true); + } + + static ConstantIdResult created(long id) { + return new ConstantIdResult(id, true, true); + } + + boolean isInvalid() { + return !valid; } } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index b5c58dcdff..33edf7de00 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -10,12 +10,14 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.join; +import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Optional; import java.util.Set; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.MutableBindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; @@ -43,6 +45,7 @@ public class LmdbIdJoinQueryEvaluationStep implements QueryEvaluationStep { private final Set sharedVariables; private final LmdbDatasetContext datasetContext; private final QueryEvaluationStep fallbackStep; + private final boolean fallbackImmediately; public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -63,7 +66,13 @@ public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, Que this.sharedVariables = computeSharedVariables(leftInfo, rightInfo); this.fallbackStep = fallbackStep; - join.setAlgorithm(LmdbIdJoinIterator.class.getSimpleName()); + boolean allowCreate = this.datasetContext.getLmdbDataset() + .map(LmdbEvaluationDataset::hasTransactionChanges) + .orElse(LmdbEvaluationStrategy.hasActiveConnectionChanges()); + ValueStore valueStore = this.datasetContext.getValueStore().orElse(null); + this.fallbackImmediately = valueStore == null + || (!allowCreate && (!constantsResolvable(leftPattern, valueStore, allowCreate) + || !constantsResolvable(rightPattern, valueStore, allowCreate))); } @@ -74,11 +83,86 @@ private Set computeSharedVariables(LmdbIdJoinIterator.PatternInfo left, return Collections.unmodifiableSet(shared); } + public boolean shouldUseFallbackImmediately() { + return fallbackImmediately; + } + + public void applyAlgorithmTag(Join join) { + join.setAlgorithm(LmdbIdJoinIterator.class.getSimpleName()); + } + + private static boolean constantsResolvable(StatementPattern pattern, ValueStore valueStore, boolean allowCreate) { + try { + org.eclipse.rdf4j.query.algebra.Var subj = pattern.getSubjectVar(); + if (subj != null && subj.hasValue()) { + if (!resolveConstantId(valueStore, subj.getValue(), true, false, allowCreate)) { + return false; + } + } + org.eclipse.rdf4j.query.algebra.Var pred = pattern.getPredicateVar(); + if (pred != null && pred.hasValue()) { + if (!resolveConstantId(valueStore, pred.getValue(), false, true, allowCreate)) { + return false; + } + } + org.eclipse.rdf4j.query.algebra.Var obj = pattern.getObjectVar(); + if (obj != null && obj.hasValue()) { + if (!resolveConstantId(valueStore, obj.getValue(), false, false, allowCreate)) { + return false; + } + } + org.eclipse.rdf4j.query.algebra.Var ctx = pattern.getContextVar(); + if (ctx != null && ctx.hasValue()) { + if (!resolveConstantId(valueStore, ctx.getValue(), true, false, allowCreate)) { + return false; + } + } + return true; + } catch (IOException e) { + return false; + } + } + + private static boolean resolveConstantId(ValueStore valueStore, Value value, boolean requireResource, + boolean requireIri, boolean allowCreate) throws IOException { + if (requireResource && !(value instanceof org.eclipse.rdf4j.model.Resource)) { + return false; + } + if (requireIri && !(value instanceof org.eclipse.rdf4j.model.IRI)) { + return false; + } + if (value instanceof org.eclipse.rdf4j.model.Resource + && ((org.eclipse.rdf4j.model.Resource) value).isTriple()) { + return false; + } + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return true; + } + } + } + long id = valueStore.getId(value); + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID && !allowCreate) { + valueStore.refreshReadTxn(); + id = valueStore.getId(value); + } + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID && allowCreate) { + id = valueStore.getId(value, true); + } + return id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + } + @Override public CloseableIteration evaluate(BindingSet bindings) { if (fallbackStep != null && LmdbEvaluationStrategy.hasActiveConnectionChanges()) { return fallbackStep.evaluate(bindings); } + if (fallbackImmediately && fallbackStep != null) { + return fallbackStep.evaluate(bindings); + } try { LmdbEvaluationDataset dataset = resolveDataset(); if (fallbackStep != null && dataset.hasTransactionChanges()) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java index dbc1e98f1c..9edfec2484 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -83,6 +83,13 @@ public LmdbIdMergeJoinQueryEvaluationStep(Join join, QueryEvaluationContext cont this.fallbackStep = fallbackStep; this.fallbackAlgorithmName = fallbackStep != null ? join.getAlgorithmName() : null; + if (leftPattern.getStatementOrder() == null) { + leftPattern.setOrder(orderVar); + } + if (rightPattern.getStatementOrder() == null) { + rightPattern.setOrder(orderVar); + } + ValueStore valueStore = this.datasetContext.getValueStore() .orElseThrow(() -> new IllegalStateException("LMDB merge join requires ValueStore access")); @@ -94,8 +101,11 @@ public LmdbIdMergeJoinQueryEvaluationStep(Join join, QueryEvaluationContext cont info = IdBindingInfo.combine(info, rightInfo); this.bindingInfo = info; - this.leftPlan = leftRaw.toPlan(info, leftPattern.getStatementOrder()); - this.rightPlan = rightRaw.toPlan(info, rightPattern.getStatementOrder()); + StatementOrder leftOrder = determineOrder(leftPattern, leftInfo); + StatementOrder rightOrder = determineOrder(rightPattern, rightInfo); + + this.leftPlan = leftRaw.toPlan(info, leftOrder); + this.rightPlan = rightRaw.toPlan(info, rightOrder); } @Override @@ -121,13 +131,6 @@ public CloseableIteration evaluate(BindingSet bindings) { return new EmptyIteration<>(); } - if (!dataset.supportsOrder(initialBinding, leftPlan.subjIndex, leftPlan.predIndex, leftPlan.objIndex, - leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order) - || !dataset.supportsOrder(initialBinding, rightPlan.subjIndex, rightPlan.predIndex, - rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order)) { - return evaluateFallback(bindings); - } - RecordIterator leftIterator = dataset.getOrderedRecordIterator(initialBinding, leftPlan.subjIndex, leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order); if (leftIterator == null) { @@ -217,6 +220,32 @@ private static long resolveId(ValueStore valueStore, Value value) throws QueryEv } } + private StatementOrder determineOrder(StatementPattern pattern, LmdbIdJoinIterator.PatternInfo info) { + StatementOrder order = pattern.getStatementOrder(); + if (order != null) { + return order; + } + + if (mergeVariable == null) { + return null; + } + + int mask = info.getPositionsMask(mergeVariable); + if ((mask & (1 << TripleStore.SUBJ_IDX)) != 0) { + return StatementOrder.S; + } + if ((mask & (1 << TripleStore.PRED_IDX)) != 0) { + return StatementOrder.P; + } + if ((mask & (1 << TripleStore.OBJ_IDX)) != 0) { + return StatementOrder.O; + } + if ((mask & (1 << TripleStore.CONTEXT_IDX)) != 0) { + return StatementOrder.C; + } + return null; + } + private static final class PatternPlan { private final long[] patternIds; private final int subjIndex; @@ -353,6 +382,9 @@ private static long constantId(ValueStore valueStore, Value value, boolean requi } } long id = valueStore.getId(value); + if (id == LmdbValue.UNKNOWN_ID) { + id = valueStore.getId(value, true); + } return id == LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; } catch (IOException e) { return Long.MIN_VALUE; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java index 37e60965c0..5a1705c8b3 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java @@ -109,6 +109,7 @@ void joinReflectsCommittedChanges(IsolationLevel isolationLevel) throws Exceptio try (TupleQueryResult result = tupleQuery.evaluate()) { beforeClear = Iterations.asList(result); } + System.out.println("DEBUG beforeClear results=" + beforeClear); assertThat(beforeClear).hasSize(1); assertThat(beforeClear.get(0).getValue("person")).isEqualTo(alice); assertThat(beforeClear.get(0).getValue("liked")).isEqualTo(pizza); @@ -123,6 +124,7 @@ void joinReflectsCommittedChanges(IsolationLevel isolationLevel) throws Exceptio try (TupleQueryResult result = tupleQuery.evaluate()) { afterClear = Iterations.asList(result); } + System.out.println("DEBUG afterClear results=" + afterClear); assertThat(afterClear).isEmpty(); conn2.commit(); From 061534e2c66ccab918312eb58a536a59030385f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 28 Oct 2025 11:54:59 +0900 Subject: [PATCH 39/79] working on new ID based join iterator --- .../sail/helpers/SailUpdateExecutor.java | 7 +++ .../rdf4j/sail/base/SailSourceBranch.java | 22 ++++++- .../optimistic/SerializableTest.java | 46 +++++++------- .../repository/optimistic/SnapshotTest.java | 61 ++++++++++--------- 4 files changed, 83 insertions(+), 53 deletions(-) diff --git a/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/helpers/SailUpdateExecutor.java b/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/helpers/SailUpdateExecutor.java index edd0f0a812..24d7e7c139 100644 --- a/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/helpers/SailUpdateExecutor.java +++ b/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/helpers/SailUpdateExecutor.java @@ -443,9 +443,16 @@ protected void executeModify(Modify modify, UpdateContext uc, int maxExecutionTi whereClause = new QueryRoot(whereClause); } + long count = 0; + try (CloseableIteration sourceBindings = evaluateWhereClause( whereClause, uc, maxExecutionTime)) { while (sourceBindings.hasNext()) { + if (logger.isDebugEnabled()) { + if (++count % 1_000_000 == 0) { + logger.debug("Update query processed {} bindings", count); + } + } BindingSet sourceBinding = sourceBindings.next(); deleteBoundTriples(sourceBinding, modify.getDeleteExpr(), uc); diff --git a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java index 95ed831dc6..21466637fd 100644 --- a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java +++ b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java @@ -375,7 +375,27 @@ void preparedChangeset(Changeset changeset) { void merge(Changeset change) { try { - semaphore.lock(); + try { + boolean locked = semaphore.tryLock(10, TimeUnit.SECONDS); + if (!locked) { + logger.warn( + "Waiting 10 seconds on semaphore for merging changesets without acquiring the semaphore. This may be a deadlock situation. Attempting to acquire semaphore interruptibly."); + if (Thread.interrupted()) { + throw new InterruptedException(); + } + semaphore.lockInterruptibly(); + } + } catch (InterruptedException e) { + logger.warn("Interrupted while trying to merge changes. Retrying once.", e); + try { + semaphore.lockInterruptibly(); + } catch (InterruptedException ex) { + logger.error("Interrupted while trying to merge changes. Giving up!", e); + Thread.currentThread().interrupt(); + throw new SailException(ex); + } + } + pending.remove(change); if (isChanged(change)) { Changeset merged; diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java index 8889c6d39f..1909635c34 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java @@ -135,7 +135,7 @@ public void tearDown() { } } - @Test + @Test(timeout = 5000) public void test_independentPattern() { a.begin(level); b.begin(level); @@ -149,7 +149,7 @@ public void test_independentPattern() { assertEquals(2, size(b, null, RDF.TYPE, PAINTER, false)); } - @Test + @Test(timeout = 5000) public void test_safePattern() { a.begin(level); b.begin(level); @@ -160,7 +160,7 @@ public void test_safePattern() { b.commit(); } - @Test + @Test(timeout = 5000) public void testPrepare_safePattern() { a.begin(level); b.begin(level); @@ -171,7 +171,7 @@ public void testPrepare_safePattern() { b.prepare(); } - @Test + @Test(timeout = 5000) public void test_afterPattern() { a.begin(level); b.begin(level); @@ -183,7 +183,7 @@ public void test_afterPattern() { assertEquals(2, size(b, null, RDF.TYPE, PAINTER, false)); } - @Test + @Test(timeout = 5000) public void test_afterInsertDataPattern() { a.begin(level); b.begin(level); @@ -195,7 +195,7 @@ public void test_afterInsertDataPattern() { assertEquals(2, size(b, null, RDF.TYPE, PAINTER, false)); } - @Test + @Test(timeout = 5000) public void test_conflictPattern() { a.begin(level); b.begin(level); @@ -214,7 +214,7 @@ public void test_conflictPattern() { } } - @Test + @Test(timeout = 5000) public void testPrepare_conflictPattern() { a.begin(level); b.begin(level); @@ -233,7 +233,7 @@ public void testPrepare_conflictPattern() { } } - @Test + @Test(timeout = 5000) public void test_safeQuery() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -255,7 +255,7 @@ public void test_safeQuery() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeInsert() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -274,7 +274,7 @@ public void test_safeInsert() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -304,7 +304,7 @@ public void test_conflictQuery() { } } - @Test + @Test(timeout = 5000) public void test_conflictInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -332,7 +332,7 @@ public void test_conflictInsert() { assertEquals(0, size(a, null, RDF.TYPE, PAINTING, false)); } - @Test + @Test(timeout = 5000) public void test_safeOptionalQuery() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -356,7 +356,7 @@ public void test_safeOptionalQuery() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeOptionalInsert() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -375,7 +375,7 @@ public void test_safeOptionalInsert() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictOptionalQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -407,7 +407,7 @@ public void test_conflictOptionalQuery() { assertEquals(7, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictOptionalInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -435,7 +435,7 @@ public void test_conflictOptionalInsert() { assertEquals(7, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeFilterQuery() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -465,7 +465,7 @@ public void test_safeFilterQuery() { } } - @Test + @Test(timeout = 5000) public void test_safeFilterInsert() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -493,7 +493,7 @@ public void test_safeFilterInsert() { } } - @Test + @Test(timeout = 5000) public void test_conflictOptionalFilterQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); a.add(PICASSO, PAINTS, GUERNICA); @@ -526,7 +526,7 @@ public void test_conflictOptionalFilterQuery() { assertEquals(9, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictOptionalFilterInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); a.add(PICASSO, PAINTS, GUERNICA); @@ -557,7 +557,7 @@ public void test_conflictOptionalFilterInsert() { assertEquals(9, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeRangeQuery() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, ARTEMISIA); @@ -593,7 +593,7 @@ public void test_safeRangeQuery() { } } - @Test + @Test(timeout = 5000) public void test_safeRangeInsert() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, ARTEMISIA); @@ -628,7 +628,7 @@ public void test_safeRangeInsert() { } } - @Test + @Test(timeout = 5000) public void test_conflictRangeQuery() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -664,7 +664,7 @@ public void test_conflictRangeQuery() { assertEquals(13, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictRangeInsert() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, NIGHTWATCH); diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SnapshotTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SnapshotTest.java index 0beb98f282..2de76268eb 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SnapshotTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SnapshotTest.java @@ -128,7 +128,7 @@ public void tearDown() { } } - @Test + @Test(timeout = 5000) public void test_independentPattern() { a.begin(level); b.begin(level); @@ -142,7 +142,7 @@ public void test_independentPattern() { assertEquals(2, size(b, null, RDF.TYPE, PAINTER, false)); } - @Test + @Test(timeout = 5000) public void test_safePattern() { a.begin(level); b.begin(level); @@ -153,7 +153,7 @@ public void test_safePattern() { b.commit(); } - @Test + @Test(timeout = 5000) public void test_afterPattern() { a.begin(level); b.begin(level); @@ -165,7 +165,7 @@ public void test_afterPattern() { assertEquals(2, size(b, null, RDF.TYPE, PAINTER, false)); } - @Test + @Test(timeout = 5000) public void test_afterInsertDataPattern() { a.begin(level); b.begin(level); @@ -177,7 +177,7 @@ public void test_afterInsertDataPattern() { assertEquals(2, size(b, null, RDF.TYPE, PAINTER, false)); } - @Test + @Test(timeout = 5000) public void test_conflictPattern() { a.begin(level); b.begin(level); @@ -195,7 +195,7 @@ public void test_conflictPattern() { } } - @Test + @Test(timeout = 5000) public void test_safeQuery() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -216,7 +216,7 @@ public void test_safeQuery() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeInsert() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -235,7 +235,7 @@ public void test_safeInsert() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -257,7 +257,7 @@ public void test_mergeQuery() { assertEquals(3, size(a, null, RDF.TYPE, PAINTING, false)); } - @Test + @Test(timeout = 5000) public void test_mergeInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -277,7 +277,7 @@ public void test_mergeInsert() { assertEquals(3, size(a, null, RDF.TYPE, PAINTING, false)); } - @Test + @Test(timeout = 5000) public void test_conflictQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -306,7 +306,7 @@ public void test_conflictQuery() { } } - @Test + @Test(timeout = 5000) public void test_conflictInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -333,7 +333,7 @@ public void test_conflictInsert() { } } - @Test + @Test(timeout = 5000) public void test_safeOptionalQuery() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -357,7 +357,7 @@ public void test_safeOptionalQuery() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeOptionalInsert() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -376,7 +376,7 @@ public void test_safeOptionalInsert() { assertEquals(9, size(b, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeOptionalQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -401,7 +401,7 @@ public void test_mergeOptionalQuery() { assertEquals(10, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeOptionalInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -421,7 +421,7 @@ public void test_mergeOptionalInsert() { assertEquals(10, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictOptionalQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -453,7 +453,7 @@ public void test_conflictOptionalQuery() { } } - @Test + @Test(timeout = 5000) public void test_conflictOptionalInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); b.add(REMBRANDT, RDF.TYPE, PAINTER); @@ -480,7 +480,7 @@ public void test_conflictOptionalInsert() { } } - @Test + @Test(timeout = 5000) public void test_safeFilterQuery() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -502,7 +502,7 @@ public void test_safeFilterQuery() { assertEquals(10, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeFilterInsert() { b.add(REMBRANDT, RDF.TYPE, PAINTER); b.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -522,7 +522,7 @@ public void test_safeFilterInsert() { assertEquals(10, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeOptionalFilterQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); a.add(PICASSO, PAINTS, GUERNICA); @@ -548,7 +548,7 @@ public void test_mergeOptionalFilterQuery() { assertEquals(12, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeOptionalFilterInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); a.add(PICASSO, PAINTS, GUERNICA); @@ -571,7 +571,7 @@ public void test_mergeOptionalFilterInsert() { assertEquals(12, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictOptionalFilterQuery() { a.add(PICASSO, RDF.TYPE, PAINTER); a.add(PICASSO, PAINTS, GUERNICA); @@ -605,7 +605,7 @@ public void test_conflictOptionalFilterQuery() { } } - @Test + @Test(timeout = 5000) public void test_conflictOptionalFilterInsert() { a.add(PICASSO, RDF.TYPE, PAINTER); a.add(PICASSO, PAINTS, GUERNICA); @@ -635,7 +635,7 @@ public void test_conflictOptionalFilterInsert() { } } - @Test + @Test(timeout = 5000) public void test_safeRangeQuery() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, ARTEMISIA); @@ -663,7 +663,7 @@ public void test_safeRangeQuery() { assertEquals(17, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_safeRangeInsert() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, ARTEMISIA); @@ -690,7 +690,7 @@ public void test_safeRangeInsert() { assertEquals(17, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeRangeQuery() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -719,7 +719,7 @@ public void test_mergeRangeQuery() { assertEquals(16, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_mergeRangeInsert() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -747,7 +747,7 @@ public void test_mergeRangeInsert() { assertEquals(16, size(a, null, null, null, false)); } - @Test + @Test(timeout = 5000) public void test_conflictRangeQuery() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -783,7 +783,7 @@ public void test_conflictRangeQuery() { } } - @Test + @Test(timeout = 5000) public void test_conflictRangeInsert() { a.add(REMBRANDT, RDF.TYPE, PAINTER); a.add(REMBRANDT, PAINTS, NIGHTWATCH); @@ -828,6 +828,9 @@ private List eval(String var, RepositoryConnection con, String qry) { try { List list = new ArrayList<>(); while (result.hasNext()) { + if (list.size() > 1000000) { + throw new RuntimeException("Too many results: " + list.size()); + } list.add(result.next().getValue(var)); } return list; From 96b40438402fff568e5791040404e3ba13f67ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 28 Oct 2025 12:22:23 +0900 Subject: [PATCH 40/79] working on new ID based join iterator --- .../java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java index 21466637fd..6dbea84596 100644 --- a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java +++ b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java @@ -378,8 +378,10 @@ void merge(Changeset change) { try { boolean locked = semaphore.tryLock(10, TimeUnit.SECONDS); if (!locked) { + logger.warn( - "Waiting 10 seconds on semaphore for merging changesets without acquiring the semaphore. This may be a deadlock situation. Attempting to acquire semaphore interruptibly."); + "Waiting 10 seconds on semaphore for merging changesets without acquiring the semaphore {}. This may be a deadlock situation. Attempting to acquire semaphore interruptibly.", + semaphore); if (Thread.interrupted()) { throw new InterruptedException(); } From 682d805e6ebed8440aa6dc4e39272e4373df43e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 28 Oct 2025 12:23:09 +0900 Subject: [PATCH 41/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbSailStore.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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 5ab5dfb6c6..619e2cbaa3 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 @@ -387,6 +387,9 @@ void rollback() throws SailException { throw e instanceof SailException ? (SailException) e : new SailException(e); } finally { tripleStoreException = null; + // Reset transaction-started flag after rollback so subsequent reads don't + // assume pending uncommitted changes and disable LMDB ID join optimizations. + storeTxnStarted.set(false); sinkStoreAccessLock.unlock(); } } @@ -644,7 +647,7 @@ public SailSink sink(IsolationLevel level) throws SailException { @Override public LmdbSailDataset dataset(IsolationLevel level) throws SailException { - return new LmdbSailDataset(explicit); + return new LmdbSailDataset(explicit, level); } } @@ -1108,10 +1111,12 @@ public boolean supportsDeprecateByQuery() { private final class LmdbSailDataset implements SailDataset, LmdbEvaluationDataset { private final boolean explicit; + private final IsolationLevel isolationLevel; private final Txn txn; - public LmdbSailDataset(boolean explicit) throws SailException { + public LmdbSailDataset(boolean explicit, IsolationLevel isolationLevel) throws SailException { this.explicit = explicit; + this.isolationLevel = isolationLevel; try { this.txn = tripleStore.getTxnManager().createReadTxn(); LmdbEvaluationStrategy.setCurrentDataset(this); @@ -1124,6 +1129,7 @@ public LmdbSailDataset(boolean explicit) throws SailException { public void close() { try { // close the associated txn + System.out.println("DEBUG dataset close txn=" + txn.get()); txn.close(); } finally { LmdbEvaluationStrategy.clearCurrentDataset(); @@ -1409,9 +1415,6 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); - System.out - .println("DEBUG getRecordIterator(long[]) s=" + subjQuery + " p=" + predQuery + " o=" + objQuery - + " c=" + ctxQuery); RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); @@ -1562,6 +1565,11 @@ public ValueStore getValueStore() { return valueStore; } + @Override + public IsolationLevel getIsolationLevel() { + return isolationLevel; + } + @Override public boolean hasTransactionChanges() { // storeTxnStarted is flipped to true when a writer begins and only cleared @@ -1693,7 +1701,6 @@ private boolean populateContext(org.eclipse.rdf4j.query.algebra.Var var, long[] return false; } long id = resolveId(ctx); - System.out.println("DEBUG populateContext value=" + ctx + " id=" + id); if (id == LmdbValue.UNKNOWN_ID) { return false; } @@ -1803,7 +1810,6 @@ private long resolveContextWithBindings(long patternId, String varName, BindingS return INVALID_ID; } long id = resolveId(ctx); - System.out.println("DEBUG resolveContextWithBindings var=" + varName + " id=" + id + " value=" + ctx); if (id == LmdbValue.UNKNOWN_ID) { return INVALID_ID; } From 85e2011a7974902a60980ca37b5d027462528094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 28 Oct 2025 12:23:15 +0900 Subject: [PATCH 42/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbSailStoreTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStoreTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStoreTest.java index b735074c00..e9cf348d02 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStoreTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStoreTest.java @@ -209,6 +209,33 @@ public void testInferredSourceHasEmptyIterationWithoutInferredStatements() throw } } + @Test + public void testTxnFlagClearedOnRollback() throws Exception { + // Acquire backing store for direct dataset access + LmdbStore sail = (LmdbStore) ((SailRepository) repo).getSail(); + LmdbSailStore backingStore = sail.getBackingStore(); + + // Begin a transaction and perform a write to flip the storeTxnStarted flag to true + try (RepositoryConnection conn = repo.getConnection()) { + // Disable isolation so writes go directly to LMDB and flip storeTxnStarted + conn.begin(IsolationLevels.NONE); + conn.add(F.createStatement(F.createIRI("urn:txflag"), RDFS.LABEL, F.createLiteral("tmp"))); + + // While the transaction is open, the dataset should report pending transaction changes + try (SailDataset ds = backingStore.getExplicitSailSource().dataset(IsolationLevels.READ_COMMITTED)) { + assertTrue(((LmdbEvaluationDataset) ds).hasTransactionChanges()); + } + + // Roll back via the backing store API to ensure the store's rollback path is exercised + backingStore.rollback(); + } + + // After rollback, the dataset must no longer report transaction changes + try (SailDataset ds = backingStore.getExplicitSailSource().dataset(IsolationLevels.READ_COMMITTED)) { + assertFalse(((LmdbEvaluationDataset) ds).hasTransactionChanges()); + } + } + @AfterEach public void after() { repo.shutDown(); From ec7aedf39ddbd0825bc60015231bee59f8beebc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 28 Oct 2025 12:28:24 +0900 Subject: [PATCH 43/79] working on new ID based join iterator --- .../sail/lmdb/LmdbEvaluationDataset.java | 8 ++ .../rdf4j/sail/lmdb/LmdbStoreConnection.java | 2 + .../eclipse/rdf4j/sail/lmdb/ValueStore.java | 6 -- .../join/LmdbIdBGPQueryEvaluationStep.java | 9 +- .../join/LmdbIdJoinQueryEvaluationStep.java | 35 ++++---- .../sail/lmdb/LmdbIdJoinIsolationTest.java | 2 - .../rdf4j/sail/lmdb/LmdbQueryDebugTest.java | 87 +++++++++++++++++++ 7 files changed, 121 insertions(+), 28 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index 922ff09354..a943abd1b0 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -12,6 +12,7 @@ import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.algebra.StatementPattern; @@ -92,6 +93,13 @@ default boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int */ ValueStore getValueStore(); + /** + * @return the isolation level associated with this dataset. + */ + default IsolationLevel getIsolationLevel() { + return null; + } + /** * Indicates whether the current evaluation should consider the active transaction as containing uncommitted changes * that require reading through an overlay rather than directly from the LMDB indexes. 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 bead908f8b..8698a33fbf 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 @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +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; 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 c0cb431a49..54a5152487 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 @@ -1279,12 +1279,6 @@ public long storeValue(Value value) throws IOException { return getId(value, true); } - public void refreshReadTxn() { - ReadTxn txn = threadLocalReadTxn.get(); - txn.close(); - threadLocalReadTxn.remove(); - } - /** * Computes a hash code for the supplied data. * diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index d8a201f166..8f6d7fb9bc 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -124,14 +124,10 @@ public CloseableIteration evaluate(BindingSet bindings) { return fallbackStep.evaluate(bindings); } if (hasInvalidPattern) { - if (fallbackStep != null) { - System.out.println("DEBUG BGP fallback: invalid pattern"); - } return fallbackStep != null ? fallbackStep.evaluate(bindings) : new EmptyIteration<>(); } if (!dataset.hasTransactionChanges() && createdDynamicIds && fallbackStep != null && !allowCreateConstantIds) { - System.out.println("DEBUG BGP fallback: dynamic id without transaction changes"); return fallbackStep.evaluate(bindings); } @@ -333,6 +329,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.SUBJ_IDX] = result.id; createdAny |= result.created; +// subjVar = subj.getName(); } } else { subjVar = subj.getName(); @@ -349,6 +346,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.PRED_IDX] = result.id; createdAny |= result.created; +// predVar = pred.getName(); } } else { predVar = pred.getName(); @@ -365,6 +363,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.OBJ_IDX] = result.id; createdAny |= result.created; +// objVar = obj.getName(); } } else { objVar = obj.getName(); @@ -381,6 +380,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.CONTEXT_IDX] = result.id; createdAny |= result.created; +// ctxVar = ctx.getName(); } } else { ctxVar = ctx.getName(); @@ -423,7 +423,6 @@ private static ConstantIdResult constantId(ValueStore valueStore, Value value, b } long id = valueStore.getId(value); if (id == LmdbValue.UNKNOWN_ID && !allowCreate) { - valueStore.refreshReadTxn(); id = valueStore.getId(value); } if (id == LmdbValue.UNKNOWN_ID) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 33edf7de00..14cfe5daa1 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -17,12 +17,15 @@ import java.util.Set; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.MutableBindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; @@ -31,6 +34,9 @@ import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +import static org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; /** * Query evaluation step that wires up the LMDB ID join iterator. @@ -93,25 +99,25 @@ public void applyAlgorithmTag(Join join) { private static boolean constantsResolvable(StatementPattern pattern, ValueStore valueStore, boolean allowCreate) { try { - org.eclipse.rdf4j.query.algebra.Var subj = pattern.getSubjectVar(); + Var subj = pattern.getSubjectVar(); if (subj != null && subj.hasValue()) { if (!resolveConstantId(valueStore, subj.getValue(), true, false, allowCreate)) { return false; } } - org.eclipse.rdf4j.query.algebra.Var pred = pattern.getPredicateVar(); + Var pred = pattern.getPredicateVar(); if (pred != null && pred.hasValue()) { if (!resolveConstantId(valueStore, pred.getValue(), false, true, allowCreate)) { return false; } } - org.eclipse.rdf4j.query.algebra.Var obj = pattern.getObjectVar(); + Var obj = pattern.getObjectVar(); if (obj != null && obj.hasValue()) { if (!resolveConstantId(valueStore, obj.getValue(), false, false, allowCreate)) { return false; } } - org.eclipse.rdf4j.query.algebra.Var ctx = pattern.getContextVar(); + Var ctx = pattern.getContextVar(); if (ctx != null && ctx.hasValue()) { if (!resolveConstantId(valueStore, ctx.getValue(), true, false, allowCreate)) { return false; @@ -125,34 +131,33 @@ private static boolean constantsResolvable(StatementPattern pattern, ValueStore private static boolean resolveConstantId(ValueStore valueStore, Value value, boolean requireResource, boolean requireIri, boolean allowCreate) throws IOException { - if (requireResource && !(value instanceof org.eclipse.rdf4j.model.Resource)) { + if (requireResource && !(value instanceof Resource)) { return false; } - if (requireIri && !(value instanceof org.eclipse.rdf4j.model.IRI)) { + if (requireIri && !(value instanceof IRI)) { return false; } - if (value instanceof org.eclipse.rdf4j.model.Resource - && ((org.eclipse.rdf4j.model.Resource) value).isTriple()) { + if (value instanceof Resource + && ((Resource) value).isTriple()) { return false; } - if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { - org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (value instanceof LmdbValue) { + LmdbValue lmdbValue = (LmdbValue) value; if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { long id = lmdbValue.getInternalID(); - if (id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + if (id != UNKNOWN_ID) { return true; } } } long id = valueStore.getId(value); - if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID && !allowCreate) { - valueStore.refreshReadTxn(); + if (id == UNKNOWN_ID && !allowCreate) { id = valueStore.getId(value); } - if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID && allowCreate) { + if (id == UNKNOWN_ID && allowCreate) { id = valueStore.getId(value, true); } - return id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + return id != UNKNOWN_ID; } @Override diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java index 5a1705c8b3..37e60965c0 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinIsolationTest.java @@ -109,7 +109,6 @@ void joinReflectsCommittedChanges(IsolationLevel isolationLevel) throws Exceptio try (TupleQueryResult result = tupleQuery.evaluate()) { beforeClear = Iterations.asList(result); } - System.out.println("DEBUG beforeClear results=" + beforeClear); assertThat(beforeClear).hasSize(1); assertThat(beforeClear.get(0).getValue("person")).isEqualTo(alice); assertThat(beforeClear.get(0).getValue("liked")).isEqualTo(pizza); @@ -124,7 +123,6 @@ void joinReflectsCommittedChanges(IsolationLevel isolationLevel) throws Exceptio try (TupleQueryResult result = tupleQuery.evaluate()) { afterClear = Iterations.asList(result); } - System.out.println("DEBUG afterClear results=" + afterClear); assertThat(afterClear).isEmpty(); conn2.commit(); diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java new file mode 100644 index 0000000000..bf41ed2b83 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; + +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.TupleQuery; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Diagnostic replication of + * {@link org.eclipse.rdf4j.testsuite.repository.RepositoryConnectionTest#testPreparedTupleQuery}. + */ +class LmdbQueryDebugTest { + + @ParameterizedTest + @MethodSource("org.eclipse.rdf4j.testsuite.repository.RepositoryConnectionTest#parameters") + void preparedTupleQuery(IsolationLevel level) throws Exception { + File dataDir = java.nio.file.Files.createTempDirectory("lmdb-debug").toFile(); + Repository repo = new SailRepository(new LmdbStore(dataDir, new LmdbStoreConfig("spoc"))); + repo.init(); + try (RepositoryConnection con = repo.getConnection(); + RepositoryConnection con2 = repo.getConnection()) { + con.setIsolationLevel(level); + con2.setIsolationLevel(level); + + ValueFactory vf = repo.getValueFactory(); + IRI foafName = vf.createIRI("http://xmlns.com/foaf/0.1/name"); + IRI foafMbox = vf.createIRI("http://xmlns.com/foaf/0.1/mbox"); + IRI foafAgent = vf.createIRI("http://xmlns.com/foaf/0.1/Agent"); + + IRI alice = vf.createIRI("urn:alice"); + IRI bob = vf.createIRI("urn:bob"); + IRI ctx1 = vf.createIRI("urn:ctx1"); + IRI ctx2 = vf.createIRI("urn:ctx2"); + + con.begin(level); + con.add(alice, foafName, vf.createLiteral("Alice"), ctx2); + con.add(alice, foafMbox, vf.createLiteral("mailto:alice@example.org"), ctx2); + con.add(ctx2, foafAgent, vf.createLiteral("Alice")); + + con.add(bob, foafName, vf.createLiteral("Bob"), ctx1); + con.add(bob, foafMbox, vf.createLiteral("mailto:bob@example.org"), ctx1); + con.add(ctx1, foafAgent, vf.createLiteral("Bob")); + con.commit(); + + long statements = Iterations.asList(con.getStatements(null, null, null, false)).size(); + long statements2 = Iterations.asList(con2.getStatements(null, null, null, false)).size(); + + String queryBuilder = " PREFIX foaf: \n" + + " SELECT ?name ?mbox \n" + + " WHERE { [] foaf:name ?name; \n" + + " foaf:mbox ?mbox . }"; + TupleQuery query = con.prepareTupleQuery(queryBuilder); + query.setBinding("name", vf.createLiteral("Bob")); + + try (TupleQueryResult result = query.evaluate()) { + System.out.println("Isolation=" + level + " stmt=" + statements + " stmt2=" + statements2 + + " result.size=" + Iterations.asList(result).size()); + } + } finally { + repo.shutDown(); + org.apache.commons.io.FileUtils.deleteDirectory(dataDir); + } + } +} From 1df8da8da3e45e54a276fc4347003734620bdf62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 28 Oct 2025 12:37:18 +0900 Subject: [PATCH 44/79] working on new ID based join iterator --- .../main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java | 1 - .../java/org/eclipse/rdf4j/sail/lmdb/LmdbStoreConnection.java | 2 -- .../rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java | 4 ++-- .../java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java | 3 --- .../testsuite/repository/optimistic/SerializableTest.java | 2 +- 5 files changed, 3 insertions(+), 9 deletions(-) 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 619e2cbaa3..3bc850e14d 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 @@ -1129,7 +1129,6 @@ public LmdbSailDataset(boolean explicit, IsolationLevel isolationLevel) throws S public void close() { try { // close the associated txn - System.out.println("DEBUG dataset close txn=" + txn.get()); txn.close(); } finally { LmdbEvaluationStrategy.clearCurrentDataset(); 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 8698a33fbf..bead908f8b 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 @@ -10,8 +10,6 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; -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; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 14cfe5daa1..a52e1ec709 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.join; +import static org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + import java.io.IOException; import java.util.Collections; import java.util.HashSet; @@ -36,8 +38,6 @@ import org.eclipse.rdf4j.sail.lmdb.ValueStore; import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; -import static org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; - /** * Query evaluation step that wires up the LMDB ID join iterator. */ diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java index bf41ed2b83..2528f86b71 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbQueryDebugTest.java @@ -10,15 +10,12 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; -import static org.assertj.core.api.Assertions.assertThat; - import java.io.File; import org.eclipse.rdf4j.common.iteration.Iterations; import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.TupleQuery; import org.eclipse.rdf4j.query.TupleQueryResult; import org.eclipse.rdf4j.repository.Repository; diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java index 1909635c34..9315662c99 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/SerializableTest.java @@ -160,7 +160,7 @@ public void test_safePattern() { b.commit(); } - @Test(timeout = 5000) + @Test public void testPrepare_safePattern() { a.begin(level); b.begin(level); From 6269d9031025a3962d6e7a8a1c9488b113d1bebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Wed, 29 Oct 2025 23:50:36 +0900 Subject: [PATCH 45/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDupRecordIterator.java | 5 +- .../sail/lmdb/LmdbEvaluationDataset.java | 8 +++ .../sail/lmdb/LmdbEvaluationStrategy.java | 57 ++++++++++++++++--- .../lmdb/LmdbOverlayEvaluationDataset.java | 2 +- .../rdf4j/sail/lmdb/LmdbSailStore.java | 12 ++++ .../join/LmdbIdBGPQueryEvaluationStep.java | 41 +++++++++---- .../join/LmdbIdFinalBindingSetIteration.java | 21 ++++++- .../join/LmdbIdJoinQueryEvaluationStep.java | 5 ++ .../LmdbIdMergeJoinQueryEvaluationStep.java | 5 ++ 9 files changed, 134 insertions(+), 22 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index d64f7796d3..2ee9422632 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -45,8 +45,9 @@ interface FallbackSupplier { RecordIterator get() throws IOException; } - /** Toggle copying of duplicate blocks for extra safety (defaults to zero-copy views). */ - private static final boolean COPY_DUP_BLOCKS = Boolean.getBoolean("rdf4j.lmdb.copyDupBlocks"); + /** Toggle copying of duplicate blocks for extra safety (defaults to copying). */ + private static final boolean COPY_DUP_BLOCKS = Boolean.parseBoolean( + System.getProperty("rdf4j.lmdb.copyDupBlocks", "true")); /** Size in bytes of one (v3,v4) tuple. */ private static final int DUP_PAIR_BYTES = Long.BYTES * 2; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index a943abd1b0..3ccbd36b75 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -100,6 +100,14 @@ default IsolationLevel getIsolationLevel() { return null; } + /** + * Refresh the underlying snapshot, if applicable, to ensure subsequent reads observe the latest committed data. + * Implementations that do not maintain a snapshot may ignore this call. + */ + default void refreshSnapshot() throws QueryEvaluationException { + // no-op by default + } + /** * Indicates whether the current evaluation should consider the active transaction as containing uncommitted changes * that require reading through an overlay rather than directly from the LMDB indexes. diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java index 7e8e3f0a4d..ce2287240b 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java @@ -15,6 +15,8 @@ import java.util.Optional; import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.common.transaction.QueryEvaluationMode; import org.eclipse.rdf4j.query.Dataset; import org.eclipse.rdf4j.query.algebra.Join; @@ -72,8 +74,12 @@ public QueryEvaluationStep precompile(TupleExpr expr) { @Override protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) { QueryEvaluationStep defaultStep = super.prepare(node, context); + if (Boolean.getBoolean("rdf4j.lmdb.disableIdJoins")) { + return defaultStep; + } if (context instanceof LmdbDatasetContext && ((LmdbDatasetContext) context).getLmdbDataset().isPresent()) { LmdbEvaluationDataset ds = ((LmdbDatasetContext) context).getLmdbDataset().get(); + Optional valueStoreOpt = ((LmdbDatasetContext) context).getValueStore(); // If the active transaction has uncommitted changes, avoid ID-only join shortcuts. if (ds.hasTransactionChanges() || connectionHasChanges()) { return defaultStep; @@ -90,20 +96,28 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(node, patterns, context, defaultStep); if (step.shouldUseFallbackImmediately()) { - Optional valueStoreOpt = ((LmdbDatasetContext) context).getValueStore(); if (valueStoreOpt.isPresent()) { LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, valueStoreOpt.get()); QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, valueStoreOpt.get()); - LmdbIdBGPQueryEvaluationStep overlayStep = new LmdbIdBGPQueryEvaluationStep(node, patterns, + return new LmdbIdBGPQueryEvaluationStep(node, patterns, overlayContext, defaultStep); - overlayStep.applyAlgorithmTag(); - return overlayStep; } return defaultStep; } + boolean hasPreBound = hasPreBoundVariables(patterns); + if ((requiresSnapshotOverlay(ds) || hasPreBound) && valueStoreOpt.isPresent()) { + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + valueStoreOpt.get()); + QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, + valueStoreOpt.get()); + LmdbIdBGPQueryEvaluationStep overlayStep = new LmdbIdBGPQueryEvaluationStep(node, patterns, + overlayContext, defaultStep); + overlayStep.applyAlgorithmTag(); + return overlayStep; + } step.applyAlgorithmTag(); return step; } @@ -112,20 +126,27 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) LmdbIdJoinQueryEvaluationStep step = new LmdbIdJoinQueryEvaluationStep(this, node, context, defaultStep); if (step.shouldUseFallbackImmediately()) { - Optional valueStoreOpt = ((LmdbDatasetContext) context).getValueStore(); if (valueStoreOpt.isPresent()) { LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, valueStoreOpt.get()); QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, valueStoreOpt.get()); - LmdbIdJoinQueryEvaluationStep overlayStep = new LmdbIdJoinQueryEvaluationStep(this, node, + return new LmdbIdJoinQueryEvaluationStep(this, node, overlayContext, defaultStep); - overlayStep.applyAlgorithmTag(node); - return overlayStep; } return defaultStep; } + if (requiresSnapshotOverlay(ds) && valueStoreOpt.isPresent()) { + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + valueStoreOpt.get()); + QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, + valueStoreOpt.get()); + LmdbIdJoinQueryEvaluationStep overlayStep = new LmdbIdJoinQueryEvaluationStep(this, node, + overlayContext, defaultStep); + overlayStep.applyAlgorithmTag(node); + return overlayStep; + } step.applyAlgorithmTag(node); return step; } @@ -133,6 +154,26 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) return defaultStep; } + private boolean hasPreBoundVariables(List patterns) { + for (StatementPattern pattern : patterns) { + if (isPreBound(pattern.getSubjectVar()) || isPreBound(pattern.getPredicateVar()) + || isPreBound(pattern.getObjectVar()) || isPreBound(pattern.getContextVar())) { + return true; + } + } + return false; + } + + private boolean isPreBound(org.eclipse.rdf4j.query.algebra.Var var) { + return var != null && var.hasValue() && !var.isConstant(); + } + + private boolean requiresSnapshotOverlay(LmdbEvaluationDataset dataset) { + IsolationLevel isolation = dataset.getIsolationLevel(); + return isolation == IsolationLevels.SNAPSHOT || isolation == IsolationLevels.SERIALIZABLE + || isolation == IsolationLevels.SNAPSHOT_READ; + } + static void setCurrentDataset(LmdbEvaluationDataset dataset) { CURRENT_DATASET.set(dataset); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 124cd6fd7d..131f25d197 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -285,7 +285,7 @@ private boolean applyValue(long[] target, int index, long value) { @Override public boolean hasTransactionChanges() { - return true; + return false; } private Value resolveValue(Var var, BindingSet bindings) { 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 3bc850e14d..c253526f05 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 @@ -36,6 +36,7 @@ import org.eclipse.rdf4j.common.iteration.UnionIteration; import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Namespace; import org.eclipse.rdf4j.model.Resource; @@ -1569,6 +1570,17 @@ public IsolationLevel getIsolationLevel() { return isolationLevel; } + @Override + public void refreshSnapshot() throws QueryEvaluationException { + if (isolationLevel == IsolationLevels.SNAPSHOT || isolationLevel == IsolationLevels.SERIALIZABLE) { + try { + txn.reset(); + } catch (IOException e) { + throw new QueryEvaluationException("Unable to refresh LMDB read transaction", e); + } + } + } + @Override public boolean hasTransactionChanges() { // storeTxnStarted is flipped to true when a writer begins and only cleared diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 8f6d7fb9bc..78d8223a4e 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -14,9 +14,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; @@ -53,6 +56,7 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private final boolean hasInvalidPattern; private final boolean createdDynamicIds; private final boolean allowCreateConstantIds; + private final java.util.Map constantBindings; public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -67,22 +71,29 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, ValueStore valueStore = this.datasetContext.getValueStore() .orElseThrow(() -> new IllegalStateException("LMDB ID BGP join requires ValueStore access")); - boolean allowCreate = this.datasetContext.getLmdbDataset() - .map(LmdbEvaluationDataset::hasTransactionChanges) + Optional datasetOpt = this.datasetContext.getLmdbDataset(); + boolean overlayDataset = datasetOpt + .map(ds -> LmdbEvaluationStrategy.getCurrentDataset().map(current -> current != ds).orElse(true)) + .orElse(false); + boolean allowCreate = datasetOpt + .map(ds -> ds.hasTransactionChanges() || overlayDataset) .orElseGet(LmdbEvaluationStrategy::hasActiveConnectionChanges); this.allowCreateConstantIds = allowCreate; List rawPatterns = new ArrayList<>(patterns.size()); boolean invalid = false; boolean created = false; + java.util.Map constants = new java.util.HashMap<>(); for (StatementPattern pattern : patterns) { RawPattern raw = RawPattern.create(pattern, valueStore, allowCreate); rawPatterns.add(raw); invalid |= raw.invalid; created |= raw.createdIds; + constants.putAll(raw.getConstantIds()); } this.hasInvalidPattern = invalid; this.createdDynamicIds = created; + this.constantBindings = java.util.Collections.unmodifiableMap(constants); if (rawPatterns.isEmpty()) { throw new IllegalArgumentException("Basic graph pattern must contain at least one statement pattern"); @@ -120,6 +131,9 @@ public CloseableIteration evaluate(BindingSet bindings) { } try { LmdbEvaluationDataset dataset = resolveDataset(); + if (!dataset.hasTransactionChanges()) { + dataset.refreshSnapshot(); + } if (fallbackStep != null && dataset.hasTransactionChanges()) { return fallbackStep.evaluate(bindings); } @@ -145,7 +159,7 @@ public CloseableIteration evaluate(BindingSet bindings) { iter = new BindingJoinRecordIterator(iter, dataset, plans.get(i)); } - return new LmdbIdFinalBindingSetIteration(iter, finalInfo, context, bindings, valueStore); + return new LmdbIdFinalBindingSetIteration(iter, finalInfo, context, bindings, valueStore, constantBindings); } catch (QueryEvaluationException e) { throw e; } @@ -258,7 +272,6 @@ public long[] next() throws QueryEvaluationException { if (leftBinding == null) { return null; } - currentRight = dataset.getRecordIterator(leftBinding, plan.subjIndex, plan.predIndex, plan.objIndex, plan.ctxIndex, plan.patternIds); } @@ -299,9 +312,11 @@ private static final class RawPattern { private final LmdbIdJoinIterator.PatternInfo patternInfo; private final boolean invalid; private final boolean createdIds; + private final java.util.Map constantIds; private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, - LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid, boolean createdIds) { + LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid, boolean createdIds, + java.util.Map constantIds) { this.patternIds = patternIds; this.subjVar = subjVar; this.predVar = predVar; @@ -310,6 +325,11 @@ private RawPattern(long[] patternIds, String subjVar, String predVar, String obj this.patternInfo = patternInfo; this.invalid = invalid; this.createdIds = createdIds; + this.constantIds = constantIds; + } + + java.util.Map getConstantIds() { + return constantIds; } static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolean allowCreate) { @@ -318,6 +338,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea boolean invalid = false; boolean createdAny = false; + java.util.Map constantIds = new java.util.HashMap<>(); Var subj = pattern.getSubjectVar(); String subjVar = null; @@ -329,7 +350,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.SUBJ_IDX] = result.id; createdAny |= result.created; -// subjVar = subj.getName(); + constantIds.put(subj.getName(), result.id); } } else { subjVar = subj.getName(); @@ -346,7 +367,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.PRED_IDX] = result.id; createdAny |= result.created; -// predVar = pred.getName(); + constantIds.put(pred.getName(), result.id); } } else { predVar = pred.getName(); @@ -363,7 +384,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.OBJ_IDX] = result.id; createdAny |= result.created; -// objVar = obj.getName(); + constantIds.put(obj.getName(), result.id); } } else { objVar = obj.getName(); @@ -380,7 +401,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } else { ids[TripleStore.CONTEXT_IDX] = result.id; createdAny |= result.created; -// ctxVar = ctx.getName(); + constantIds.put(ctx.getName(), result.id); } } else { ctxVar = ctx.getName(); @@ -388,7 +409,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } LmdbIdJoinIterator.PatternInfo info = LmdbIdJoinIterator.PatternInfo.create(pattern); - return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, info, invalid, createdAny); + return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, info, invalid, createdAny, constantIds); } PatternPlan toPlan(IdBindingInfo finalInfo) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java index c3db04c3fa..b68081e820 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java @@ -10,7 +10,10 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.join; +import java.io.IOException; + import org.eclipse.rdf4j.common.iteration.LookAheadIteration; +import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.MutableBindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; @@ -29,14 +32,16 @@ final class LmdbIdFinalBindingSetIteration extends LookAheadIteration constantBindings; LmdbIdFinalBindingSetIteration(RecordIterator input, IdBindingInfo info, QueryEvaluationContext context, - BindingSet initial, ValueStore valueStore) { + BindingSet initial, ValueStore valueStore, java.util.Map constantBindings) { this.input = input; this.info = info; this.context = context; this.initial = initial; this.valueStore = valueStore; + this.constantBindings = constantBindings; } @Override @@ -44,6 +49,20 @@ protected BindingSet getNextElement() throws QueryEvaluationException { long[] rec; while ((rec = input.next()) != null) { MutableBindingSet bs = context.createBindingSet(initial); + if (!constantBindings.isEmpty()) { + for (java.util.Map.Entry entry : constantBindings.entrySet()) { + String name = entry.getKey(); + if (bs.hasBinding(name)) { + continue; + } + try { + Value constantValue = valueStore.getLazyValue(entry.getValue()); + bs.setBinding(name, constantValue); + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } + } if (info.applyRecord(rec, bs, valueStore)) { return bs; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index a52e1ec709..a6b426f469 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -19,6 +19,8 @@ import java.util.Set; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; @@ -170,6 +172,9 @@ public CloseableIteration evaluate(BindingSet bindings) { } try { LmdbEvaluationDataset dataset = resolveDataset(); + if (!dataset.hasTransactionChanges()) { + dataset.refreshSnapshot(); + } if (fallbackStep != null && dataset.hasTransactionChanges()) { return fallbackStep.evaluate(bindings); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java index 9edfec2484..04702fd018 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -17,6 +17,8 @@ import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; @@ -115,6 +117,9 @@ public CloseableIteration evaluate(BindingSet bindings) { } try { LmdbEvaluationDataset dataset = resolveDataset(); + if (!dataset.hasTransactionChanges()) { + dataset.refreshSnapshot(); + } if (fallbackStep != null && dataset.hasTransactionChanges()) { return fallbackStep.evaluate(bindings); } From 5da66c42028599e2d73a5c050dd6ead3755d9ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 30 Oct 2025 07:55:51 +0900 Subject: [PATCH 46/79] working on new ID based join iterator --- .../lmdb/LmdbOverlayEvaluationDataset.java | 2 + .../rdf4j/sail/lmdb/LmdbSailStore.java | 6 +- .../join/LmdbIdFinalBindingSetIteration.java | 32 ++++++++-- .../sail/lmdb/join/LmdbIdJoinIterator.java | 64 ++++++++++++++++--- 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 131f25d197..d9b89074a8 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -155,6 +155,8 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI final boolean defaultOnly = requireDefaultContext; return new RecordIterator() { + private final long[] scratch = java.util.Arrays.copyOf(binding, binding.length); + @Override public long[] next() throws QueryEvaluationException { while (true) { 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 c253526f05..84eb5a5305 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 @@ -1419,15 +1419,15 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); return new RecordIterator() { + @Override public long[] next() throws QueryEvaluationException { try { long[] quad; while ((quad = base.next()) != null) { long[] merged = mergeBinding(binding, quad[TripleStore.SUBJ_IDX], - quad[TripleStore.PRED_IDX], - quad[TripleStore.OBJ_IDX], quad[TripleStore.CONTEXT_IDX], subjIndex, predIndex, - objIndex, ctxIndex); + quad[TripleStore.PRED_IDX], quad[TripleStore.OBJ_IDX], + quad[TripleStore.CONTEXT_IDX], subjIndex, predIndex, objIndex, ctxIndex); if (merged != null) { return merged; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java index b68081e820..5c4393caee 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java @@ -11,6 +11,9 @@ package org.eclipse.rdf4j.sail.lmdb.join; import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.eclipse.rdf4j.common.iteration.LookAheadIteration; import org.eclipse.rdf4j.model.Value; @@ -32,34 +35,49 @@ final class LmdbIdFinalBindingSetIteration extends LookAheadIteration constantBindings; + private final Map constantBindings; + private Map constantValues; LmdbIdFinalBindingSetIteration(RecordIterator input, IdBindingInfo info, QueryEvaluationContext context, - BindingSet initial, ValueStore valueStore, java.util.Map constantBindings) { + BindingSet initial, ValueStore valueStore, Map constantBindings) { this.input = input; this.info = info; this.context = context; this.initial = initial; this.valueStore = valueStore; this.constantBindings = constantBindings; + this.constantValues = null; // lazily initialized } @Override protected BindingSet getNextElement() throws QueryEvaluationException { + // Lazily resolve constant IDs to Values once to avoid repeated lookups + if (constantValues == null && !constantBindings.isEmpty()) { + Map resolved = new HashMap<>(); + for (Map.Entry entry : constantBindings.entrySet()) { + try { + Value v = valueStore.getLazyValue(entry.getValue()); + if (v != null) { + resolved.put(entry.getKey(), v); + } + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } + constantValues = Collections.unmodifiableMap(resolved); + } long[] rec; while ((rec = input.next()) != null) { MutableBindingSet bs = context.createBindingSet(initial); if (!constantBindings.isEmpty()) { - for (java.util.Map.Entry entry : constantBindings.entrySet()) { + for (Map.Entry entry : constantBindings.entrySet()) { String name = entry.getKey(); if (bs.hasBinding(name)) { continue; } - try { - Value constantValue = valueStore.getLazyValue(entry.getValue()); + Value constantValue = constantValues.get(name); + if (constantValue != null) { bs.setBinding(name, constantValue); - } catch (IOException e) { - throw new QueryEvaluationException(e); } } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index 8305abf90a..bb9e63858a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -23,6 +23,7 @@ import org.eclipse.rdf4j.query.MutableBindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; import org.eclipse.rdf4j.sail.lmdb.IdAccessor; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; @@ -76,7 +77,7 @@ static PatternInfo create(StatementPattern pattern) { return new PatternInfo(map, hasContext); } - private static boolean registerVar(Map map, org.eclipse.rdf4j.query.algebra.Var var, int index) { + private static boolean registerVar(Map map, Var var, int index) { if (var == null || var.hasValue()) { return false; } @@ -136,22 +137,66 @@ boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueSto String name = entry.getKey(); int[] indices = entry.getValue(); Value existing = target.getValue(name); + + // Fast path: if an existing binding is an LmdbValue from the same store, + // compare IDs directly and avoid resolving candidate Values. + if (existing instanceof LmdbValue) { + LmdbValue lmdbExisting = (LmdbValue) existing; + if (lmdbExisting.getValueStoreRevision().getValueStore() == valueStore) { + long existingId = lmdbExisting.getInternalID(); + if (existingId != LmdbValue.UNKNOWN_ID) { + for (int index : indices) { + long id = record[index]; + // Context id of 0 is effectively null; conflicts with an existing non-null + if (index == TripleStore.CONTEXT_IDX && id == 0L) { + return false; + } + if (id != LmdbValue.UNKNOWN_ID && id != existingId) { + return false; + } + } + continue; // this variable satisfied + } + } + } + + // General path: collect a consistent candidate id across positions (if any), + // minimize value resolutions to at most one per variable. + long chosenId = LmdbValue.UNKNOWN_ID; + boolean haveId = false; for (int index : indices) { long id = record[index]; - Value candidate = resolveValue(id, index, valueStore); - if (candidate == null) { + // Treat default context (0) as null candidate; it conflicts with an existing non-null value. + if (index == TripleStore.CONTEXT_IDX && id == 0L) { if (existing != null) { return false; } continue; } + if (id == LmdbValue.UNKNOWN_ID) { + continue; + } if (existing != null) { - if (!existing.equals(candidate)) { + // Fallback: existing is non-LMDB or unknown id; resolve candidate once and compare + Value candidate = resolveValue(id, index, valueStore); + if (candidate == null || !existing.equals(candidate)) { return false; } } else { + if (!haveId) { + chosenId = id; + haveId = true; + } else if (chosenId != id) { + // Variable appears in multiple positions with conflicting ids + return false; + } + } + } + + if (existing == null && haveId) { + Value candidate = resolveValue(chosenId, TripleStore.SUBJ_IDX /* ignored */, valueStore); + if (candidate != null) { target.setBinding(name, candidate); - existing = candidate; } } } @@ -250,11 +295,10 @@ private boolean matchesJoin(long[] leftRecord, long[] rightRecord) { } private long[] nextRecord(RecordIterator iterator) throws QueryEvaluationException { - long[] record = iterator.next(); - if (record == null) { - return null; - } - return Arrays.copyOf(record, record.length); + // Avoid per-record array copy: LmdbRecordIterator returns a stable array instance + // that is only mutated on subsequent next() calls. We consume the array fully + // before advancing the iterator again, so returning it directly is safe here. + return iterator.next(); } @Override From 3e3c5feba6491d39a5fee7eaf20d3468fdcb7ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 30 Oct 2025 08:45:30 +0900 Subject: [PATCH 47/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/IdBindingInfo.java | 209 ++++++++++++++- .../lmdb/LmdbOverlayEvaluationDataset.java | 137 ++-------- .../rdf4j/sail/lmdb/LmdbSailStore.java | 237 ++++++++++-------- .../join/LmdbIdBGPQueryEvaluationStep.java | 4 +- .../join/LmdbIdJoinQueryEvaluationStep.java | 13 + .../LmdbIdMergeJoinQueryEvaluationStep.java | 4 +- 6 files changed, 380 insertions(+), 224 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java index 35ffd8b1d3..48b61283ba 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java @@ -15,11 +15,16 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.MutableBindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.ArrayBindingSet; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; @@ -31,10 +36,35 @@ public final class IdBindingInfo implements IdAccessor { private final Map indexByVar; // insertion order preserved private final Map positionsMaskByVar; // aggregated from contributing patterns + private final Entry[] prepared; // optional fast path, aligned with insertion order IdBindingInfo(LinkedHashMap indexByVar, Map positionsMaskByVar) { + this(indexByVar, positionsMaskByVar, null); + } + + IdBindingInfo(LinkedHashMap indexByVar, Map positionsMaskByVar, + QueryEvaluationContext ctx) { this.indexByVar = Collections.unmodifiableMap(new LinkedHashMap<>(indexByVar)); this.positionsMaskByVar = Collections.unmodifiableMap(positionsMaskByVar); + if (ctx != null) { + this.prepared = buildPrepared(ctx); + } else { + this.prepared = null; + } + } + + private Entry[] buildPrepared(QueryEvaluationContext ctx) { + Entry[] arr = new Entry[indexByVar.size()]; + int i = 0; + for (Map.Entry e : indexByVar.entrySet()) { + String name = e.getKey(); + int idx = e.getValue(); + int mask = positionsMaskByVar.getOrDefault(name, 0); + Function getter = ctx.getValue(name); + BiConsumer setter = ctx.setBinding(name); + arr[i++] = new Entry(name, idx, mask, getter, setter); + } + return arr; } @Override @@ -72,6 +102,100 @@ public long getId(long[] record, String varName) { public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) throws QueryEvaluationException { + // Fast path: pre-resolved getters/setters from the evaluation context + if (prepared != null) { + for (Entry entry : prepared) { + long id = record[entry.recordIndex]; + if (id == LmdbValue.UNKNOWN_ID) { + continue; + } + if (((entry.positionsMask >> TripleStore.CONTEXT_IDX) & 1) == 1 && id == 0L) { + continue; // default graph treated as null (unbound) + } + + Value existing = entry.getter.apply(target); + if (existing != null) { + if (existing instanceof LmdbValue + && ((LmdbValue) existing).getValueStoreRevision().getValueStore() == valueStore) { + long existingId = ((LmdbValue) existing).getInternalID(); + if (existingId != LmdbValue.UNKNOWN_ID && existingId != id) { + return false; + } + continue; + } + // Fallback: resolve and compare + Value candidate; + try { + candidate = valueStore.getLazyValue(id); + } catch (IOException ex) { + throw new QueryEvaluationException(ex); + } + if (!existing.equals(candidate)) { + return false; + } + } else { + Value candidate; + try { + candidate = valueStore.getLazyValue(id); + } catch (IOException ex) { + throw new QueryEvaluationException(ex); + } + entry.setter.accept(candidate, target); + } + } + return true; + } + if (target instanceof ArrayBindingSet) { + ArrayBindingSet abs = (ArrayBindingSet) target; + for (Map.Entry e : indexByVar.entrySet()) { + String name = e.getKey(); + int idx = e.getValue(); + long id = record[idx]; + if (id == LmdbValue.UNKNOWN_ID) { + continue; + } + int mask = getPositionsMask(name); + if (((mask >> TripleStore.CONTEXT_IDX) & 1) == 1 && id == 0L) { + continue; // default graph treated as null (unbound) + } + + // Fast existing lookup without map churn + Value existing = abs.getDirectGetValue(name).apply(abs); + if (existing != null) { + // If existing is an LmdbValue from same store, compare by ID first + if (existing instanceof LmdbValue + && ((LmdbValue) existing).getValueStoreRevision().getValueStore() == valueStore) { + long existingId = ((LmdbValue) existing).getInternalID(); + if (existingId != LmdbValue.UNKNOWN_ID && existingId != id) { + return false; + } + continue; + } + // Fallback: resolve candidate and compare + Value candidate; + try { + candidate = valueStore.getLazyValue(id); + } catch (IOException ex) { + throw new QueryEvaluationException(ex); + } + if (!existing.equals(candidate)) { + return false; + } + } else { + // No existing value: resolve once and set via direct setter + Value candidate; + try { + candidate = valueStore.getLazyValue(id); + } catch (IOException ex) { + throw new QueryEvaluationException(ex); + } + abs.getDirectSetBinding(name).accept(candidate, abs); + } + } + return true; + } + + // Generic path for (Map.Entry e : indexByVar.entrySet()) { String name = e.getKey(); int idx = e.getValue(); @@ -81,10 +205,20 @@ public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore v } int mask = getPositionsMask(name); if (((mask >> TripleStore.CONTEXT_IDX) & 1) == 1 && id == 0L) { - // default graph treated as null (unbound) - continue; + continue; // default graph treated as null (unbound) } Value existing = target.getValue(name); + if (existing != null) { + // If existing is an LmdbValue from same store, compare by ID first + if (existing instanceof LmdbValue + && ((LmdbValue) existing).getValueStoreRevision().getValueStore() == valueStore) { + long existingId = ((LmdbValue) existing).getInternalID(); + if (existingId != LmdbValue.UNKNOWN_ID && existingId != id) { + return false; + } + continue; + } + } Value candidate; try { candidate = valueStore.getLazyValue(id); @@ -158,6 +292,77 @@ public static IdBindingInfo fromFirstPattern(LmdbIdJoinIterator.PatternInfo firs return new IdBindingInfo(map, masks); } + public static IdBindingInfo combine(IdBindingInfo left, LmdbIdJoinIterator.PatternInfo right, + QueryEvaluationContext ctx) { + LinkedHashMap map = new LinkedHashMap<>(); + Map masks = new java.util.HashMap<>(); + if (left != null) { + for (String v : left.getVariableNames()) { + map.put(v, map.size()); + masks.put(v, left.getPositionsMask(v)); + } + } + for (String v : right.getVariableNames()) { + Integer mask = right.getPositionsMask(v); + if (!map.containsKey(v)) { + map.put(v, map.size()); + masks.put(v, mask); + } else { + masks.put(v, masks.getOrDefault(v, 0) | mask); + } + } + return new IdBindingInfo(map, masks, ctx); + } + + public static IdBindingInfo fromFirstPattern(LmdbIdJoinIterator.PatternInfo first, QueryEvaluationContext ctx) { + LinkedHashMap map = new LinkedHashMap<>(); + Map masks = new java.util.HashMap<>(); + String s = firstVarName(first, TripleStore.SUBJ_IDX); + if (s != null) { + map.put(s, map.size()); + masks.put(s, first.getPositionsMask(s)); + } + String p = firstVarName(first, TripleStore.PRED_IDX); + if (p != null && !map.containsKey(p)) { + map.put(p, map.size()); + masks.put(p, first.getPositionsMask(p)); + } + String o = firstVarName(first, TripleStore.OBJ_IDX); + if (o != null && !map.containsKey(o)) { + map.put(o, map.size()); + masks.put(o, first.getPositionsMask(o)); + } + String c = firstVarName(first, TripleStore.CONTEXT_IDX); + if (c != null && !map.containsKey(c)) { + map.put(c, map.size()); + masks.put(c, first.getPositionsMask(c)); + } + for (String v : first.getVariableNames()) { + if (!map.containsKey(v)) { + map.put(v, map.size()); + masks.put(v, first.getPositionsMask(v)); + } + } + return new IdBindingInfo(map, masks, ctx); + } + + private static final class Entry { + final String name; + final int recordIndex; + final int positionsMask; + final Function getter; + final BiConsumer setter; + + Entry(String name, int recordIndex, int positionsMask, Function getter, + BiConsumer setter) { + this.name = name; + this.recordIndex = recordIndex; + this.positionsMask = positionsMask; + this.getter = getter; + this.setter = setter; + } + } + private static String firstVarName(LmdbIdJoinIterator.PatternInfo info, int position) { for (String v : info.getVariableNames()) { int mask = info.getPositionsMask(v); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index d9b89074a8..2bab766f97 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -1,14 +1,13 @@ -/***/ /******************************************************************************* -* Copyright (c) 2025 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 -*******************************************************************************/ + * Copyright (c) 2025 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.sail.lmdb; import java.util.Arrays; @@ -25,11 +24,11 @@ import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; /** - * An {@link LmdbEvaluationDataset} implementation that reads via the delegated Sail layer (through the - * {@link TripleSource}) to ensure transaction overlays and isolation levels are honored, while exposing LMDB record - * iterators for the ID join. + * Delegates reads via the Sail {@link TripleSource} to honor transaction overlays and isolation while exposing + * LMDB-style {@link RecordIterator}s for ID-based joins. */ final class LmdbOverlayEvaluationDataset implements LmdbEvaluationDataset { @@ -62,13 +61,13 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin if (ctxVal != null && !(ctxVal instanceof Resource)) { return LmdbIdJoinIterator.emptyRecordIterator(); } - if (ctxVal instanceof Resource && ((Resource) ctxVal).isTriple()) { + if (ctxVal != null && ctxVal.isTriple()) { return LmdbIdJoinIterator.emptyRecordIterator(); } Resource[] contexts; if (ctxVal == null) { - contexts = new Resource[0]; // no restriction: all contexts + contexts = new Resource[0]; } else { contexts = new Resource[] { (Resource) ctxVal }; } @@ -96,7 +95,6 @@ public long[] next() throws QueryEvaluationException { long c = st.getContext() == null ? 0L : resolveId(st.getContext()); return new long[] { s, p, o, c }; } catch (Exception e) { - // ensure iterator is closed on error try { stmts.close(); } catch (Exception ignore) { @@ -143,7 +141,6 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI return LmdbIdJoinIterator.emptyRecordIterator(); } contexts = new Resource[] { ctxRes }; - requireDefaultContext = false; } else { contexts = new Resource[0]; } @@ -155,8 +152,6 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI final boolean defaultOnly = requireDefaultContext; return new RecordIterator() { - private final long[] scratch = java.util.Arrays.copyOf(binding, binding.length); - @Override public long[] next() throws QueryEvaluationException { while (true) { @@ -219,22 +214,19 @@ public ValueStore getValueStore() { } private long selectQueryId(long patternId, long[] binding, int index) { - if (patternId != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + if (patternId != LmdbValue.UNKNOWN_ID) { return patternId; } if (index >= 0 && index < binding.length) { - long bound = binding[index]; - if (bound != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { - return bound; - } + return binding[index]; } - return org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + return LmdbValue.UNKNOWN_ID; } private Value valueForQuery(long patternId, long[] binding, int index, boolean requireResource, boolean requireIri) throws QueryEvaluationException { long id = selectQueryId(patternId, binding, index); - if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + if (id == LmdbValue.UNKNOWN_ID) { return null; } try { @@ -245,7 +237,7 @@ private Value valueForQuery(long patternId, long[] binding, int index, boolean r if (requireIri && !(value instanceof IRI)) { throw new QueryEvaluationException("Expected IRI-bound value"); } - if (value instanceof Resource && ((Resource) value).isTriple()) { + if (value instanceof Resource && value.isTriple()) { throw new QueryEvaluationException("Triple-valued resources are not supported in LMDB joins"); } return value; @@ -278,18 +270,13 @@ private boolean applyValue(long[] target, int index, long value) { return true; } long existing = target[index]; - if (existing != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID && existing != value) { + if (existing != LmdbValue.UNKNOWN_ID && existing != value) { return false; } target[index] = value; return true; } - @Override - public boolean hasTransactionChanges() { - return false; - } - private Value resolveValue(Var var, BindingSet bindings) { if (var == null) { return null; @@ -298,93 +285,21 @@ private Value resolveValue(Var var, BindingSet bindings) { return var.getValue(); } if (bindings != null) { - Value bound = bindings.getValue(var.getName()); - if (bound != null) { - return bound; - } + return bindings.getValue(var.getName()); } return null; } private long resolveId(Value value) throws Exception { if (value == null) { - return org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + return LmdbValue.UNKNOWN_ID; } - if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { - org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; - if (valueStore.getRevision().equals(lmdbValue.getValueStoreRevision())) { - return lmdbValue.getInternalID(); + if (value instanceof LmdbValue) { + LmdbValue lmdb = (LmdbValue) value; + if (valueStore.getRevision().equals(lmdb.getValueStoreRevision())) { + return lmdb.getInternalID(); } } return valueStore.getId(value); } - - private StatementPattern toStatementPattern(long[] patternIds, String[] varNames) throws QueryEvaluationException { - if (patternIds == null || varNames == null || patternIds.length != 4 || varNames.length != 4) { - throw new IllegalArgumentException("Pattern arrays must have length 4"); - } - org.eclipse.rdf4j.query.algebra.Var subj = buildVar(patternIds[0], varNames[0], "s", true, false); - org.eclipse.rdf4j.query.algebra.Var pred = buildVar(patternIds[1], varNames[1], "p", false, true); - org.eclipse.rdf4j.query.algebra.Var obj = buildVar(patternIds[2], varNames[2], "o", false, false); - org.eclipse.rdf4j.query.algebra.Var ctx = buildContextVar(patternIds[3], varNames[3]); - return new StatementPattern(subj, pred, obj, ctx); - } - - private org.eclipse.rdf4j.query.algebra.Var buildVar(long id, String name, String placeholder, - boolean requireResource, - boolean requireIri) throws QueryEvaluationException { - if (name != null) { - return new org.eclipse.rdf4j.query.algebra.Var(name); - } - if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { - // Should not occur for subject/predicate/object when derived from StatementPattern - return new org.eclipse.rdf4j.query.algebra.Var(placeholder); - } - try { - org.eclipse.rdf4j.model.Value value = valueStore.getLazyValue(id); - if (value == null) { - throw new QueryEvaluationException("Unable to resolve value for ID " + id); - } - if (requireResource && !(value instanceof org.eclipse.rdf4j.model.Resource)) { - throw new QueryEvaluationException("Expected resource value for subject ID " + id); - } - if (requireIri && !(value instanceof org.eclipse.rdf4j.model.IRI)) { - throw new QueryEvaluationException("Expected IRI value for predicate ID " + id); - } - return new org.eclipse.rdf4j.query.algebra.Var(placeholder, value); - } catch (Exception e) { - if (e instanceof QueryEvaluationException) { - throw (QueryEvaluationException) e; - } - throw new QueryEvaluationException(e); - } - } - - private org.eclipse.rdf4j.query.algebra.Var buildContextVar(long id, String name) throws QueryEvaluationException { - if (name != null) { - return new org.eclipse.rdf4j.query.algebra.Var(name); - } - if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { - return null; - } - try { - org.eclipse.rdf4j.model.Value value = valueStore.getLazyValue(id); - if (value == null) { - throw new QueryEvaluationException("Unable to resolve context value for ID " + id); - } - if (!(value instanceof org.eclipse.rdf4j.model.Resource)) { - throw new QueryEvaluationException("Context ID " + id + " does not map to a resource"); - } - org.eclipse.rdf4j.model.Resource ctx = (org.eclipse.rdf4j.model.Resource) value; - if (ctx.isTriple()) { - throw new QueryEvaluationException("Triple-valued contexts are not supported"); - } - return new org.eclipse.rdf4j.query.algebra.Var("ctx", ctx); - } catch (Exception e) { - if (e instanceof QueryEvaluationException) { - throw (QueryEvaluationException) e; - } - throw new QueryEvaluationException(e); - } - } } 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 84eb5a5305..ac9f0165e7 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 @@ -18,6 +18,8 @@ import java.util.Arrays; import java.util.Comparator; import java.util.EnumSet; +import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -88,16 +90,16 @@ public void close() { // Precomputed lookup: for each bound-mask (bits for S=1,P=2,O=4,C=8), the union of // supported StatementOrder across all configured indexes that are compatible with that mask. @SuppressWarnings("unchecked") - private volatile EnumSet[] supportedOrdersLookup = (EnumSet[]) new EnumSet[16]; + private final EnumSet[] supportedOrdersLookup = (EnumSet[]) new EnumSet[16]; @SuppressWarnings("unchecked") - private volatile java.util.List[] compatibleIndexesByMask = (java.util.List[]) new java.util.List[16]; + private final List[] compatibleIndexesByMask = (List[]) new List[16]; // firstFreeOrderByIndexAndMask[indexPos][mask] -> first free StatementOrder in that index for that mask, or null - private volatile StatementOrder[][] firstFreeOrderByIndexAndMask; + private StatementOrder[][] firstFreeOrderByIndexAndMask; // Map index instance -> its stable position used in the lookup arrays - private volatile Map indexPositionMap; + private Map indexPositionMap; private final ExecutorService tripleStoreExecutor = Executors.newCachedThreadPool(); private final CircularBuffer opQueue = new CircularBuffer<>(1024); @@ -118,7 +120,7 @@ public void close() { * * @param Type of elements within this buffer */ - final class CircularBuffer { + static final class CircularBuffer { private final T[] elements; private volatile int head = 0; @@ -308,7 +310,7 @@ private void buildSupportedOrdersLookup(EnumSet[] table) { seqs[i] = indexes.get(i).getFieldSeq(); } // Build index position map - Map posMap = new java.util.IdentityHashMap<>(); + Map posMap = new IdentityHashMap<>(); for (int i = 0; i < indexes.size(); i++) { posMap.put(indexes.get(i), i); } @@ -548,19 +550,19 @@ CloseableIteration createStatementIterator( Resource cachedS = null; IRI cachedP = null; Value cachedO = null; - if (sBound && subj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (sBound && subj instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) subj).getValueStoreRevision())) { + .equals(((LmdbValue) subj).getValueStoreRevision())) { cachedS = subj; } - if (pBound && pred instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (pBound && pred instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) pred).getValueStoreRevision())) { + .equals(((LmdbValue) pred).getValueStoreRevision())) { cachedP = pred; } - if (oBound && obj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (oBound && obj instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) obj).getValueStoreRevision())) { + .equals(((LmdbValue) obj).getValueStoreRevision())) { cachedO = obj; } LmdbStatementIterator.StatementCreator creator = new LmdbStatementIterator.StatementCreator(valueStore, @@ -592,27 +594,27 @@ CloseableIteration createStatementIterator( IRI cachedP = null; Value cachedO = null; Resource cachedC = null; - if (sBound && subj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (sBound && subj instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) subj).getValueStoreRevision())) { + .equals(((LmdbValue) subj).getValueStoreRevision())) { cachedS = subj; } - if (pBound && pred instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (pBound && pred instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) pred).getValueStoreRevision())) { + .equals(((LmdbValue) pred).getValueStoreRevision())) { cachedP = pred; } - if (oBound && obj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (oBound && obj instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) obj).getValueStoreRevision())) { + .equals(((LmdbValue) obj).getValueStoreRevision())) { cachedO = obj; } // If exactly one context was provided and is revision-compatible LmdbValue, pass it if (contexts.length == 1) { Resource ctx = contexts[0]; - if (ctx != null && !ctx.isTriple() && ctx instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (ctx != null && !ctx.isTriple() && ctx instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) ctx).getValueStoreRevision())) { + .equals(((LmdbValue) ctx).getValueStoreRevision())) { cachedC = ctx; } } @@ -1247,39 +1249,36 @@ public CloseableIteration getStatements(StatementOrder stat boolean pBound = pred != null; boolean oBound = obj != null; boolean cBound; - if (contexts == null || contexts.length == 0) { - cBound = false; - } else { - cBound = true; // exactly one context allowed at this point - } + // exactly one context allowed at this point + cBound = contexts != null && contexts.length != 0; Resource cachedS = null; IRI cachedP = null; Value cachedO = null; Resource cachedC = null; - if (sBound && subj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (sBound && subj instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) subj).getValueStoreRevision())) { + .equals(((LmdbValue) subj).getValueStoreRevision())) { cachedS = subj; } - if (pBound && pred instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (pBound && pred instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) pred).getValueStoreRevision())) { + .equals(((LmdbValue) pred).getValueStoreRevision())) { cachedP = pred; } - if (oBound && obj instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + if (oBound && obj instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) obj).getValueStoreRevision())) { + .equals(((LmdbValue) obj).getValueStoreRevision())) { cachedO = obj; } - if (cBound && contexts != null && contexts.length == 1) { + if (cBound) { Resource ctx = contexts[0]; if (ctx != null && !ctx.isTriple() - && ctx instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue + && ctx instanceof LmdbValue && valueStore.getRevision() - .equals(((org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) ctx) + .equals(((LmdbValue) ctx) .getValueStoreRevision())) { cachedC = ctx; } @@ -1321,50 +1320,6 @@ public Set getSupportedOrders(Resource subj, IRI pred, Value obj return res.isEmpty() ? Set.of() : EnumSet.copyOf(res); } - private boolean isIndexCompatible(char[] seq, boolean sBound, boolean pBound, boolean oBound, boolean cBound) { - boolean seenUnbound = false; - for (char f : seq) { - boolean bound = isBound(f, sBound, pBound, oBound, cBound); - if (!bound) { - seenUnbound = true; - } else if (seenUnbound) { - // bound after an unbound earlier field -> cannot use index prefix, not compatible - return false; - } - } - return true; - } - - private boolean isBound(char f, boolean sBound, boolean pBound, boolean oBound, boolean cBound) { - switch (f) { - case 's': - return sBound; - case 'p': - return pBound; - case 'o': - return oBound; - case 'c': - return cBound; - default: - return false; - } - } - - private StatementOrder toStatementOrder(char f) { - switch (f) { - case 's': - return StatementOrder.S; - case 'p': - return StatementOrder.P; - case 'o': - return StatementOrder.O; - case 'c': - return StatementOrder.C; - default: - throw new IllegalArgumentException("Unknown field: " + f); - } - } - @Override public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException { @@ -1418,8 +1373,77 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); - return new RecordIterator() { + if (Boolean.getBoolean("rdf4j.lmdb.experimentalScratchReuse")) { + return new RecordIterator() { + private final long[] scratch = Arrays.copyOf(binding, binding.length); + @Override + public long[] next() throws QueryEvaluationException { + try { + long[] quad; + while ((quad = base.next()) != null) { + boolean conflict = false; + // subject + if (subjIndex >= 0) { + long baseVal = binding[subjIndex]; + long v = quad[TripleStore.SUBJ_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[subjIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + // predicate + if (!conflict && predIndex >= 0) { + long baseVal = binding[predIndex]; + long v = quad[TripleStore.PRED_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[predIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + // object + if (!conflict && objIndex >= 0) { + long baseVal = binding[objIndex]; + long v = quad[TripleStore.OBJ_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[objIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + // context (0 means default graph); treat like any other ID for conflict + if (!conflict && ctxIndex >= 0) { + long baseVal = binding[ctxIndex]; + long v = quad[TripleStore.CONTEXT_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[ctxIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + + if (!conflict) { + return scratch; + } + } + return null; + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public void close() { + base.close(); + } + }; + } + + return new RecordIterator() { @Override public long[] next() throws QueryEvaluationException { try { @@ -1492,8 +1516,11 @@ public boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int o long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); try { - if (orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery) != null) { - return true; + try (RecordIterator recordIterator = orderedRecordIterator(order, subjQuery, predQuery, objQuery, + ctxQuery)) { + if (recordIterator != null) { + return true; + } } return order == StatementOrder.S && indexForOrder(order, subjIndex, predIndex, objIndex, ctxIndex) >= 0; } catch (IOException e) { @@ -1550,9 +1577,10 @@ public RecordIterator getOrderedRecordIterator(StatementPattern pattern, Binding return emptyRecordIterator(); } - RecordIterator orderedIter = orderedRecordIterator(order, subjID, predID, objID, contextID); - if (orderedIter != null) { - return orderedIter; + try (RecordIterator orderedIter = orderedRecordIterator(order, subjID, predID, objID, contextID)) { + if (orderedIter != null) { + return orderedIter; + } } return null; } catch (IOException e) { @@ -1601,9 +1629,7 @@ private Value resolveValue(Var var, BindingSet bindings) { } if (bindings != null) { Value bound = bindings.getValue(var.getName()); - if (bound != null) { - return bound; - } + return bound; } return null; } @@ -1623,7 +1649,7 @@ private PatternArrays describePattern(StatementPattern pattern) throws IOExcepti return new PatternArrays(ids, varNames, valid); } - private boolean populateSubject(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + private boolean populateSubject(Var var, long[] ids, String[] varNames) throws IOException { if (var == null) { ids[TripleStore.SUBJ_IDX] = LmdbValue.UNKNOWN_ID; @@ -1648,7 +1674,7 @@ private boolean populateSubject(org.eclipse.rdf4j.query.algebra.Var var, long[] return true; } - private boolean populatePredicate(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + private boolean populatePredicate(Var var, long[] ids, String[] varNames) throws IOException { if (var == null) { ids[TripleStore.PRED_IDX] = LmdbValue.UNKNOWN_ID; @@ -1673,7 +1699,7 @@ private boolean populatePredicate(org.eclipse.rdf4j.query.algebra.Var var, long[ return true; } - private boolean populateObject(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + private boolean populateObject(Var var, long[] ids, String[] varNames) throws IOException { if (var == null) { ids[TripleStore.OBJ_IDX] = LmdbValue.UNKNOWN_ID; @@ -1695,7 +1721,7 @@ private boolean populateObject(org.eclipse.rdf4j.query.algebra.Var var, long[] i return true; } - private boolean populateContext(org.eclipse.rdf4j.query.algebra.Var var, long[] ids, String[] varNames) + private boolean populateContext(Var var, long[] ids, String[] varNames) throws IOException { if (var == null) { ids[TripleStore.CONTEXT_IDX] = LmdbValue.UNKNOWN_ID; @@ -1730,9 +1756,7 @@ private long selectQueryId(long patternId, long[] binding, int index) { } if (index >= 0 && index < binding.length) { long bound = binding[index]; - if (bound != LmdbValue.UNKNOWN_ID) { - return bound; - } + return bound; } return LmdbValue.UNKNOWN_ID; } @@ -1782,13 +1806,13 @@ private long resolveIdWithBindings(long patternId, String varName, BindingSet bi if (bound == null) { return LmdbValue.UNKNOWN_ID; } - if (requireResource && !(bound instanceof Resource)) { + if (requireResource && !(bound.isResource())) { return INVALID_ID; } - if (requireIri && !(bound instanceof IRI)) { + if (requireIri && !(bound.isIRI())) { return INVALID_ID; } - if (bound instanceof Resource && ((Resource) bound).isTriple()) { + if (bound.isTriple()) { return INVALID_ID; } long id = resolveId(bound); @@ -1843,8 +1867,8 @@ private long resolveId(Value value) throws IOException { if (value == null) { return LmdbValue.UNKNOWN_ID; } - if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { - org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (value instanceof LmdbValue) { + LmdbValue lmdbValue = (LmdbValue) value; if (valueStore.getRevision().equals(lmdbValue.getValueStoreRevision())) { return lmdbValue.getInternalID(); } @@ -1883,18 +1907,17 @@ private int indexForOrder(StatementOrder order, int subjIndex, int predIndex, in private RecordIterator sortedRecordIterator(RecordIterator base, int sortIndex) throws QueryEvaluationException { - java.util.List rows = new java.util.ArrayList<>(); - try { + List rows = new ArrayList<>(); + try (base) { long[] next; while ((next = base.next()) != null) { - rows.add(next); + // Ensure buffered rows are immutable snapshots regardless of iterator reuse + rows.add(next.clone()); } - } finally { - base.close(); } - rows.sort(java.util.Comparator.comparingLong(a -> a[sortIndex])); + rows.sort(Comparator.comparingLong(a -> a[sortIndex])); - java.util.Iterator iterator = rows.iterator(); + Iterator iterator = rows.iterator(); return new RecordIterator() { @Override public long[] next() { @@ -1921,7 +1944,7 @@ private TripleStore.TripleIndex chooseIndexForOrder(StatementOrder order, long s boolean cBound = c >= 0; // 0 is a concrete (null) context; unknown is -1 int mask = (sBound ? 1 : 0) | (pBound ? (1 << 1) : 0) | (oBound ? (1 << 2) : 0) | (cBound ? (1 << 3) : 0); - java.util.List compat = compatibleIndexesByMask[mask]; + List compat = compatibleIndexesByMask[mask]; if (compat == null || compat.isEmpty()) { return null; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 78d8223a4e..07e963035a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -102,9 +102,9 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, IdBindingInfo info = null; for (RawPattern raw : rawPatterns) { if (info == null) { - info = IdBindingInfo.fromFirstPattern(raw.patternInfo); + info = IdBindingInfo.fromFirstPattern(raw.patternInfo, context); } else { - info = IdBindingInfo.combine(info, raw.patternInfo); + info = IdBindingInfo.combine(info, raw.patternInfo, context); } } this.finalInfo = info; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index a6b426f469..55e0f7f140 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -13,8 +13,10 @@ import static org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Optional; import java.util.Set; @@ -178,6 +180,17 @@ public CloseableIteration evaluate(BindingSet bindings) { if (fallbackStep != null && dataset.hasTransactionChanges()) { return fallbackStep.evaluate(bindings); } + + if (Boolean.getBoolean("rdf4j.lmdb.experimentalTwoPatternArrayJoin")) { + // Experimental: use the array-API two-pattern BGP pipeline + List patterns = Arrays + .asList(leftPattern, rightPattern); + LmdbIdBGPQueryEvaluationStep bgpStep = new LmdbIdBGPQueryEvaluationStep( + new Join(leftPattern, rightPattern), patterns, context, fallbackStep); + return bgpStep.evaluate(bindings); + } + + // Default: materialize right side via BindingSet and use LmdbIdJoinIterator ValueStore valueStore = dataset.getValueStore(); RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java index 04702fd018..cc42d9454e 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -99,8 +99,8 @@ public LmdbIdMergeJoinQueryEvaluationStep(Join join, QueryEvaluationContext cont RawPattern rightRaw = RawPattern.create(rightPattern, valueStore); this.hasInvalidPattern = leftRaw.invalid || rightRaw.invalid; - IdBindingInfo info = IdBindingInfo.fromFirstPattern(leftInfo); - info = IdBindingInfo.combine(info, rightInfo); + IdBindingInfo info = IdBindingInfo.fromFirstPattern(leftInfo, context); + info = IdBindingInfo.combine(info, rightInfo, context); this.bindingInfo = info; StatementOrder leftOrder = determineOrder(leftPattern, leftInfo); From 93d8cc88c2005c73f2ce764c421732e13a7090ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 30 Oct 2025 09:11:56 +0900 Subject: [PATCH 48/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbSailStore.java | 2 +- .../join/LmdbIdFinalBindingSetIteration.java | 73 ++++++---- .../sail/lmdb/join/LmdbIdJoinIterator.java | 12 +- .../join/LmdbIdJoinQueryEvaluationStep.java | 133 +++++++++++++++++- 4 files changed, 181 insertions(+), 39 deletions(-) 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 ac9f0165e7..033d0d0141 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 @@ -1373,7 +1373,7 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); - if (Boolean.getBoolean("rdf4j.lmdb.experimentalScratchReuse")) { + if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalScratchReuse", "true"))) { return new RecordIterator() { private final long[] scratch = Arrays.copyOf(binding, binding.length); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java index 5c4393caee..1c4c9a6a44 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java @@ -11,9 +11,13 @@ package org.eclipse.rdf4j.sail.lmdb.join; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; import org.eclipse.rdf4j.common.iteration.LookAheadIteration; import org.eclipse.rdf4j.model.Value; @@ -36,7 +40,8 @@ final class LmdbIdFinalBindingSetIteration extends LookAheadIteration constantBindings; - private Map constantValues; + private List constantEntries; + private Map> constantSetters; LmdbIdFinalBindingSetIteration(RecordIterator input, IdBindingInfo info, QueryEvaluationContext context, BindingSet initial, ValueStore valueStore, Map constantBindings) { @@ -46,38 +51,42 @@ final class LmdbIdFinalBindingSetIteration extends LookAheadIteration entries = new ArrayList<>(constantBindings.size()); + for (Map.Entry e : constantBindings.entrySet()) { + String name = e.getKey(); + long id = e.getValue(); + Function getter = context.getValue(name); + BiConsumer setter = context.setBinding(name); + entries.add(new ConstEntry(getter, setter, id)); + } + this.constantEntries = Collections.unmodifiableList(entries); + } } @Override protected BindingSet getNextElement() throws QueryEvaluationException { - // Lazily resolve constant IDs to Values once to avoid repeated lookups - if (constantValues == null && !constantBindings.isEmpty()) { - Map resolved = new HashMap<>(); - for (Map.Entry entry : constantBindings.entrySet()) { - try { - Value v = valueStore.getLazyValue(entry.getValue()); - if (v != null) { - resolved.put(entry.getKey(), v); - } - } catch (IOException e) { - throw new QueryEvaluationException(e); - } - } - constantValues = Collections.unmodifiableMap(resolved); - } + // No global map; each constant entry resolves lazily once. long[] rec; while ((rec = input.next()) != null) { MutableBindingSet bs = context.createBindingSet(initial); - if (!constantBindings.isEmpty()) { - for (Map.Entry entry : constantBindings.entrySet()) { - String name = entry.getKey(); - if (bs.hasBinding(name)) { + if (!constantEntries.isEmpty()) { + for (ConstEntry ce : constantEntries) { + // Only set constants not already present + if (ce.getter.apply(bs) != null) { continue; } - Value constantValue = constantValues.get(name); - if (constantValue != null) { - bs.setBinding(name, constantValue); + if (!ce.valueResolved) { + try { + ce.value = valueStore.getLazyValue(ce.id); + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + ce.valueResolved = true; + } + if (ce.value != null) { + ce.setter.accept(ce.value, bs); } } } @@ -92,4 +101,20 @@ protected BindingSet getNextElement() throws QueryEvaluationException { protected void handleClose() throws QueryEvaluationException { input.close(); } + + private static final class ConstEntry { + final Function getter; + final BiConsumer setter; + final long id; + boolean valueResolved; + Value value; + + ConstEntry(Function getter, + BiConsumer setter, + long id) { + this.getter = getter; + this.setter = setter; + this.id = id; + } + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index bb9e63858a..9c90daada4 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -253,7 +253,10 @@ protected BindingSet getNextElement() throws QueryEvaluationException { if (!matchesJoin(currentLeftRecord, rightRecord)) { continue; } - MutableBindingSet result = context.createBindingSet(currentLeftBinding); + MutableBindingSet result = context.createBindingSet(initialBindings); + if (!leftInfo.applyRecord(currentLeftRecord, result, valueStore)) { + continue; + } if (!rightInfo.applyRecord(rightRecord, result, valueStore)) { continue; } @@ -268,13 +271,8 @@ protected BindingSet getNextElement() throws QueryEvaluationException { return null; } - MutableBindingSet leftBinding = context.createBindingSet(initialBindings); - if (!leftInfo.applyRecord(leftRecord, leftBinding, valueStore)) { - continue; - } - currentLeftRecord = leftRecord; - currentLeftBinding = leftBinding; + currentLeftBinding = null; currentRightIterator = rightFactory.apply(leftRecord); if (currentRightIterator == null) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 55e0f7f140..1601673061 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -35,6 +35,7 @@ import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; @@ -181,13 +182,39 @@ public CloseableIteration evaluate(BindingSet bindings) { return fallbackStep.evaluate(bindings); } - if (Boolean.getBoolean("rdf4j.lmdb.experimentalTwoPatternArrayJoin")) { - // Experimental: use the array-API two-pattern BGP pipeline - List patterns = Arrays - .asList(leftPattern, rightPattern); - LmdbIdBGPQueryEvaluationStep bgpStep = new LmdbIdBGPQueryEvaluationStep( - new Join(leftPattern, rightPattern), patterns, context, fallbackStep); - return bgpStep.evaluate(bindings); + if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalTwoPatternArrayJoin", "true"))) { + ValueStore valueStore = dataset.getValueStore(); + IdBindingInfo bindingInfo = IdBindingInfo.combine( + IdBindingInfo.fromFirstPattern(leftInfo, context), rightInfo, context); + + int subjIdx = indexFor(rightPattern.getSubjectVar(), bindingInfo); + int predIdx = indexFor(rightPattern.getPredicateVar(), bindingInfo); + int objIdx = indexFor(rightPattern.getObjectVar(), bindingInfo); + int ctxIdx = indexFor(rightPattern.getContextVar(), bindingInfo); + long[] patternIds = resolvePatternIds(rightPattern, valueStore); + + long[] initialBinding = createInitialBinding(bindingInfo, bindings, valueStore); + if (initialBinding == null) { + return new org.eclipse.rdf4j.common.iteration.EmptyIteration<>(); + } + + RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); + LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { + long[] snapshot = Arrays.copyOf(initialBinding, initialBinding.length); + for (String name : leftInfo.getVariableNames()) { + int pos = bindingInfo.getIndex(name); + if (pos >= 0) { + long id = leftInfo.getId(leftRecord, name); + if (id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + snapshot[pos] = id; + } + } + } + return dataset.getRecordIterator(snapshot, subjIdx, predIdx, objIdx, ctxIdx, patternIds); + }; + + return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, rightInfo, sharedVariables, context, + bindings, valueStore); } // Default: materialize right side via BindingSet and use LmdbIdJoinIterator @@ -221,4 +248,96 @@ private LmdbEvaluationDataset resolveDataset() { return LmdbEvaluationStrategy.getCurrentDataset() .orElseThrow(() -> new IllegalStateException("No active LMDB dataset available for join evaluation")); } + + private static int indexFor(Var var, IdBindingInfo info) { + if (var == null || var.hasValue()) { + return -1; + } + return info.getIndex(var.getName()); + } + + private static long[] resolvePatternIds(StatementPattern pattern, ValueStore valueStore) + throws QueryEvaluationException { + long[] ids = new long[4]; + ids[org.eclipse.rdf4j.sail.lmdb.TripleStore.SUBJ_IDX] = resolveIdIfConstant(pattern.getSubjectVar(), valueStore, + true, false); + ids[org.eclipse.rdf4j.sail.lmdb.TripleStore.PRED_IDX] = resolveIdIfConstant(pattern.getPredicateVar(), + valueStore, + false, true); + ids[org.eclipse.rdf4j.sail.lmdb.TripleStore.OBJ_IDX] = resolveIdIfConstant(pattern.getObjectVar(), valueStore, + false, false); + ids[org.eclipse.rdf4j.sail.lmdb.TripleStore.CONTEXT_IDX] = resolveIdIfConstant(pattern.getContextVar(), + valueStore, true, false); + return ids; + } + + private static long resolveIdIfConstant(Var var, ValueStore valueStore, boolean requireResource, boolean requireIri) + throws QueryEvaluationException { + if (var == null || !var.hasValue()) { + return org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + } + Value v = var.getValue(); + if (requireResource && !(v instanceof org.eclipse.rdf4j.model.Resource)) { + return Long.MIN_VALUE; + } + if (requireIri && !(v instanceof org.eclipse.rdf4j.model.IRI)) { + return Long.MIN_VALUE; + } + if (v instanceof org.eclipse.rdf4j.model.Resource && ((org.eclipse.rdf4j.model.Resource) v).isTriple()) { + return Long.MIN_VALUE; + } + try { + long id = valueStore.getId(v); + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + id = valueStore.getId(v, true); + } + return id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID ? Long.MIN_VALUE : id; + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } + + private static long[] createInitialBinding(IdBindingInfo info, BindingSet bindings, ValueStore valueStore) + throws QueryEvaluationException { + long[] binding = new long[info.size()]; + Arrays.fill(binding, org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID); + if (bindings == null || bindings.isEmpty()) { + return binding; + } + for (String name : info.getVariableNames()) { + Value value = bindings.getValue(name); + if (value == null) { + continue; + } + long id = resolveId(valueStore, value); + if (id == org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return null; + } + int index = info.getIndex(name); + if (index >= 0) { + binding[index] = id; + } + } + return binding; + } + + private static long resolveId(ValueStore valueStore, Value value) throws QueryEvaluationException { + if (value == null) { + return org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID; + } + if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { + org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { + return id; + } + } + } + try { + return valueStore.getId(value); + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } } From e1ff211d7fab6ea86e54299a941e918d7b49517f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 30 Oct 2025 09:19:26 +0900 Subject: [PATCH 49/79] working on new ID based join iterator --- .../lmdb/join/LmdbIdMergeJoinIterator.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java index 4d10342514..3d5e26cf36 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java @@ -118,12 +118,9 @@ private boolean advanceLeft() throws QueryEvaluationException { while (leftIterator.hasNext()) { long[] candidate = leftIterator.next(); - MutableBindingSet binding = context.createBindingSet(initialBindings); - if (!leftInfo.applyRecord(candidate, binding, valueStore)) { - continue; - } + // Defer left materialization; keep only the ID record. currentLeftRecord = candidate; - currentLeftBinding = binding; + currentLeftBinding = null; currentLeftKey = key(leftInfo, candidate); hasCurrentLeftKey = true; return true; @@ -190,11 +187,14 @@ private void doLeftPeek() throws QueryEvaluationException { } private BindingSet joinWithCurrentLeft(long[] rightRecord) throws QueryEvaluationException { - MutableBindingSet result = context.createBindingSet(currentLeftBinding); - if (rightInfo.applyRecord(rightRecord, result, valueStore)) { - return result; + MutableBindingSet result = context.createBindingSet(initialBindings); + if (!leftInfo.applyRecord(currentLeftRecord, result, valueStore)) { + return null; } - return null; + if (!rightInfo.applyRecord(rightRecord, result, valueStore)) { + return null; + } + return result; } private int compare(long left, long right) { From f6e6cf7e4ed0aa879352e8337faba23adfc75fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 30 Oct 2025 17:37:13 +0900 Subject: [PATCH 50/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDelegatingSailDataset.java | 122 +++++++++ .../sail/lmdb/LmdbEvaluationStrategy.java | 25 +- .../rdf4j/sail/lmdb/LmdbIdTripleSource.java | 46 ++++ .../sail/lmdb/LmdbIdTripleSourceAdapter.java | 242 ++++++++++++++++++ .../lmdb/LmdbOverlayEvaluationDataset.java | 36 +++ .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 14 + .../lmdb/LmdbSailDatasetTripleSource.java | 235 +++++++++++++++++ .../rdf4j/sail/lmdb/LmdbStoreConnection.java | 9 +- .../rdf4j/sail/lmdb/LmdbUnionSailDataset.java | 196 ++++++++++++++ .../sail/lmdb/benchmark/QueryBenchmark.java | 24 +- 10 files changed, 927 insertions(+), 22 deletions(-) create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java new file mode 100644 index 0000000000..df09e2c942 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Comparator; +import java.util.Set; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Namespace; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.base.SailDataset; + +/** + * LMDB-aware delegating dataset that forwards ID-level calls when the delegate supports them, otherwise conservatively + * falls back to value-based conversion using a ValueStore. + */ +final class LmdbDelegatingSailDataset implements SailDataset, LmdbEvaluationDataset { + + private final SailDataset delegate; + private final ValueStore valueStore; + + LmdbDelegatingSailDataset(SailDataset delegate, ValueStore valueStore) { + this.delegate = delegate; + this.valueStore = valueStore; + } + + @Override + public void close() throws SailException { + delegate.close(); + } + + @Override + public CloseableIteration getNamespaces() throws SailException { + return delegate.getNamespaces(); + } + + @Override + public String getNamespace(String prefix) throws SailException { + return delegate.getNamespace(prefix); + } + + @Override + public CloseableIteration getContextIDs() throws SailException { + return delegate.getContextIDs(); + } + + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) throws SailException { + return delegate.getStatements(subj, pred, obj, contexts); + } + + @Override + public CloseableIteration getStatements(StatementOrder statementOrder, Resource subj, IRI pred, + Value obj, Resource... contexts) throws SailException { + return delegate.getStatements(statementOrder, subj, pred, obj, contexts); + } + + @Override + public Set getSupportedOrders(Resource subj, IRI pred, Value obj, Resource... contexts) { + return delegate.getSupportedOrders(subj, pred, obj, contexts); + } + + @Override + public Comparator getComparator() { + return delegate.getComparator(); + } + + @Override + public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.StatementPattern pattern, + org.eclipse.rdf4j.query.BindingSet bindings) throws QueryEvaluationException { + if (delegate instanceof LmdbEvaluationDataset) { + return ((LmdbEvaluationDataset) delegate).getRecordIterator(pattern, bindings); + } + // Fallback via overlay helper using ValueStore (conservative) + LmdbOverlayEvaluationDataset helper = new LmdbOverlayEvaluationDataset( + new LmdbSailDatasetTripleSource(valueStore, delegate), valueStore); + return helper.getRecordIterator(pattern, bindings); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws QueryEvaluationException { + if (delegate instanceof LmdbEvaluationDataset) { + return ((LmdbEvaluationDataset) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, + ctxIndex, patternIds); + } + // Fallback via TripleSource with Value conversion + return new LmdbSailDatasetTripleSource(valueStore, delegate) + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + if (delegate instanceof LmdbEvaluationDataset) { + return ((LmdbEvaluationDataset) delegate).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, + ctxIndex, patternIds, order); + } + return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order); + } + + @Override + public ValueStore getValueStore() { + return valueStore; + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java index ce2287240b..ac22a1a3cb 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationStrategy.java @@ -56,7 +56,10 @@ public QueryEvaluationStep precompile(TupleExpr expr) { ValueStore valueStore = datasetRef != null ? datasetRef.getValueStore() : null; LmdbEvaluationDataset effectiveDataset = datasetRef; if (connectionHasChanges() && valueStore != null) { - effectiveDataset = new LmdbOverlayEvaluationDataset(tripleSource, valueStore); + TripleSource idCapableTs = (tripleSource instanceof LmdbIdTripleSource) + ? tripleSource + : new LmdbIdTripleSourceAdapter(tripleSource, valueStore); + effectiveDataset = new LmdbOverlayEvaluationDataset(idCapableTs, valueStore); } LmdbQueryEvaluationContext baseContext = new LmdbQueryEvaluationContext(dataset, tripleSource.getValueFactory(), tripleSource.getComparator(), effectiveDataset, valueStore); @@ -97,7 +100,10 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) defaultStep); if (step.shouldUseFallbackImmediately()) { if (valueStoreOpt.isPresent()) { - LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + TripleSource idCapableTs = (tripleSource instanceof LmdbIdTripleSource) + ? tripleSource + : new LmdbIdTripleSourceAdapter(tripleSource, valueStoreOpt.get()); + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(idCapableTs, valueStoreOpt.get()); QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, @@ -109,7 +115,10 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) } boolean hasPreBound = hasPreBoundVariables(patterns); if ((requiresSnapshotOverlay(ds) || hasPreBound) && valueStoreOpt.isPresent()) { - LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + TripleSource idCapableTs = (tripleSource instanceof LmdbIdTripleSource) + ? tripleSource + : new LmdbIdTripleSourceAdapter(tripleSource, valueStoreOpt.get()); + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(idCapableTs, valueStoreOpt.get()); QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, valueStoreOpt.get()); @@ -127,7 +136,10 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) defaultStep); if (step.shouldUseFallbackImmediately()) { if (valueStoreOpt.isPresent()) { - LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + TripleSource idCapableTs = (tripleSource instanceof LmdbIdTripleSource) + ? tripleSource + : new LmdbIdTripleSourceAdapter(tripleSource, valueStoreOpt.get()); + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(idCapableTs, valueStoreOpt.get()); QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, @@ -138,7 +150,10 @@ protected QueryEvaluationStep prepare(Join node, QueryEvaluationContext context) return defaultStep; } if (requiresSnapshotOverlay(ds) && valueStoreOpt.isPresent()) { - LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(tripleSource, + TripleSource idCapableTs = (tripleSource instanceof LmdbIdTripleSource) + ? tripleSource + : new LmdbIdTripleSourceAdapter(tripleSource, valueStoreOpt.get()); + LmdbOverlayEvaluationDataset overlay = new LmdbOverlayEvaluationDataset(idCapableTs, valueStoreOpt.get()); QueryEvaluationContext overlayContext = new LmdbDelegatingQueryEvaluationContext(context, overlay, valueStoreOpt.get()); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java new file mode 100644 index 0000000000..b40476965b --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.query.QueryEvaluationException; + +/** + * LMDB extension of TripleSource that supports ID-level statement access without materializing RDF4J Value objects. + */ +@InternalUseOnly +public interface LmdbIdTripleSource { + + /** + * Create an iterator over ID-level bindings for the given pattern and binding snapshot. + * + * @param binding current binding snapshot (read-only for implementations) + * @param subjIndex index in binding for subject var, or -1 + * @param predIndex index in binding for predicate var, or -1 + * @param objIndex index in binding for object var, or -1 + * @param ctxIndex index in binding for context var, or -1 + * @param patternIds constants for S/P/O/C positions (UNKNOWN_ID for wildcard) + */ + RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws QueryEvaluationException; + + /** + * Create an ordered iterator over ID-level bindings; may fall back to the unordered iterator if unsupported. + */ + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + return null; + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java new file mode 100644 index 0000000000..eae5161dc7 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Comparator; +import java.util.Set; + +import org.eclipse.rdf4j.common.annotation.Experimental; +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +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.model.ValueFactory; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.sail.TripleSourceIterationWrapper; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +/** + * Adapter that adds ID-level access to an arbitrary TripleSource by resolving IDs via the provided ValueStore. This + * does not avoid materialization when the underlying TripleSource cannot serve IDs directly; it centralizes the + * fallback for overlay scenarios. + */ +final class LmdbIdTripleSourceAdapter implements TripleSource, LmdbIdTripleSource { + + private final TripleSource delegate; + private final ValueStore valueStore; + + LmdbIdTripleSourceAdapter(TripleSource delegate, ValueStore valueStore) { + this.delegate = delegate; + this.valueStore = valueStore; + } + + // TripleSource delegation + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) throws QueryEvaluationException { + CloseableIteration statements = delegate.getStatements(subj, pred, obj, contexts); + if (statements instanceof EmptyIteration) { + return statements; + } + return new TripleSourceIterationWrapper<>(statements); + } + + @Override + @Experimental + public CloseableIteration getStatements(org.eclipse.rdf4j.common.order.StatementOrder order, + Resource subj, IRI pred, Value obj, Resource... contexts) throws QueryEvaluationException { + return delegate.getStatements(order, subj, pred, obj, contexts); + } + + @Override + @Experimental + public Set getSupportedOrders(Resource subj, IRI pred, Value obj, + Resource... contexts) { + return delegate.getSupportedOrders(subj, pred, obj, contexts); + } + + @Override + @Experimental + public Comparator getComparator() { + return delegate.getComparator(); + } + + @Override + public ValueFactory getValueFactory() { + return delegate.getValueFactory(); + } + + // ID-level implementation + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws QueryEvaluationException { + // Prefer direct ID-level access if the delegate already supports it + if (delegate instanceof LmdbIdTripleSource) { + return ((LmdbIdTripleSource) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds); + } + + // If no active connection changes, delegate to the current LMDB dataset to avoid materialization + if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { + var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); + if (dsOpt.isPresent()) { + return dsOpt.get().getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + } + + // Fallback: materializing path via delegate TripleSource (only when unavoidable) + Value subjValue = valueForQuery(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex, true, false); + Resource subjRes = subjValue == null ? null : (Resource) subjValue; + Value predValue = valueForQuery(patternIds[TripleStore.PRED_IDX], binding, predIndex, false, true); + IRI predIri = predValue == null ? null : (IRI) predValue; + Value objValue = valueForQuery(patternIds[TripleStore.OBJ_IDX], binding, objIndex, false, false); + long ctxQueryId = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + boolean requireDefaultContext = ctxQueryId == 0; + Resource[] contexts; + if (ctxQueryId > 0) { + try { + Value ctxValue = valueStore.getLazyValue(ctxQueryId); + if (!(ctxValue instanceof Resource) || ((Resource) ctxValue).isTriple()) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + contexts = new Resource[] { (Resource) ctxValue }; + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + } else { + contexts = new Resource[0]; + } + + CloseableIteration stmts = contexts.length == 0 + ? delegate.getStatements(subjRes, predIri, objValue) + : delegate.getStatements(subjRes, predIri, objValue, contexts); + + final boolean defaultOnly = requireDefaultContext; + return new RecordIterator() { + @Override + public long[] next() throws QueryEvaluationException { + while (true) { + try { + if (!stmts.hasNext()) { + stmts.close(); + return null; + } + Statement st = stmts.next(); + if (defaultOnly && st.getContext() != null) { + continue; + } + long subjId = resolveId(st.getSubject()); + long predId = resolveId(st.getPredicate()); + long objId = resolveId(st.getObject()); + long ctxId = st.getContext() == null ? 0L : resolveId(st.getContext()); + long[] merged = mergeBinding(binding, subjId, predId, objId, ctxId, subjIndex, predIndex, + objIndex, ctxIndex); + if (merged != null) { + return merged; + } + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + try { + stmts.close(); + } catch (Exception ignore) { + } + throw new QueryEvaluationException(e); + } + } + } + + @Override + public void close() { + try { + stmts.close(); + } catch (Exception ignore) { + } + } + }; + } + + private long selectQueryId(long patternId, long[] binding, int index) { + if (patternId != LmdbValue.UNKNOWN_ID) { + return patternId; + } + if (index >= 0 && index < binding.length) { + return binding[index]; + } + return LmdbValue.UNKNOWN_ID; + } + + private Value valueForQuery(long patternId, long[] binding, int index, boolean requireResource, boolean requireIri) + throws QueryEvaluationException { + long id = selectQueryId(patternId, binding, index); + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + try { + Value value = valueStore.getLazyValue(id); + if (requireResource && !(value instanceof Resource)) { + throw new QueryEvaluationException("Expected resource-bound value"); + } + if (requireIri && !(value instanceof IRI)) { + throw new QueryEvaluationException("Expected IRI-bound value"); + } + if (value instanceof Resource && value.isTriple()) { + throw new QueryEvaluationException("Triple-valued resources are not supported in LMDB joins"); + } + return value; + } catch (Exception e) { + throw e instanceof QueryEvaluationException ? (QueryEvaluationException) e + : new QueryEvaluationException(e); + } + } + + private long[] mergeBinding(long[] binding, long subjId, long predId, long objId, long ctxId, int subjIndex, + int predIndex, int objIndex, int ctxIndex) { + long[] out = java.util.Arrays.copyOf(binding, binding.length); + if (!applyValue(out, subjIndex, subjId)) + return null; + if (!applyValue(out, predIndex, predId)) + return null; + if (!applyValue(out, objIndex, objId)) + return null; + if (!applyValue(out, ctxIndex, ctxId)) + return null; + return out; + } + + private boolean applyValue(long[] target, int index, long value) { + if (index < 0) + return true; + long existing = target[index]; + if (existing != LmdbValue.UNKNOWN_ID && existing != value) + return false; + target[index] = value; + return true; + } + + private long resolveId(Value value) throws Exception { + if (value == null) { + return LmdbValue.UNKNOWN_ID; + } + if (value instanceof LmdbValue) { + LmdbValue lmdb = (LmdbValue) value; + if (valueStore.getRevision().equals(lmdb.getValueStoreRevision())) { + return lmdb.getInternalID(); + } + } + return valueStore.getId(value); + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 2bab766f97..f8e5b57cd2 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -44,6 +44,18 @@ final class LmdbOverlayEvaluationDataset implements LmdbEvaluationDataset { public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException { + // Fast path: no active connection changes → delegate directly to LMDB dataset to avoid materialization + try { + if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { + var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); + if (dsOpt.isPresent()) { + return dsOpt.get().getRecordIterator(pattern, bindings); + } + } + } catch (Exception ignore) { + // fall through to overlay path + } + Value subj = resolveValue(pattern.getSubjectVar(), bindings); if (subj != null && !(subj instanceof Resource)) { return LmdbIdJoinIterator.emptyRecordIterator(); @@ -119,6 +131,30 @@ public void close() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { + // Fast path: no active connection changes → use the current LMDB dataset's ID-level iterator + try { + if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { + var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); + if (dsOpt.isPresent()) { + return dsOpt.get().getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + } + } catch (Exception ignore) { + // fall back to overlay tripleSource path + } + // Prefer an ID-level path if the TripleSource supports it and we can trust overlay correctness. + if (tripleSource instanceof LmdbIdTripleSource) { + // The overlay dataset is represented by this LmdbOverlayEvaluationDataset; the tripleSource reflects the + // current branch dataset state (including transaction overlays). Therefore, using ID-level access here is + // correct when available. + RecordIterator viaIds = ((LmdbIdTripleSource) tripleSource) + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + if (viaIds != null) { + return viaIds; + } + } + + // Fallback: Value-based overlay path with per-statement ID resolution (minimal unavoidable materialization). try { Value subjValue = valueForQuery(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex, true, false); Resource subjRes = subjValue == null ? null : (Resource) subjValue; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index 957201c4fb..8d97e02ea8 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -57,6 +57,13 @@ class LmdbRecordIterator implements RecordIterator { private final boolean matchValues; private GroupMatcher groupMatcher; + /** + * True when late-bound variables exist beyond the contiguous prefix of the chosen index order, requiring + * value-level filtering. When false, range bounds already guarantee that every visited key matches and the + * GroupMatcher is redundant. + */ + private final boolean needMatcher; + private final Txn txnRef; private long txnRefVersion; @@ -124,6 +131,9 @@ class LmdbRecordIterator implements RecordIterator { } this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; + int prefixLen = index.getPatternScore(subj, pred, obj, context); + int boundCount = (subj > 0 ? 1 : 0) + (pred > 0 ? 1 : 0) + (obj > 0 ? 1 : 0) + (context >= 0 ? 1 : 0); + this.needMatcher = boundCount > prefixLen; this.dbi = index.getDB(explicit); this.txnRef = txnRef; @@ -252,6 +262,10 @@ public long[] next() { } private boolean matches() { + // When there are no late-bound variables beyond the contiguous prefix, range bounds fully determine matches. + if (!needMatcher) { + return false; + } if (groupMatcher != null) { return !this.groupMatcher.matches(keyData.mv_data()); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java new file mode 100644 index 0000000000..218a892a0a --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Arrays; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +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.model.ValueFactory; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.TripleSourceIterationWrapper; +import org.eclipse.rdf4j.sail.base.SailDataset; +import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +/** + * LMDB-aware {@link TripleSource} that exposes ID-level access when supported by the backing dataset. + */ +public class LmdbSailDatasetTripleSource extends SailDatasetTripleSource implements LmdbIdTripleSource { + + private final SailDataset dataset; + + public LmdbSailDatasetTripleSource(ValueFactory vf, SailDataset dataset) { + super(vf, dataset); + this.dataset = dataset; + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws QueryEvaluationException { + + // Fast path: backing dataset supports ID-level access + if (dataset instanceof LmdbEvaluationDataset) { + return ((LmdbEvaluationDataset) dataset).getRecordIterator(binding, subjIndex, predIndex, objIndex, + ctxIndex, patternIds); + } + + // Fallback path: value-level iteration converted to IDs using ValueStore + ValueStore valueStore = LmdbEvaluationStrategy.getCurrentDataset() + .map(LmdbEvaluationDataset::getValueStore) + .orElse(null); + if (valueStore == null) { + // No way to resolve IDs safely; return empty to avoid incorrect results + return LmdbIdJoinIterator.emptyRecordIterator(); + } + + // Resolve fixed values for the pattern using the binding and pattern IDs + Value subjValue = valueForQuery(valueStore, patternIds[TripleStore.SUBJ_IDX], binding, subjIndex, true, false); + Resource subjRes = subjValue == null ? null : (Resource) subjValue; + + Value predValue = valueForQuery(valueStore, patternIds[TripleStore.PRED_IDX], binding, predIndex, false, true); + IRI predIri = predValue == null ? null : (IRI) predValue; + + Value objValue = valueForQuery(valueStore, patternIds[TripleStore.OBJ_IDX], binding, objIndex, false, false); + + long ctxQueryId = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); + boolean requireDefaultContext = ctxQueryId == 0; + Resource[] contexts; + if (ctxQueryId > 0) { + try { + Value ctxValue = valueStore.getLazyValue(ctxQueryId); + if (!(ctxValue instanceof Resource) || ((Resource) ctxValue).isTriple()) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + contexts = new Resource[] { (Resource) ctxValue }; + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + } else { + contexts = new Resource[0]; + } + + try { + final CloseableIteration stmts = contexts.length == 0 + ? dataset.getStatements(subjRes, predIri, objValue) + : dataset.getStatements(subjRes, predIri, objValue, contexts); + + if (stmts instanceof EmptyIteration) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + + return new RecordIterator() { + @Override + public long[] next() throws QueryEvaluationException { + while (true) { + try { + if (!stmts.hasNext()) { + stmts.close(); + return null; + } + Statement st = stmts.next(); + if (requireDefaultContext && st.getContext() != null) { + continue; + } + + long subjId = resolveId(valueStore, st.getSubject()); + long predId = resolveId(valueStore, st.getPredicate()); + long objId = resolveId(valueStore, st.getObject()); + long ctxId = st.getContext() == null ? 0L : resolveId(valueStore, st.getContext()); + + long[] merged = mergeBinding(binding, subjId, predId, objId, ctxId, subjIndex, predIndex, + objIndex, ctxIndex); + if (merged != null) { + return merged; + } + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + try { + stmts.close(); + } catch (Exception ignore) { + } + throw new QueryEvaluationException(e); + } + } + } + + @Override + public void close() { + try { + stmts.close(); + } catch (Exception ignore) { + } + } + }; + } catch (SailException e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + if (dataset instanceof LmdbEvaluationDataset) { + return ((LmdbEvaluationDataset) dataset).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, + ctxIndex, patternIds, order); + } + return LmdbIdTripleSource.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order); + } + + private long selectQueryId(long patternId, long[] binding, int index) { + if (patternId != LmdbValue.UNKNOWN_ID) { + return patternId; + } + if (index >= 0 && index < binding.length) { + return binding[index]; + } + return LmdbValue.UNKNOWN_ID; + } + + private Value valueForQuery(ValueStore valueStore, long patternId, long[] binding, int index, + boolean requireResource, + boolean requireIri) throws QueryEvaluationException { + long id = selectQueryId(patternId, binding, index); + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + try { + Value value = valueStore.getLazyValue(id); + if (requireResource && !(value instanceof Resource)) { + throw new QueryEvaluationException("Expected resource-bound value"); + } + if (requireIri && !(value instanceof IRI)) { + throw new QueryEvaluationException("Expected IRI-bound value"); + } + if (value instanceof Resource && value.isTriple()) { + throw new QueryEvaluationException("Triple-valued resources are not supported in LMDB joins"); + } + return value; + } catch (Exception e) { + throw e instanceof QueryEvaluationException ? (QueryEvaluationException) e + : new QueryEvaluationException(e); + } + } + + private long[] mergeBinding(long[] binding, long subjId, long predId, long objId, long ctxId, int subjIndex, + int predIndex, int objIndex, int ctxIndex) { + long[] out = Arrays.copyOf(binding, binding.length); + if (!applyValue(out, subjIndex, subjId)) { + return null; + } + if (!applyValue(out, predIndex, predId)) { + return null; + } + if (!applyValue(out, objIndex, objId)) { + return null; + } + if (!applyValue(out, ctxIndex, ctxId)) { + return null; + } + return out; + } + + private boolean applyValue(long[] target, int index, long value) { + if (index < 0) { + return true; + } + long existing = target[index]; + if (existing != LmdbValue.UNKNOWN_ID && existing != value) { + return false; + } + target[index] = value; + return true; + } + + private long resolveId(ValueStore valueStore, Value value) throws Exception { + if (value == null) { + return LmdbValue.UNKNOWN_ID; + } + if (value instanceof LmdbValue) { + LmdbValue lmdb = (LmdbValue) value; + if (valueStore.getRevision().equals(lmdb.getValueStoreRevision())) { + return lmdb.getInternalID(); + } + } + return valueStore.getId(value); + } +} 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 bead908f8b..b8e3339fc2 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 @@ -150,15 +150,8 @@ protected CloseableIteration evaluateInternal(TupleExpr tu CloseableIteration base = super.evaluateInternal(tupleExpr, dataset, bindings, includeInferred); success = true; - // ensure that all elements of the binding set are initialized (lazy values are resolved) + // Do not force materialization of lazy values; simply ensure we pop the thread-local flag. return new IterationWrapper(base) { - @Override - public BindingSet next() throws QueryEvaluationException { - BindingSet bs = super.next(); - bs.forEach(b -> initValue(b.getValue())); - return bs; - } - @Override protected void handleClose() throws QueryEvaluationException { try { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java new file mode 100644 index 0000000000..6642fa2739 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Set; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.DualUnionIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Namespace; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Triple; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.base.SailDataset; + +/** + * LMDB-aware union dataset that exposes ID-level access when both sides provide it, with a conservative fallback. + */ +final class LmdbUnionSailDataset implements SailDataset, LmdbEvaluationDataset { + + private final SailDataset dataset1; + private final SailDataset dataset2; + private final ValueStore valueStore; + + LmdbUnionSailDataset(SailDataset dataset1, SailDataset dataset2, ValueStore valueStore) { + this.dataset1 = dataset1; + this.dataset2 = dataset2; + this.valueStore = valueStore; + } + + @Override + public void close() throws SailException { + try { + dataset1.close(); + } finally { + dataset2.close(); + } + } + + @Override + public CloseableIteration getNamespaces() throws SailException { + return DualUnionIteration.getWildcardInstance(dataset1.getNamespaces(), dataset2.getNamespaces()); + } + + @Override + public String getNamespace(String prefix) throws SailException { + String ns = dataset1.getNamespace(prefix); + return ns != null ? ns : dataset2.getNamespace(prefix); + } + + @Override + public CloseableIteration getContextIDs() throws SailException { + return DualUnionIteration.getWildcardInstance(dataset1.getContextIDs(), dataset2.getContextIDs()); + } + + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) throws SailException { + return DualUnionIteration.getWildcardInstance(dataset1.getStatements(subj, pred, obj, contexts), + dataset2.getStatements(subj, pred, obj, contexts)); + } + + @Override + public CloseableIteration getTriples(Resource subj, IRI pred, Value obj) throws SailException { + return DualUnionIteration.getWildcardInstance(dataset1.getTriples(subj, pred, obj), + dataset2.getTriples(subj, pred, obj)); + } + + @Override + public CloseableIteration getStatements(StatementOrder statementOrder, Resource subj, IRI pred, + Value obj, Resource... contexts) throws SailException { + CloseableIteration it1 = dataset1.getStatements(statementOrder, subj, pred, obj, contexts); + CloseableIteration it2 = dataset2.getStatements(statementOrder, subj, pred, obj, contexts); + Comparator cmp = dataset1.getComparator(); + assert cmp != null && dataset2.getComparator() != null; + return DualUnionIteration.getWildcardInstance(statementOrder.getComparator(cmp), it1, it2); + } + + @Override + public Set getSupportedOrders(Resource subj, IRI pred, Value obj, Resource... contexts) { + Set s1 = dataset1.getSupportedOrders(subj, pred, obj, contexts); + if (s1.isEmpty()) + return Set.of(); + Set s2 = dataset2.getSupportedOrders(subj, pred, obj, contexts); + if (s2.isEmpty()) + return Set.of(); + if (s1.equals(s2)) + return s1; + EnumSet common = EnumSet.copyOf(s1); + common.retainAll(s2); + return common; + } + + @Override + public Comparator getComparator() { + Comparator c1 = dataset1.getComparator(); + Comparator c2 = dataset2.getComparator(); + if (c1 == null || c2 == null) + return null; + return c1; + } + + @Override + public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.StatementPattern pattern, + org.eclipse.rdf4j.query.BindingSet bindings) throws org.eclipse.rdf4j.query.QueryEvaluationException { + boolean d1 = dataset1 instanceof LmdbEvaluationDataset; + boolean d2 = dataset2 instanceof LmdbEvaluationDataset; + if (d1 && d2) { + RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getRecordIterator(pattern, bindings); + RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getRecordIterator(pattern, bindings); + return chain(r1, r2); + } + LmdbDelegatingSailDataset a = new LmdbDelegatingSailDataset(dataset1, valueStore); + LmdbDelegatingSailDataset b = new LmdbDelegatingSailDataset(dataset2, valueStore); + return chain(a.getRecordIterator(pattern, bindings), b.getRecordIterator(pattern, bindings)); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds) throws org.eclipse.rdf4j.query.QueryEvaluationException { + boolean d1 = dataset1 instanceof LmdbEvaluationDataset; + boolean d2 = dataset2 instanceof LmdbEvaluationDataset; + if (d1 && d2) { + RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getRecordIterator(binding, subjIndex, predIndex, + objIndex, ctxIndex, patternIds); + RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getRecordIterator(binding, subjIndex, predIndex, + objIndex, ctxIndex, patternIds); + return chain(r1, r2); + } + LmdbDelegatingSailDataset a = new LmdbDelegatingSailDataset(dataset1, valueStore); + LmdbDelegatingSailDataset b = new LmdbDelegatingSailDataset(dataset2, valueStore); + return chain(a.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds), + b.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds)); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order) + throws org.eclipse.rdf4j.query.QueryEvaluationException { + boolean d1 = dataset1 instanceof LmdbEvaluationDataset; + boolean d2 = dataset2 instanceof LmdbEvaluationDataset; + if (d1 && d2) { + RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getOrderedRecordIterator(binding, subjIndex, + predIndex, objIndex, ctxIndex, patternIds, order); + RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getOrderedRecordIterator(binding, subjIndex, + predIndex, objIndex, ctxIndex, patternIds, order); + return chain(r1, r2); + } + return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order); + } + + @Override + public ValueStore getValueStore() { + return valueStore; + } + + private RecordIterator chain(RecordIterator left, RecordIterator right) { + return new RecordIterator() { + boolean leftDone = false; + + @Override + public long[] next() throws org.eclipse.rdf4j.query.QueryEvaluationException { + if (!leftDone) { + long[] n = left.next(); + if (n != null) + return n; + leftDone = true; + } + return right.next(); + } + + @Override + public void close() { + try { + left.close(); + } finally { + right.close(); + } + } + }; + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java index 410474f8a2..5ea0cd451b 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java @@ -124,15 +124,21 @@ public class QueryBenchmark { private File file; - public static void main(String[] args) throws RunnerException { - Options opt = new OptionsBuilder() - .include("QueryBenchmark.complexQuery") // adapt to run other benchmark tests - .warmupIterations(0) - .measurementIterations(10) - .forks(0) - .build(); - - new Runner(opt).run(); + public static void main(String[] args) throws RunnerException, IOException { + QueryBenchmark queryBenchmark = new QueryBenchmark(); + queryBenchmark.beforeClass(); + queryBenchmark.complexQuery(); + queryBenchmark.afterClass(); + +// +// Options opt = new OptionsBuilder() +// .include("QueryBenchmark.complexQuery") // adapt to run other benchmark tests +// .warmupIterations(0) +// .measurementIterations(10) +// .forks(0) +// .build(); +// +// new Runner(opt).run(); } @Setup(Level.Trial) From bf0e3e3dca8a8c80ce20568af27246361fbf2de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 31 Oct 2025 19:08:43 +0900 Subject: [PATCH 51/79] working on new ID based join iterator --- .../java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 97782c8c26..41d0210214 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.lmdb; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.io.File; import java.io.IOException; @@ -165,6 +166,7 @@ public void complexQuery() { count = stream.count(); } System.out.println("count: " + count); + assertEquals(1485, count); } } From 9d88a010ed8245f8f90162aef8c4c52448a8afa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 1 Nov 2025 06:48:12 +0900 Subject: [PATCH 52/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 41d0210214..37e2a61f54 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.rules.TemporaryFolder; /** @@ -148,6 +149,7 @@ public static void afterClass() { } @Test + @Timeout(30) public void groupByQuery() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -159,6 +161,7 @@ public void groupByQuery() { } @Test + @Timeout(30) public void complexQuery() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -171,6 +174,7 @@ public void complexQuery() { } @Test + @Timeout(30) public void distinctPredicatesQuery() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -182,6 +186,7 @@ public void distinctPredicatesQuery() { } @Test + @Timeout(30) public void optionalLhsFilterQueryProducesExpectedCount() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -193,6 +198,7 @@ public void optionalLhsFilterQueryProducesExpectedCount() { } @Test + @Timeout(30) public void optionalRhsFilterQueryProducesExpectedCount() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -204,6 +210,7 @@ public void optionalRhsFilterQueryProducesExpectedCount() { } @Test + @Timeout(30) public void orderedUnionLimitQueryProducesExpectedCount() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -215,6 +222,7 @@ public void orderedUnionLimitQueryProducesExpectedCount() { } @Test + @Timeout(30) public void subSelectQueryProducesExpectedCount() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -226,6 +234,7 @@ public void subSelectQueryProducesExpectedCount() { } @Test + @Timeout(30) public void multipleSubSelectQueryProducesExpectedCount() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; @@ -237,6 +246,7 @@ public void multipleSubSelectQueryProducesExpectedCount() { } @Test + @Timeout(30) public void removeByQuery() { try (SailRepositoryConnection connection = repository.getConnection()) { connection.begin(IsolationLevels.NONE); @@ -251,6 +261,7 @@ public void removeByQuery() { } @Test + @Timeout(30) public void removeByQueryReadCommitted() { try (SailRepositoryConnection connection = repository.getConnection()) { connection.begin(IsolationLevels.READ_COMMITTED); @@ -265,6 +276,7 @@ public void removeByQueryReadCommitted() { } @Test + @Timeout(30) public void simpleUpdateQueryIsolationReadCommitted() { try (SailRepositoryConnection connection = repository.getConnection()) { connection.begin(IsolationLevels.READ_COMMITTED); @@ -282,6 +294,7 @@ public void simpleUpdateQueryIsolationReadCommitted() { } @Test + @Timeout(30) public void simpleUpdateQueryIsolationNone() { try (SailRepositoryConnection connection = repository.getConnection()) { connection.begin(IsolationLevels.NONE); @@ -299,6 +312,7 @@ public void simpleUpdateQueryIsolationNone() { } @Test + @Timeout(30) public void ordered_union_limit() { try (SailRepositoryConnection connection = repository.getConnection()) { long count = count(connection From 3bb3da3678f3d003a055c9d3d19b5d6a37e251ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 1 Nov 2025 06:48:39 +0900 Subject: [PATCH 53/79] working on new ID based join iterator --- AGENTS.md | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 961be562b3..8014456504 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,46 +66,6 @@ It is illegal to `-q` when running tests! When writing complex features or significant refactors, use an ExecPlan (as described in PLANS.md) from design to implementation. -## PIOSEE Decision Model (Adopted) - -Use this as a compact, repeatable loop for anything from a one‑line bug fix to a multi‑quarter program. - -### P — **Problem** - -**Goal:** State the core problem and what “good” looks like. -**Ask:** Who’s affected? What outcome is required? What happens if we do nothing? -**Tip:** Include measurable target(s): error rate ↓, latency p95 ↓, revenue ↑, risk ↓. - -### I — **Information** - -**Goal:** Gather only the facts needed to move. -**Ask:** What do logs/metrics/user feedback say? What constraints (security, compliance, budget, SLA/SLO)? What assumptions must we test? - -### O — **Options** - -**Goal:** Generate viable ways forward, including “do nothing.” -**Ask:** What are 2–4 distinct approaches (patch, redesign, buy vs. build, defer)? What risks, costs, and second‑order effects? -**Tip:** Check guardrails: reliability, security/privacy, accessibility, performance, operability, unit economics. - -### S — **Select** - -**Goal:** Decide deliberately and document why. -**Ask:** Which option best meets the success criteria under constraints? Who is the decision owner? What’s the fallback/abort condition? -**Tip:** Use lightweight scoring (e.g., Impact×Confidence÷Effort) to avoid bike‑shedding. - -### E — **Execute** - -**Goal:** Ship safely and visibly. -**Ask:** What is the smallest safe slice? How do we de‑risk (feature flag, canary, dark launch, rollback)? Who owns what? -**Checklist:** Traces/logs/alerts; security & privacy checks; docs & changelog; incident plan if relevant. - -### E — **Evaluate** - -**Goal:** Verify outcomes and learn. -**Ask:** Did metrics hit targets? Any regressions or side effects? What will we keep/change next loop? -**Output:** Post‑release review (or retro), decision log entry, follow‑ups (tickets), debt captured. -**Tip:** If outcomes miss, either **iterate** (new Options) or **reframe** (back to Problem). - --- ## Proportionality Model (Think before you test) @@ -399,7 +359,6 @@ When writing complex features or significant refactors, use an ExecPlan (as desc ## Working Loop -* **PIOSEE first:** restate Problem, gather Information, list Options; then Select, Execute, Evaluate. * **Plan:** small, verifiable steps; keep one `in_progress`, or follow PLANS.md (ExecPlans) * **Change:** minimal, surgical edits; keep style/structure consistent. * **Format:** `mvn -o -Dmaven.repo.local=.m2_repo -q -T 2C formatter:format impsort:sort xml-format:xml-format` @@ -564,7 +523,6 @@ Do **not** modify existing headers’ years. * **Files touched:** list file paths. * **Commands run:** key build/test commands. * **Verification:** which tests passed, where you checked reports. -* **PIOSEE trace (concise):** P/I/O summary, selected option/routine, key evaluate outcomes. * **Evidence:** *Routine A:* failing output (pre‑fix) and passing output (post‑fix). *Routine B:* pre‑ and post‑green snippets from the **same selection** + **Hit Proof**. From bf395084b9d094fa32462eb541f10531911074c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 1 Nov 2025 07:06:00 +0900 Subject: [PATCH 54/79] working on new ID based join iterator --- .../LmdbRecordIteratorLateBindingTest.java | 82 ++++++++++++++++++ .../LmdbRecordIteratorPrefixScanTest.java | 84 +++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorLateBindingTest.java create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorPrefixScanTest.java diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorLateBindingTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorLateBindingTest.java new file mode 100644 index 0000000000..4f0ce6f9f9 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorLateBindingTest.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbRecordIteratorLateBindingTest { + + @TempDir + File dataDir; + + private TripleStore tripleStore; + + @BeforeEach + void setUp() throws Exception { + tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"), null); + + tripleStore.startTransaction(); + tripleStore.storeTriple(1, 2, 3, 0, true); + tripleStore.storeTriple(1, 5, 6, 0, true); + tripleStore.storeTriple(1, 6, 9, 0, true); + tripleStore.storeTriple(2, 5, 6, 0, true); + tripleStore.commit(); + } + + @AfterEach + void tearDown() throws Exception { + if (tripleStore != null) { + tripleStore.close(); + } + } + + @Test + void subjectObjectPatternStillFiltersWithMatcher() throws Exception { + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + LmdbRecordIterator iter = (LmdbRecordIterator) tripleStore.getTriples(txn, 1, -1, 6, -1, true)) { + assertThat(iter).isInstanceOf(LmdbRecordIterator.class); + assertThat(getBooleanField(iter, "needMatcher")).isTrue(); + + List seen = new ArrayList<>(); + long[] next; + while ((next = iter.next()) != null) { + seen.add(next.clone()); + } + + assertThat(seen).containsExactly(new long[] { 1, 5, 6, 0 }); + assertThat(getField(iter, "groupMatcher")).isNotNull(); + } + } + + private static boolean getBooleanField(Object instance, String name) throws Exception { + Field field = LmdbRecordIterator.class.getDeclaredField(name); + field.setAccessible(true); + return field.getBoolean(instance); + } + + private static Object getField(Object instance, String name) throws Exception { + Field field = LmdbRecordIterator.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(instance); + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorPrefixScanTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorPrefixScanTest.java new file mode 100644 index 0000000000..cd6e87b32e --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIteratorPrefixScanTest.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbRecordIteratorPrefixScanTest { + + @TempDir + File dataDir; + + private TripleStore tripleStore; + + @BeforeEach + void setUp() throws Exception { + tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"), null); + + tripleStore.startTransaction(); + tripleStore.storeTriple(1, 2, 3, 0, true); + tripleStore.storeTriple(1, 5, 6, 0, true); + tripleStore.storeTriple(1, 7, 8, 1, true); + tripleStore.storeTriple(2, 2, 3, 0, true); + tripleStore.commit(); + } + + @AfterEach + void tearDown() throws Exception { + if (tripleStore != null) { + tripleStore.close(); + } + } + + @Test + void subjectPrefixScanSkipsGroupMatcher() throws Exception { + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + LmdbRecordIterator iter = (LmdbRecordIterator) tripleStore.getTriples(txn, 1, -1, -1, -1, true)) { + assertThat(iter).isInstanceOf(LmdbRecordIterator.class); + assertThat(getBooleanField(iter, "needMatcher")).isFalse(); + + List seen = new ArrayList<>(); + long[] next; + while ((next = iter.next()) != null) { + seen.add(next.clone()); + } + + assertThat(seen) + .containsExactlyInAnyOrder(new long[] { 1, 2, 3, 0 }, new long[] { 1, 5, 6, 0 }, + new long[] { 1, 7, 8, 1 }); + assertThat(getField(iter, "groupMatcher")).isNull(); + } + } + + private static boolean getBooleanField(Object instance, String name) throws Exception { + Field field = LmdbRecordIterator.class.getDeclaredField(name); + field.setAccessible(true); + return field.getBoolean(instance); + } + + private static Object getField(Object instance, String name) throws Exception { + Field field = LmdbRecordIterator.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(instance); + } +} From 4ad6a3fd8173c7a70adb26ee79f187da5b71a7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sat, 1 Nov 2025 07:42:00 +0900 Subject: [PATCH 55/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/IdBindingInfo.java | 11 +++-- .../join/LmdbIdFinalBindingSetIteration.java | 4 +- .../sail/lmdb/join/LmdbIdJoinIterator.java | 2 +- .../sail/lmdb/join/LmdbIdJoinSettings.java | 49 +++++++++++++++++++ 4 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinSettings.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java index 48b61283ba..98b5293b57 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java @@ -26,6 +26,7 @@ import org.eclipse.rdf4j.query.algebra.evaluation.ArrayBindingSet; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; +import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinSettings; import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; /** @@ -126,7 +127,7 @@ public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore v // Fallback: resolve and compare Value candidate; try { - candidate = valueStore.getLazyValue(id); + candidate = LmdbIdJoinSettings.resolveValue(valueStore, id); } catch (IOException ex) { throw new QueryEvaluationException(ex); } @@ -136,7 +137,7 @@ public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore v } else { Value candidate; try { - candidate = valueStore.getLazyValue(id); + candidate = LmdbIdJoinSettings.resolveValue(valueStore, id); } catch (IOException ex) { throw new QueryEvaluationException(ex); } @@ -174,7 +175,7 @@ public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore v // Fallback: resolve candidate and compare Value candidate; try { - candidate = valueStore.getLazyValue(id); + candidate = LmdbIdJoinSettings.resolveValue(valueStore, id); } catch (IOException ex) { throw new QueryEvaluationException(ex); } @@ -185,7 +186,7 @@ public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore v // No existing value: resolve once and set via direct setter Value candidate; try { - candidate = valueStore.getLazyValue(id); + candidate = LmdbIdJoinSettings.resolveValue(valueStore, id); } catch (IOException ex) { throw new QueryEvaluationException(ex); } @@ -221,7 +222,7 @@ public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore v } Value candidate; try { - candidate = valueStore.getLazyValue(id); + candidate = LmdbIdJoinSettings.resolveValue(valueStore, id); } catch (IOException ex) { throw new QueryEvaluationException(ex); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java index 1c4c9a6a44..aa9eefee67 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdFinalBindingSetIteration.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -41,7 +40,6 @@ final class LmdbIdFinalBindingSetIteration extends LookAheadIteration constantBindings; private List constantEntries; - private Map> constantSetters; LmdbIdFinalBindingSetIteration(RecordIterator input, IdBindingInfo info, QueryEvaluationContext context, BindingSet initial, ValueStore valueStore, Map constantBindings) { @@ -79,7 +77,7 @@ protected BindingSet getNextElement() throws QueryEvaluationException { } if (!ce.valueResolved) { try { - ce.value = valueStore.getLazyValue(ce.id); + ce.value = LmdbIdJoinSettings.resolveValue(valueStore, ce.id); } catch (IOException e) { throw new QueryEvaluationException(e); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index 9c90daada4..0c2e8ab2ed 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -211,7 +211,7 @@ private Value resolveValue(long id, int position, ValueStore valueStore) throws return null; } try { - return valueStore.getLazyValue(id); + return LmdbIdJoinSettings.resolveValue(valueStore, id); } catch (IOException e) { throw new QueryEvaluationException(e); } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinSettings.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinSettings.java new file mode 100644 index 0000000000..e5d828cc59 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinSettings.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.io.IOException; + +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +/** + * Centralizes runtime switches for LMDB ID-based joins. + */ +public final class LmdbIdJoinSettings { + + private static final String LAZY_MATERIALIZATION_PROPERTY = "rdf4j.lmdb.idJoin.lazyMaterialization"; + + private LmdbIdJoinSettings() { + // no instances + } + + public static boolean lazyMaterializationEnabled() { + String value = System.getProperty(LAZY_MATERIALIZATION_PROPERTY); + if (value == null) { + return true; + } + return !("false".equalsIgnoreCase(value) || "0".equals(value) || "off".equalsIgnoreCase(value)); + } + + public static LmdbValue resolveLmdbValue(ValueStore valueStore, long id) throws IOException { + LmdbValue value = valueStore.getLazyValue(id); + if (value != null && !lazyMaterializationEnabled()) { + value.init(); + } + return value; + } + + public static Value resolveValue(ValueStore valueStore, long id) throws IOException { + return resolveLmdbValue(valueStore, id); + } +} From 675c0635478442da408bf8744254c14760376b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Sun, 2 Nov 2025 09:12:47 +0900 Subject: [PATCH 56/79] working on new ID based join iterator --- .../LmdbIdJoinLazyMaterializationTest.java | 68 +++++++++++++++++++ .../repository/optimistic/DeadLockTest.java | 9 +-- .../optimistic/DeleteInsertTest.java | 2 +- .../optimistic/IsolationLevelTest.java | 24 +++---- 4 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinLazyMaterializationTest.java diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinLazyMaterializationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinLazyMaterializationTest.java new file mode 100644 index 0000000000..faa4a00077 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinLazyMaterializationTest.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.ArrayBindingSet; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; +import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; + +@Isolated +class LmdbIdJoinLazyMaterializationTest { + + private static final String PROPERTY = "rdf4j.lmdb.idJoin.lazyMaterialization"; + + @Test + void eagerMaterializationWhenDisabled() throws Exception { + System.setProperty(PROPERTY, "false"); + try { + ValueStore valueStore = mock(ValueStore.class); + LmdbValue materialized = mock(LmdbValue.class); + when(valueStore.getLazyValue(1L)).thenReturn(materialized); + when(valueStore.getLazyValue(2L)).thenReturn(materialized); + + StatementPattern pattern = new StatementPattern( + new Var("person"), + new Var("predicate", SimpleValueFactory.getInstance().createIRI("http://example.com/p")), + new Var("item")); + LmdbIdJoinIterator.PatternInfo patternInfo = LmdbIdJoinIterator.PatternInfo.create(pattern); + IdBindingInfo info = IdBindingInfo.fromFirstPattern(patternInfo); + + long[] record = new long[] { 1L, 2L }; + MutableBindingSet target = new ArrayBindingSet(new String[] { "person", "item" }); + boolean result = info.applyRecord(record, target, valueStore); + + assertThat(result).isTrue(); + verify(valueStore).getLazyValue(1L); + verify(valueStore).getLazyValue(2L); + verify(materialized, times(2)).init(); + verify(valueStore, never()).getValue(anyLong()); + assertThat(target.getValue("person")).isSameAs(materialized); + assertThat(target.getValue("item")).isSameAs(materialized); + } finally { + System.clearProperty(PROPERTY); + } + } +} diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeadLockTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeadLockTest.java index e02fb007d0..c048e13f2f 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeadLockTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeadLockTest.java @@ -13,6 +13,7 @@ import static org.junit.Assert.assertNull; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.common.transaction.IsolationLevels; @@ -91,7 +92,7 @@ public void test() throws Exception { start.countDown(); a.begin(level); a.add(PICASSO, RDF.TYPE, PAINTER); - commit.await(); + commit.await(10, TimeUnit.SECONDS); a.commit(); } catch (Exception e) { e1.initCause(e); @@ -106,7 +107,7 @@ public void test() throws Exception { start.countDown(); b.begin(level); b.add(REMBRANDT, RDF.TYPE, PAINTER); - commit.await(); + commit.await(10, TimeUnit.SECONDS); b.commit(); } catch (Exception e) { e2.initCause(e); @@ -115,10 +116,10 @@ public void test() throws Exception { end.countDown(); } }).start(); - start.await(); + start.await(10, TimeUnit.SECONDS); commit.countDown(); Thread.sleep(500); - end.await(); + end.await(10, TimeUnit.SECONDS); assertNull(e1.getCause()); assertNull(e2.getCause()); } diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeleteInsertTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeleteInsertTest.java index 01405ca957..9fe7b8c621 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeleteInsertTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/DeleteInsertTest.java @@ -66,7 +66,7 @@ public void tearDown() { } } - @Test + @Test(timeout = 10000) public void test() throws Exception { String load = IOUtil.readString(cl.getResource("test/insert-data.ru")); con.prepareUpdate(QueryLanguage.SPARQL, load, NS).execute(); diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/IsolationLevelTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/IsolationLevelTest.java index 04ca3a0a91..29ef14e034 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/IsolationLevelTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/optimistic/IsolationLevelTest.java @@ -107,7 +107,7 @@ public void testReadUncommitted() { readPending(IsolationLevels.READ_UNCOMMITTED); } - @Test + @Test(timeout = 5000) public void testReadCommitted() throws Exception { readCommitted(IsolationLevels.READ_COMMITTED); rollbackTriple(IsolationLevels.READ_COMMITTED); @@ -194,7 +194,7 @@ private void readCommitted(final IsolationLevel level) throws Exception { Thread writer = new Thread(() -> { try (RepositoryConnection write = store.getConnection()) { start.countDown(); - start.await(); + start.await(10, TimeUnit.SECONDS); write.begin(level); write.add(RDF.NIL, RDF.TYPE, RDF.LIST); begin.countDown(); @@ -207,8 +207,8 @@ private void readCommitted(final IsolationLevel level) throws Exception { Thread reader = new Thread(() -> { try (RepositoryConnection read = store.getConnection()) { start.countDown(); - start.await(); - begin.await(); + start.await(10, TimeUnit.SECONDS); + begin.await(10, TimeUnit.SECONDS); read.begin(level); // must not read uncommitted changes long counted = count(read, RDF.NIL, RDF.TYPE, RDF.LIST, false); @@ -245,13 +245,13 @@ private void repeatableRead(final IsolationLevels level) throws Exception { Thread writer = new Thread(() -> { try (RepositoryConnection write = store.getConnection()) { start.countDown(); - start.await(); + start.await(10, TimeUnit.SECONDS); write.begin(level); write.add(RDF.NIL, RDF.TYPE, RDF.LIST); write.commit(); begin.countDown(); - observed.await(); + observed.await(10, TimeUnit.SECONDS); write.begin(level); write.remove(RDF.NIL, RDF.TYPE, RDF.LIST); @@ -264,8 +264,8 @@ private void repeatableRead(final IsolationLevels level) throws Exception { Thread reader = new Thread(() -> { try (RepositoryConnection read = store.getConnection()) { start.countDown(); - start.await(); - begin.await(); + start.await(10, TimeUnit.SECONDS); + begin.await(10, TimeUnit.SECONDS); read.begin(level); long first = count(read, RDF.NIL, RDF.TYPE, RDF.LIST, false); assertEquals(1, first); @@ -343,7 +343,7 @@ private void snapshot(final IsolationLevels level) throws Exception { try { try (RepositoryConnection write = store.getConnection()) { start.countDown(); - start.await(); + start.await(10, TimeUnit.SECONDS); write.begin(level); insertTestStatement(write, 1); write.commit(); @@ -363,8 +363,8 @@ private void snapshot(final IsolationLevels level) throws Exception { Thread reader = new Thread(() -> { try (RepositoryConnection read = store.getConnection()) { start.countDown(); - start.await(); - begin.await(); + start.await(10, TimeUnit.SECONDS); + begin.await(10, TimeUnit.SECONDS); read.begin(level); long first = count(read, null, null, null, false); observed.countDown(); @@ -431,7 +431,7 @@ protected Thread incrementBy(final CountDownLatch start, final CountDownLatch ob return new Thread(() -> { try (RepositoryConnection con = store.getConnection()) { start.countDown(); - start.await(); + start.await(10, TimeUnit.SECONDS); con.begin(level); Literal o1 = readLiteral(con, subj, pred); observed.countDown(); From 4007b2b7bd9d30e2fd49a26d8b02b83789b27b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 10:01:11 +0900 Subject: [PATCH 57/79] working on new ID based join iterator --- .../sail/lmdb/LmdbIdTripleSourceAdapter.java | 1 - .../lmdb/LmdbSailDatasetTripleSource.java | 1 - .../join/LmdbIdBGPQueryEvaluationStep.java | 2 -- .../join/LmdbIdJoinQueryEvaluationStep.java | 3 --- .../LmdbIdMergeJoinQueryEvaluationStep.java | 2 -- .../lmdb/MultipleSubselectRegressionTest.java | 19 ++++++++++++++----- .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 6 ++++-- .../sail/lmdb/benchmark/QueryBenchmark.java | 3 --- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java index eae5161dc7..9db736e13a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java @@ -16,7 +16,6 @@ import org.eclipse.rdf4j.common.annotation.Experimental; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; -import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java index 218a892a0a..2deacf61aa 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java @@ -23,7 +23,6 @@ import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; import org.eclipse.rdf4j.sail.SailException; -import org.eclipse.rdf4j.sail.TripleSourceIterationWrapper; import org.eclipse.rdf4j.sail.base.SailDataset; import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 07e963035a..d7aea0a095 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -18,8 +18,6 @@ import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; -import org.eclipse.rdf4j.common.transaction.IsolationLevel; -import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 1601673061..5341d695b9 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -16,13 +16,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.Set; import org.eclipse.rdf4j.common.iteration.CloseableIteration; -import org.eclipse.rdf4j.common.transaction.IsolationLevel; -import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java index cc42d9454e..bafe4aeaae 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -17,8 +17,6 @@ import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; import org.eclipse.rdf4j.common.order.StatementOrder; -import org.eclipse.rdf4j.common.transaction.IsolationLevel; -import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java index 9954cfb1fc..a8dc06a872 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/MultipleSubselectRegressionTest.java @@ -21,12 +21,15 @@ import org.apache.commons.io.IOUtils; import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.TupleQuery; +import org.eclipse.rdf4j.query.TupleQueryResult; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.eclipse.rdf4j.rio.RDFFormat; import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; import org.eclipse.rdf4j.sail.memory.MemoryStore; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.io.TempDir; class MultipleSubselectRegressionTest { @@ -45,6 +48,7 @@ class MultipleSubselectRegressionTest { } @Test + @Timeout(10 * 1000) void lmdbMatchesMemoryForMultipleSubSelect(@TempDir Path tempDir) throws Exception { LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); SailRepository lmdbRepository = new SailRepository(new LmdbStore(tempDir.toFile(), config)); @@ -73,11 +77,16 @@ private static void loadDataset(SailRepositoryConnection connection) throws IOEx } private static long evaluateMultipleSubselectCount(SailRepositoryConnection connection) { - return connection - .prepareTupleQuery(MULTIPLE_SUB_SELECT_QUERY) - .evaluate() - .stream() - .count(); + TupleQuery tupleQuery = connection.prepareTupleQuery(MULTIPLE_SUB_SELECT_QUERY); + + tupleQuery.setMaxExecutionTime(10); + + try (TupleQueryResult evaluate = tupleQuery.evaluate()) { + return evaluate + .stream() + .count(); + } + } private static List evaluateDatasetLanguages(SailRepositoryConnection connection) { diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 37e2a61f54..26e2ad2ab0 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -12,7 +12,6 @@ package org.eclipse.rdf4j.sail.lmdb; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.io.File; import java.io.IOException; @@ -28,6 +27,7 @@ import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.TupleQuery; import org.eclipse.rdf4j.query.TupleQueryResult; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; @@ -226,7 +226,9 @@ public void orderedUnionLimitQueryProducesExpectedCount() { public void subSelectQueryProducesExpectedCount() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; - try (var stream = connection.prepareTupleQuery(sub_select).evaluate().stream()) { + TupleQuery tupleQuery = connection.prepareTupleQuery(sub_select); + tupleQuery.setMaxExecutionTime(30); + try (var stream = tupleQuery.evaluate().stream()) { count = stream.count(); } assertEquals(16035L, count); diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java index 5ea0cd451b..9f961db2f7 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/QueryBenchmark.java @@ -40,10 +40,7 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; /** * @author Håvard Ottestad From 19b9ec57499ddd5dc6432db7bf471c96aef57871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 10:19:00 +0900 Subject: [PATCH 58/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 22 +++++++++++++++++-- .../rdf4j/sail/lmdb/LmdbSailStore.java | 9 ++++++-- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 21 ++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index 8d97e02ea8..ebf7840eed 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -27,6 +27,7 @@ import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.KeyBuilder; import org.eclipse.rdf4j.sail.lmdb.TripleStore.TripleIndex; import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; @@ -95,6 +96,11 @@ class LmdbRecordIterator implements RecordIterator { LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef); + } + + LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { this.subj = subj; this.pred = pred; this.obj = obj; @@ -107,14 +113,25 @@ class LmdbRecordIterator implements RecordIterator { this.index = index; if (rangeSearch) { minKeyBuf = pool.getKeyBuffer(); - index.getMinKey(minKeyBuf, subj, pred, obj, context); + if (keyBuilder != null) { + minKeyBuf.clear(); + keyBuilder.writeMin(minKeyBuf); + } else { + index.getMinKey(minKeyBuf, subj, pred, obj, context); + } minKeyBuf.flip(); this.maxKey = pool.getVal(); this.maxKeyBuf = pool.getKeyBuffer(); - index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + if (keyBuilder != null) { + maxKeyBuf.clear(); + keyBuilder.writeMax(maxKeyBuf); + } else { + index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + } maxKeyBuf.flip(); this.maxKey.mv_data(maxKeyBuf); + } else { // Even when we can't bound with a prefix (no rangeSearch), we can still // position the cursor closer to the first potentially matching key when @@ -128,6 +145,7 @@ class LmdbRecordIterator implements RecordIterator { minKeyBuf = null; } this.maxKey = null; + this.maxKeyBuf = null; } this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; 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 033d0d0141..bb5ce8966d 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 @@ -1242,8 +1242,13 @@ public CloseableIteration getStatements(StatementOrder stat } boolean rangeSearch = chosen.getPatternScore(subjID, predID, objID, contextID) > 0; - RecordIterator records = new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, - explicit, txn); + TripleStore.KeyBuilder keyBuilder = rangeSearch + ? chosen.keyBuilder(subjID, predID, objID, contextID) + : null; + RecordIterator records = keyBuilder != null + ? new LmdbRecordIterator(chosen, keyBuilder, rangeSearch, subjID, predID, objID, contextID, + explicit, txn) + : new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, explicit, txn); boolean sBound = subj != null; boolean pBound = pred != null; 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 86d5aacab4..e8e13c63f9 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 @@ -1571,6 +1571,12 @@ interface DupIndex { int getPatternScore(long subj, long pred, long obj, long context); } + interface KeyBuilder { + void writeMin(ByteBuffer buffer); + + void writeMax(ByteBuffer buffer); + } + @FunctionalInterface private interface PatternScoreFunction { int score(long subj, long pred, long obj, long context); @@ -1741,6 +1747,21 @@ public int getPatternScore(long subj, long pred, long obj, long context) { return patternScoreFunction.score(subj, pred, obj, context); } + KeyBuilder keyBuilder(long subj, long pred, long obj, long context) { + return new KeyBuilder() { + + @Override + public void writeMin(ByteBuffer buffer) { + getMinKey(buffer, subj, pred, obj, context); + } + + @Override + public void writeMax(ByteBuffer buffer) { + getMaxKey(buffer, subj, pred, obj, context); + } + }; + } + void getMinKey(ByteBuffer bb, long subj, long pred, long obj, long context) { subj = subj <= 0 ? 0 : subj; pred = pred <= 0 ? 0 : pred; From e30c859ad15b8fcb4f24ca3a6f53a4b857883b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 11:20:28 +0900 Subject: [PATCH 59/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 12 +++ .../join/LmdbIdMergeJoinIteratorTest.java | 85 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 26e2ad2ab0..a14a4b5580 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -221,6 +221,18 @@ public void orderedUnionLimitQueryProducesExpectedCount() { } } + @Test + @Timeout(30) + public void long_chain() { + try (SailRepositoryConnection connection = repository.getConnection()) { + long count; + try (var stream = connection.prepareTupleQuery(long_chain).evaluate().stream()) { + count = stream.count(); + } +// assertEquals(???, count); + } + } + @Test @Timeout(30) public void subSelectQueryProducesExpectedCount() { diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java new file mode 100644 index 0000000000..30d8f62a52 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; + +@Isolated +class LmdbIdMergeJoinIteratorTest { + + @Test + void mergeJoinProducesIdRecordsWithoutMaterialization() throws Exception { + StatementPattern leftPattern = new StatementPattern( + new Var("x"), + new Var("pl", SimpleValueFactory.getInstance().createIRI("urn:p:left")), + new Var("ol", SimpleValueFactory.getInstance().createIRI("urn:o:left"))); + StatementPattern rightPattern = new StatementPattern( + new Var("x"), + new Var("pr", SimpleValueFactory.getInstance().createIRI("urn:p:right")), + new Var("or", SimpleValueFactory.getInstance().createIRI("urn:o:right"))); + + LmdbIdJoinIterator.PatternInfo leftInfo = LmdbIdJoinIterator.PatternInfo.create(leftPattern); + LmdbIdJoinIterator.PatternInfo rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPattern); + IdBindingInfo bindingInfo = IdBindingInfo.combine(IdBindingInfo.fromFirstPattern(leftInfo), rightInfo, null); + + long[] leftRecord = new long[] { 7L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L }; + long[] rightRecord = new long[] { 7L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L }; + + RecordIterator leftIterator = new ArrayRecordIterator(leftRecord); + RecordIterator rightIterator = new ArrayRecordIterator(rightRecord); + + LmdbIdMergeJoinIterator iterator = new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, + "x", bindingInfo); + try { + Object record = iterator.next(); + assertThat(record).isInstanceOf(long[].class); + assertThat((long[]) record).containsExactly(7L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L); + } finally { + iterator.close(); + } + } + + private static final class ArrayRecordIterator implements RecordIterator { + private final long[][] data; + private int index; + + private ArrayRecordIterator(long[]... records) { + this.data = Arrays.stream(records) + .map(arr -> Arrays.copyOf(arr, arr.length)) + .toArray(long[][]::new); + } + + @Override + public long[] next() { + if (index >= data.length) { + return null; + } + long[] source = data[index++]; + return Arrays.copyOf(source, source.length); + } + + @Override + public void close() { + // nothing to do + } + } +} From b23fc6fa586d122f94df219afc62dd1780c07e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 12:33:56 +0900 Subject: [PATCH 60/79] working on new ID based join iterator --- .../lmdb/join/LmdbIdMergeJoinIterator.java | 209 ++++++++++++++---- .../LmdbIdMergeJoinQueryEvaluationStep.java | 7 +- .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 20 +- .../join/LmdbIdMergeJoinIteratorTest.java | 35 ++- 4 files changed, 220 insertions(+), 51 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java index 3d5e26cf36..7c00841efd 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java @@ -10,28 +10,34 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb.join; -import org.eclipse.rdf4j.common.iteration.LookAheadIteration; -import org.eclipse.rdf4j.query.BindingSet; -import org.eclipse.rdf4j.query.MutableBindingSet; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + import org.eclipse.rdf4j.query.QueryEvaluationException; -import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; +import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; -import org.eclipse.rdf4j.sail.lmdb.ValueStore; +import org.eclipse.rdf4j.sail.lmdb.TripleStore; import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; -public class LmdbIdMergeJoinIterator extends LookAheadIteration { +/** + * Merge join iterator that operates entirely on LMDB ID records. Materialization to + * {@link org.eclipse.rdf4j.query.BindingSet} happens in a separate {@link LmdbIdFinalBindingSetIteration}. + */ +public class LmdbIdMergeJoinIterator implements RecordIterator { private final PeekMarkRecordIterator leftIterator; private final PeekMarkRecordIterator rightIterator; - private final LmdbIdJoinIterator.PatternInfo leftInfo; - private final LmdbIdJoinIterator.PatternInfo rightInfo; private final String mergeVariable; - private final QueryEvaluationContext context; - private final BindingSet initialBindings; - private final ValueStore valueStore; + private final IdBindingInfo bindingInfo; + private final Set sharedVariables; + private final int mergeIndex; + private final int bindingSize; + private static final int COLUMN_COUNT = TripleStore.CONTEXT_IDX + 1; private long[] currentLeftRecord; - private MutableBindingSet currentLeftBinding; private long currentLeftKey; private boolean hasCurrentLeftKey; @@ -39,30 +45,71 @@ public class LmdbIdMergeJoinIterator extends LookAheadIteration { private long leftPeekKey; private boolean hasLeftPeekKey; private int currentLeftValueAndPeekEquals = -1; + private boolean closed; + private final int[] leftColumnBindingIndex; + private final int[] rightColumnBindingIndex; + private static final boolean DEBUG = Boolean.getBoolean("rdf4j.lmdb.mergeJoinDebug"); + private final AtomicInteger debugCounter = DEBUG ? new AtomicInteger() : null; public LmdbIdMergeJoinIterator(RecordIterator leftIterator, RecordIterator rightIterator, LmdbIdJoinIterator.PatternInfo leftInfo, LmdbIdJoinIterator.PatternInfo rightInfo, String mergeVariable, - QueryEvaluationContext context, BindingSet initialBindings, ValueStore valueStore) { - this.leftIterator = new PeekMarkRecordIterator(leftIterator); - this.rightIterator = new PeekMarkRecordIterator(rightIterator); - this.leftInfo = leftInfo; - this.rightInfo = rightInfo; - this.mergeVariable = mergeVariable; - this.context = context; - this.initialBindings = initialBindings; - this.valueStore = valueStore; - + IdBindingInfo bindingInfo) { if (mergeVariable == null || mergeVariable.isEmpty()) { throw new IllegalArgumentException("Merge variable must be provided for LMDB merge join"); } + if (bindingInfo == null) { + throw new IllegalArgumentException("Binding info must be provided for LMDB merge join"); + } if (leftInfo.getRecordIndex(mergeVariable) < 0 || rightInfo.getRecordIndex(mergeVariable) < 0) { throw new IllegalArgumentException("Merge variable " + mergeVariable + " must be present in both join operands for LMDB merge join"); } + + this.leftIterator = new PeekMarkRecordIterator(leftIterator); + this.rightIterator = new PeekMarkRecordIterator(rightIterator); + this.mergeVariable = mergeVariable; + this.bindingInfo = bindingInfo; + + int idx = bindingInfo.getIndex(mergeVariable); + if (idx < 0) { + throw new IllegalArgumentException( + "Merge variable " + mergeVariable + " is not tracked in the binding info for LMDB merge join"); + } + this.mergeIndex = idx; + this.bindingSize = bindingInfo.size(); + + Set shared = new HashSet<>(leftInfo.getVariableNames()); + shared.retainAll(rightInfo.getVariableNames()); + this.sharedVariables = Collections.unmodifiableSet(shared); + this.leftColumnBindingIndex = computeColumnBindingMap(leftInfo, bindingInfo); + this.rightColumnBindingIndex = computeColumnBindingMap(rightInfo, bindingInfo); + if (DEBUG) { + System.out.println("DEBUG bindingSize=" + bindingSize + " mergeVar=" + mergeVariable + " shared=" + + shared + " bindingVars=" + bindingInfo.getVariableNames()); + } } @Override - protected BindingSet getNextElement() throws QueryEvaluationException { + public long[] next() { + if (closed) { + return null; + } + try { + long[] nextRecord = computeNextElement(); + if (nextRecord == null) { + close(); + } + return nextRecord; + } catch (RuntimeException e) { + close(); + throw e; + } + } + + private long[] computeNextElement() throws QueryEvaluationException { + if (closed) { + return null; + } if (!ensureCurrentLeft()) { return null; } @@ -84,9 +131,9 @@ protected BindingSet getNextElement() throws QueryEvaluationException { return null; } - int compare = compare(currentLeftKey, key(rightInfo, rightPeek)); + int compare = compare(currentLeftKey, key(rightPeek)); if (compare == 0) { - BindingSet result = equal(); + long[] result = equal(); if (result != null) { return result; } @@ -104,35 +151,39 @@ protected BindingSet getNextElement() throws QueryEvaluationException { } } - private boolean ensureCurrentLeft() throws QueryEvaluationException { + private boolean ensureCurrentLeft() { if (currentLeftRecord != null) { return true; } return advanceLeft(); } - private boolean advanceLeft() throws QueryEvaluationException { + private boolean advanceLeft() { leftPeekRecord = null; hasLeftPeekKey = false; currentLeftValueAndPeekEquals = -1; while (leftIterator.hasNext()) { long[] candidate = leftIterator.next(); - // Defer left materialization; keep only the ID record. + if (candidate == null) { + continue; + } + if (DEBUG && debugCounter.getAndIncrement() < 5) { + System.out.println("DEBUG advanceLeft len=" + candidate.length + " record=" + + Arrays.toString(candidate)); + } currentLeftRecord = candidate; - currentLeftBinding = null; - currentLeftKey = key(leftInfo, candidate); + currentLeftKey = key(candidate); hasCurrentLeftKey = true; return true; } currentLeftRecord = null; - currentLeftBinding = null; hasCurrentLeftKey = false; return false; } - private boolean lessThan() throws QueryEvaluationException { + private boolean lessThan() { long previous = hasCurrentLeftKey ? currentLeftKey : Long.MIN_VALUE; if (!advanceLeft()) { return false; @@ -147,10 +198,10 @@ private boolean lessThan() throws QueryEvaluationException { return true; } - private BindingSet equal() throws QueryEvaluationException { + private long[] equal() { while (rightIterator.hasNext()) { if (rightIterator.isResettable()) { - BindingSet result = joinWithCurrentLeft(rightIterator.next()); + long[] result = joinWithCurrentLeft(rightIterator.next()); if (result != null) { return result; } @@ -159,7 +210,7 @@ private BindingSet equal() throws QueryEvaluationException { if (currentLeftValueAndPeekEquals == 0 && !rightIterator.isMarked()) { rightIterator.mark(); } - BindingSet result = joinWithCurrentLeft(rightIterator.next()); + long[] result = joinWithCurrentLeft(rightIterator.next()); if (result != null) { return result; } @@ -168,11 +219,11 @@ private BindingSet equal() throws QueryEvaluationException { return null; } - private void doLeftPeek() throws QueryEvaluationException { + private void doLeftPeek() { if (leftPeekRecord == null) { leftPeekRecord = leftIterator.peek(); if (leftPeekRecord != null) { - leftPeekKey = key(leftInfo, leftPeekRecord); + leftPeekKey = key(leftPeekRecord); hasLeftPeekKey = true; } else { hasLeftPeekKey = false; @@ -186,23 +237,75 @@ private void doLeftPeek() throws QueryEvaluationException { } } - private BindingSet joinWithCurrentLeft(long[] rightRecord) throws QueryEvaluationException { - MutableBindingSet result = context.createBindingSet(initialBindings); - if (!leftInfo.applyRecord(currentLeftRecord, result, valueStore)) { + private long[] joinWithCurrentLeft(long[] rightRecord) { + if (rightRecord == null) { return null; } - if (!rightInfo.applyRecord(rightRecord, result, valueStore)) { + + long[] combined = new long[bindingSize]; + Arrays.fill(combined, LmdbValue.UNKNOWN_ID); + + if (!mergeRecordInto(combined, currentLeftRecord, leftColumnBindingIndex)) { return null; } - return result; + if (!mergeRecordInto(combined, rightRecord, rightColumnBindingIndex)) { + return null; + } + if (DEBUG && debugCounter.get() < 50) { + System.out.println("DEBUG combined=" + Arrays.toString(combined)); + } + return combined; + } + + private static int[] computeColumnBindingMap(LmdbIdJoinIterator.PatternInfo patternInfo, IdBindingInfo bindingInfo) { + int[] map = new int[COLUMN_COUNT]; + Arrays.fill(map, -1); + for (String var : patternInfo.getVariableNames()) { + int bindingIdx = bindingInfo.getIndex(var); + if (bindingIdx < 0) { + continue; + } + int mask = patternInfo.getPositionsMask(var); + for (int pos = 0; pos < COLUMN_COUNT; pos++) { + if (((mask >> pos) & 1) != 0) { + map[pos] = bindingIdx; + } + } + } + return map; + } + + private boolean mergeRecordInto(long[] target, long[] source, int[] colMap) { + if (source == null) { + return true; + } + for (int col = 0; col < colMap.length; col++) { + int bindingIdx = colMap[col]; + if (bindingIdx < 0 || bindingIdx >= target.length) { + continue; + } + long value = col < source.length ? source[col] : LmdbValue.UNKNOWN_ID; + if (col == TripleStore.CONTEXT_IDX && value == 0L) { + value = LmdbValue.UNKNOWN_ID; + } + long existing = target[bindingIdx]; + if (existing == LmdbValue.UNKNOWN_ID) { + if (value != LmdbValue.UNKNOWN_ID) { + target[bindingIdx] = value; + } + } else if (value != LmdbValue.UNKNOWN_ID && existing != value) { + return false; + } + } + return true; } private int compare(long left, long right) { return Long.compare(left, right); } - private long key(LmdbIdJoinIterator.PatternInfo info, long[] record) throws QueryEvaluationException { - long id = info.getId(record, mergeVariable); + private long key(long[] record) { + long id = valueAt(record, mergeIndex); if (id == LmdbValue.UNKNOWN_ID) { throw new QueryEvaluationException( "Merge variable " + mergeVariable + " is unbound in the current record; cannot perform merge join"); @@ -211,11 +314,27 @@ private long key(LmdbIdJoinIterator.PatternInfo info, long[] record) throws Quer } @Override - protected void handleClose() throws QueryEvaluationException { + public void close() { + if (closed) { + return; + } + closed = true; + currentLeftRecord = null; + hasCurrentLeftKey = false; + leftPeekRecord = null; + hasLeftPeekKey = false; + currentLeftValueAndPeekEquals = -1; try { leftIterator.close(); } finally { rightIterator.close(); } } + + private static long valueAt(long[] record, int index) { + if (index < 0 || index >= record.length) { + return LmdbValue.UNKNOWN_ID; + } + return record[index]; + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java index bafe4aeaae..4cabcf741d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.Optional; import org.eclipse.rdf4j.common.iteration.CloseableIteration; @@ -151,8 +152,10 @@ public CloseableIteration evaluate(BindingSet bindings) { } join.setAlgorithm(LmdbIdMergeJoinIterator.class.getSimpleName()); - return new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, mergeVariable, context, - bindings, valueStore); + RecordIterator mergeIterator = new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, + mergeVariable, bindingInfo); + return new LmdbIdFinalBindingSetIteration(mergeIterator, bindingInfo, context, bindings, valueStore, + Collections.emptyMap()); } catch (QueryEvaluationException e) { throw e; } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index a14a4b5580..157df5ddbe 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -156,7 +156,7 @@ public void groupByQuery() { try (var stream = connection.prepareTupleQuery(query1).evaluate().stream()) { count = stream.count(); } - System.out.println(count); + assertEquals(5, count); } } @@ -181,7 +181,7 @@ public void distinctPredicatesQuery() { try (var stream = connection.prepareTupleQuery(query_distinct_predicates).evaluate().stream()) { count = stream.count(); } - System.out.println(count); + assertEquals(55, count); } } @@ -229,7 +229,21 @@ public void long_chain() { try (var stream = connection.prepareTupleQuery(long_chain).evaluate().stream()) { count = stream.count(); } -// assertEquals(???, count); + assertEquals(0, count); + } + } + + @Test + @Timeout(30) + public void long_chain_debugPreview() { + try (SailRepositoryConnection connection = repository.getConnection()) { + TupleQuery tupleQuery = connection.prepareTupleQuery(long_chain); + try (TupleQueryResult result = tupleQuery.evaluate()) { + for (int i = 0; result.hasNext() && i < 5; i++) { + BindingSet bs = result.next(); + System.out.println("DEBUG long_chain #" + i + ": " + bs); + } + } } } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java index 30d8f62a52..b964fa2d83 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java @@ -52,7 +52,40 @@ void mergeJoinProducesIdRecordsWithoutMaterialization() throws Exception { try { Object record = iterator.next(); assertThat(record).isInstanceOf(long[].class); - assertThat((long[]) record).containsExactly(7L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L); + long[] ids = (long[]) record; + assertThat(ids.length).isEqualTo(bindingInfo.size()); + int idx = bindingInfo.getIndex("x"); + assertThat(ids[idx]).isEqualTo(7L); + } finally { + iterator.close(); + } + } + + @Test + void nextReturnsNullWhenNoMatches() throws Exception { + StatementPattern leftPattern = new StatementPattern( + new Var("x"), + new Var("pl", SimpleValueFactory.getInstance().createIRI("urn:p:left")), + new Var("ol", SimpleValueFactory.getInstance().createIRI("urn:o:left"))); + StatementPattern rightPattern = new StatementPattern( + new Var("x"), + new Var("pr", SimpleValueFactory.getInstance().createIRI("urn:p:right")), + new Var("or", SimpleValueFactory.getInstance().createIRI("urn:o:right"))); + + LmdbIdJoinIterator.PatternInfo leftInfo = LmdbIdJoinIterator.PatternInfo.create(leftPattern); + LmdbIdJoinIterator.PatternInfo rightInfo = LmdbIdJoinIterator.PatternInfo.create(rightPattern); + IdBindingInfo bindingInfo = IdBindingInfo.combine(IdBindingInfo.fromFirstPattern(leftInfo), rightInfo, null); + + long[] leftRecord = new long[] { 7L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L }; + long[] rightRecord = new long[] { 8L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L }; + + RecordIterator leftIterator = new ArrayRecordIterator(leftRecord); + RecordIterator rightIterator = new ArrayRecordIterator(rightRecord); + + LmdbIdMergeJoinIterator iterator = new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, + "x", bindingInfo); + try { + assertThat(iterator.next()).isNull(); } finally { iterator.close(); } From d09591a49094f3bd030330ad202279fb3b773d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 13:39:31 +0900 Subject: [PATCH 61/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 157df5ddbe..73f1ac3c09 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -233,20 +233,6 @@ public void long_chain() { } } - @Test - @Timeout(30) - public void long_chain_debugPreview() { - try (SailRepositoryConnection connection = repository.getConnection()) { - TupleQuery tupleQuery = connection.prepareTupleQuery(long_chain); - try (TupleQueryResult result = tupleQuery.evaluate()) { - for (int i = 0; result.hasNext() && i < 5; i++) { - BindingSet bs = result.next(); - System.out.println("DEBUG long_chain #" + i + ": " + bs); - } - } - } - } - @Test @Timeout(30) public void subSelectQueryProducesExpectedCount() { From 353965c3cf31dda25682a480ed344eced56fb6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 14:45:33 +0900 Subject: [PATCH 62/79] working on new ID based join iterator --- .../eclipse/rdf4j/sail/lmdb/IdAccessor.java | 5 + .../rdf4j/sail/lmdb/IdBindingInfo.java | 1 + .../sail/lmdb/join/LmdbIdJoinIterator.java | 11 ++- .../join/LmdbIdJoinQueryEvaluationStep.java | 4 +- .../lmdb/join/LmdbIdMergeJoinIterator.java | 25 +++-- .../sail/lmdb/join/LmdbIdOrderVerifier.java | 64 ++++++++++++ .../sail/lmdb/LmdbIdJoinEvaluationTest.java | 97 +++++++++++++++++++ .../join/LmdbIdMergeJoinIteratorTest.java | 18 ++++ 8 files changed, 204 insertions(+), 21 deletions(-) create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdOrderVerifier.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java index fd24b168e6..80888ff5dc 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdAccessor.java @@ -13,6 +13,8 @@ import java.util.Set; import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.query.MutableBindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; /** * Read-only accessor for variable ID lookups against a record long[] produced by a RecordIterator. @@ -28,4 +30,7 @@ public interface IdAccessor { * should return {@code -1} when the variable is not part of the record. */ int getRecordIndex(String varName); + + boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) + throws QueryEvaluationException; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java index 98b5293b57..cbe7e15c5a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java @@ -101,6 +101,7 @@ public long getId(long[] record, String varName) { return record[i]; } + @Override public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) throws QueryEvaluationException { // Fast path: pre-resolved getters/setters from the evaluation context diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index 0c2e8ab2ed..5e73c616d1 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -131,7 +131,8 @@ public long getId(long[] record, String varName) { return record[indices[0]]; } - boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) + @Override + public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) throws QueryEvaluationException { for (Map.Entry entry : indexByVar.entrySet()) { String name = entry.getKey(); @@ -220,8 +221,8 @@ private Value resolveValue(long id, int position, ValueStore valueStore) throws private final RecordIterator leftIterator; private final RecordIteratorFactory rightFactory; - private final PatternInfo leftInfo; - private final PatternInfo rightInfo; + private final IdAccessor leftInfo; + private final IdAccessor rightInfo; private final Set sharedVariables; private final QueryEvaluationContext context; private final BindingSet initialBindings; @@ -231,8 +232,8 @@ private Value resolveValue(long id, int position, ValueStore valueStore) throws private long[] currentLeftRecord; private BindingSet currentLeftBinding; - LmdbIdJoinIterator(RecordIterator leftIterator, RecordIteratorFactory rightFactory, PatternInfo leftInfo, - PatternInfo rightInfo, Set sharedVariables, QueryEvaluationContext context, + LmdbIdJoinIterator(RecordIterator leftIterator, RecordIteratorFactory rightFactory, IdAccessor leftInfo, + IdAccessor rightInfo, Set sharedVariables, QueryEvaluationContext context, BindingSet initialBindings, ValueStore valueStore) { this.leftIterator = leftIterator; this.rightFactory = rightFactory; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 5341d695b9..8ef61db330 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -210,8 +210,8 @@ public CloseableIteration evaluate(BindingSet bindings) { return dataset.getRecordIterator(snapshot, subjIdx, predIdx, objIdx, ctxIdx, patternIds); }; - return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, rightInfo, sharedVariables, context, - bindings, valueStore); + return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, bindingInfo, sharedVariables, + context, bindings, valueStore); } // Default: materialize right side via BindingSet and use LmdbIdJoinIterator diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java index 7c00841efd..daa264d5b9 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java @@ -33,7 +33,6 @@ public class LmdbIdMergeJoinIterator implements RecordIterator { private final String mergeVariable; private final IdBindingInfo bindingInfo; private final Set sharedVariables; - private final int mergeIndex; private final int bindingSize; private static final int COLUMN_COUNT = TripleStore.CONTEXT_IDX + 1; @@ -48,6 +47,8 @@ public class LmdbIdMergeJoinIterator implements RecordIterator { private boolean closed; private final int[] leftColumnBindingIndex; private final int[] rightColumnBindingIndex; + private final int leftMergeColumn; + private final int rightMergeColumn; private static final boolean DEBUG = Boolean.getBoolean("rdf4j.lmdb.mergeJoinDebug"); private final AtomicInteger debugCounter = DEBUG ? new AtomicInteger() : null; @@ -65,6 +66,8 @@ public LmdbIdMergeJoinIterator(RecordIterator leftIterator, RecordIterator right + " must be present in both join operands for LMDB merge join"); } + this.leftMergeColumn = leftInfo.getRecordIndex(mergeVariable); + this.rightMergeColumn = rightInfo.getRecordIndex(mergeVariable); this.leftIterator = new PeekMarkRecordIterator(leftIterator); this.rightIterator = new PeekMarkRecordIterator(rightIterator); this.mergeVariable = mergeVariable; @@ -75,7 +78,6 @@ public LmdbIdMergeJoinIterator(RecordIterator leftIterator, RecordIterator right throw new IllegalArgumentException( "Merge variable " + mergeVariable + " is not tracked in the binding info for LMDB merge join"); } - this.mergeIndex = idx; this.bindingSize = bindingInfo.size(); Set shared = new HashSet<>(leftInfo.getVariableNames()); @@ -131,7 +133,7 @@ private long[] computeNextElement() throws QueryEvaluationException { return null; } - int compare = compare(currentLeftKey, key(rightPeek)); + int compare = compare(currentLeftKey, key(rightPeek, rightMergeColumn)); if (compare == 0) { long[] result = equal(); if (result != null) { @@ -173,7 +175,7 @@ private boolean advanceLeft() { + Arrays.toString(candidate)); } currentLeftRecord = candidate; - currentLeftKey = key(candidate); + currentLeftKey = key(candidate, leftMergeColumn); hasCurrentLeftKey = true; return true; } @@ -223,7 +225,7 @@ private void doLeftPeek() { if (leftPeekRecord == null) { leftPeekRecord = leftIterator.peek(); if (leftPeekRecord != null) { - leftPeekKey = key(leftPeekRecord); + leftPeekKey = key(leftPeekRecord, leftMergeColumn); hasLeftPeekKey = true; } else { hasLeftPeekKey = false; @@ -257,7 +259,8 @@ private long[] joinWithCurrentLeft(long[] rightRecord) { return combined; } - private static int[] computeColumnBindingMap(LmdbIdJoinIterator.PatternInfo patternInfo, IdBindingInfo bindingInfo) { + private static int[] computeColumnBindingMap(LmdbIdJoinIterator.PatternInfo patternInfo, + IdBindingInfo bindingInfo) { int[] map = new int[COLUMN_COUNT]; Arrays.fill(map, -1); for (String var : patternInfo.getVariableNames()) { @@ -304,8 +307,8 @@ private int compare(long left, long right) { return Long.compare(left, right); } - private long key(long[] record) { - long id = valueAt(record, mergeIndex); + private long key(long[] record, int columnIndex) { + long id = record[columnIndex]; if (id == LmdbValue.UNKNOWN_ID) { throw new QueryEvaluationException( "Merge variable " + mergeVariable + " is unbound in the current record; cannot perform merge join"); @@ -331,10 +334,4 @@ public void close() { } } - private static long valueAt(long[] record, int index) { - if (index < 0 || index >= record.length) { - return LmdbValue.UNKNOWN_ID; - } - return record[index]; - } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdOrderVerifier.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdOrderVerifier.java new file mode 100644 index 0000000000..22928b7eea --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdOrderVerifier.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.join; + +import java.util.Arrays; + +import org.eclipse.rdf4j.sail.lmdb.RecordIterator; + +public final class LmdbIdOrderVerifier { + + private LmdbIdOrderVerifier() { + } + + public static RecordIterator wrap(String label, int mergeIndex, RecordIterator delegate) { + if (label == null || label.isEmpty()) { + throw new IllegalArgumentException("Label must be provided"); + } + if (mergeIndex < 0) { + throw new IllegalArgumentException("Merge index must be non-negative"); + } + return new RecordIterator() { + private long position = 0; + private long previous = Long.MIN_VALUE; + private boolean first = true; + + @Override + public long[] next() { + long[] record = delegate.next(); + if (record == null) { + return null; + } + if (mergeIndex >= record.length) { + throw new AssertionError(String.format( + "%s iterator produced record #%d shorter than merge index %d: %s", + label, position, mergeIndex, Arrays.toString(record))); + } + long current = record[mergeIndex]; + if (!first && current < previous) { + throw new AssertionError(String.format( + "%s iterator out of order at position %d: previous=%d current=%d (record=%s)", + label, position, previous, current, Arrays.toString(record))); + } + first = false; + previous = current; + long[] copy = Arrays.copyOf(record, record.length); + position++; + return copy; + } + + @Override + public void close() { + delegate.close(); + } + }; + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java index 7d4da17527..161d083de2 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java @@ -26,6 +26,8 @@ import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.QueryLanguage; @@ -42,6 +44,7 @@ import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; import org.eclipse.rdf4j.query.explanation.Explanation; import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.query.impl.MapBindingSet; import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; import org.eclipse.rdf4j.query.parser.QueryParserUtil; import org.eclipse.rdf4j.repository.RepositoryConnection; @@ -247,6 +250,100 @@ public void mergeJoinUsesArrayDatasetApi(@TempDir Path tempDir) throws Exception } } + @Test + public void mergeJoinRespectsQueryBindings(@TempDir Path tempDir) throws Exception { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI painter = vf.createIRI(NS, "Painter"); + IRI painting = vf.createIRI(NS, "Painting"); + IRI paints = vf.createIRI(NS, "paints"); + IRI picasso = vf.createIRI(NS, "picasso"); + IRI guernica = vf.createIRI(NS, "guernica"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(painter, RDF.TYPE, RDFS.CLASS); + conn.add(painting, RDF.TYPE, RDFS.CLASS); + conn.add(picasso, RDF.TYPE, painter); + conn.add(guernica, RDF.TYPE, painting); + conn.add(picasso, paints, guernica); + } + + String query = "SELECT ?X WHERE {\n" + + " ?X a ?Y .\n" + + " ?Y a .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr joinExpr = unwrap(tupleExpr); + assertThat(joinExpr).isInstanceOf(Join.class); + Join join = (Join) joinExpr; + + StatementPattern left = (StatementPattern) join.getLeftArg(); + StatementPattern right = (StatementPattern) join.getRightArg(); + Var joinVar = left.getObjectVar(); + join.setOrder(joinVar); + left.setOrder(joinVar); + right.setOrder(right.getSubjectVar()); + join.setMergeJoin(true); + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + + try { + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, tripleSource, + store.getBackingStore().getEvaluationStatistics()); + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + RecordingDataset recordingDataset = new RecordingDataset(lmdbDataset); + QueryEvaluationContext context = new LmdbQueryEvaluationContext(null, + tripleSource.getValueFactory(), tripleSource.getComparator(), recordingDataset, + lmdbDataset.getValueStore()); + + QueryEvaluationStep step = strategy.precompile(join, context); + + MapBindingSet bindings = new MapBindingSet(2); + + try (CloseableIteration iter = step.evaluate(bindings)) { + List results = Iterations.asList(iter); + assertThat(results).hasSize(2); + assertThat(results).extracting(bs -> bs.getValue("X")).containsExactlyInAnyOrder(picasso, guernica); + } + + bindings.addBinding("Y", painter); + try (CloseableIteration iter = step.evaluate(bindings)) { + List results = Iterations.asList(iter); + assertThat(results).hasSize(1); + assertThat(results.get(0).getValue("X")).isEqualTo(picasso); + } + + bindings.addBinding("Z", painting); + try (CloseableIteration iter = step.evaluate(bindings)) { + List results = Iterations.asList(iter); + assertThat(results).hasSize(1); + assertThat(results.get(0).getValue("X")).isEqualTo(picasso); + } + + bindings.removeBinding("Y"); + try (CloseableIteration iter = step.evaluate(bindings)) { + List results = Iterations.asList(iter); + assertThat(results).hasSize(2); + assertThat(results).extracting(bs -> bs.getValue("X")).containsExactlyInAnyOrder(picasso, guernica); + } + + assertThat(recordingDataset.wasArrayOrderedApiUsed()).isTrue(); + assertThat(join.getAlgorithmName()).isEqualTo("LmdbIdMergeJoinIterator"); + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + @Test public void mergeJoinFallsBackWhenOrderUnsupported(@TempDir Path tempDir) throws Exception { LmdbStore store = new LmdbStore(tempDir.toFile()); diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java index b964fa2d83..affd3f18de 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIteratorTest.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.lmdb.join; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Arrays; @@ -91,6 +92,23 @@ void nextReturnsNullWhenNoMatches() throws Exception { } } + @Test + void orderVerifierDetectsDisorder() { + RecordIterator unordered = new ArrayRecordIterator( + new long[] { 1L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L }, + new long[] { 0L, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, 0L }); + + RecordIterator verifying = LmdbIdOrderVerifier.wrap("left", 0, unordered); + + assertThatThrownBy(() -> { + while (verifying.next() != null) { + // consume iterator + } + }) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("left"); + } + private static final class ArrayRecordIterator implements RecordIterator { private final long[][] data; private int index; From 7c72ba92bb03c618f14fe36576002d66b9d1f128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 15:22:31 +0900 Subject: [PATCH 63/79] working on new ID based join iterator --- .../join/LmdbIdBGPQueryEvaluationStep.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index d7aea0a095..620caafc6c 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -13,7 +13,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import org.eclipse.rdf4j.common.iteration.CloseableIteration; @@ -54,7 +57,7 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private final boolean hasInvalidPattern; private final boolean createdDynamicIds; private final boolean allowCreateConstantIds; - private final java.util.Map constantBindings; + private final Map constantBindings; public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -81,7 +84,7 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, List rawPatterns = new ArrayList<>(patterns.size()); boolean invalid = false; boolean created = false; - java.util.Map constants = new java.util.HashMap<>(); + Map constants = new HashMap<>(); for (StatementPattern pattern : patterns) { RawPattern raw = RawPattern.create(pattern, valueStore, allowCreate); rawPatterns.add(raw); @@ -91,7 +94,7 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, } this.hasInvalidPattern = invalid; this.createdDynamicIds = created; - this.constantBindings = java.util.Collections.unmodifiableMap(constants); + this.constantBindings = Collections.unmodifiableMap(constants); if (rawPatterns.isEmpty()) { throw new IllegalArgumentException("Basic graph pattern must contain at least one statement pattern"); @@ -164,7 +167,7 @@ public CloseableIteration evaluate(BindingSet bindings) { } private LmdbEvaluationDataset resolveDataset() { - java.util.Optional fromContext = datasetContext.getLmdbDataset(); + Optional fromContext = datasetContext.getLmdbDataset(); if (fromContext.isPresent()) { return fromContext.get(); } @@ -200,8 +203,8 @@ private static long resolveId(ValueStore valueStore, Value value) throws QueryEv if (value == null) { return LmdbValue.UNKNOWN_ID; } - if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { - org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (value instanceof LmdbValue) { + LmdbValue lmdbValue = (LmdbValue) value; if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { long id = lmdbValue.getInternalID(); if (id != LmdbValue.UNKNOWN_ID) { @@ -310,11 +313,11 @@ private static final class RawPattern { private final LmdbIdJoinIterator.PatternInfo patternInfo; private final boolean invalid; private final boolean createdIds; - private final java.util.Map constantIds; + private final Map constantIds; private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid, boolean createdIds, - java.util.Map constantIds) { + Map constantIds) { this.patternIds = patternIds; this.subjVar = subjVar; this.predVar = predVar; @@ -326,7 +329,7 @@ private RawPattern(long[] patternIds, String subjVar, String predVar, String obj this.constantIds = constantIds; } - java.util.Map getConstantIds() { + Map getConstantIds() { return constantIds; } @@ -336,7 +339,7 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea boolean invalid = false; boolean createdAny = false; - java.util.Map constantIds = new java.util.HashMap<>(); + Map constantIds = new HashMap<>(); Var subj = pattern.getSubjectVar(); String subjVar = null; @@ -431,8 +434,8 @@ private static ConstantIdResult constantId(ValueStore valueStore, Value value, b return ConstantIdResult.invalid(); } try { - if (value instanceof org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) { - org.eclipse.rdf4j.sail.lmdb.model.LmdbValue lmdbValue = (org.eclipse.rdf4j.sail.lmdb.model.LmdbValue) value; + if (value instanceof LmdbValue) { + LmdbValue lmdbValue = (LmdbValue) value; if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { long id = lmdbValue.getInternalID(); if (id != LmdbValue.UNKNOWN_ID) { From b5973b6c9e9d818a0bad6840bbe9b3a21a49b982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 3 Nov 2025 15:55:18 +0900 Subject: [PATCH 64/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/IdBindingInfo.java | 77 +++++++++++++++++++ .../join/LmdbIdBGPQueryEvaluationStep.java | 47 +---------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java index cbe7e15c5a..a73087beb1 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/IdBindingInfo.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.lmdb; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -101,6 +102,82 @@ public long getId(long[] record, String varName) { return record[i]; } + public long[] createInitialBinding(BindingSet bindings, ValueStore valueStore) throws QueryEvaluationException { + long[] binding = new long[size()]; + Arrays.fill(binding, LmdbValue.UNKNOWN_ID); + if (bindings == null || bindings.isEmpty()) { + return binding; + } + + if (prepared != null) { + for (Entry entry : prepared) { + Value value = entry.getter.apply(bindings); + if (value == null) { + continue; + } + long id = resolveId(valueStore, value); + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + binding[entry.recordIndex] = id; + } + return binding; + } + + if (bindings instanceof ArrayBindingSet) { + ArrayBindingSet abs = (ArrayBindingSet) bindings; + for (Map.Entry entry : indexByVar.entrySet()) { + Function getter = abs.getDirectGetValue(entry.getKey()); + if (getter == null) { + continue; + } + Value value = getter.apply(abs); + if (value == null) { + continue; + } + long id = resolveId(valueStore, value); + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + binding[entry.getValue()] = id; + } + return binding; + } + + for (Map.Entry entry : indexByVar.entrySet()) { + Value value = bindings.getValue(entry.getKey()); + if (value == null) { + continue; + } + long id = resolveId(valueStore, value); + if (id == LmdbValue.UNKNOWN_ID) { + return null; + } + binding[entry.getValue()] = id; + } + return binding; + } + + private static long resolveId(ValueStore valueStore, Value value) throws QueryEvaluationException { + if (value == null) { + return LmdbValue.UNKNOWN_ID; + } + if (value instanceof LmdbValue) { + LmdbValue lmdbValue = (LmdbValue) value; + if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { + long id = lmdbValue.getInternalID(); + if (id != LmdbValue.UNKNOWN_ID) { + return id; + } + } + } + try { + return valueStore.getId(value); + } catch (IOException e) { + throw new QueryEvaluationException(e); + } + } + @Override public boolean applyRecord(long[] record, MutableBindingSet target, ValueStore valueStore) throws QueryEvaluationException { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 620caafc6c..e3d8a9731c 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -147,7 +147,7 @@ public CloseableIteration evaluate(BindingSet bindings) { } ValueStore valueStore = dataset.getValueStore(); - long[] initialBinding = createInitialBinding(finalInfo, bindings, valueStore); + long[] initialBinding = finalInfo.createInitialBinding(bindings, valueStore); if (initialBinding == null) { return new EmptyIteration<>(); } @@ -175,51 +175,6 @@ private LmdbEvaluationDataset resolveDataset() { .orElseThrow(() -> new IllegalStateException("No active LMDB dataset available for join evaluation")); } - private static long[] createInitialBinding(IdBindingInfo info, BindingSet bindings, ValueStore valueStore) - throws QueryEvaluationException { - long[] binding = new long[info.size()]; - Arrays.fill(binding, LmdbValue.UNKNOWN_ID); - if (bindings == null || bindings.isEmpty()) { - return binding; - } - for (String name : info.getVariableNames()) { - Value value = bindings.getValue(name); - if (value == null) { - continue; - } - long id = resolveId(valueStore, value); - if (id == LmdbValue.UNKNOWN_ID) { - return null; - } - int index = info.getIndex(name); - if (index >= 0) { - binding[index] = id; - } - } - return binding; - } - - private static long resolveId(ValueStore valueStore, Value value) throws QueryEvaluationException { - if (value == null) { - return LmdbValue.UNKNOWN_ID; - } - if (value instanceof LmdbValue) { - LmdbValue lmdbValue = (LmdbValue) value; - if (lmdbValue.getValueStoreRevision().getValueStore() == valueStore) { - long id = lmdbValue.getInternalID(); - if (id != LmdbValue.UNKNOWN_ID) { - return id; - } - } - } - try { - long id = valueStore.getId(value); - return id; - } catch (IOException e) { - throw new QueryEvaluationException(e); - } - } - public static boolean flattenBGP(TupleExpr expr, List out) { if (expr instanceof StatementPattern) { out.add((StatementPattern) expr); From 8a00d62f604e3a41fb20384cf0679513fe4aa259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 4 Nov 2025 20:38:27 +0900 Subject: [PATCH 65/79] working on new ID based join iterator --- .../join/LmdbIdBGPQueryEvaluationStep.java | 290 ++++++++++++++++-- 1 file changed, 269 insertions(+), 21 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index e3d8a9731c..216afa8230 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -15,12 +15,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; @@ -49,6 +52,7 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private static final String ID_JOIN_ALGORITHM = LmdbIdJoinIterator.class.getSimpleName(); private final List plans; + private final List rawPatterns; private final IdBindingInfo finalInfo; private final QueryEvaluationContext context; private final LmdbDatasetContext datasetContext; @@ -58,6 +62,8 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private final boolean createdDynamicIds; private final boolean allowCreateConstantIds; private final Map constantBindings; + private final List mergeSpecs; + private final Set mergeJoinNodes; public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -81,11 +87,16 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, .orElseGet(LmdbEvaluationStrategy::hasActiveConnectionChanges); this.allowCreateConstantIds = allowCreate; - List rawPatterns = new ArrayList<>(patterns.size()); + List mergeSpecs = new ArrayList<>(); + List flattened = new ArrayList<>(patterns.size()); + boolean flattenedOk = flattenBGP(root, flattened, mergeSpecs) && !flattened.isEmpty(); + List effectivePatterns = flattenedOk ? flattened : patterns; + + List rawPatterns = new ArrayList<>(effectivePatterns.size()); boolean invalid = false; boolean created = false; Map constants = new HashMap<>(); - for (StatementPattern pattern : patterns) { + for (StatementPattern pattern : effectivePatterns) { RawPattern raw = RawPattern.create(pattern, valueStore, allowCreate); rawPatterns.add(raw); invalid |= raw.invalid; @@ -111,10 +122,18 @@ public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, this.finalInfo = info; List planList = new ArrayList<>(rawPatterns.size()); - for (RawPattern raw : rawPatterns) { - planList.add(raw.toPlan(finalInfo)); + for (int i = 0; i < rawPatterns.size(); i++) { + planList.add(rawPatterns.get(i).toPlan(finalInfo)); } this.plans = planList; + this.rawPatterns = rawPatterns; + this.mergeSpecs = mergeSpecs; + this.mergeJoinNodes = new HashSet<>(); + for (MergeSpec spec : mergeSpecs) { + if (spec.join != null) { + mergeJoinNodes.add(spec.join); + } + } } public boolean shouldUseFallbackImmediately() { @@ -122,7 +141,7 @@ public boolean shouldUseFallbackImmediately() { } public void applyAlgorithmTag() { - markJoinTreeWithIdAlgorithm(root); + markJoinTreeWithIdAlgorithm(root, mergeJoinNodes); } @Override @@ -152,12 +171,14 @@ public CloseableIteration evaluate(BindingSet bindings) { return new EmptyIteration<>(); } - PatternPlan firstPlan = plans.get(0); - RecordIterator iter = dataset.getRecordIterator(initialBinding, firstPlan.subjIndex, firstPlan.predIndex, - firstPlan.objIndex, firstPlan.ctxIndex, firstPlan.patternIds); + List stages = buildStages(); + if (stages.isEmpty()) { + return new EmptyIteration<>(); + } - for (int i = 1; i < plans.size(); i++) { - iter = new BindingJoinRecordIterator(iter, dataset, plans.get(i)); + RecordIterator iter = stages.get(0).createInitialIterator(dataset, initialBinding, valueStore); + for (int i = 1; i < stages.size(); i++) { + iter = stages.get(i).joinWith(iter, dataset, valueStore); } return new LmdbIdFinalBindingSetIteration(iter, finalInfo, context, bindings, valueStore, constantBindings); @@ -176,27 +197,223 @@ private LmdbEvaluationDataset resolveDataset() { } public static boolean flattenBGP(TupleExpr expr, List out) { + return flattenBGP(expr, out, null); + } + + public static boolean flattenBGP(TupleExpr expr, List out, List merges) { if (expr instanceof StatementPattern) { out.add((StatementPattern) expr); return true; } if (expr instanceof Join) { Join j = (Join) expr; - // merge joins only; we avoid mergeJoin or other special joins - if (j.isMergeJoin()) { + int leftStart = out.size(); + boolean left = flattenBGP(j.getLeftArg(), out, merges); + int leftEnd = out.size(); + boolean right = flattenBGP(j.getRightArg(), out, merges); + int rightEnd = out.size(); + if (!left || !right) { return false; } - return flattenBGP(j.getLeftArg(), out) && flattenBGP(j.getRightArg(), out); + if (j.isMergeJoin() && merges != null) { + if (rightEnd - leftStart != 2 || leftEnd - leftStart != 1) { + return false; + } + Var orderVar = j.getOrder(); + if (orderVar == null) { + return false; + } + merges.add(new MergeSpec(leftStart, leftEnd, rightEnd, orderVar.getName(), j)); + } + return true; } return false; } - private static void markJoinTreeWithIdAlgorithm(TupleExpr expr) { + private static void markJoinTreeWithIdAlgorithm(TupleExpr expr, Set mergeJoins) { if (expr instanceof Join) { Join join = (Join) expr; - join.setAlgorithm(ID_JOIN_ALGORITHM); - markJoinTreeWithIdAlgorithm(join.getLeftArg()); - markJoinTreeWithIdAlgorithm(join.getRightArg()); + if (mergeJoins != null && mergeJoins.contains(join)) { + join.setAlgorithm(LmdbIdMergeJoinIterator.class.getSimpleName()); + } else { + join.setAlgorithm(ID_JOIN_ALGORITHM); + } + markJoinTreeWithIdAlgorithm(join.getLeftArg(), mergeJoins); + markJoinTreeWithIdAlgorithm(join.getRightArg(), mergeJoins); + } + } + + private List buildStages() { + List stages = new ArrayList<>(); + boolean[] consumed = new boolean[plans.size()]; + for (MergeSpec spec : mergeSpecs) { + int leftIndex = spec.leftIndex; + int rightIndex = spec.rightIndex(); + if (leftIndex < 0 || rightIndex >= plans.size()) { + continue; + } + RawPattern leftRaw = rawPatterns.get(leftIndex); + RawPattern rightRaw = rawPatterns.get(rightIndex); + StatementOrder leftOrder = determineOrder(leftRaw, spec.mergeVariable); + StatementOrder rightOrder = determineOrder(rightRaw, spec.mergeVariable); + PatternPlan leftPlan = leftRaw.toPlan(finalInfo, leftOrder); + PatternPlan rightPlan = rightRaw.toPlan(finalInfo, rightOrder); + stages.add(new MergeStage(leftPlan, rightPlan, leftRaw.patternInfo, rightRaw.patternInfo, + spec.mergeVariable)); + consumed[leftIndex] = true; + consumed[rightIndex] = true; + } + for (int i = 0; i < plans.size(); i++) { + if (!consumed[i]) { + stages.add(new PatternStage(plans.get(i))); + } + } + return stages; + } + + private StatementOrder determineOrder(RawPattern pattern, String mergeVariable) { + if (pattern.pattern.getStatementOrder() != null) { + return pattern.pattern.getStatementOrder(); + } + if (mergeVariable == null) { + return null; + } + int mask = pattern.patternInfo.getPositionsMask(mergeVariable); + if ((mask & (1 << TripleStore.SUBJ_IDX)) != 0) { + return StatementOrder.S; + } + if ((mask & (1 << TripleStore.PRED_IDX)) != 0) { + return StatementOrder.P; + } + if ((mask & (1 << TripleStore.OBJ_IDX)) != 0) { + return StatementOrder.O; + } + if ((mask & (1 << TripleStore.CONTEXT_IDX)) != 0) { + return StatementOrder.C; + } + return null; + } + + private interface Stage { + RecordIterator createInitialIterator(LmdbEvaluationDataset dataset, long[] initialBinding, + ValueStore valueStore) throws QueryEvaluationException; + + RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset dataset, ValueStore valueStore) + throws QueryEvaluationException; + } + + private static final class PatternStage implements Stage { + private final PatternPlan plan; + + private PatternStage(PatternPlan plan) { + this.plan = plan; + } + + @Override + public RecordIterator createInitialIterator(LmdbEvaluationDataset dataset, long[] initialBinding, + ValueStore valueStore) throws QueryEvaluationException { + RecordIterator iter = dataset.getRecordIterator(initialBinding, plan.subjIndex, plan.predIndex, + plan.objIndex, plan.ctxIndex, plan.patternIds); + return iter == null ? LmdbIdJoinIterator.emptyRecordIterator() : iter; + } + + @Override + public RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset dataset, ValueStore valueStore) + throws QueryEvaluationException { + return new BindingJoinRecordIterator(left, dataset, plan); + } + } + + private final class MergeStage implements Stage { + private final PatternPlan leftPlan; + private final PatternPlan rightPlan; + private final LmdbIdJoinIterator.PatternInfo leftInfo; + private final LmdbIdJoinIterator.PatternInfo rightInfo; + private final String mergeVariable; + + private MergeStage(PatternPlan leftPlan, PatternPlan rightPlan, LmdbIdJoinIterator.PatternInfo leftInfo, + LmdbIdJoinIterator.PatternInfo rightInfo, String mergeVariable) { + this.leftPlan = leftPlan; + this.rightPlan = rightPlan; + this.leftInfo = leftInfo; + this.rightInfo = rightInfo; + this.mergeVariable = mergeVariable; + } + + @Override + public RecordIterator createInitialIterator(LmdbEvaluationDataset dataset, long[] initialBinding, + ValueStore valueStore) throws QueryEvaluationException { + RecordIterator merge = createMergeIterator(dataset, initialBinding, valueStore); + return merge == null ? LmdbIdJoinIterator.emptyRecordIterator() : merge; + } + + @Override + public RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset dataset, ValueStore valueStore) + throws QueryEvaluationException { + return new RecordIterator() { + private final RecordIterator leftIter = left; + private RecordIterator currentMerge; + + @Override + public long[] next() throws QueryEvaluationException { + while (true) { + if (currentMerge != null) { + long[] merged = currentMerge.next(); + if (merged != null) { + return merged; + } + currentMerge.close(); + currentMerge = null; + } + long[] leftBinding = leftIter.next(); + if (leftBinding == null) { + return null; + } + currentMerge = createMergeIterator(dataset, leftBinding.clone(), valueStore); + } + } + + @Override + public void close() { + if (currentMerge != null) { + currentMerge.close(); + } + leftIter.close(); + } + }; + } + + private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] binding, ValueStore valueStore) + throws QueryEvaluationException { + if (leftPlan.order == null || rightPlan.order == null) { + return createSequentialIterator(dataset, binding, valueStore); + } + RecordIterator leftIterator = dataset.getOrderedRecordIterator(binding, leftPlan.subjIndex, + leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order); + if (leftIterator == null) { + return createSequentialIterator(dataset, binding, valueStore); + } + + RecordIterator rightIterator = dataset.getOrderedRecordIterator(binding, rightPlan.subjIndex, + rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order); + if (rightIterator == null) { + leftIterator.close(); + return createSequentialIterator(dataset, binding, valueStore); + } + + return new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, mergeVariable, + finalInfo); + } + + private RecordIterator createSequentialIterator(LmdbEvaluationDataset dataset, long[] binding, + ValueStore valueStore) throws QueryEvaluationException { + long[] base = binding.clone(); + RecordIterator leftIterator = dataset.getRecordIterator(base, leftPlan.subjIndex, leftPlan.predIndex, + leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds); + if (leftIterator == null) { + return LmdbIdJoinIterator.emptyRecordIterator(); + } + return new BindingJoinRecordIterator(leftIterator, dataset, rightPlan); } } @@ -249,17 +466,21 @@ private static final class PatternPlan { private final int predIndex; private final int objIndex; private final int ctxIndex; + private final StatementOrder order; - private PatternPlan(long[] patternIds, int subjIndex, int predIndex, int objIndex, int ctxIndex) { + private PatternPlan(long[] patternIds, int subjIndex, int predIndex, int objIndex, int ctxIndex, + StatementOrder order) { this.patternIds = patternIds; this.subjIndex = subjIndex; this.predIndex = predIndex; this.objIndex = objIndex; this.ctxIndex = ctxIndex; + this.order = order; } } private static final class RawPattern { + private final StatementPattern pattern; private final long[] patternIds; private final String subjVar; private final String predVar; @@ -270,9 +491,11 @@ private static final class RawPattern { private final boolean createdIds; private final Map constantIds; - private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, + private RawPattern(StatementPattern pattern, long[] patternIds, String subjVar, String predVar, String objVar, + String ctxVar, LmdbIdJoinIterator.PatternInfo patternInfo, boolean invalid, boolean createdIds, Map constantIds) { + this.pattern = pattern; this.patternIds = patternIds; this.subjVar = subjVar; this.predVar = predVar; @@ -365,12 +588,17 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore, boolea } LmdbIdJoinIterator.PatternInfo info = LmdbIdJoinIterator.PatternInfo.create(pattern); - return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, info, invalid, createdAny, constantIds); + return new RawPattern(pattern, ids, subjVar, predVar, objVar, ctxVar, info, invalid, createdAny, + constantIds); } PatternPlan toPlan(IdBindingInfo finalInfo) { + return toPlan(finalInfo, null); + } + + PatternPlan toPlan(IdBindingInfo finalInfo, StatementOrder order) { return new PatternPlan(patternIds.clone(), indexFor(subjVar, finalInfo), - indexFor(predVar, finalInfo), indexFor(objVar, finalInfo), indexFor(ctxVar, finalInfo)); + indexFor(predVar, finalInfo), indexFor(objVar, finalInfo), indexFor(ctxVar, finalInfo), order); } private static int indexFor(String varName, IdBindingInfo info) { @@ -446,4 +674,24 @@ boolean isInvalid() { } } } + + private static final class MergeSpec { + private final int leftIndex; + private final int leftEnd; + private final int rightEnd; + private final String mergeVariable; + private final Join join; + + private MergeSpec(int leftIndex, int leftEnd, int rightEnd, String mergeVariable, Join join) { + this.leftIndex = leftIndex; + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.mergeVariable = mergeVariable; + this.join = join; + } + + int rightIndex() { + return rightEnd - 1; + } + } } From 81a7e400d7f370667214cb38b0b8fce5f8bbbc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 4 Nov 2025 22:10:51 +0900 Subject: [PATCH 66/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDelegatingSailDataset.java | 20 ++++++++--- .../sail/lmdb/LmdbEvaluationDataset.java | 32 ++++++++++++++++- .../rdf4j/sail/lmdb/LmdbIdTripleSource.java | 19 ++++++++++ .../sail/lmdb/LmdbIdTripleSourceAdapter.java | 12 +++++-- .../lmdb/LmdbOverlayEvaluationDataset.java | 19 ++++++++-- .../lmdb/LmdbSailDatasetTripleSource.java | 18 ++++++++-- .../rdf4j/sail/lmdb/LmdbSailStore.java | 27 +++++++++++--- .../rdf4j/sail/lmdb/LmdbUnionSailDataset.java | 27 ++++++++++---- .../join/LmdbIdBGPQueryEvaluationStep.java | 36 +++++++++++++++---- .../join/LmdbIdJoinQueryEvaluationStep.java | 9 +++-- .../lmdb/join/LmdbIdMergeJoinIterator.java | 4 ++- 11 files changed, 189 insertions(+), 34 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java index df09e2c942..3a5cd4edbc 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java @@ -95,24 +95,36 @@ public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.Statemen @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { if (delegate instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds); + ctxIndex, patternIds, reuse); } // Fallback via TripleSource with Value conversion return new LmdbSailDatasetTripleSource(valueStore, delegate) - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { if (delegate instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) delegate).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, order); + ctxIndex, patternIds, order, reuse); } return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order); + patternIds, order, reuse); } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index 3ccbd36b75..be3c5e3de5 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -42,7 +42,10 @@ public interface LmdbEvaluationDataset { * read-only. The {@code patternIds} array contains four entries (subject, predicate, object, context) where a value * of {@link org.eclipse.rdf4j.sail.lmdb.model.LmdbValue#UNKNOWN_ID} indicates that the corresponding position is * unbound in the pattern. The index arguments point to the slots in {@code binding} where the resolved IDs should - * be written (or {@code -1} if that pattern position does not correspond to a variable). + * be written (or {@code -1} if that pattern position does not correspond to a variable). Implementations may reuse + * internal buffers by copying {@code binding} into a scratch array before mutating; callers can supply such an + * array via {@link #getRecordIterator(long[], int, int, int, int, long[], long[])} to avoid per-iterator + * allocation. *

    * * @param binding the current binding snapshot; implementations must copy before mutating @@ -58,6 +61,20 @@ public interface LmdbEvaluationDataset { RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException; + /** + * Variant of {@link #getRecordIterator(long[], int, int, int, int, long[])} that allows callers to supply a + * reusable scratch buffer. Implementations should treat {@code binding} as read-only and (when {@code reuse} is + * non-null and large enough) seed the scratch buffer with the binding state before producing rows from this + * iterator. + * + * @param reuse optional scratch buffer that may be mutated and returned from {@link RecordIterator#next()} + */ + @InternalUseOnly + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + /** * Create an ordered {@link RecordIterator} for the supplied pattern expressed via internal IDs and binding indexes. * Implementations may fall back to the unordered iterator when the requested order is unsupported. @@ -71,6 +88,19 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i return null; } + /** + * Variant of {@link #getOrderedRecordIterator(long[], int, int, int, int, long[], StatementOrder)} that accepts a + * reusable scratch buffer. + */ + @InternalUseOnly + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + } + return null; + } + /** * Create an ordered {@link RecordIterator} for the supplied pattern. Implementations may fall back to the unordered * iterator when the requested order is unsupported. diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java index b40476965b..dcb48dd37a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java @@ -33,6 +33,14 @@ public interface LmdbIdTripleSource { RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException; + /** + * Variant that accepts a reusable scratch buffer. + */ + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + /** * Create an ordered iterator over ID-level bindings; may fall back to the unordered iterator if unsupported. */ @@ -43,4 +51,15 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i } return null; } + + /** + * Variant that accepts a reusable scratch buffer. + */ + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + } + return null; + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java index 9db736e13a..be44edae8d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java @@ -82,17 +82,25 @@ public ValueFactory getValueFactory() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { // Prefer direct ID-level access if the delegate already supports it if (delegate instanceof LmdbIdTripleSource) { return ((LmdbIdTripleSource) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds); + patternIds, reuse); } // If no active connection changes, delegate to the current LMDB dataset to avoid materialization if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); if (dsOpt.isPresent()) { - return dsOpt.get().getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + return dsOpt.get() + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, + reuse); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index f8e5b57cd2..2f910a6770 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -131,12 +131,20 @@ public void close() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { // Fast path: no active connection changes → use the current LMDB dataset's ID-level iterator try { if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); if (dsOpt.isPresent()) { - return dsOpt.get().getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + return dsOpt.get() + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, + reuse); } } } catch (Exception ignore) { @@ -148,7 +156,7 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI // current branch dataset state (including transaction overlays). Therefore, using ID-level access here is // correct when available. RecordIterator viaIds = ((LmdbIdTripleSource) tripleSource) - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); if (viaIds != null) { return viaIds; } @@ -244,6 +252,13 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in patternIds, order); } + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { + return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order, reuse); + } + @Override public ValueStore getValueStore() { return valueStore; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java index 2deacf61aa..a496270686 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java @@ -43,11 +43,17 @@ public LmdbSailDatasetTripleSource(ValueFactory vf, SailDataset dataset) { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { // Fast path: backing dataset supports ID-level access if (dataset instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) dataset).getRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds); + ctxIndex, patternIds, reuse); } // Fallback path: value-level iteration converted to IDs using ValueStore @@ -146,12 +152,18 @@ public void close() { @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { if (dataset instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) dataset).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, order); + ctxIndex, patternIds, order, reuse); } return LmdbIdTripleSource.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order); + patternIds, order, reuse); } private long selectQueryId(long patternId, long[] binding, int index) { 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 bb5ce8966d..7820f65f81 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 @@ -1370,6 +1370,13 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, + long[] patternIds, long[] reuse) throws QueryEvaluationException { try { long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); @@ -1379,9 +1386,15 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalScratchReuse", "true"))) { - return new RecordIterator() { - private final long[] scratch = Arrays.copyOf(binding, binding.length); + final long[] scratch; + if (reuse != null && reuse.length >= binding.length) { + System.arraycopy(binding, 0, reuse, 0, binding.length); + scratch = reuse; + } else { + scratch = Arrays.copyOf(binding, binding.length); + } + return new RecordIterator() { @Override public long[] next() throws QueryEvaluationException { try { @@ -1482,8 +1495,14 @@ public void close() { @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); } try { long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); @@ -1500,7 +1519,7 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in int sortIndex = indexForOrder(order, subjIndex, predIndex, objIndex, ctxIndex); if (sortIndex >= 0) { RecordIterator fallback = getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds); + patternIds, reuse); return sortedRecordIterator(fallback, sortIndex); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java index 6642fa2739..4f4f426c38 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java @@ -131,36 +131,49 @@ public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.Statemen @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws org.eclipse.rdf4j.query.QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse) throws org.eclipse.rdf4j.query.QueryEvaluationException { boolean d1 = dataset1 instanceof LmdbEvaluationDataset; boolean d2 = dataset2 instanceof LmdbEvaluationDataset; if (d1 && d2) { RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getRecordIterator(binding, subjIndex, predIndex, - objIndex, ctxIndex, patternIds); + objIndex, ctxIndex, patternIds, reuse); RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getRecordIterator(binding, subjIndex, predIndex, - objIndex, ctxIndex, patternIds); + objIndex, ctxIndex, patternIds, reuse); return chain(r1, r2); } LmdbDelegatingSailDataset a = new LmdbDelegatingSailDataset(dataset1, valueStore); LmdbDelegatingSailDataset b = new LmdbDelegatingSailDataset(dataset2, valueStore); - return chain(a.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds), - b.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds)); + return chain(a.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse), + b.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse)); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws org.eclipse.rdf4j.query.QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) + throws org.eclipse.rdf4j.query.QueryEvaluationException { boolean d1 = dataset1 instanceof LmdbEvaluationDataset; boolean d2 = dataset2 instanceof LmdbEvaluationDataset; if (d1 && d2) { RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getOrderedRecordIterator(binding, subjIndex, - predIndex, objIndex, ctxIndex, patternIds, order); + predIndex, objIndex, ctxIndex, patternIds, order, reuse); RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getOrderedRecordIterator(binding, subjIndex, - predIndex, objIndex, ctxIndex, patternIds, order); + predIndex, objIndex, ctxIndex, patternIds, order, reuse); return chain(r1, r2); } return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order); + patternIds, order, reuse); } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 216afa8230..c2f75d6589 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -330,6 +330,9 @@ private final class MergeStage implements Stage { private final LmdbIdJoinIterator.PatternInfo leftInfo; private final LmdbIdJoinIterator.PatternInfo rightInfo; private final String mergeVariable; + private long[] sequentialLeftScratch; + private long[] orderedLeftScratch; + private long[] orderedRightScratch; private MergeStage(PatternPlan leftPlan, PatternPlan rightPlan, LmdbIdJoinIterator.PatternInfo leftInfo, LmdbIdJoinIterator.PatternInfo rightInfo, String mergeVariable) { @@ -353,6 +356,7 @@ public RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset datase return new RecordIterator() { private final RecordIterator leftIter = left; private RecordIterator currentMerge; + private long[] mergeScratch; @Override public long[] next() throws QueryEvaluationException { @@ -369,7 +373,11 @@ public long[] next() throws QueryEvaluationException { if (leftBinding == null) { return null; } - currentMerge = createMergeIterator(dataset, leftBinding.clone(), valueStore); + if (mergeScratch == null || mergeScratch.length < leftBinding.length) { + mergeScratch = new long[leftBinding.length]; + } + System.arraycopy(leftBinding, 0, mergeScratch, 0, leftBinding.length); + currentMerge = createMergeIterator(dataset, mergeScratch, valueStore); } } @@ -388,14 +396,22 @@ private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] if (leftPlan.order == null || rightPlan.order == null) { return createSequentialIterator(dataset, binding, valueStore); } + if (orderedLeftScratch == null || orderedLeftScratch.length < binding.length) { + orderedLeftScratch = new long[binding.length]; + } RecordIterator leftIterator = dataset.getOrderedRecordIterator(binding, leftPlan.subjIndex, - leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order); + leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order, + orderedLeftScratch); if (leftIterator == null) { return createSequentialIterator(dataset, binding, valueStore); } + if (orderedRightScratch == null || orderedRightScratch.length < binding.length) { + orderedRightScratch = new long[binding.length]; + } RecordIterator rightIterator = dataset.getOrderedRecordIterator(binding, rightPlan.subjIndex, - rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order); + rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order, + orderedRightScratch); if (rightIterator == null) { leftIterator.close(); return createSequentialIterator(dataset, binding, valueStore); @@ -407,9 +423,11 @@ private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] private RecordIterator createSequentialIterator(LmdbEvaluationDataset dataset, long[] binding, ValueStore valueStore) throws QueryEvaluationException { - long[] base = binding.clone(); - RecordIterator leftIterator = dataset.getRecordIterator(base, leftPlan.subjIndex, leftPlan.predIndex, - leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds); + if (sequentialLeftScratch == null || sequentialLeftScratch.length < binding.length) { + sequentialLeftScratch = new long[binding.length]; + } + RecordIterator leftIterator = dataset.getRecordIterator(binding, leftPlan.subjIndex, leftPlan.predIndex, + leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, sequentialLeftScratch); if (leftIterator == null) { return LmdbIdJoinIterator.emptyRecordIterator(); } @@ -422,6 +440,7 @@ private static final class BindingJoinRecordIterator implements RecordIterator { private final LmdbEvaluationDataset dataset; private final PatternPlan plan; private RecordIterator currentRight; + private long[] rightScratch; private BindingJoinRecordIterator(RecordIterator left, LmdbEvaluationDataset dataset, PatternPlan plan) { this.left = left; @@ -445,8 +464,11 @@ public long[] next() throws QueryEvaluationException { if (leftBinding == null) { return null; } + if (rightScratch == null || rightScratch.length < leftBinding.length) { + rightScratch = new long[leftBinding.length]; + } currentRight = dataset.getRecordIterator(leftBinding, plan.subjIndex, plan.predIndex, plan.objIndex, - plan.ctxIndex, plan.patternIds); + plan.ctxIndex, plan.patternIds, rightScratch); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 8ef61db330..4a5e885434 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -196,18 +196,21 @@ public CloseableIteration evaluate(BindingSet bindings) { } RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); + long[] bindingSnapshot = new long[initialBinding.length]; + long[] rightScratch = new long[initialBinding.length]; LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { - long[] snapshot = Arrays.copyOf(initialBinding, initialBinding.length); + System.arraycopy(initialBinding, 0, bindingSnapshot, 0, initialBinding.length); for (String name : leftInfo.getVariableNames()) { int pos = bindingInfo.getIndex(name); if (pos >= 0) { long id = leftInfo.getId(leftRecord, name); if (id != org.eclipse.rdf4j.sail.lmdb.model.LmdbValue.UNKNOWN_ID) { - snapshot[pos] = id; + bindingSnapshot[pos] = id; } } } - return dataset.getRecordIterator(snapshot, subjIdx, predIdx, objIdx, ctxIdx, patternIds); + return dataset.getRecordIterator(bindingSnapshot, subjIdx, predIdx, objIdx, ctxIdx, patternIds, + rightScratch); }; return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, bindingInfo, sharedVariables, diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java index daa264d5b9..0dc24ca3ad 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinIterator.java @@ -35,6 +35,7 @@ public class LmdbIdMergeJoinIterator implements RecordIterator { private final Set sharedVariables; private final int bindingSize; private static final int COLUMN_COUNT = TripleStore.CONTEXT_IDX + 1; + private final long[] combinedScratch; private long[] currentLeftRecord; private long currentLeftKey; @@ -85,6 +86,7 @@ public LmdbIdMergeJoinIterator(RecordIterator leftIterator, RecordIterator right this.sharedVariables = Collections.unmodifiableSet(shared); this.leftColumnBindingIndex = computeColumnBindingMap(leftInfo, bindingInfo); this.rightColumnBindingIndex = computeColumnBindingMap(rightInfo, bindingInfo); + this.combinedScratch = new long[bindingSize]; if (DEBUG) { System.out.println("DEBUG bindingSize=" + bindingSize + " mergeVar=" + mergeVariable + " shared=" + shared + " bindingVars=" + bindingInfo.getVariableNames()); @@ -244,7 +246,7 @@ private long[] joinWithCurrentLeft(long[] rightRecord) { return null; } - long[] combined = new long[bindingSize]; + long[] combined = combinedScratch; Arrays.fill(combined, LmdbValue.UNKNOWN_ID); if (!mergeRecordInto(combined, currentLeftRecord, leftColumnBindingIndex)) { From 6b54eed6f6588c38006a2fa1312c57dc28042edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Tue, 4 Nov 2025 22:52:14 +0900 Subject: [PATCH 67/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDelegatingSailDataset.java | 27 +++++-- .../sail/lmdb/LmdbDupRecordIterator.java | 4 +- .../sail/lmdb/LmdbEvaluationDataset.java | 31 ++++++-- .../rdf4j/sail/lmdb/LmdbIdTripleSource.java | 23 ++++-- .../sail/lmdb/LmdbIdTripleSourceAdapter.java | 14 ++-- .../lmdb/LmdbOverlayEvaluationDataset.java | 24 +++++-- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 26 +++++-- .../lmdb/LmdbSailDatasetTripleSource.java | 25 +++++-- .../rdf4j/sail/lmdb/LmdbSailStore.java | 39 ++++++++-- .../rdf4j/sail/lmdb/LmdbUnionSailDataset.java | 34 ++++++--- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 71 +++++++++++-------- .../join/LmdbIdBGPQueryEvaluationStep.java | 35 +++++++-- .../join/LmdbIdJoinQueryEvaluationStep.java | 3 +- 13 files changed, 268 insertions(+), 88 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java index 3a5cd4edbc..d66c49ea99 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java @@ -95,36 +95,51 @@ public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.Statemen @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { if (delegate instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, reuse); + ctxIndex, patternIds, reuse, quadReuse); } // Fallback via TripleSource with Value conversion return new LmdbSailDatasetTripleSource(valueStore, delegate) - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, quadReuse); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { - return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, reuse, + null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { if (delegate instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) delegate).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, order, reuse); + ctxIndex, patternIds, order, reuse, quadReuse); } return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order, reuse); + patternIds, order, reuse, quadReuse); } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 2ee9422632..72057b7eb1 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -42,7 +42,7 @@ class LmdbDupRecordIterator implements RecordIterator { @FunctionalInterface interface FallbackSupplier { - RecordIterator get() throws IOException; + RecordIterator get(long[] quadReuse) throws IOException; } /** Toggle copying of duplicate blocks for extra safety (defaults to copying). */ @@ -390,6 +390,6 @@ private RecordIterator createFallback() throws IOException { if (fallbackSupplier == null) { return null; } - return fallbackSupplier.get(); + return fallbackSupplier.get(quad); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index be3c5e3de5..e6e8d3b352 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -62,16 +62,24 @@ RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, i long[] patternIds) throws QueryEvaluationException; /** - * Variant of {@link #getRecordIterator(long[], int, int, int, int, long[])} that allows callers to supply a - * reusable scratch buffer. Implementations should treat {@code binding} as read-only and (when {@code reuse} is + * Variant of {@link #getRecordIterator(long[], int, int, int, int, long[])} that allows callers to supply reusable + * scratch buffers. Implementations should treat {@code binding} as read-only and (when {@code bindingReuse} is * non-null and large enough) seed the scratch buffer with the binding state before producing rows from this - * iterator. + * iterator. The {@code quadReuse} buffer (length 4) can be forwarded to iterators that materialize raw quadruples + * from the underlying store. * - * @param reuse optional scratch buffer that may be mutated and returned from {@link RecordIterator#next()} + * @param bindingReuse optional binding scratch buffer returned from {@link RecordIterator#next()} + * @param quadReuse optional quad scratch buffer (length 4) */ @InternalUseOnly default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] reuse) throws QueryEvaluationException { + long[] patternIds, long[] bindingReuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, null); + } + + @InternalUseOnly + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); } @@ -96,7 +104,18 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + return null; + } + + @InternalUseOnly + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] bindingReuse, long[] quadReuse) + throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, + quadReuse); } return null; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java index dcb48dd37a..7a9dc047f2 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java @@ -34,10 +34,15 @@ RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, i long[] patternIds) throws QueryEvaluationException; /** - * Variant that accepts a reusable scratch buffer. + * Variant that accepts reusable scratch buffers for bindings and quads. */ default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] reuse) throws QueryEvaluationException { + long[] patternIds, long[] bindingReuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, null); + } + + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); } @@ -53,12 +58,22 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i } /** - * Variant that accepts a reusable scratch buffer. + * Variant that accepts reusable scratch buffers. */ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + return null; + } + + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] bindingReuse, long[] quadReuse) + throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, + quadReuse); } return null; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java index be44edae8d..c04726543d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java @@ -82,16 +82,22 @@ public ValueFactory getValueFactory() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { // Prefer direct ID-level access if the delegate already supports it if (delegate instanceof LmdbIdTripleSource) { return ((LmdbIdTripleSource) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, reuse); + patternIds, reuse, quadReuse); } // If no active connection changes, delegate to the current LMDB dataset to avoid materialization @@ -99,8 +105,8 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); if (dsOpt.isPresent()) { return dsOpt.get() - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, - reuse); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, + quadReuse); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 2f910a6770..231906698d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -131,12 +131,18 @@ public void close() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { // Fast path: no active connection changes → use the current LMDB dataset's ID-level iterator try { if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { @@ -144,7 +150,7 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI if (dsOpt.isPresent()) { return dsOpt.get() .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, - reuse); + reuse, quadReuse); } } } catch (Exception ignore) { @@ -156,7 +162,7 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI // current branch dataset state (including transaction overlays). Therefore, using ID-level access here is // correct when available. RecordIterator viaIds = ((LmdbIdTripleSource) tripleSource) - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, quadReuse); if (viaIds != null) { return viaIds; } @@ -249,14 +255,22 @@ public void close() { public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order); + patternIds, order, null, null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order, reuse); + patternIds, order, reuse, null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { + return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order, reuse, quadReuse); } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index ebf7840eed..e962c22760 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -86,7 +86,6 @@ class LmdbRecordIterator implements RecordIterator { private int lastResult; private final long[] quad; - private final long[] originalQuad; private boolean fetchNext = false; @@ -96,17 +95,34 @@ class LmdbRecordIterator implements RecordIterator { LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { - this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef); + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, null); + } + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse); } LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { + this(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, null); + } + + LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { this.subj = subj; this.pred = pred; this.obj = obj; this.context = context; - this.originalQuad = new long[] { subj, pred, obj, context }; - this.quad = new long[] { subj, pred, obj, context }; + if (quadReuse != null && quadReuse.length >= 4) { + this.quad = quadReuse; + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = obj; + this.quad[3] = context; + } else { + this.quad = new long[] { subj, pred, obj, context }; + } this.pool = Pool.get(); this.keyData = pool.getVal(); this.valueData = pool.getVal(); @@ -266,7 +282,7 @@ public long[] next() { lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); } else { // Matching value found - index.keyToQuad(keyData.mv_data(), originalQuad, quad); + index.keyToQuad(keyData.mv_data(), subj, pred, obj, context, quad); // fetch next value fetchNext = true; return quad; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java index a496270686..f183598e73 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java @@ -43,17 +43,23 @@ public LmdbSailDatasetTripleSource(ValueFactory vf, SailDataset dataset) { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { // Fast path: backing dataset supports ID-level access if (dataset instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) dataset).getRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, reuse); + ctxIndex, patternIds, reuse, quadReuse); } // Fallback path: value-level iteration converted to IDs using ValueStore @@ -152,18 +158,27 @@ public void close() { @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { - return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, reuse, + null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { if (dataset instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) dataset).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, order, reuse); + ctxIndex, patternIds, order, reuse, quadReuse); } return LmdbIdTripleSource.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order, reuse); + patternIds, order, reuse, quadReuse); } private long selectQueryId(long patternId, long[] binding, int index) { 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 7820f65f81..426d4a0e76 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 @@ -1370,20 +1370,28 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, + long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { try { long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); - RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit); + RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit, + quadReuse); if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalScratchReuse", "true"))) { final long[] scratch; @@ -1495,14 +1503,24 @@ public void close() { @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { - return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, reuse, + null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, + quadReuse); } try { long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); @@ -1510,7 +1528,8 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); - RecordIterator orderedIter = orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery); + RecordIterator orderedIter = orderedRecordIterator(order, subjQuery, predQuery, objQuery, ctxQuery, + quadReuse); if (orderedIter != null) { return orderedIter; } @@ -1519,7 +1538,7 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in int sortIndex = indexForOrder(order, subjIndex, predIndex, objIndex, ctxIndex); if (sortIndex >= 0) { RecordIterator fallback = getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, reuse); + patternIds, reuse, quadReuse); return sortedRecordIterator(fallback, sortIndex); } } @@ -1903,6 +1922,11 @@ private long resolveId(Value value) throws IOException { private RecordIterator orderedRecordIterator(StatementOrder order, long subjID, long predID, long objID, long contextID) throws IOException { + return orderedRecordIterator(order, subjID, predID, objID, contextID, null); + } + + private RecordIterator orderedRecordIterator(StatementOrder order, long subjID, long predID, long objID, + long contextID, long[] quadReuse) throws IOException { if (order == null) { return null; } @@ -1911,7 +1935,8 @@ private RecordIterator orderedRecordIterator(StatementOrder order, long subjID, return null; } boolean rangeSearch = chosen.getPatternScore(subjID, predID, objID, contextID) > 0; - return new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, explicit, txn); + return new LmdbRecordIterator(chosen, rangeSearch, subjID, predID, objID, contextID, explicit, txn, + quadReuse); } private int indexForOrder(StatementOrder order, int subjIndex, int predIndex, int objIndex, int ctxIndex) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java index 4f4f426c38..23bbc05ee2 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbUnionSailDataset.java @@ -131,49 +131,65 @@ public RecordIterator getRecordIterator(org.eclipse.rdf4j.query.algebra.Statemen @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws org.eclipse.rdf4j.query.QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws org.eclipse.rdf4j.query.QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, long[] reuse, long[] quadReuse) throws org.eclipse.rdf4j.query.QueryEvaluationException { boolean d1 = dataset1 instanceof LmdbEvaluationDataset; boolean d2 = dataset2 instanceof LmdbEvaluationDataset; if (d1 && d2) { RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getRecordIterator(binding, subjIndex, predIndex, - objIndex, ctxIndex, patternIds, reuse); + objIndex, ctxIndex, patternIds, reuse, quadReuse); RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getRecordIterator(binding, subjIndex, predIndex, - objIndex, ctxIndex, patternIds, reuse); + objIndex, ctxIndex, patternIds, reuse, quadReuse); return chain(r1, r2); } LmdbDelegatingSailDataset a = new LmdbDelegatingSailDataset(dataset1, valueStore); LmdbDelegatingSailDataset b = new LmdbDelegatingSailDataset(dataset2, valueStore); - return chain(a.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse), - b.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse)); + return chain(a.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, + quadReuse), + b.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, quadReuse)); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws org.eclipse.rdf4j.query.QueryEvaluationException { - return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null); + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws org.eclipse.rdf4j.query.QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, reuse, + null); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse, long[] quadReuse) + throws org.eclipse.rdf4j.query.QueryEvaluationException { boolean d1 = dataset1 instanceof LmdbEvaluationDataset; boolean d2 = dataset2 instanceof LmdbEvaluationDataset; if (d1 && d2) { RecordIterator r1 = ((LmdbEvaluationDataset) dataset1).getOrderedRecordIterator(binding, subjIndex, - predIndex, objIndex, ctxIndex, patternIds, order, reuse); + predIndex, objIndex, ctxIndex, patternIds, order, reuse, quadReuse); RecordIterator r2 = ((LmdbEvaluationDataset) dataset2).getOrderedRecordIterator(binding, subjIndex, - predIndex, objIndex, ctxIndex, patternIds, order, reuse); + predIndex, objIndex, ctxIndex, patternIds, order, reuse, quadReuse); return chain(r1, r2); } return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, order, reuse); + patternIds, order, reuse, quadReuse); } @Override 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 e8e13c63f9..db731f9c63 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 @@ -86,6 +86,7 @@ import org.eclipse.rdf4j.sail.lmdb.TxnRecordCache.Record; import org.eclipse.rdf4j.sail.lmdb.TxnRecordCache.RecordCacheIterator; import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; import org.eclipse.rdf4j.sail.lmdb.util.IndexKeyWriters; import org.lwjgl.PointerBuffer; @@ -583,9 +584,9 @@ public RecordIterator getAllTriplesSortedByContext(Txn txn) throws IOException { for (TripleIndex index : indexes) { if (index.getFieldSeq()[0] == 'c') { // found a context-first index - LmdbDupRecordIterator.FallbackSupplier fallback = () -> new LmdbRecordIterator(index, false, -1, -1, -1, - -1, true, txn); - return getTriplesUsingIndex(txn, -1, -1, -1, -1, true, index, false, fallback); + LmdbDupRecordIterator.FallbackSupplier fallback = quad -> new LmdbRecordIterator(index, false, -1, -1, + -1, -1, true, txn, quad); + return getTriplesUsingIndex(txn, -1, -1, -1, -1, true, index, false, fallback, null); } } return null; @@ -593,11 +594,16 @@ public RecordIterator getAllTriplesSortedByContext(Txn txn) throws IOException { public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit) throws IOException { + return getTriples(txn, subj, pred, obj, context, explicit, null); + } + + public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit, + long[] quadReuse) throws IOException { TripleIndex index = getBestIndex(subj, pred, obj, context); // System.out.println("get triples: " + Arrays.asList(subj, pred, obj,context)); boolean doRangeSearch = index.getPatternScore(subj, pred, obj, context) > 0; - LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = () -> new LmdbRecordIterator(index, doRangeSearch, - subj, pred, obj, context, explicit, txn); + LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = quad -> new LmdbRecordIterator(index, doRangeSearch, + subj, pred, obj, context, explicit, txn, quad); if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj == -1 && context == -1) { assert context == -1 && obj == -1 : "subject-predicate index can only be used for (s,p,?,?) patterns"; // Use SP dup iterator, but union with the standard iterator to guard against any edge cases @@ -605,7 +611,8 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, fallbackSupplier); } - return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch, fallbackSupplier); + return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch, fallbackSupplier, + quadReuse); } boolean hasTriples(boolean explicit) throws IOException { @@ -619,8 +626,8 @@ boolean hasTriples(boolean explicit) throws IOException { private RecordIterator getTriplesUsingIndex(Txn txn, long subj, long pred, long obj, long context, boolean explicit, TripleIndex index, boolean rangeSearch, - LmdbDupRecordIterator.FallbackSupplier fallbackSupplier) throws IOException { - return fallbackSupplier.get(); + LmdbDupRecordIterator.FallbackSupplier fallbackSupplier, long[] quadReuse) throws IOException { + return fallbackSupplier.get(quadReuse); } private int leadingBoundCount(char[] fieldSeq, long subj, long pred, long obj, long context) { @@ -1882,27 +1889,33 @@ void keyToQuad(ByteBuffer key, long[] quad) { readQuadUnsigned(key, indexMap, quad); } - void keyToQuad(ByteBuffer key, long[] originalQuad, long[] quad) { - // directly use index map to read values in to correct positions - if (originalQuad[indexMap[0]] != -1) { - Varint.skipUnsigned(key); - } else { - quad[indexMap[0]] = Varint.readUnsigned(key); - } - if (originalQuad[indexMap[1]] != -1) { - Varint.skipUnsigned(key); - } else { - quad[indexMap[1]] = Varint.readUnsigned(key); - } - if (originalQuad[indexMap[2]] != -1) { - Varint.skipUnsigned(key); - } else { - quad[indexMap[2]] = Varint.readUnsigned(key); - } - if (originalQuad[indexMap[3]] != -1) { - Varint.skipUnsigned(key); - } else { - quad[indexMap[3]] = Varint.readUnsigned(key); + void keyToQuad(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + for (int i = 0; i < indexMap.length; i++) { + int component = indexMap[i]; + long bound; + switch (component) { + case SUBJ_IDX: + bound = subj; + break; + case PRED_IDX: + bound = pred; + break; + case OBJ_IDX: + bound = obj; + break; + case CONTEXT_IDX: + bound = context; + break; + default: + bound = LmdbValue.UNKNOWN_ID; + break; + } + if (bound != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[component] = bound; + } else { + quad[component] = Varint.readUnsigned(key); + } } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index c2f75d6589..154c113573 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -304,6 +304,8 @@ RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset dataset, Valu private static final class PatternStage implements Stage { private final PatternPlan plan; + private long[] bindingScratch; + private long[] quadScratch; private PatternStage(PatternPlan plan) { this.plan = plan; @@ -312,8 +314,14 @@ private PatternStage(PatternPlan plan) { @Override public RecordIterator createInitialIterator(LmdbEvaluationDataset dataset, long[] initialBinding, ValueStore valueStore) throws QueryEvaluationException { + if (bindingScratch == null || bindingScratch.length < initialBinding.length) { + bindingScratch = new long[initialBinding.length]; + } + if (quadScratch == null) { + quadScratch = new long[4]; + } RecordIterator iter = dataset.getRecordIterator(initialBinding, plan.subjIndex, plan.predIndex, - plan.objIndex, plan.ctxIndex, plan.patternIds); + plan.objIndex, plan.ctxIndex, plan.patternIds, bindingScratch, quadScratch); return iter == null ? LmdbIdJoinIterator.emptyRecordIterator() : iter; } @@ -333,6 +341,9 @@ private final class MergeStage implements Stage { private long[] sequentialLeftScratch; private long[] orderedLeftScratch; private long[] orderedRightScratch; + private long[] sequentialLeftQuadScratch; + private long[] orderedLeftQuadScratch; + private long[] orderedRightQuadScratch; private MergeStage(PatternPlan leftPlan, PatternPlan rightPlan, LmdbIdJoinIterator.PatternInfo leftInfo, LmdbIdJoinIterator.PatternInfo rightInfo, String mergeVariable) { @@ -399,9 +410,12 @@ private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] if (orderedLeftScratch == null || orderedLeftScratch.length < binding.length) { orderedLeftScratch = new long[binding.length]; } + if (orderedLeftQuadScratch == null) { + orderedLeftQuadScratch = new long[4]; + } RecordIterator leftIterator = dataset.getOrderedRecordIterator(binding, leftPlan.subjIndex, leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order, - orderedLeftScratch); + orderedLeftScratch, orderedLeftQuadScratch); if (leftIterator == null) { return createSequentialIterator(dataset, binding, valueStore); } @@ -409,9 +423,12 @@ private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] if (orderedRightScratch == null || orderedRightScratch.length < binding.length) { orderedRightScratch = new long[binding.length]; } + if (orderedRightQuadScratch == null) { + orderedRightQuadScratch = new long[4]; + } RecordIterator rightIterator = dataset.getOrderedRecordIterator(binding, rightPlan.subjIndex, rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order, - orderedRightScratch); + orderedRightScratch, orderedRightQuadScratch); if (rightIterator == null) { leftIterator.close(); return createSequentialIterator(dataset, binding, valueStore); @@ -426,8 +443,12 @@ private RecordIterator createSequentialIterator(LmdbEvaluationDataset dataset, l if (sequentialLeftScratch == null || sequentialLeftScratch.length < binding.length) { sequentialLeftScratch = new long[binding.length]; } + if (sequentialLeftQuadScratch == null) { + sequentialLeftQuadScratch = new long[4]; + } RecordIterator leftIterator = dataset.getRecordIterator(binding, leftPlan.subjIndex, leftPlan.predIndex, - leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, sequentialLeftScratch); + leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, sequentialLeftScratch, + sequentialLeftQuadScratch); if (leftIterator == null) { return LmdbIdJoinIterator.emptyRecordIterator(); } @@ -441,6 +462,7 @@ private static final class BindingJoinRecordIterator implements RecordIterator { private final PatternPlan plan; private RecordIterator currentRight; private long[] rightScratch; + private long[] quadScratch; private BindingJoinRecordIterator(RecordIterator left, LmdbEvaluationDataset dataset, PatternPlan plan) { this.left = left; @@ -467,8 +489,11 @@ public long[] next() throws QueryEvaluationException { if (rightScratch == null || rightScratch.length < leftBinding.length) { rightScratch = new long[leftBinding.length]; } + if (quadScratch == null) { + quadScratch = new long[4]; + } currentRight = dataset.getRecordIterator(leftBinding, plan.subjIndex, plan.predIndex, plan.objIndex, - plan.ctxIndex, plan.patternIds, rightScratch); + plan.ctxIndex, plan.patternIds, rightScratch, quadScratch); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 4a5e885434..70e7ead92b 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -198,6 +198,7 @@ public CloseableIteration evaluate(BindingSet bindings) { RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); long[] bindingSnapshot = new long[initialBinding.length]; long[] rightScratch = new long[initialBinding.length]; + long[] rightQuadScratch = new long[4]; LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { System.arraycopy(initialBinding, 0, bindingSnapshot, 0, initialBinding.length); for (String name : leftInfo.getVariableNames()) { @@ -210,7 +211,7 @@ public CloseableIteration evaluate(BindingSet bindings) { } } return dataset.getRecordIterator(bindingSnapshot, subjIdx, predIdx, objIdx, ctxIdx, patternIds, - rightScratch); + rightScratch, rightQuadScratch); }; return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, bindingInfo, sharedVariables, From 7812d82c5e650d88437c5e669cc678c33be0392b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Wed, 5 Nov 2025 16:32:08 +0900 Subject: [PATCH 68/79] working on new ID based join iterator --- .../rdf4j/query/algebra/StatementPattern.java | 84 +++++ .../sail/lmdb/LmdbDupRecordIterator.java | 6 +- .../sail/lmdb/LmdbEvaluationDataset.java | 123 +++++++- .../rdf4j/sail/lmdb/LmdbIdTripleSource.java | 22 ++ .../sail/lmdb/LmdbIdTripleSourceAdapter.java | 33 +- .../lmdb/LmdbOverlayEvaluationDataset.java | 108 +++++-- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 247 ++++++++++----- .../rdf4j/sail/lmdb/LmdbSailStore.java | 298 ++++++++++++------ .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 42 ++- .../join/LmdbIdBGPQueryEvaluationStep.java | 134 +++++++- .../join/LmdbIdJoinQueryEvaluationStep.java | 47 ++- .../LmdbIdMergeJoinQueryEvaluationStep.java | 29 +- .../sail/lmdb/LmdbIdBGPEvaluationTest.java | 103 +++++- .../sail/lmdb/LmdbIdJoinEvaluationTest.java | 32 ++ .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 54 ++++ 15 files changed, 1095 insertions(+), 267 deletions(-) diff --git a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/StatementPattern.java b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/StatementPattern.java index a2c7392059..6f199e137e 100644 --- a/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/StatementPattern.java +++ b/core/queryalgebra/model/src/main/java/org/eclipse/rdf4j/query/algebra/StatementPattern.java @@ -48,6 +48,77 @@ public enum Scope { NAMED_CONTEXTS } + /** + * Index selection hint for statement pattern evaluation. + */ + @Experimental + public enum Index { + S, + P, + O, + C, + SP, + SO, + SC, + PS, + PO, + PC, + OS, + OP, + OC, + CS, + CP, + CO, + SPO, + SPC, + SOP, + SOC, + SCP, + SCO, + PSO, + PSC, + POS, + POC, + PCS, + PCO, + OSP, + OSC, + OPS, + OPC, + OCS, + OCP, + CSP, + CSO, + CPS, + CPO, + COS, + COP, + SPOC, + SPCO, + SOPC, + SOCP, + SCPO, + SCOP, + PSOC, + PSCO, + POSC, + POCS, + PCSO, + PCOS, + OSPC, + OSCP, + OPSC, + OPCS, + OCSP, + OCPS, + CSPO, + CSOP, + CPSO, + CPOS, + COSP, + COPS + } + /*-----------* * Variables * *-----------*/ @@ -66,6 +137,8 @@ public enum Scope { private String indexName; + private Index index; + private Set assuredBindingNames; private List varList; @@ -427,6 +500,7 @@ public StatementPattern clone() { clone.assuredBindingNames = assuredBindingNames; clone.varList = null; clone.statementOrder = statementOrder; + clone.index = index; return clone; } @@ -540,8 +614,18 @@ public String getIndexName() { return indexName; } + @Experimental + public Index getIndex() { + return index; + } + @Experimental public void setIndexName(String indexName) { this.indexName = indexName; } + + @Experimental + public void setIndex(Index index) { + this.index = index; + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 72057b7eb1..165da883dd 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -42,7 +42,9 @@ class LmdbDupRecordIterator implements RecordIterator { @FunctionalInterface interface FallbackSupplier { - RecordIterator get(long[] quadReuse) throws IOException; + RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, + LmdbRecordIterator iteratorReuse) + throws IOException; } /** Toggle copying of duplicate blocks for extra safety (defaults to copying). */ @@ -390,6 +392,6 @@ private RecordIterator createFallback() throws IOException { if (fallbackSupplier == null) { return null; } - return fallbackSupplier.get(quad); + return fallbackSupplier.get(quad, null, null, null); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index e6e8d3b352..6882f83f21 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +import java.nio.ByteBuffer; + import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.common.transaction.IsolationLevel; @@ -22,6 +24,30 @@ */ public interface LmdbEvaluationDataset { + @InternalUseOnly + final class KeyRangeBuffers { + private final ByteBuffer minKey; + private final ByteBuffer maxKey; + + public KeyRangeBuffers(ByteBuffer minKey, ByteBuffer maxKey) { + this.minKey = minKey; + this.maxKey = maxKey; + } + + public static KeyRangeBuffers acquire() { + Pool pool = Pool.get(); + return new KeyRangeBuffers(pool.getKeyBuffer(), pool.getKeyBuffer()); + } + + public ByteBuffer minKey() { + return minKey; + } + + public ByteBuffer maxKey() { + return maxKey; + } + } + /** * Create a {@link RecordIterator} for the supplied {@link StatementPattern}, taking into account any existing * bindings. @@ -33,6 +59,21 @@ public interface LmdbEvaluationDataset { */ RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException; + @InternalUseOnly + default RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, KeyRangeBuffers keyBuffers) + throws QueryEvaluationException { + return getRecordIterator(pattern, bindings); + } + + @InternalUseOnly + default RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, KeyRangeBuffers keyBuffers, + RecordIterator iteratorReuse) throws QueryEvaluationException { + if (iteratorReuse != null) { + iteratorReuse.close(); + } + return getRecordIterator(pattern, bindings, keyBuffers); + } + /** * Create a {@link RecordIterator} for the supplied pattern, expressed as internal IDs, using the current binding * snapshot. @@ -61,6 +102,12 @@ public interface LmdbEvaluationDataset { RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException; + @InternalUseOnly + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + /** * Variant of {@link #getRecordIterator(long[], int, int, int, int, long[])} that allows callers to supply reusable * scratch buffers. Implementations should treat {@code binding} as read-only and (when {@code bindingReuse} is @@ -74,15 +121,36 @@ RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, i @InternalUseOnly default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] bindingReuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, bindingReuse, + null); } @InternalUseOnly default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, bindingReuse, + quadReuse); + } + + @InternalUseOnly + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse) + throws QueryEvaluationException { return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); } + @InternalUseOnly + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse, + RecordIterator iteratorReuse) + throws QueryEvaluationException { + if (iteratorReuse != null) { + iteratorReuse.close(); + } + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + bindingReuse, quadReuse); + } + /** * Create an ordered {@link RecordIterator} for the supplied pattern expressed via internal IDs and binding indexes. * Implementations may fall back to the unordered iterator when the requested order is unsupported. @@ -90,10 +158,8 @@ default RecordIterator getRecordIterator(long[] binding, int subjIndex, int pred @InternalUseOnly default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { - if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); - } - return null; + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + null, null); } /** @@ -103,31 +169,54 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i @InternalUseOnly default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { - if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); - } - return null; + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + reuse, null); } @InternalUseOnly default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + bindingReuse, quadReuse); + } + + @InternalUseOnly + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, + long[] quadReuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, - quadReuse); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + bindingReuse, quadReuse); } return null; } + @InternalUseOnly + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, + long[] quadReuse, RecordIterator iteratorReuse) throws QueryEvaluationException { + if (iteratorReuse != null) { + iteratorReuse.close(); + } + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, + keyBuffers, + bindingReuse, quadReuse); + } + /** * Create an ordered {@link RecordIterator} for the supplied pattern. Implementations may fall back to the unordered * iterator when the requested order is unsupported. */ default RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order) throws QueryEvaluationException { + return getOrderedRecordIterator(pattern, bindings, order, null); + } + + default RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order, + KeyRangeBuffers keyBuffers) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(pattern, bindings); + return getRecordIterator(pattern, bindings, keyBuffers); } return null; } @@ -137,6 +226,16 @@ default boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int return order == null; } + /** + * Determine the most suitable LMDB index for the supplied binding pattern. + * + * @return the field sequence of the selected index (e.g. {@code \"spoc\"}), or {@code null} when no advice is + * available + */ + default String selectBestIndex(long subj, long pred, long obj, long context) { + return null; + } + /** * @return the {@link ValueStore} backing this dataset. */ diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java index 7a9dc047f2..036953dead 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java @@ -33,6 +33,11 @@ public interface LmdbIdTripleSource { RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException; + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + /** * Variant that accepts reusable scratch buffers for bindings and quads. */ @@ -46,6 +51,13 @@ default RecordIterator getRecordIterator(long[] binding, int subjIndex, int pred return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); } + default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse) + throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, + quadReuse); + } + /** * Create an ordered iterator over ID-level bindings; may fall back to the unordered iterator if unsupported. */ @@ -77,4 +89,14 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i } return null; } + + default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, + long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + bindingReuse, quadReuse); + } + return null; + } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java index c04726543d..56c9c07902 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java @@ -16,6 +16,7 @@ import org.eclipse.rdf4j.common.annotation.Experimental; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.EmptyIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; @@ -82,22 +83,29 @@ public ValueFactory getValueFactory() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, quadReuse); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { // Prefer direct ID-level access if the delegate already supports it if (delegate instanceof LmdbIdTripleSource) { return ((LmdbIdTripleSource) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, reuse, quadReuse); + patternIds, keyBuffers, reuse, quadReuse); } // If no active connection changes, delegate to the current LMDB dataset to avoid materialization @@ -105,8 +113,8 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); if (dsOpt.isPresent()) { return dsOpt.get() - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, - quadReuse); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + reuse, quadReuse); } } @@ -182,6 +190,21 @@ public void close() { }; } + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, + long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { + if (order == null) { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + bindingReuse, quadReuse); + } + if (delegate instanceof LmdbIdTripleSource) { + return ((LmdbIdTripleSource) delegate).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, + ctxIndex, patternIds, order, keyBuffers, bindingReuse, quadReuse); + } + return null; + } + private long selectQueryId(long patternId, long[] binding, int index) { if (patternId != LmdbValue.UNKNOWN_ID) { return patternId; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 231906698d..a4dc8fe77f 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -43,18 +43,18 @@ final class LmdbOverlayEvaluationDataset implements LmdbEvaluationDataset { @Override public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException { + return getRecordIteratorInternal(pattern, bindings, null); + } - // Fast path: no active connection changes → delegate directly to LMDB dataset to avoid materialization - try { - if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { - var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); - if (dsOpt.isPresent()) { - return dsOpt.get().getRecordIterator(pattern, bindings); - } - } - } catch (Exception ignore) { - // fall through to overlay path - } + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, KeyRangeBuffers keyBuffers) + throws QueryEvaluationException { + return getRecordIteratorInternal(pattern, bindings, keyBuffers); + } + + private RecordIterator getRecordIteratorInternal(StatementPattern pattern, BindingSet bindings, + KeyRangeBuffers keyBuffers) + throws QueryEvaluationException { Value subj = resolveValue(pattern.getSubjectVar(), bindings); if (subj != null && !(subj instanceof Resource)) { @@ -131,38 +131,50 @@ public void close() { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null, + null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, + null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { - // Fast path: no active connection changes → use the current LMDB dataset's ID-level iterator - try { - if (!LmdbEvaluationStrategy.hasActiveConnectionChanges()) { - var dsOpt = LmdbEvaluationStrategy.getCurrentDataset(); - if (dsOpt.isPresent()) { - return dsOpt.get() - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, - reuse, quadReuse); - } - } - } catch (Exception ignore) { - // fall back to overlay tripleSource path - } + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, + quadReuse); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + reuse, quadReuse); + } + + private RecordIterator getRecordIteratorInternal(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { // Prefer an ID-level path if the TripleSource supports it and we can trust overlay correctness. if (tripleSource instanceof LmdbIdTripleSource) { // The overlay dataset is represented by this LmdbOverlayEvaluationDataset; the tripleSource reflects the // current branch dataset state (including transaction overlays). Therefore, using ID-level access here is // correct when available. RecordIterator viaIds = ((LmdbIdTripleSource) tripleSource) - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, quadReuse); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, reuse, + quadReuse); if (viaIds != null) { return viaIds; } @@ -171,13 +183,12 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI // Fallback: Value-based overlay path with per-statement ID resolution (minimal unavoidable materialization). try { Value subjValue = valueForQuery(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex, true, false); - Resource subjRes = subjValue == null ? null : (Resource) subjValue; - Value predValue = valueForQuery(patternIds[TripleStore.PRED_IDX], binding, predIndex, false, true); - IRI predIri = predValue == null ? null : (IRI) predValue; - Value objValue = valueForQuery(patternIds[TripleStore.OBJ_IDX], binding, objIndex, false, false); + Resource subjRes = subjValue == null ? null : (Resource) subjValue; + IRI predIri = predValue == null ? null : (IRI) predValue; + long ctxQueryId = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); boolean requireDefaultContext = ctxQueryId == 0; Resource[] contexts; @@ -273,11 +284,44 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in patternIds, order, reuse, quadReuse); } + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, + long[] quadReuse) throws QueryEvaluationException { + if (order == null) { + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + bindingReuse, quadReuse); + } + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, + bindingReuse, quadReuse); + } + + @Override + public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order, + KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + if (order == null) { + return getRecordIteratorInternal(pattern, bindings, keyBuffers); + } + return LmdbEvaluationDataset.super.getOrderedRecordIterator(pattern, bindings, order, keyBuffers); + } + @Override public ValueStore getValueStore() { return valueStore; } + @Override + public String selectBestIndex(long subj, long pred, long obj, long context) { + var currentDataset = LmdbEvaluationStrategy.getCurrentDataset(); + if (currentDataset.isPresent()) { + LmdbEvaluationDataset delegate = currentDataset.get(); + if (delegate != this) { + return delegate.selectBestIndex(subj, pred, obj, context); + } + } + return null; + } + private long selectQueryId(long patternId, long[] binding, int index) { if (patternId != LmdbValue.UNKNOWN_ID) { return patternId; @@ -365,6 +409,6 @@ private long resolveId(Value value) throws Exception { return lmdb.getInternalID(); } } - return valueStore.getId(value); + return valueStore.getId(value, true); } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index e962c22760..7f3f9671f0 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -42,20 +42,20 @@ */ class LmdbRecordIterator implements RecordIterator { private static final Logger log = LoggerFactory.getLogger(LmdbRecordIterator.class); - private final Pool pool; + private final Pool pool = Pool.get(); - private final TripleIndex index; + private TripleIndex index; - private final long subj; - private final long pred; - private final long obj; - private final long context; + private long subj; + private long pred; + private long obj; + private long context; - private final long cursor; + private long cursor; - private final MDBVal maxKey; + private MDBVal maxKey; - private final boolean matchValues; + private boolean matchValues; private GroupMatcher groupMatcher; /** @@ -63,111 +63,165 @@ class LmdbRecordIterator implements RecordIterator { * value-level filtering. When false, range bounds already guarantee that every visited key matches and the * GroupMatcher is redundant. */ - private final boolean needMatcher; + private boolean needMatcher; - private final Txn txnRef; + private Txn txnRef; private long txnRefVersion; - private final long txn; + private long txn; - private final int dbi; + private int dbi; private volatile boolean closed = false; - private final MDBVal keyData; + private MDBVal keyData; - private final MDBVal valueData; + private MDBVal valueData; private ByteBuffer minKeyBuf; private ByteBuffer maxKeyBuf; + private boolean externalMinKeyBuf; + private boolean externalMaxKeyBuf; private int lastResult; - private final long[] quad; + private long[] quad; private boolean fetchNext = false; - private final StampedLongAdderLockManager txnLockManager; + private StampedLongAdderLockManager txnLockManager; private final Thread ownerThread = Thread.currentThread(); + private boolean initialized = false; + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { - this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, null); + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, null, null, null); } LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { - this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse); + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, null, null); + } + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, + ByteBuffer maxKeyBufParam) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, minKeyBufParam, + maxKeyBufParam); } LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { - this(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, null); + this(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, null, null, null); } LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, - long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, + ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, + minKeyBufParam, maxKeyBufParam); + initialized = true; + } + + void initialize(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, + ByteBuffer maxKeyBufParam) throws IOException { + if (initialized && !closed) { + throw new IllegalStateException("Cannot initialize LMDB record iterator while it is open"); + } + initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, + minKeyBufParam, maxKeyBufParam); + initialized = true; + } + + private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, + ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + this.index = index; this.subj = subj; this.pred = pred; this.obj = obj; this.context = context; + if (quadReuse != null && quadReuse.length >= 4) { this.quad = quadReuse; - this.quad[0] = subj; - this.quad[1] = pred; - this.quad[2] = obj; - this.quad[3] = context; - } else { + } else if (this.quad == null || this.quad.length < 4) { this.quad = new long[] { subj, pred, obj, context }; } - this.pool = Pool.get(); - this.keyData = pool.getVal(); - this.valueData = pool.getVal(); - this.index = index; + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = obj; + this.quad[3] = context; + + if (this.keyData == null) { + this.keyData = pool.getVal(); + } + if (this.valueData == null) { + this.valueData = pool.getVal(); + } + if (rangeSearch) { - minKeyBuf = pool.getKeyBuffer(); + this.externalMinKeyBuf = minKeyBufParam != null; + this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : pool.getKeyBuffer(); + minKeyBuf.clear(); if (keyBuilder != null) { - minKeyBuf.clear(); keyBuilder.writeMin(minKeyBuf); } else { index.getMinKey(minKeyBuf, subj, pred, obj, context); } minKeyBuf.flip(); - this.maxKey = pool.getVal(); - this.maxKeyBuf = pool.getKeyBuffer(); + if (this.maxKey == null) { + this.maxKey = pool.getVal(); + } + this.externalMaxKeyBuf = maxKeyBufParam != null; + this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : pool.getKeyBuffer(); + maxKeyBuf.clear(); if (keyBuilder != null) { - maxKeyBuf.clear(); keyBuilder.writeMax(maxKeyBuf); } else { index.getMaxKey(maxKeyBuf, subj, pred, obj, context); } maxKeyBuf.flip(); this.maxKey.mv_data(maxKeyBuf); - } else { - // Even when we can't bound with a prefix (no rangeSearch), we can still - // position the cursor closer to the first potentially matching key when - // there are any constraints (matchValues). This avoids scanning from the - // absolute beginning for patterns like ?p ?o constC etc. + if (this.maxKey != null) { + pool.free(maxKey); + this.maxKey = null; + } + if (this.maxKeyBuf != null && !externalMaxKeyBuf) { + pool.free(maxKeyBuf); + } + this.externalMaxKeyBuf = maxKeyBufParam != null; + this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : null; + if (subj > 0 || pred > 0 || obj > 0 || context >= 0) { - minKeyBuf = pool.getKeyBuffer(); + this.externalMinKeyBuf = minKeyBufParam != null; + this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : pool.getKeyBuffer(); + minKeyBuf.clear(); index.getMinKey(minKeyBuf, subj, pred, obj, context); minKeyBuf.flip(); } else { - minKeyBuf = null; + if (this.minKeyBuf != null && !externalMinKeyBuf) { + pool.free(minKeyBuf); + } + this.externalMinKeyBuf = minKeyBufParam != null; + this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : null; } - this.maxKey = null; - this.maxKeyBuf = null; } this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; int prefixLen = index.getPatternScore(subj, pred, obj, context); int boundCount = (subj > 0 ? 1 : 0) + (pred > 0 ? 1 : 0) + (obj > 0 ? 1 : 0) + (context >= 0 ? 1 : 0); this.needMatcher = boundCount > prefixLen; + this.groupMatcher = null; + this.fetchNext = false; + this.lastResult = MDB_SUCCESS; + this.closed = false; this.dbi = index.getDB(explicit); this.txnRef = txnRef; @@ -221,18 +275,21 @@ class LmdbRecordIterator implements RecordIterator { @Override public long[] next() { + if (closed) { + log.debug("Calling next() on an LmdbRecordIterator that is already closed, returning null"); + return null; + } + StampedLongAdderLockManager manager = txnLockManager; + if (manager == null) { + throw new SailException("Iterator not initialized"); + } long readStamp; try { - readStamp = txnLockManager.readLock(); + readStamp = manager.readLock(); } catch (InterruptedException e) { throw new SailException(e); } try { - if (closed) { - log.debug("Calling next() on an LmdbRecordIterator that is already closed, returning null"); - return null; - } - if (txnRefVersion != txnRef.version()) { // TODO: None of the tests in the LMDB Store cover this case! // cursor must be renewed @@ -291,7 +348,7 @@ public long[] next() { closeInternal(false); return null; } finally { - txnLockManager.unlockRead(readStamp); + manager.unlockRead(readStamp); } } @@ -312,40 +369,66 @@ private boolean matches() { } private void closeInternal(boolean maybeCalledAsync) { - if (!closed) { - long writeStamp = 0L; - boolean writeLocked = false; - if (maybeCalledAsync && ownerThread != Thread.currentThread()) { - try { - writeStamp = txnLockManager.writeLock(); - writeLocked = true; - } catch (InterruptedException e) { - throw new SailException(e); - } - } + StampedLongAdderLockManager manager = this.txnLockManager; + if (closed) { + return; + } + long writeStamp = 0L; + boolean writeLocked = false; + if (maybeCalledAsync && ownerThread != Thread.currentThread() && manager != null) { try { - if (!closed) { - if (txnRef.isReadOnly()) { - pool.freeCursor(dbi, index, cursor); - } else { - mdb_cursor_close(cursor); - } - pool.free(keyData); - pool.free(valueData); - if (minKeyBuf != null) { - pool.free(minKeyBuf); - } - if (maxKey != null) { - pool.free(maxKeyBuf); - pool.free(maxKey); - } - } - } finally { - closed = true; - if (writeLocked) { - txnLockManager.unlockWrite(writeStamp); + writeStamp = manager.writeLock(); + writeLocked = true; + } catch (InterruptedException e) { + throw new SailException(e); + } + } + try { + if (cursor != 0L && txnRef != null) { + if (txnRef.isReadOnly()) { + pool.freeCursor(dbi, index, cursor); + } else { + mdb_cursor_close(cursor); } + cursor = 0L; + } + if (keyData != null) { + pool.free(keyData); + keyData = null; + } + if (valueData != null) { + pool.free(valueData); + valueData = null; + } + if (minKeyBuf != null && !externalMinKeyBuf) { + pool.free(minKeyBuf); + } + if (maxKeyBuf != null && !externalMaxKeyBuf) { + pool.free(maxKeyBuf); + } + if (maxKey != null) { + pool.free(maxKey); + maxKey = null; + } + minKeyBuf = null; + maxKeyBuf = null; + externalMinKeyBuf = false; + externalMaxKeyBuf = false; + groupMatcher = null; + fetchNext = false; + lastResult = 0; + matchValues = false; + needMatcher = false; + txnRef = null; + txn = 0L; + dbi = 0; + index = null; + } finally { + closed = true; + if (writeLocked) { + manager.unlockWrite(writeStamp); } + txnLockManager = null; } } 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 426d4a0e76..c61fa87d24 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 @@ -81,6 +81,9 @@ public void close() { } }; + private static final boolean SCRATCH_REUSE_ENABLED = !"false" + .equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalScratchReuse", "true")); + final Logger logger = LoggerFactory.getLogger(LmdbSailStore.class); private final TripleStore tripleStore; @@ -1112,7 +1115,6 @@ public boolean supportsDeprecateByQuery() { } private final class LmdbSailDataset implements SailDataset, LmdbEvaluationDataset { - private final boolean explicit; private final IsolationLevel isolationLevel; private final Txn txn; @@ -1328,6 +1330,19 @@ public Set getSupportedOrders(Resource subj, IRI pred, Value obj @Override public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) throws QueryEvaluationException { + return getRecordIterator(pattern, bindings, null); + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, + KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + return getRecordIterator(pattern, bindings, keyBuffers, null); + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, + KeyRangeBuffers keyBuffers, + RecordIterator iteratorReuse) throws QueryEvaluationException { try { PatternArrays arrays = describePattern(pattern); if (!arrays.valid) { @@ -1360,7 +1375,13 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin return emptyRecordIterator(); } - return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit); + ByteBuffer minKeyBuf = keyBuffers != null ? keyBuffers.minKey() : null; + ByteBuffer maxKeyBuf = keyBuffers != null ? keyBuffers.maxKey() : null; + LmdbRecordIterator reuse = (iteratorReuse instanceof LmdbRecordIterator) + ? (LmdbRecordIterator) iteratorReuse + : null; + return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit, minKeyBuf, maxKeyBuf, + null, reuse); } catch (IOException e) { throw new QueryEvaluationException("Unable to create LMDB record iterator", e); } @@ -1370,88 +1391,72 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null, null, + null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, null, + null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, + quadReuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, reuse, + quadReuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse, + RecordIterator iteratorReuse) + throws QueryEvaluationException { try { long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); long predQuery = selectQueryId(patternIds[TripleStore.PRED_IDX], binding, predIndex); long objQuery = selectQueryId(patternIds[TripleStore.OBJ_IDX], binding, objIndex); long ctxQuery = selectQueryId(patternIds[TripleStore.CONTEXT_IDX], binding, ctxIndex); - RecordIterator base = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit, - quadReuse); + ByteBuffer minKeyBuf = keyBuffers != null ? keyBuffers.minKey() : null; + ByteBuffer maxKeyBuf = keyBuffers != null ? keyBuffers.maxKey() : null; - if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalScratchReuse", "true"))) { - final long[] scratch; - if (reuse != null && reuse.length >= binding.length) { - System.arraycopy(binding, 0, reuse, 0, binding.length); - scratch = reuse; - } else { - scratch = Arrays.copyOf(binding, binding.length); - } + BindingProjectingIterator projectingReuse = iteratorReuse instanceof BindingProjectingIterator + ? (BindingProjectingIterator) iteratorReuse + : null; + LmdbRecordIterator baseReuse = projectingReuse != null ? projectingReuse.getBase() + : (iteratorReuse instanceof LmdbRecordIterator ? (LmdbRecordIterator) iteratorReuse : null); + + RecordIterator raw = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit, + minKeyBuf, maxKeyBuf, quadReuse, baseReuse); + if (!SCRATCH_REUSE_ENABLED) { + RecordIterator baseIterator = raw; return new RecordIterator() { @Override public long[] next() throws QueryEvaluationException { try { long[] quad; - while ((quad = base.next()) != null) { - boolean conflict = false; - // subject - if (subjIndex >= 0) { - long baseVal = binding[subjIndex]; - long v = quad[TripleStore.SUBJ_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[subjIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - // predicate - if (!conflict && predIndex >= 0) { - long baseVal = binding[predIndex]; - long v = quad[TripleStore.PRED_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[predIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - // object - if (!conflict && objIndex >= 0) { - long baseVal = binding[objIndex]; - long v = quad[TripleStore.OBJ_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[objIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - // context (0 means default graph); treat like any other ID for conflict - if (!conflict && ctxIndex >= 0) { - long baseVal = binding[ctxIndex]; - long v = quad[TripleStore.CONTEXT_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[ctxIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - - if (!conflict) { - return scratch; + while ((quad = baseIterator.next()) != null) { + long[] merged = mergeBinding(binding, quad[TripleStore.SUBJ_IDX], + quad[TripleStore.PRED_IDX], quad[TripleStore.OBJ_IDX], + quad[TripleStore.CONTEXT_IDX], subjIndex, predIndex, objIndex, ctxIndex); + if (merged != null) { + return merged; } } return null; @@ -1464,39 +1469,121 @@ public long[] next() throws QueryEvaluationException { @Override public void close() { - base.close(); + baseIterator.close(); } }; } - return new RecordIterator() { - @Override - public long[] next() throws QueryEvaluationException { - try { - long[] quad; - while ((quad = base.next()) != null) { - long[] merged = mergeBinding(binding, quad[TripleStore.SUBJ_IDX], - quad[TripleStore.PRED_IDX], quad[TripleStore.OBJ_IDX], - quad[TripleStore.CONTEXT_IDX], subjIndex, predIndex, objIndex, ctxIndex); - if (merged != null) { - return merged; - } + BindingProjectingIterator result = projectingReuse != null ? projectingReuse + : new BindingProjectingIterator(); + result.configure(raw, raw instanceof LmdbRecordIterator ? (LmdbRecordIterator) raw : null, binding, + subjIndex, predIndex, objIndex, ctxIndex, reuse); + return result; + } catch (IOException e) { + throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + } + } + + private final class BindingProjectingIterator implements RecordIterator { + private RecordIterator base; + private LmdbRecordIterator lmdbBase; + private long[] binding; + private int subjIndex; + private int predIndex; + private int objIndex; + private int ctxIndex; + private long[] scratch; + + void configure(RecordIterator base, LmdbRecordIterator lmdbBase, long[] binding, int subjIndex, + int predIndex, int objIndex, + int ctxIndex, long[] bindingReuse) { + this.base = base; + this.lmdbBase = lmdbBase; + this.binding = binding; + this.subjIndex = subjIndex; + this.predIndex = predIndex; + this.objIndex = objIndex; + this.ctxIndex = ctxIndex; + int bindingLength = binding.length; + if (bindingReuse != null && bindingReuse.length >= bindingLength) { + System.arraycopy(binding, 0, bindingReuse, 0, bindingLength); + this.scratch = bindingReuse; + } else if (this.scratch == null || this.scratch.length != bindingLength) { + this.scratch = Arrays.copyOf(binding, bindingLength); + } else { + System.arraycopy(binding, 0, this.scratch, 0, bindingLength); + } + } + + LmdbRecordIterator getBase() { + return lmdbBase; + } + + @Override + public long[] next() throws QueryEvaluationException { + if (base == null) { + return null; + } + try { + long[] quad; + while ((quad = base.next()) != null) { + System.arraycopy(binding, 0, scratch, 0, binding.length); + boolean conflict = false; + if (subjIndex >= 0) { + long baseVal = binding[subjIndex]; + long v = quad[TripleStore.SUBJ_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[subjIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict && predIndex >= 0) { + long baseVal = binding[predIndex]; + long v = quad[TripleStore.PRED_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[predIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; } - return null; - } catch (QueryEvaluationException e) { - throw e; - } catch (Exception e) { - throw new QueryEvaluationException(e); + } + if (!conflict && objIndex >= 0) { + long baseVal = binding[objIndex]; + long v = quad[TripleStore.OBJ_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[objIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict && ctxIndex >= 0) { + long baseVal = binding[ctxIndex]; + long v = quad[TripleStore.CONTEXT_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[ctxIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict) { + return scratch; } } + return null; + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + } - @Override - public void close() { - base.close(); - } - }; - } catch (IOException e) { - throw new QueryEvaluationException("Unable to create LMDB record iterator", e); + @Override + public void close() { + if (base != null) { + base.close(); + base = null; + lmdbBase = null; + } } } @@ -1504,23 +1591,31 @@ public void close() { public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, - null); + null, null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { - return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, reuse, - null); + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + reuse, null); } @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse, long[] quadReuse) throws QueryEvaluationException { + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, null, + reuse, quadReuse); + } + + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, + long[] quadReuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, - quadReuse); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + bindingReuse, quadReuse); } try { long subjQuery = selectQueryId(patternIds[TripleStore.SUBJ_IDX], binding, subjIndex); @@ -1538,7 +1633,7 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in int sortIndex = indexForOrder(order, subjIndex, predIndex, objIndex, ctxIndex); if (sortIndex >= 0) { RecordIterator fallback = getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, reuse, quadReuse); + patternIds, keyBuffers, bindingReuse, quadReuse); return sortedRecordIterator(fallback, sortIndex); } } @@ -1574,8 +1669,14 @@ public boolean supportsOrder(long[] binding, int subjIndex, int predIndex, int o @Override public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order) throws QueryEvaluationException { + return getOrderedRecordIterator(pattern, bindings, order, null); + } + + @Override + public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, + StatementOrder order, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(pattern, bindings); + return getRecordIterator(pattern, bindings, keyBuffers); } try { Value subj = resolveValue(pattern.getSubjectVar(), bindings); @@ -1620,12 +1721,11 @@ public RecordIterator getOrderedRecordIterator(StatementPattern pattern, Binding return emptyRecordIterator(); } - try (RecordIterator orderedIter = orderedRecordIterator(order, subjID, predID, objID, contextID)) { - if (orderedIter != null) { - return orderedIter; - } + RecordIterator orderedIter = orderedRecordIterator(order, subjID, predID, objID, contextID); + if (orderedIter != null) { + return orderedIter; } - return null; + return getRecordIterator(pattern, bindings, keyBuffers); } catch (IOException e) { throw new QueryEvaluationException("Unable to create ordered LMDB record iterator", e); } @@ -1636,6 +1736,12 @@ public ValueStore getValueStore() { return valueStore; } + @Override + public String selectBestIndex(long subj, long pred, long obj, long context) { + TripleStore.TripleIndex index = tripleStore.getBestIndex(subj, pred, obj, context); + return index == null ? null : new String(index.getFieldSeq()); + } + @Override public IsolationLevel getIsolationLevel() { return isolationLevel; 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 db731f9c63..8bbcf180af 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 @@ -584,9 +584,14 @@ public RecordIterator getAllTriplesSortedByContext(Txn txn) throws IOException { for (TripleIndex index : indexes) { if (index.getFieldSeq()[0] == 'c') { // found a context-first index - LmdbDupRecordIterator.FallbackSupplier fallback = quad -> new LmdbRecordIterator(index, false, -1, -1, - -1, -1, true, txn, quad); - return getTriplesUsingIndex(txn, -1, -1, -1, -1, true, index, false, fallback, null); + LmdbDupRecordIterator.FallbackSupplier fallback = (quad, minBuf, maxBuf, reuse) -> { + if (reuse != null) { + reuse.initialize(index, null, false, -1, -1, -1, -1, true, txn, quad, minBuf, maxBuf); + return reuse; + } + return new LmdbRecordIterator(index, null, false, -1, -1, -1, -1, true, txn, quad, minBuf, maxBuf); + }; + return getTriplesUsingIndex(txn, -1, -1, -1, -1, true, index, false, fallback, null, null, null, null); } } return null; @@ -594,16 +599,34 @@ public RecordIterator getAllTriplesSortedByContext(Txn txn) throws IOException { public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit) throws IOException { - return getTriples(txn, subj, pred, obj, context, explicit, null); + return getTriples(txn, subj, pred, obj, context, explicit, null, null, null); } public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit, long[] quadReuse) throws IOException { + return getTriples(txn, subj, pred, obj, context, explicit, null, null, quadReuse, null); + } + + public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit, + ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, long[] quadReuse) throws IOException { + return getTriples(txn, subj, pred, obj, context, explicit, minKeyBuf, maxKeyBuf, quadReuse, null); + } + + public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit, + ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, long[] quadReuse, LmdbRecordIterator iteratorReuse) + throws IOException { TripleIndex index = getBestIndex(subj, pred, obj, context); // System.out.println("get triples: " + Arrays.asList(subj, pred, obj,context)); boolean doRangeSearch = index.getPatternScore(subj, pred, obj, context) > 0; - LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = quad -> new LmdbRecordIterator(index, doRangeSearch, - subj, pred, obj, context, explicit, txn, quad); + LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = (quad, minBuf, maxBuf, reuse) -> { + if (reuse != null) { + reuse.initialize(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, minBuf, + maxBuf); + return reuse; + } + return new LmdbRecordIterator(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, + minBuf, maxBuf); + }; if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj == -1 && context == -1) { assert context == -1 && obj == -1 : "subject-predicate index can only be used for (s,p,?,?) patterns"; // Use SP dup iterator, but union with the standard iterator to guard against any edge cases @@ -612,7 +635,7 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c fallbackSupplier); } return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch, fallbackSupplier, - quadReuse); + minKeyBuf, maxKeyBuf, quadReuse, iteratorReuse); } boolean hasTriples(boolean explicit) throws IOException { @@ -626,8 +649,9 @@ boolean hasTriples(boolean explicit) throws IOException { private RecordIterator getTriplesUsingIndex(Txn txn, long subj, long pred, long obj, long context, boolean explicit, TripleIndex index, boolean rangeSearch, - LmdbDupRecordIterator.FallbackSupplier fallbackSupplier, long[] quadReuse) throws IOException { - return fallbackSupplier.get(quadReuse); + LmdbDupRecordIterator.FallbackSupplier fallbackSupplier, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, + long[] quadReuse, LmdbRecordIterator iteratorReuse) throws IOException { + return fallbackSupplier.get(quadReuse, minKeyBuf, maxKeyBuf, iteratorReuse); } private int leadingBoundCount(char[] fieldSeq, long subj, long pred, long obj, long context) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 154c113573..695967a49a 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -38,6 +39,7 @@ import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset.KeyRangeBuffers; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; import org.eclipse.rdf4j.sail.lmdb.TripleStore; @@ -64,6 +66,7 @@ public final class LmdbIdBGPQueryEvaluationStep implements QueryEvaluationStep { private final Map constantBindings; private final List mergeSpecs; private final Set mergeJoinNodes; + private final Map patternBuffers = new HashMap<>(); public LmdbIdBGPQueryEvaluationStep(Join root, List patterns, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -171,6 +174,7 @@ public CloseableIteration evaluate(BindingSet bindings) { return new EmptyIteration<>(); } + applyIndexHints(dataset, initialBinding); List stages = buildStages(); if (stages.isEmpty()) { return new EmptyIteration<>(); @@ -258,14 +262,17 @@ private List buildStages() { StatementOrder rightOrder = determineOrder(rightRaw, spec.mergeVariable); PatternPlan leftPlan = leftRaw.toPlan(finalInfo, leftOrder); PatternPlan rightPlan = rightRaw.toPlan(finalInfo, rightOrder); - stages.add(new MergeStage(leftPlan, rightPlan, leftRaw.patternInfo, rightRaw.patternInfo, - spec.mergeVariable)); + KeyRangeBuffers leftBuffers = keyBuffersFor(leftPlan.pattern); + KeyRangeBuffers rightBuffers = keyBuffersFor(rightPlan.pattern); + stages.add(new MergeStage(leftPlan, rightPlan, leftBuffers, rightBuffers, leftRaw.patternInfo, + rightRaw.patternInfo, spec.mergeVariable)); consumed[leftIndex] = true; consumed[rightIndex] = true; } for (int i = 0; i < plans.size(); i++) { if (!consumed[i]) { - stages.add(new PatternStage(plans.get(i))); + PatternPlan plan = plans.get(i); + stages.add(new PatternStage(plan, keyBuffersFor(plan.pattern))); } } return stages; @@ -294,6 +301,54 @@ private StatementOrder determineOrder(RawPattern pattern, String mergeVariable) return null; } + private void applyIndexHints(LmdbEvaluationDataset dataset) { + applyIndexHints(dataset, null); + } + + private void applyIndexHints(LmdbEvaluationDataset dataset, long[] binding) { + for (int i = 0; i < rawPatterns.size(); i++) { + RawPattern raw = rawPatterns.get(i); + StatementPattern pattern = raw.pattern; + pattern.setIndex(null); + pattern.setIndexName(null); + if (dataset == null) { + continue; + } + long subj = resolveComponent(raw.patternIds[TripleStore.SUBJ_IDX], plans.get(i).subjIndex, binding); + long pred = resolveComponent(raw.patternIds[TripleStore.PRED_IDX], plans.get(i).predIndex, binding); + long obj = resolveComponent(raw.patternIds[TripleStore.OBJ_IDX], plans.get(i).objIndex, binding); + long ctx = resolveComponent(raw.patternIds[TripleStore.CONTEXT_IDX], plans.get(i).ctxIndex, binding); + String fieldSeq = dataset.selectBestIndex(subj, pred, obj, ctx); + if (fieldSeq == null) { + continue; + } + pattern.setIndexName(fieldSeq); + String enumKey = fieldSeq.toUpperCase(Locale.ROOT); + try { + pattern.setIndex(StatementPattern.Index.valueOf(enumKey)); + } catch (IllegalArgumentException ignore) { + pattern.setIndex(null); + } + } + } + + private KeyRangeBuffers keyBuffersFor(StatementPattern pattern) { + return patternBuffers.computeIfAbsent(pattern, p -> KeyRangeBuffers.acquire()); + } + + private static long resolveComponent(long constantId, int bindingIndex, long[] binding) { + if (constantId != LmdbValue.UNKNOWN_ID) { + return constantId; + } + if (binding != null && bindingIndex >= 0 && bindingIndex < binding.length) { + long fromBinding = binding[bindingIndex]; + if (fromBinding != LmdbValue.UNKNOWN_ID) { + return fromBinding; + } + } + return LmdbValue.UNKNOWN_ID; + } + private interface Stage { RecordIterator createInitialIterator(LmdbEvaluationDataset dataset, long[] initialBinding, ValueStore valueStore) throws QueryEvaluationException; @@ -304,11 +359,14 @@ RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset dataset, Valu private static final class PatternStage implements Stage { private final PatternPlan plan; + private final KeyRangeBuffers keyBuffers; private long[] bindingScratch; private long[] quadScratch; + private RecordIterator reusableIterator; - private PatternStage(PatternPlan plan) { + private PatternStage(PatternPlan plan, KeyRangeBuffers keyBuffers) { this.plan = plan; + this.keyBuffers = keyBuffers; } @Override @@ -321,20 +379,28 @@ public RecordIterator createInitialIterator(LmdbEvaluationDataset dataset, long[ quadScratch = new long[4]; } RecordIterator iter = dataset.getRecordIterator(initialBinding, plan.subjIndex, plan.predIndex, - plan.objIndex, plan.ctxIndex, plan.patternIds, bindingScratch, quadScratch); + plan.objIndex, plan.ctxIndex, plan.patternIds, keyBuffers, bindingScratch, quadScratch, + reusableIterator); + if (iter != null && iter != LmdbIdJoinIterator.emptyRecordIterator()) { + reusableIterator = iter; + } else { + reusableIterator = null; + } return iter == null ? LmdbIdJoinIterator.emptyRecordIterator() : iter; } @Override public RecordIterator joinWith(RecordIterator left, LmdbEvaluationDataset dataset, ValueStore valueStore) throws QueryEvaluationException { - return new BindingJoinRecordIterator(left, dataset, plan); + return new BindingJoinRecordIterator(left, dataset, plan, keyBuffers); } } private final class MergeStage implements Stage { private final PatternPlan leftPlan; private final PatternPlan rightPlan; + private final KeyRangeBuffers leftKeyBuffers; + private final KeyRangeBuffers rightKeyBuffers; private final LmdbIdJoinIterator.PatternInfo leftInfo; private final LmdbIdJoinIterator.PatternInfo rightInfo; private final String mergeVariable; @@ -344,11 +410,17 @@ private final class MergeStage implements Stage { private long[] sequentialLeftQuadScratch; private long[] orderedLeftQuadScratch; private long[] orderedRightQuadScratch; + private RecordIterator sequentialLeftReusable; + private RecordIterator orderedLeftReusable; + private RecordIterator orderedRightReusable; - private MergeStage(PatternPlan leftPlan, PatternPlan rightPlan, LmdbIdJoinIterator.PatternInfo leftInfo, + private MergeStage(PatternPlan leftPlan, PatternPlan rightPlan, KeyRangeBuffers leftKeyBuffers, + KeyRangeBuffers rightKeyBuffers, LmdbIdJoinIterator.PatternInfo leftInfo, LmdbIdJoinIterator.PatternInfo rightInfo, String mergeVariable) { this.leftPlan = leftPlan; this.rightPlan = rightPlan; + this.leftKeyBuffers = leftKeyBuffers; + this.rightKeyBuffers = rightKeyBuffers; this.leftInfo = leftInfo; this.rightInfo = rightInfo; this.mergeVariable = mergeVariable; @@ -415,10 +487,16 @@ private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] } RecordIterator leftIterator = dataset.getOrderedRecordIterator(binding, leftPlan.subjIndex, leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order, - orderedLeftScratch, orderedLeftQuadScratch); + leftKeyBuffers, orderedLeftScratch, orderedLeftQuadScratch, orderedLeftReusable); if (leftIterator == null) { + orderedLeftReusable = null; return createSequentialIterator(dataset, binding, valueStore); } + if (leftIterator != LmdbIdJoinIterator.emptyRecordIterator()) { + orderedLeftReusable = leftIterator; + } else { + orderedLeftReusable = null; + } if (orderedRightScratch == null || orderedRightScratch.length < binding.length) { orderedRightScratch = new long[binding.length]; @@ -428,11 +506,17 @@ private RecordIterator createMergeIterator(LmdbEvaluationDataset dataset, long[] } RecordIterator rightIterator = dataset.getOrderedRecordIterator(binding, rightPlan.subjIndex, rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order, - orderedRightScratch, orderedRightQuadScratch); + rightKeyBuffers, orderedRightScratch, orderedRightQuadScratch, orderedRightReusable); if (rightIterator == null) { leftIterator.close(); + orderedRightReusable = null; return createSequentialIterator(dataset, binding, valueStore); } + if (rightIterator != LmdbIdJoinIterator.emptyRecordIterator()) { + orderedRightReusable = rightIterator; + } else { + orderedRightReusable = null; + } return new LmdbIdMergeJoinIterator(leftIterator, rightIterator, leftInfo, rightInfo, mergeVariable, finalInfo); @@ -447,12 +531,17 @@ private RecordIterator createSequentialIterator(LmdbEvaluationDataset dataset, l sequentialLeftQuadScratch = new long[4]; } RecordIterator leftIterator = dataset.getRecordIterator(binding, leftPlan.subjIndex, leftPlan.predIndex, - leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, sequentialLeftScratch, - sequentialLeftQuadScratch); + leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftKeyBuffers, sequentialLeftScratch, + sequentialLeftQuadScratch, sequentialLeftReusable); + if (leftIterator != null && leftIterator != LmdbIdJoinIterator.emptyRecordIterator()) { + sequentialLeftReusable = leftIterator; + } else { + sequentialLeftReusable = null; + } if (leftIterator == null) { return LmdbIdJoinIterator.emptyRecordIterator(); } - return new BindingJoinRecordIterator(leftIterator, dataset, rightPlan); + return new BindingJoinRecordIterator(leftIterator, dataset, rightPlan, rightKeyBuffers); } } @@ -460,14 +549,18 @@ private static final class BindingJoinRecordIterator implements RecordIterator { private final RecordIterator left; private final LmdbEvaluationDataset dataset; private final PatternPlan plan; + private final KeyRangeBuffers keyBuffers; private RecordIterator currentRight; private long[] rightScratch; private long[] quadScratch; + private RecordIterator reusableRight; - private BindingJoinRecordIterator(RecordIterator left, LmdbEvaluationDataset dataset, PatternPlan plan) { + private BindingJoinRecordIterator(RecordIterator left, LmdbEvaluationDataset dataset, PatternPlan plan, + KeyRangeBuffers keyBuffers) { this.left = left; this.dataset = dataset; this.plan = plan; + this.keyBuffers = keyBuffers; } @Override @@ -493,7 +586,12 @@ public long[] next() throws QueryEvaluationException { quadScratch = new long[4]; } currentRight = dataset.getRecordIterator(leftBinding, plan.subjIndex, plan.predIndex, plan.objIndex, - plan.ctxIndex, plan.patternIds, rightScratch, quadScratch); + plan.ctxIndex, plan.patternIds, keyBuffers, rightScratch, quadScratch, reusableRight); + if (currentRight != null && currentRight != LmdbIdJoinIterator.emptyRecordIterator()) { + reusableRight = currentRight; + } else { + reusableRight = null; + } } } @@ -504,6 +602,7 @@ public void close() { currentRight = null; } left.close(); + reusableRight = null; } } @@ -514,15 +613,17 @@ private static final class PatternPlan { private final int objIndex; private final int ctxIndex; private final StatementOrder order; + private final StatementPattern pattern; private PatternPlan(long[] patternIds, int subjIndex, int predIndex, int objIndex, int ctxIndex, - StatementOrder order) { + StatementOrder order, StatementPattern pattern) { this.patternIds = patternIds; this.subjIndex = subjIndex; this.predIndex = predIndex; this.objIndex = objIndex; this.ctxIndex = ctxIndex; this.order = order; + this.pattern = pattern; } } @@ -645,7 +746,8 @@ PatternPlan toPlan(IdBindingInfo finalInfo) { PatternPlan toPlan(IdBindingInfo finalInfo, StatementOrder order) { return new PatternPlan(patternIds.clone(), indexFor(subjVar, finalInfo), - indexFor(predVar, finalInfo), indexFor(objVar, finalInfo), indexFor(ctxVar, finalInfo), order); + indexFor(predVar, finalInfo), indexFor(objVar, finalInfo), indexFor(ctxVar, finalInfo), order, + pattern); } private static int indexFor(String varName, IdBindingInfo info) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index 70e7ead92b..e8c2339263 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -15,7 +15,9 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -35,6 +37,7 @@ import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset.KeyRangeBuffers; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; import org.eclipse.rdf4j.sail.lmdb.ValueStore; @@ -54,6 +57,7 @@ public class LmdbIdJoinQueryEvaluationStep implements QueryEvaluationStep { private final LmdbDatasetContext datasetContext; private final QueryEvaluationStep fallbackStep; private final boolean fallbackImmediately; + private final Map patternBuffers = new HashMap<>(); public LmdbIdJoinQueryEvaluationStep(EvaluationStrategy strategy, Join join, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -91,6 +95,10 @@ private Set computeSharedVariables(LmdbIdJoinIterator.PatternInfo left, return Collections.unmodifiableSet(shared); } + private KeyRangeBuffers keyBuffersFor(StatementPattern pattern) { + return patternBuffers.computeIfAbsent(pattern, p -> KeyRangeBuffers.acquire()); + } + public boolean shouldUseFallbackImmediately() { return fallbackImmediately; } @@ -179,6 +187,9 @@ public CloseableIteration evaluate(BindingSet bindings) { return fallbackStep.evaluate(bindings); } + final RecordIterator[] leftReuseHolder = new RecordIterator[1]; + final RecordIterator[] rightReuseHolder = new RecordIterator[1]; + if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalTwoPatternArrayJoin", "true"))) { ValueStore valueStore = dataset.getValueStore(); IdBindingInfo bindingInfo = IdBindingInfo.combine( @@ -195,7 +206,13 @@ public CloseableIteration evaluate(BindingSet bindings) { return new org.eclipse.rdf4j.common.iteration.EmptyIteration<>(); } - RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); + RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings, + keyBuffersFor(leftPattern), leftReuseHolder[0]); + if (leftIterator != null && leftIterator != LmdbIdJoinIterator.emptyRecordIterator()) { + leftReuseHolder[0] = leftIterator; + } else { + leftReuseHolder[0] = null; + } long[] bindingSnapshot = new long[initialBinding.length]; long[] rightScratch = new long[initialBinding.length]; long[] rightQuadScratch = new long[4]; @@ -210,8 +227,15 @@ public CloseableIteration evaluate(BindingSet bindings) { } } } - return dataset.getRecordIterator(bindingSnapshot, subjIdx, predIdx, objIdx, ctxIdx, patternIds, - rightScratch, rightQuadScratch); + RecordIterator iter = dataset.getRecordIterator(bindingSnapshot, subjIdx, predIdx, objIdx, ctxIdx, + patternIds, keyBuffersFor(rightPattern), rightScratch, rightQuadScratch, + rightReuseHolder[0]); + if (iter != null && iter != LmdbIdJoinIterator.emptyRecordIterator()) { + rightReuseHolder[0] = iter; + } else { + rightReuseHolder[0] = null; + } + return iter; }; return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, bindingInfo, sharedVariables, @@ -220,7 +244,13 @@ public CloseableIteration evaluate(BindingSet bindings) { // Default: materialize right side via BindingSet and use LmdbIdJoinIterator ValueStore valueStore = dataset.getValueStore(); - RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings); + RecordIterator leftIterator = dataset.getRecordIterator(leftPattern, bindings, + keyBuffersFor(leftPattern), leftReuseHolder[0]); + if (leftIterator != null && leftIterator != LmdbIdJoinIterator.emptyRecordIterator()) { + leftReuseHolder[0] = leftIterator; + } else { + leftReuseHolder[0] = null; + } LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { MutableBindingSet bs = context.createBindingSet(); @@ -228,7 +258,14 @@ public CloseableIteration evaluate(BindingSet bindings) { if (!leftInfo.applyRecord(leftRecord, bs, valueStore)) { return LmdbIdJoinIterator.emptyRecordIterator(); } - return dataset.getRecordIterator(rightPattern, bs); + RecordIterator rightIter = dataset.getRecordIterator(rightPattern, bs, keyBuffersFor(rightPattern), + rightReuseHolder[0]); + if (rightIter != null && rightIter != LmdbIdJoinIterator.emptyRecordIterator()) { + rightReuseHolder[0] = rightIter; + } else { + rightReuseHolder[0] = null; + } + return rightIter; }; return new LmdbIdJoinIterator(leftIterator, rightFactory, leftInfo, rightInfo, sharedVariables, context, diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java index 4cabcf741d..cc7b8e31ef 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdMergeJoinQueryEvaluationStep.java @@ -13,6 +13,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import org.eclipse.rdf4j.common.iteration.CloseableIteration; @@ -31,6 +33,7 @@ import org.eclipse.rdf4j.sail.lmdb.IdBindingInfo; import org.eclipse.rdf4j.sail.lmdb.LmdbDatasetContext; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset.KeyRangeBuffers; import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationStrategy; import org.eclipse.rdf4j.sail.lmdb.RecordIterator; import org.eclipse.rdf4j.sail.lmdb.TripleStore; @@ -54,6 +57,7 @@ public class LmdbIdMergeJoinQueryEvaluationStep implements QueryEvaluationStep { private final QueryEvaluationStep fallbackStep; private final boolean hasInvalidPattern; private final String fallbackAlgorithmName; + private final Map patternBuffers = new HashMap<>(); public LmdbIdMergeJoinQueryEvaluationStep(Join join, QueryEvaluationContext context, QueryEvaluationStep fallbackStep) { @@ -135,14 +139,19 @@ public CloseableIteration evaluate(BindingSet bindings) { return new EmptyIteration<>(); } + KeyRangeBuffers leftBuffers = keyBuffersFor(leftPlan.pattern); + KeyRangeBuffers rightBuffers = keyBuffersFor(rightPlan.pattern); + RecordIterator leftIterator = dataset.getOrderedRecordIterator(initialBinding, leftPlan.subjIndex, - leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order); + leftPlan.predIndex, leftPlan.objIndex, leftPlan.ctxIndex, leftPlan.patternIds, leftPlan.order, + leftBuffers, null, null); if (leftIterator == null) { return evaluateFallback(bindings); } RecordIterator rightIterator = dataset.getOrderedRecordIterator(initialBinding, rightPlan.subjIndex, - rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order); + rightPlan.predIndex, rightPlan.objIndex, rightPlan.ctxIndex, rightPlan.patternIds, rightPlan.order, + rightBuffers, null, null); if (rightIterator == null) { try { leftIterator.close(); @@ -205,6 +214,10 @@ private static long[] createInitialBinding(IdBindingInfo info, BindingSet bindin return binding; } + private KeyRangeBuffers keyBuffersFor(StatementPattern pattern) { + return patternBuffers.computeIfAbsent(pattern, p -> KeyRangeBuffers.acquire()); + } + private static long resolveId(ValueStore valueStore, Value value) throws QueryEvaluationException { if (value == null) { return LmdbValue.UNKNOWN_ID; @@ -259,15 +272,17 @@ private static final class PatternPlan { private final int objIndex; private final int ctxIndex; private final StatementOrder order; + private final StatementPattern pattern; private PatternPlan(long[] patternIds, int subjIndex, int predIndex, int objIndex, int ctxIndex, - StatementOrder order) { + StatementOrder order, StatementPattern pattern) { this.patternIds = patternIds; this.subjIndex = subjIndex; this.predIndex = predIndex; this.objIndex = objIndex; this.ctxIndex = ctxIndex; this.order = order; + this.pattern = pattern; } } @@ -278,15 +293,17 @@ private static final class RawPattern { private final String objVar; private final String ctxVar; private final boolean invalid; + private final StatementPattern pattern; private RawPattern(long[] patternIds, String subjVar, String predVar, String objVar, String ctxVar, - boolean invalid) { + boolean invalid, StatementPattern pattern) { this.patternIds = patternIds; this.subjVar = subjVar; this.predVar = predVar; this.objVar = objVar; this.ctxVar = ctxVar; this.invalid = invalid; + this.pattern = pattern; } static RawPattern create(StatementPattern pattern, ValueStore valueStore) { @@ -354,12 +371,12 @@ static RawPattern create(StatementPattern pattern, ValueStore valueStore) { } } - return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, invalid); + return new RawPattern(ids, subjVar, predVar, objVar, ctxVar, invalid, pattern); } PatternPlan toPlan(IdBindingInfo bindingInfo, StatementOrder order) { return new PatternPlan(patternIds.clone(), indexFor(subjVar, bindingInfo), indexFor(predVar, bindingInfo), - indexFor(objVar, bindingInfo), indexFor(ctxVar, bindingInfo), order); + indexFor(objVar, bindingInfo), indexFor(ctxVar, bindingInfo), order, pattern); } private static int indexFor(String varName, IdBindingInfo info) { diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java index 51d309d7b6..cb1166a9a8 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdBGPEvaluationTest.java @@ -12,8 +12,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.SingletonIteration; @@ -29,7 +31,9 @@ import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.QueryLanguage; import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern.Index; import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext; import org.eclipse.rdf4j.query.impl.EmptyBindingSet; @@ -41,6 +45,7 @@ import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; import org.eclipse.rdf4j.sail.base.SailSource; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdBGPQueryEvaluationStep; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -52,7 +57,7 @@ public class LmdbIdBGPEvaluationTest { private static final String NS = "http://example.com/"; @Test - public void bgpPrefersContextOverlayDataset(@TempDir java.nio.file.Path tempDir) throws Exception { + public void bgpPrefersContextOverlayDataset(@TempDir java.nio.file.Path tempDir) throws IOException { LmdbStore store = new LmdbStore(tempDir.toFile()); SailRepository repository = new SailRepository(store); repository.init(); @@ -164,7 +169,7 @@ public java.util.Comparator getComparator() { } @Test - public void bgpUsesIdArrayIterator(@TempDir java.nio.file.Path tempDir) throws Exception { + public void bgpUsesIdArrayIterator(@TempDir java.nio.file.Path tempDir) throws IOException { LmdbStore store = new LmdbStore(tempDir.toFile()); SailRepository repository = new SailRepository(store); repository.init(); @@ -234,6 +239,100 @@ public void bgpUsesIdArrayIterator(@TempDir java.nio.file.Path tempDir) throws E } } + @Test + public void recordsChosenIndexesForPatterns(@TempDir java.nio.file.Path tempDir) throws IOException { + LmdbStore store = new LmdbStore(tempDir.toFile()); + SailRepository repository = new SailRepository(store); + repository.init(); + + ValueFactory vf = SimpleValueFactory.getInstance(); + IRI alice = vf.createIRI(NS, "alice"); + IRI bob = vf.createIRI(NS, "bob"); + IRI knows = vf.createIRI(NS, "knows"); + IRI likes = vf.createIRI(NS, "likes"); + IRI pizza = vf.createIRI(NS, "pizza"); + + try (RepositoryConnection conn = repository.getConnection()) { + conn.add(alice, knows, bob); + conn.add(alice, likes, pizza); + } + + String query = "SELECT ?person ?item\n" + + "WHERE {\n" + + " ?person .\n" + + " ?person ?item .\n" + + "}"; + + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + TupleExpr tupleExpr = parsed.getTupleExpr(); + TupleExpr current = tupleExpr; + if (current instanceof org.eclipse.rdf4j.query.algebra.QueryRoot) { + current = ((org.eclipse.rdf4j.query.algebra.QueryRoot) current).getArg(); + } + if (current instanceof org.eclipse.rdf4j.query.algebra.Projection) { + current = ((org.eclipse.rdf4j.query.algebra.Projection) current).getArg(); + } + if (!(current instanceof Join)) { + throw new AssertionError("expected Join at root of algebra"); + } + Join join = (Join) current; + + List patterns = new ArrayList<>(); + boolean flattened = LmdbIdBGPQueryEvaluationStep.flattenBGP(join, patterns); + assertThat(flattened).isTrue(); + assertThat(patterns).hasSize(2); + + SailSource branch = store.getBackingStore().getExplicitSailSource(); + SailDataset dataset = branch.dataset(IsolationLevels.SNAPSHOT_READ); + + try { + LmdbEvaluationDataset lmdbDataset = (LmdbEvaluationDataset) dataset; + SailDatasetTripleSource tripleSource = new SailDatasetTripleSource(repository.getValueFactory(), dataset); + + QueryEvaluationContext ctx = new LmdbQueryEvaluationContext(null, tripleSource.getValueFactory(), + tripleSource.getComparator(), lmdbDataset, lmdbDataset.getValueStore()); + + LmdbIdBGPQueryEvaluationStep step = new LmdbIdBGPQueryEvaluationStep(join, patterns, ctx, null); + + try (CloseableIteration iter = step.evaluate(EmptyBindingSet.getInstance())) { + while (iter.hasNext()) { + iter.next(); + } + } + + assertIndexMatchesDataset(lmdbDataset, patterns.get(0)); + assertIndexMatchesDataset(lmdbDataset, patterns.get(1)); + } finally { + dataset.close(); + branch.close(); + repository.shutDown(); + } + } + + private void assertIndexMatchesDataset(LmdbEvaluationDataset dataset, + org.eclipse.rdf4j.query.algebra.StatementPattern pattern) + throws IOException { + long subjId = resolveId(dataset, pattern.getSubjectVar()); + long predId = resolveId(dataset, pattern.getPredicateVar()); + long objId = resolveId(dataset, pattern.getObjectVar()); + long ctxId = resolveId(dataset, pattern.getContextVar()); + + String fieldSeq = dataset.selectBestIndex(subjId, predId, objId, ctxId); + if (fieldSeq == null) { + assertThat(pattern.getIndex()).isNull(); + return; + } + String enumKey = fieldSeq.toUpperCase(Locale.ROOT); + assertThat(pattern.getIndex()).isEqualTo(Index.valueOf(enumKey)); + } + + private long resolveId(LmdbEvaluationDataset dataset, Var var) throws IOException { + if (var == null || !var.hasValue()) { + return LmdbValue.UNKNOWN_ID; + } + return dataset.getValueStore().getId(var.getValue()); + } + private static final class RecordingDataset implements LmdbEvaluationDataset { private final LmdbEvaluationDataset delegate; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java index 161d083de2..522239e003 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java @@ -54,6 +54,8 @@ import org.eclipse.rdf4j.sail.base.SailDataset; import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; import org.eclipse.rdf4j.sail.base.SailSource; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; +import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset.KeyRangeBuffers; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinQueryEvaluationStep; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -532,12 +534,26 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin return delegate.getRecordIterator(pattern, bindings); } + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, + KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + return delegate.getRecordIterator(pattern, bindings, keyBuffers); + } + @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { return delegate.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); } + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + throws QueryEvaluationException { + return delegate.getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, + keyBuffers, reuse, quadReuse); + } + @Override public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order) throws QueryEvaluationException { @@ -545,6 +561,13 @@ public RecordIterator getOrderedRecordIterator(StatementPattern pattern, Binding return delegate.getOrderedRecordIterator(pattern, bindings, order); } + @Override + public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, + StatementOrder order, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + legacyOrderedApiUsed = true; + return delegate.getOrderedRecordIterator(pattern, bindings, order, keyBuffers); + } + @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { @@ -553,6 +576,15 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in order); } + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, + long[] quadReuse) throws QueryEvaluationException { + arrayOrderedApiUsed = true; + return delegate.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, + order, keyBuffers, bindingReuse, quadReuse); + } + boolean wasLegacyOrderedApiUsed() { return legacyOrderedApiUsed; } diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 73f1ac3c09..7d0c035d58 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -12,12 +12,15 @@ package org.eclipse.rdf4j.sail.lmdb; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; @@ -29,6 +32,8 @@ import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.TupleQuery; import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.query.explanation.GenericPlanNode; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; import org.eclipse.rdf4j.rio.RDFFormat; @@ -225,6 +230,9 @@ public void orderedUnionLimitQueryProducesExpectedCount() { @Timeout(30) public void long_chain() { try (SailRepositoryConnection connection = repository.getConnection()) { + Explanation explain = connection.prepareTupleQuery(long_chain).explain(Explanation.Level.Executed); + System.out.println(explain); + long count; try (var stream = connection.prepareTupleQuery(long_chain).evaluate().stream()) { count = stream.count(); @@ -233,6 +241,36 @@ public void long_chain() { } } + @Test + @Timeout(30) + public void long_chain_prefersIdJoinAlgorithms() { + try (SailRepositoryConnection connection = repository.getConnection()) { + TupleQuery tupleQuery = connection.prepareTupleQuery(long_chain); + Explanation explanation = tupleQuery.explain(Explanation.Level.Optimized); + GenericPlanNode root = explanation.toGenericPlanNode(); + + List algorithms = new ArrayList<>(); + collectAlgorithms(root, algorithms); + System.out.println("Optimized join algorithms: " + algorithms); + + boolean hasJoinIterator = algorithms.stream() + .filter(Objects::nonNull) + .anyMatch("JoinIterator"::equals); + + boolean hasLmdbIdAlgorithm = algorithms.stream() + .filter(Objects::nonNull) + .anyMatch(name -> name.startsWith("LmdbId")); + boolean hasMergeJoin = algorithms.stream() + .filter(Objects::nonNull) + .anyMatch("LmdbIdMergeJoinIterator"::equals); + + assertTrue(hasLmdbIdAlgorithm, "Expected LMDB ID-based join algorithms to appear in optimized plan"); + assertTrue(hasMergeJoin, "Expected optimized plan to include LmdbIdMergeJoinIterator"); + assertTrue(!hasJoinIterator, + () -> "Expected optimized plan to avoid generic JoinIterator but found: " + algorithms); + } + } + @Test @Timeout(30) public void subSelectQueryProducesExpectedCount() { @@ -336,6 +374,22 @@ public void ordered_union_limit() { } } + private static void collectAlgorithms(GenericPlanNode node, List algorithms) { + if (node == null) { + return; + } + String algorithm = node.getAlgorithm(); + if (algorithm != null) { + algorithms.add(algorithm); + } + List children = node.getPlans(); + if (children != null) { + for (GenericPlanNode child : children) { + collectAlgorithms(child, algorithms); + } + } + } + private boolean hasStatement() { try (SailRepositoryConnection connection = repository.getConnection()) { return connection.hasStatement(RDF.TYPE, RDF.TYPE, RDF.TYPE, true); From 2b5354de1b1ba8ce3bc02b1a1f7599f81f41d69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Wed, 5 Nov 2025 17:00:10 +0900 Subject: [PATCH 69/79] working on new ID based join iterator --- .../lmdb/LmdbOverlayEvaluationDataset.java | 4 ++-- .../rdf4j/sail/lmdb/LmdbSailStore.java | 1 - .../sail/lmdb/join/LmdbIdJoinIterator.java | 24 ++++++++++++++++--- .../join/LmdbIdJoinQueryEvaluationStep.java | 20 ++++------------ 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index a4dc8fe77f..5843ce87bc 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -292,8 +292,8 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, bindingReuse, quadReuse); } - return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, - bindingReuse, quadReuse); + return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, + patternIds, order, keyBuffers, bindingReuse, quadReuse); } @Override 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 c61fa87d24..a726bc027f 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 @@ -1582,7 +1582,6 @@ public void close() { if (base != null) { base.close(); base = null; - lmdbBase = null; } } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index 5e73c616d1..93c092a89d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -38,7 +38,7 @@ public class LmdbIdJoinIterator extends LookAheadIteration { @FunctionalInterface interface RecordIteratorFactory { - RecordIterator apply(long[] leftRecord) throws QueryEvaluationException; + RecordIterator apply(long[] leftRecord, RecordIterator reuse) throws QueryEvaluationException; } private static final RecordIterator EMPTY_RECORD_ITERATOR = new RecordIterator() { @@ -228,6 +228,7 @@ private Value resolveValue(long id, int position, ValueStore valueStore) throws private final BindingSet initialBindings; private final ValueStore valueStore; + private RecordIterator reusableRightIterator; private RecordIterator currentRightIterator; private long[] currentLeftRecord; private BindingSet currentLeftBinding; @@ -263,21 +264,38 @@ protected BindingSet getNextElement() throws QueryEvaluationException { } return result; } - currentRightIterator.close(); + RecordIterator completed = currentRightIterator; currentRightIterator = null; + if (completed != EMPTY_RECORD_ITERATOR) { + reusableRightIterator = completed; + } else { + reusableRightIterator = null; + } + completed.close(); } long[] leftRecord = nextRecord(leftIterator); if (leftRecord == null) { + reusableRightIterator = null; return null; } currentLeftRecord = leftRecord; currentLeftBinding = null; - currentRightIterator = rightFactory.apply(leftRecord); + RecordIterator reuseCandidate = reusableRightIterator; + if (reuseCandidate == EMPTY_RECORD_ITERATOR) { + reuseCandidate = null; + } + + currentRightIterator = rightFactory.apply(leftRecord, reuseCandidate); if (currentRightIterator == null) { currentRightIterator = emptyRecordIterator(); + reusableRightIterator = null; + } else if (currentRightIterator == EMPTY_RECORD_ITERATOR) { + reusableRightIterator = null; + } else { + reusableRightIterator = currentRightIterator; } } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java index e8c2339263..4090314dac 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinQueryEvaluationStep.java @@ -188,7 +188,6 @@ public CloseableIteration evaluate(BindingSet bindings) { } final RecordIterator[] leftReuseHolder = new RecordIterator[1]; - final RecordIterator[] rightReuseHolder = new RecordIterator[1]; if (!"false".equalsIgnoreCase(System.getProperty("rdf4j.lmdb.experimentalTwoPatternArrayJoin", "true"))) { ValueStore valueStore = dataset.getValueStore(); @@ -216,7 +215,7 @@ public CloseableIteration evaluate(BindingSet bindings) { long[] bindingSnapshot = new long[initialBinding.length]; long[] rightScratch = new long[initialBinding.length]; long[] rightQuadScratch = new long[4]; - LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { + LmdbIdJoinIterator.RecordIteratorFactory rightFactory = (leftRecord, reuse) -> { System.arraycopy(initialBinding, 0, bindingSnapshot, 0, initialBinding.length); for (String name : leftInfo.getVariableNames()) { int pos = bindingInfo.getIndex(name); @@ -228,13 +227,7 @@ public CloseableIteration evaluate(BindingSet bindings) { } } RecordIterator iter = dataset.getRecordIterator(bindingSnapshot, subjIdx, predIdx, objIdx, ctxIdx, - patternIds, keyBuffersFor(rightPattern), rightScratch, rightQuadScratch, - rightReuseHolder[0]); - if (iter != null && iter != LmdbIdJoinIterator.emptyRecordIterator()) { - rightReuseHolder[0] = iter; - } else { - rightReuseHolder[0] = null; - } + patternIds, keyBuffersFor(rightPattern), rightScratch, rightQuadScratch, reuse); return iter; }; @@ -252,19 +245,14 @@ patternIds, keyBuffersFor(rightPattern), rightScratch, rightQuadScratch, leftReuseHolder[0] = null; } - LmdbIdJoinIterator.RecordIteratorFactory rightFactory = leftRecord -> { + LmdbIdJoinIterator.RecordIteratorFactory rightFactory = (leftRecord, reuse) -> { MutableBindingSet bs = context.createBindingSet(); bindings.forEach(binding -> bs.addBinding(binding.getName(), binding.getValue())); if (!leftInfo.applyRecord(leftRecord, bs, valueStore)) { return LmdbIdJoinIterator.emptyRecordIterator(); } RecordIterator rightIter = dataset.getRecordIterator(rightPattern, bs, keyBuffersFor(rightPattern), - rightReuseHolder[0]); - if (rightIter != null && rightIter != LmdbIdJoinIterator.emptyRecordIterator()) { - rightReuseHolder[0] = rightIter; - } else { - rightReuseHolder[0] = null; - } + reuse); return rightIter; }; From 980cddd1804e1e1a25fc4edee8ab9b9b9622275b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Wed, 5 Nov 2025 17:54:03 +0900 Subject: [PATCH 70/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDupRecordIterator.java | 121 ++++++++++++------ .../rdf4j/sail/lmdb/LmdbSailStore.java | 29 +++-- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 16 ++- 3 files changed, 113 insertions(+), 53 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 165da883dd..dde82b1dc4 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -54,26 +54,26 @@ RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, /** Size in bytes of one (v3,v4) tuple. */ private static final int DUP_PAIR_BYTES = Long.BYTES * 2; - private final Pool pool; - private final DupIndex index; - private final int dupDbi; - private final long cursor; + private final Pool pool = Pool.get(); + private DupIndex index; + private int dupDbi; + private long cursor; - private final Txn txnRef; + private Txn txnRef; private long txn; // refreshed on txn version changes private long txnRefVersion; - private final StampedLongAdderLockManager txnLockManager; + private StampedLongAdderLockManager txnLockManager; private final Thread ownerThread = Thread.currentThread(); - private final MDBVal keyData; - private final MDBVal valueData; + private MDBVal keyData; + private MDBVal valueData; /** Reused output buffer required by the RecordIterator API. */ - private final long[] quad; + private long[] quad; /** Scalars defining the prefix to scan (subject, predicate). */ - private final long prefixSubj; - private final long prefixPred; + private long prefixSubj; + private long prefixPred; private ByteBuffer prefixKeyBuf; @@ -83,33 +83,67 @@ RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, private int dupLimit; private int lastResult; - private boolean closed = false; + private boolean closed = true; - private final RecordIterator fallback; - private final FallbackSupplier fallbackSupplier; + private RecordIterator fallback; + private FallbackSupplier fallbackSupplier; LmdbDupRecordIterator(DupIndex index, long subj, long pred, boolean explicit, Txn txnRef, FallbackSupplier fallbackSupplier) throws IOException { + initialize(index, subj, pred, explicit, txnRef, null, fallbackSupplier); + } + + LmdbDupRecordIterator(DupIndex index, long subj, long pred, + boolean explicit, Txn txnRef, long[] quadReuse, FallbackSupplier fallbackSupplier) throws IOException { + initialize(index, subj, pred, explicit, txnRef, quadReuse, fallbackSupplier); + } + + void initialize(DupIndex index, long subj, long pred, boolean explicit, Txn txnRef, long[] quadReuse, + FallbackSupplier fallbackSupplier) throws IOException { + if (!closed || fallback != null) { + throw new IllegalStateException("Cannot initialize LMDB dup iterator while it is open"); + } + this.index = index; + this.dupDbi = index.getDupDB(explicit); + this.txnRef = txnRef; + this.txnLockManager = txnRef.lockManager(); + this.fallbackSupplier = fallbackSupplier; + this.fallback = null; + + this.prefixSubj = subj; + this.prefixPred = pred; - // Output buffer (s,p are constant for the life of this iterator) - this.quad = new long[4]; + if (quadReuse != null && quadReuse.length >= 4) { + this.quad = quadReuse; + } else if (this.quad == null || this.quad.length < 4) { + this.quad = new long[4]; + } this.quad[0] = subj; this.quad[1] = pred; this.quad[2] = -1L; this.quad[3] = -1L; - this.prefixSubj = subj; - this.prefixPred = pred; + if (this.keyData == null) { + this.keyData = pool.getVal(); + } + if (this.valueData == null) { + this.valueData = pool.getVal(); + } - this.fallbackSupplier = fallbackSupplier; + if (this.prefixKeyBuf == null) { + this.prefixKeyBuf = pool.getKeyBuffer(); + } + prefixKeyBuf.clear(); + Varint.writeUnsigned(prefixKeyBuf, prefixSubj); + Varint.writeUnsigned(prefixKeyBuf, prefixPred); + prefixKeyBuf.flip(); - this.pool = Pool.get(); - this.keyData = pool.getVal(); - this.valueData = pool.getVal(); - this.dupDbi = index.getDupDB(explicit); - this.txnRef = txnRef; - this.txnLockManager = txnRef.lockManager(); + this.dupBuf = null; + this.dupPos = 0; + this.dupLimit = 0; + this.lastResult = MDB_SUCCESS; + this.closed = false; RecordIterator fallbackIterator = null; @@ -125,13 +159,6 @@ RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); - prefixKeyBuf = pool.getKeyBuffer(); - prefixKeyBuf.clear(); - Varint.writeUnsigned(prefixKeyBuf, prefixSubj); - Varint.writeUnsigned(prefixKeyBuf, prefixPred); - // index.toDupKeyPrefix(prefixKeyBuf, subj, pred, 0, 0); - prefixKeyBuf.flip(); - boolean positioned = positionOnPrefix(); if (positioned) { positioned = primeDuplicateBlock(); @@ -340,15 +367,25 @@ private void closeInternal(boolean maybeCalledAsync) { } try { if (!closed) { - if (txnRef.isReadOnly()) { - pool.freeCursor(dupDbi, index, cursor); - } else { - mdb_cursor_close(cursor); + if (cursor != 0L && txnRef != null) { + if (txnRef.isReadOnly()) { + pool.freeCursor(dupDbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } + } + cursor = 0L; + if (keyData != null) { + pool.free(keyData); + keyData = null; + } + if (valueData != null) { + pool.free(valueData); + valueData = null; } - pool.free(keyData); - pool.free(valueData); if (prefixKeyBuf != null) { pool.free(prefixKeyBuf); + prefixKeyBuf = null; } } } finally { @@ -356,6 +393,9 @@ private void closeInternal(boolean maybeCalledAsync) { if (writeLocked) { txnLockManager.unlockWrite(writeStamp); } + txnRef = null; + dupBuf = null; + dupPos = dupLimit = 0; } } } @@ -364,9 +404,14 @@ private void closeInternal(boolean maybeCalledAsync) { public void close() { if (fallback != null) { fallback.close(); + fallback = null; + fallbackSupplier = null; } else { closeInternal(true); + fallbackSupplier = null; } + txnLockManager = null; + txnRef = null; } private long openCursor(long txn, int dbi, boolean tryReuse) throws IOException { 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 a726bc027f..4c2ac62c2e 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 @@ -1377,9 +1377,8 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin ByteBuffer minKeyBuf = keyBuffers != null ? keyBuffers.minKey() : null; ByteBuffer maxKeyBuf = keyBuffers != null ? keyBuffers.maxKey() : null; - LmdbRecordIterator reuse = (iteratorReuse instanceof LmdbRecordIterator) - ? (LmdbRecordIterator) iteratorReuse - : null; + RecordIterator reuse = (iteratorReuse instanceof LmdbRecordIterator + || iteratorReuse instanceof LmdbDupRecordIterator) ? iteratorReuse : null; return tripleStore.getTriples(txn, subjID, predID, objID, contextID, explicit, minKeyBuf, maxKeyBuf, null, reuse); } catch (IOException e) { @@ -1438,8 +1437,10 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI BindingProjectingIterator projectingReuse = iteratorReuse instanceof BindingProjectingIterator ? (BindingProjectingIterator) iteratorReuse : null; - LmdbRecordIterator baseReuse = projectingReuse != null ? projectingReuse.getBase() - : (iteratorReuse instanceof LmdbRecordIterator ? (LmdbRecordIterator) iteratorReuse : null); + RecordIterator baseReuse = projectingReuse != null ? projectingReuse.getReusableBase() + : (iteratorReuse instanceof LmdbRecordIterator || iteratorReuse instanceof LmdbDupRecordIterator + ? iteratorReuse + : null); RecordIterator raw = tripleStore.getTriples(txn, subjQuery, predQuery, objQuery, ctxQuery, explicit, minKeyBuf, maxKeyBuf, quadReuse, baseReuse); @@ -1476,8 +1477,11 @@ public void close() { BindingProjectingIterator result = projectingReuse != null ? projectingReuse : new BindingProjectingIterator(); - result.configure(raw, raw instanceof LmdbRecordIterator ? (LmdbRecordIterator) raw : null, binding, - subjIndex, predIndex, objIndex, ctxIndex, reuse); + RecordIterator reusableBase = (raw instanceof LmdbRecordIterator + || raw instanceof LmdbDupRecordIterator) + ? raw + : null; + result.configure(raw, reusableBase, binding, subjIndex, predIndex, objIndex, ctxIndex, reuse); return result; } catch (IOException e) { throw new QueryEvaluationException("Unable to create LMDB record iterator", e); @@ -1486,7 +1490,7 @@ public void close() { private final class BindingProjectingIterator implements RecordIterator { private RecordIterator base; - private LmdbRecordIterator lmdbBase; + private RecordIterator reusableBase; private long[] binding; private int subjIndex; private int predIndex; @@ -1494,11 +1498,11 @@ private final class BindingProjectingIterator implements RecordIterator { private int ctxIndex; private long[] scratch; - void configure(RecordIterator base, LmdbRecordIterator lmdbBase, long[] binding, int subjIndex, + void configure(RecordIterator base, RecordIterator reusableBase, long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] bindingReuse) { this.base = base; - this.lmdbBase = lmdbBase; + this.reusableBase = reusableBase; this.binding = binding; this.subjIndex = subjIndex; this.predIndex = predIndex; @@ -1515,8 +1519,8 @@ void configure(RecordIterator base, LmdbRecordIterator lmdbBase, long[] binding, } } - LmdbRecordIterator getBase() { - return lmdbBase; + RecordIterator getReusableBase() { + return reusableBase; } @Override @@ -1582,6 +1586,7 @@ public void close() { if (base != null) { base.close(); base = null; + reusableBase = null; } } } 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 8bbcf180af..303e9a680c 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 @@ -613,7 +613,7 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c } public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long context, boolean explicit, - ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, long[] quadReuse, LmdbRecordIterator iteratorReuse) + ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, long[] quadReuse, RecordIterator iteratorReuse) throws IOException { TripleIndex index = getBestIndex(subj, pred, obj, context); // System.out.println("get triples: " + Arrays.asList(subj, pred, obj,context)); @@ -627,15 +627,25 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c return new LmdbRecordIterator(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, minBuf, maxBuf); }; + LmdbRecordIterator recordReuse = iteratorReuse instanceof LmdbRecordIterator + ? (LmdbRecordIterator) iteratorReuse + : null; + LmdbDupRecordIterator dupReuse = iteratorReuse instanceof LmdbDupRecordIterator + ? (LmdbDupRecordIterator) iteratorReuse + : null; if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj == -1 && context == -1) { assert context == -1 && obj == -1 : "subject-predicate index can only be used for (s,p,?,?) patterns"; // Use SP dup iterator, but union with the standard iterator to guard against any edge cases // in SP storage/retrieval; de-duplicate at the record level. - return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, + if (dupReuse != null) { + dupReuse.initialize(subjectPredicateIndex, subj, pred, explicit, txn, quadReuse, fallbackSupplier); + return dupReuse; + } + return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, quadReuse, fallbackSupplier); } return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch, fallbackSupplier, - minKeyBuf, maxKeyBuf, quadReuse, iteratorReuse); + minKeyBuf, maxKeyBuf, quadReuse, recordReuse); } boolean hasTriples(boolean explicit) throws IOException { From ab00f9201626d6bbb70e4368e260fffb1a4ca5e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 10:50:19 +0900 Subject: [PATCH 71/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDelegatingSailDataset.java | 3 +- .../sail/lmdb/LmdbEvaluationDataset.java | 11 +-- .../rdf4j/sail/lmdb/LmdbIdTripleSource.java | 40 ++------ .../sail/lmdb/LmdbIdTripleSourceAdapter.java | 28 +----- .../lmdb/LmdbOverlayEvaluationDataset.java | 49 ++++++++-- .../lmdb/LmdbSailDatasetTripleSource.java | 17 +--- .../rdf4j/sail/lmdb/LmdbSailStore.java | 2 - .../join/LmdbIdBGPQueryEvaluationStep.java | 5 +- .../sail/lmdb/join/LmdbIdJoinIterator.java | 2 +- ...mdbEvaluationDatasetIteratorReuseTest.java | 94 +++++++++++++++++++ 10 files changed, 161 insertions(+), 90 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDatasetIteratorReuseTest.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java index d66c49ea99..8e3e944faa 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDelegatingSailDataset.java @@ -113,7 +113,8 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI } // Fallback via TripleSource with Value conversion return new LmdbSailDatasetTripleSource(valueStore, delegate) - .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, quadReuse); + .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, + quadReuse, null); } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java index 6882f83f21..bba475824d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDataset.java @@ -68,9 +68,6 @@ default RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bi @InternalUseOnly default RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, KeyRangeBuffers keyBuffers, RecordIterator iteratorReuse) throws QueryEvaluationException { - if (iteratorReuse != null) { - iteratorReuse.close(); - } return getRecordIterator(pattern, bindings, keyBuffers); } @@ -105,6 +102,8 @@ RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, i @InternalUseOnly default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { + assert keyBuffers == null : "We are not passing keyBuffers into the next method"; + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); } @@ -144,9 +143,6 @@ default RecordIterator getRecordIterator(long[] binding, int subjIndex, int pred long[] patternIds, KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse, RecordIterator iteratorReuse) throws QueryEvaluationException { - if (iteratorReuse != null) { - iteratorReuse.close(); - } return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, bindingReuse, quadReuse); } @@ -196,9 +192,6 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse, RecordIterator iteratorReuse) throws QueryEvaluationException { - if (iteratorReuse != null) { - iteratorReuse.close(); - } return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, keyBuffers, bindingReuse, quadReuse); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java index 036953dead..78662bb317 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSource.java @@ -31,32 +31,8 @@ public interface LmdbIdTripleSource { * @param patternIds constants for S/P/O/C positions (UNKNOWN_ID for wildcard) */ RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds) throws QueryEvaluationException; - - default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); - } - - /** - * Variant that accepts reusable scratch buffers for bindings and quads. - */ - default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] bindingReuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, null); - } - - default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); - } - - default RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse) - throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, - quadReuse); - } + long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse, + RecordIterator reuse) throws QueryEvaluationException; /** * Create an ordered iterator over ID-level bindings; may fall back to the unordered iterator if unsupported. @@ -64,7 +40,8 @@ default RecordIterator getRecordIterator(long[] binding, int subjIndex, int pred default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null, null, + null); } return null; } @@ -75,7 +52,8 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order, long[] reuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, null, + null); } return null; } @@ -84,8 +62,8 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i int ctxIndex, long[] patternIds, StatementOrder order, long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { if (order == null) { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, bindingReuse, - quadReuse); + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, bindingReuse, + quadReuse, null); } return null; } @@ -95,7 +73,7 @@ default RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, i long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { if (order == null) { return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, - bindingReuse, quadReuse); + bindingReuse, quadReuse, null); } return null; } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java index 56c9c07902..e261c3f83b 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdTripleSourceAdapter.java @@ -79,33 +79,15 @@ public ValueFactory getValueFactory() { return delegate.getValueFactory(); } - // ID-level implementation @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null, null); - } - - @Override - public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] reuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, null); - } - - @Override - public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, quadReuse); - } - - @Override - public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse, + RecordIterator iteratorReuse) throws QueryEvaluationException { // Prefer direct ID-level access if the delegate already supports it if (delegate instanceof LmdbIdTripleSource) { return ((LmdbIdTripleSource) delegate).getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, - patternIds, keyBuffers, reuse, quadReuse); + patternIds, keyBuffers, reuse, quadReuse, iteratorReuse); } // If no active connection changes, delegate to the current LMDB dataset to avoid materialization @@ -114,7 +96,7 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI if (dsOpt.isPresent()) { return dsOpt.get() .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, - reuse, quadReuse); + reuse, quadReuse, iteratorReuse); } } @@ -196,7 +178,7 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in long[] bindingReuse, long[] quadReuse) throws QueryEvaluationException { if (order == null) { return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, - bindingReuse, quadReuse); + bindingReuse, quadReuse, null); } if (delegate instanceof LmdbIdTripleSource) { return ((LmdbIdTripleSource) delegate).getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java index 5843ce87bc..e655feaf78 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbOverlayEvaluationDataset.java @@ -25,6 +25,8 @@ import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinIterator; import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Delegates reads via the Sail {@link TripleSource} to honor transaction overlays and isolation while exposing @@ -32,6 +34,7 @@ */ final class LmdbOverlayEvaluationDataset implements LmdbEvaluationDataset { + private static final Logger log = LoggerFactory.getLogger(LmdbOverlayEvaluationDataset.class); private final TripleSource tripleSource; private final ValueStore valueStore; @@ -52,6 +55,15 @@ public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bin return getRecordIteratorInternal(pattern, bindings, keyBuffers); } + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings, KeyRangeBuffers keyBuffers, + RecordIterator iteratorReuse) throws QueryEvaluationException { + if (iteratorReuse != null) { + iteratorReuse.close(); + } + return getRecordIteratorInternal(pattern, bindings, keyBuffers); + } + private RecordIterator getRecordIteratorInternal(StatementPattern pattern, BindingSet bindings, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { @@ -132,28 +144,28 @@ public void close() { public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds) throws QueryEvaluationException { return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null, - null); + null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, - null, null); + null, null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse) throws QueryEvaluationException { return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, - null); + null, null); } @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, reuse, - quadReuse); + quadReuse, null); } @Override @@ -161,11 +173,20 @@ public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predI long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) throws QueryEvaluationException { return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, - reuse, quadReuse); + reuse, quadReuse, null); + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, + long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse, RecordIterator iteratorReuse) + throws QueryEvaluationException { + return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, + reuse, quadReuse, iteratorReuse); } private RecordIterator getRecordIteratorInternal(long[] binding, int subjIndex, int predIndex, int objIndex, - int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse) + int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers, long[] reuse, long[] quadReuse, + RecordIterator iteratorReuse) throws QueryEvaluationException { // Prefer an ID-level path if the TripleSource supports it and we can trust overlay correctness. if (tripleSource instanceof LmdbIdTripleSource) { @@ -174,7 +195,7 @@ private RecordIterator getRecordIteratorInternal(long[] binding, int subjIndex, // correct when available. RecordIterator viaIds = ((LmdbIdTripleSource) tripleSource) .getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, reuse, - quadReuse); + quadReuse, iteratorReuse); if (viaIds != null) { return viaIds; } @@ -290,12 +311,24 @@ public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, in long[] quadReuse) throws QueryEvaluationException { if (order == null) { return getRecordIteratorInternal(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, keyBuffers, - bindingReuse, quadReuse); + bindingReuse, quadReuse, null); } return LmdbEvaluationDataset.super.getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, keyBuffers, bindingReuse, quadReuse); } + @Override + public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, StatementOrder order, KeyRangeBuffers keyBuffers, long[] bindingReuse, + long[] quadReuse, RecordIterator iteratorReuse) throws QueryEvaluationException { + if (iteratorReuse != null) { + iteratorReuse.close(); + log.warn("getOrderedRecordIterator does not support reusing RecordIterator"); + } + return getOrderedRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, order, + keyBuffers, bindingReuse, quadReuse); + } + @Override public RecordIterator getOrderedRecordIterator(StatementPattern pattern, BindingSet bindings, StatementOrder order, KeyRangeBuffers keyBuffers) throws QueryEvaluationException { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java index f183598e73..a14f80b7c3 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailDatasetTripleSource.java @@ -42,24 +42,13 @@ public LmdbSailDatasetTripleSource(ValueFactory vf, SailDataset dataset) { @Override public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, null, null); - } - - @Override - public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] reuse) throws QueryEvaluationException { - return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds, reuse, null); - } - - @Override - public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, - long[] patternIds, long[] reuse, long[] quadReuse) throws QueryEvaluationException { + long[] patternIds, LmdbEvaluationDataset.KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse, + RecordIterator iteratorReuse) throws QueryEvaluationException { // Fast path: backing dataset supports ID-level access if (dataset instanceof LmdbEvaluationDataset) { return ((LmdbEvaluationDataset) dataset).getRecordIterator(binding, subjIndex, predIndex, objIndex, - ctxIndex, patternIds, reuse, quadReuse); + ctxIndex, patternIds, keyBuffers, bindingReuse, quadReuse, iteratorReuse); } // Fallback path: value-level iteration converted to IDs using ValueStore 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 4c2ac62c2e..c6cf9947f9 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 @@ -1585,8 +1585,6 @@ public long[] next() throws QueryEvaluationException { public void close() { if (base != null) { base.close(); - base = null; - reusableBase = null; } } } diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 695967a49a..9648e5432b 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -572,6 +572,9 @@ public long[] next() throws QueryEvaluationException { return next; } currentRight.close(); + if (currentRight != LmdbIdJoinIterator.EMPTY_RECORD_ITERATOR) { + reusableRight = currentRight; + } currentRight = null; } @@ -587,7 +590,7 @@ public long[] next() throws QueryEvaluationException { } currentRight = dataset.getRecordIterator(leftBinding, plan.subjIndex, plan.predIndex, plan.objIndex, plan.ctxIndex, plan.patternIds, keyBuffers, rightScratch, quadScratch, reusableRight); - if (currentRight != null && currentRight != LmdbIdJoinIterator.emptyRecordIterator()) { + if (currentRight != null && currentRight != LmdbIdJoinIterator.EMPTY_RECORD_ITERATOR) { reusableRight = currentRight; } else { reusableRight = null; diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java index 93c092a89d..ac0483f200 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdJoinIterator.java @@ -41,7 +41,7 @@ interface RecordIteratorFactory { RecordIterator apply(long[] leftRecord, RecordIterator reuse) throws QueryEvaluationException; } - private static final RecordIterator EMPTY_RECORD_ITERATOR = new RecordIterator() { + public static final RecordIterator EMPTY_RECORD_ITERATOR = new RecordIterator() { @Override public long[] next() { return null; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDatasetIteratorReuseTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDatasetIteratorReuseTest.java new file mode 100644 index 0000000000..d26f4fccad --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbEvaluationDatasetIteratorReuseTest.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.junit.jupiter.api.Test; + +class LmdbEvaluationDatasetIteratorReuseTest { + + @Test + void defaultImplementationDoesNotCloseReusableIterator() throws Exception { + RecordingDataset dataset = new RecordingDataset(); + long[] binding = new long[] { 0L }; + long[] patternIds = new long[] { 0L, 0L, 0L, 0L }; + RecordingIterator reusable = new RecordingIterator(); + + dataset.getRecordIterator(binding, -1, -1, -1, -1, patternIds, null, null, null, reusable); + + assertFalse(reusable.closed, "reusable iterator should remain open"); + } + + private static final class RecordingDataset implements LmdbEvaluationDataset { + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds) throws QueryEvaluationException { + return new RecordIterator() { + @Override + public long[] next() { + return null; + } + + @Override + public void close() { + // no-op + } + }; + } + + @Override + public RecordIterator getRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, + int ctxIndex, long[] patternIds, KeyRangeBuffers keyBuffers, long[] bindingReuse, long[] quadReuse) + throws QueryEvaluationException { + return getRecordIterator(binding, subjIndex, predIndex, objIndex, ctxIndex, patternIds); + } + + @Override + public RecordIterator getRecordIterator(StatementPattern pattern, BindingSet bindings) + throws QueryEvaluationException { + return new RecordIterator() { + @Override + public long[] next() { + return null; + } + + @Override + public void close() { + // no-op + } + }; + } + + @Override + public ValueStore getValueStore() { + return null; + } + } + + private static final class RecordingIterator implements RecordIterator { + private boolean closed; + + @Override + public long[] next() { + return null; + } + + @Override + public void close() { + closed = true; + } + } +} From 9e7e2c41395e5e3fa2125a7dcba8f035182ffc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 11:35:06 +0900 Subject: [PATCH 72/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDupRecordIterator.java | 41 ++++--------------- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 38 ++++++++++------- .../sail/lmdb/LmdbIdJoinEvaluationTest.java | 2 - 3 files changed, 33 insertions(+), 48 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index dde82b1dc4..b24daccc2d 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -85,22 +85,19 @@ RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, private int lastResult; private boolean closed = true; - private RecordIterator fallback; - private FallbackSupplier fallbackSupplier; - LmdbDupRecordIterator(DupIndex index, long subj, long pred, - boolean explicit, Txn txnRef, FallbackSupplier fallbackSupplier) throws IOException { - initialize(index, subj, pred, explicit, txnRef, null, fallbackSupplier); + boolean explicit, Txn txnRef) throws IOException { + initialize(index, subj, pred, explicit, txnRef, null); } LmdbDupRecordIterator(DupIndex index, long subj, long pred, - boolean explicit, Txn txnRef, long[] quadReuse, FallbackSupplier fallbackSupplier) throws IOException { - initialize(index, subj, pred, explicit, txnRef, quadReuse, fallbackSupplier); + boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { + initialize(index, subj, pred, explicit, txnRef, quadReuse); } - void initialize(DupIndex index, long subj, long pred, boolean explicit, Txn txnRef, long[] quadReuse, - FallbackSupplier fallbackSupplier) throws IOException { - if (!closed || fallback != null) { + void initialize(DupIndex index, long subj, long pred, boolean explicit, Txn txnRef, long[] quadReuse) + throws IOException { + if (!closed) { throw new IllegalStateException("Cannot initialize LMDB dup iterator while it is open"); } @@ -108,8 +105,6 @@ void initialize(DupIndex index, long subj, long pred, boolean explicit, Txn txnR this.dupDbi = index.getDupDB(explicit); this.txnRef = txnRef; this.txnLockManager = txnRef.lockManager(); - this.fallbackSupplier = fallbackSupplier; - this.fallback = null; this.prefixSubj = subj; this.prefixPred = pred; @@ -165,21 +160,16 @@ void initialize(DupIndex index, long subj, long pred, boolean explicit, Txn txnR } if (!positioned) { closeInternal(false); - fallbackIterator = createFallback(); + // TODO: We must be empty???? } } finally { txnLockManager.unlockRead(readStamp); } - this.fallback = fallbackIterator; } @Override public long[] next() { - if (fallback != null) { - return fallback.next(); - } - long readStamp; try { readStamp = txnLockManager.readLock(); @@ -402,14 +392,7 @@ private void closeInternal(boolean maybeCalledAsync) { @Override public void close() { - if (fallback != null) { - fallback.close(); - fallback = null; - fallbackSupplier = null; - } else { - closeInternal(true); - fallbackSupplier = null; - } + closeInternal(true); txnLockManager = null; txnRef = null; } @@ -433,10 +416,4 @@ private long openCursor(long txn, int dbi, boolean tryReuse) throws IOException } } - private RecordIterator createFallback() throws IOException { - if (fallbackSupplier == null) { - return null; - } - return fallbackSupplier.get(quad, null, null, null); - } } 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 303e9a680c..91ac53605a 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 @@ -618,15 +618,7 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c TripleIndex index = getBestIndex(subj, pred, obj, context); // System.out.println("get triples: " + Arrays.asList(subj, pred, obj,context)); boolean doRangeSearch = index.getPatternScore(subj, pred, obj, context) > 0; - LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = (quad, minBuf, maxBuf, reuse) -> { - if (reuse != null) { - reuse.initialize(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, minBuf, - maxBuf); - return reuse; - } - return new LmdbRecordIterator(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, - minBuf, maxBuf); - }; + LmdbRecordIterator recordReuse = iteratorReuse instanceof LmdbRecordIterator ? (LmdbRecordIterator) iteratorReuse : null; @@ -635,17 +627,35 @@ public RecordIterator getTriples(Txn txn, long subj, long pred, long obj, long c : null; if (dupsortRead && subjectPredicateIndex != null && subj >= 0 && pred >= 0 && obj == -1 && context == -1) { assert context == -1 && obj == -1 : "subject-predicate index can only be used for (s,p,?,?) patterns"; + +// LmdbDupRecordIterator.FallbackSupplier fallbackSupplier = (quad, minBuf, maxBuf, reuse) -> { +// if (reuse != null) { +// reuse.initialize(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, minBuf, +// maxBuf); +// return reuse; +// } +// return new LmdbRecordIterator(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quad, +// minBuf, maxBuf); +// }; + // Use SP dup iterator, but union with the standard iterator to guard against any edge cases // in SP storage/retrieval; de-duplicate at the record level. if (dupReuse != null) { - dupReuse.initialize(subjectPredicateIndex, subj, pred, explicit, txn, quadReuse, fallbackSupplier); + dupReuse.initialize(subjectPredicateIndex, subj, pred, explicit, txn, quadReuse); return dupReuse; } - return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, quadReuse, - fallbackSupplier); + return new LmdbDupRecordIterator(subjectPredicateIndex, subj, pred, explicit, txn, quadReuse); } - return getTriplesUsingIndex(txn, subj, pred, obj, context, explicit, index, doRangeSearch, fallbackSupplier, - minKeyBuf, maxKeyBuf, quadReuse, recordReuse); + + if (recordReuse != null) { + recordReuse.initialize(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quadReuse, + minKeyBuf, + maxKeyBuf); + return recordReuse; + } + return new LmdbRecordIterator(index, null, doRangeSearch, subj, pred, obj, context, explicit, txn, quadReuse, + minKeyBuf, maxKeyBuf); + } boolean hasTriples(boolean explicit) throws IOException { diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java index 522239e003..3444bd728e 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIdJoinEvaluationTest.java @@ -54,8 +54,6 @@ import org.eclipse.rdf4j.sail.base.SailDataset; import org.eclipse.rdf4j.sail.base.SailDatasetTripleSource; import org.eclipse.rdf4j.sail.base.SailSource; -import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset; -import org.eclipse.rdf4j.sail.lmdb.LmdbEvaluationDataset.KeyRangeBuffers; import org.eclipse.rdf4j.sail.lmdb.join.LmdbIdJoinQueryEvaluationStep; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; From c7b2f15dde2cae28950c8822ca6b000908adebba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 12:37:41 +0900 Subject: [PATCH 73/79] working on new ID based join iterator --- .../sail/lmdb/LmdbDupRecordIterator.java | 204 +++++++++++++----- .../join/LmdbIdBGPQueryEvaluationStep.java | 56 ++++- 2 files changed, 195 insertions(+), 65 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index b24daccc2d..185bdccf63 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -40,6 +40,10 @@ */ class LmdbDupRecordIterator implements RecordIterator { + private boolean explicit; + private boolean exhausted; + private boolean wasEmpty; + @FunctionalInterface interface FallbackSupplier { RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, @@ -97,79 +101,160 @@ RecordIterator get(long[] quadReuse, ByteBuffer minKeyBuf, ByteBuffer maxKeyBuf, void initialize(DupIndex index, long subj, long pred, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { - if (!closed) { - throw new IllegalStateException("Cannot initialize LMDB dup iterator while it is open"); - } - this.index = index; - this.dupDbi = index.getDupDB(explicit); - this.txnRef = txnRef; - this.txnLockManager = txnRef.lockManager(); + this.exhausted = false; - this.prefixSubj = subj; - this.prefixPred = pred; + try { - if (quadReuse != null && quadReuse.length >= 4) { - this.quad = quadReuse; - } else if (this.quad == null || this.quad.length < 4) { - this.quad = new long[4]; - } - this.quad[0] = subj; - this.quad[1] = pred; - this.quad[2] = -1L; - this.quad[3] = -1L; + // TODO: Find out why this is slower than if we called close() +// close(); + if (closed) { + this.index = index; + this.explicit = explicit; + this.dupDbi = index.getDupDB(explicit); + this.txnRef = txnRef; + assert this.txnRef != null; + this.txnLockManager = txnRef.lockManager(); + this.prefixSubj = subj; + this.prefixPred = pred; + assert this.keyData == null; + assert this.valueData == null; + assert this.prefixKeyBuf == null; + this.keyData = pool.getVal(); + this.valueData = pool.getVal(); + this.prefixKeyBuf = pool.getKeyBuffer(); + + prefixKeyBuf.clear(); + Varint.writeUnsigned(prefixKeyBuf, prefixSubj); + Varint.writeUnsigned(prefixKeyBuf, prefixPred); + prefixKeyBuf.flip(); + + if (quadReuse != null && quadReuse.length >= 4) { + this.quad = quadReuse; + } else if (this.quad == null || this.quad.length < 4) { + this.quad = new long[4]; + } - if (this.keyData == null) { - this.keyData = pool.getVal(); - } - if (this.valueData == null) { - this.valueData = pool.getVal(); - } + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = -1L; + this.quad[3] = -1L; + + } else { + // TODO + assert this.index == index; + assert this.explicit == explicit; + assert this.txnRef == txnRef; + assert this.txnRef != null; + assert this.txnLockManager != null; + assert this.keyData != null; + assert this.valueData != null; + assert this.prefixKeyBuf != null; + + if (this.prefixSubj == subj && this.prefixPred == pred) { + assert this.quad[0] == this.prefixSubj; + assert this.quad[1] == this.prefixPred; + + if (wasEmpty) { + exhausted = true; + return; + } - if (this.prefixKeyBuf == null) { - this.prefixKeyBuf = pool.getKeyBuffer(); - } - prefixKeyBuf.clear(); - Varint.writeUnsigned(prefixKeyBuf, prefixSubj); - Varint.writeUnsigned(prefixKeyBuf, prefixPred); - prefixKeyBuf.flip(); + // We can do a lot more reuse here! + this.quad[2] = -1L; + this.quad[3] = -1L; + } else { + this.prefixSubj = subj; + this.prefixPred = pred; + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = -1L; + this.quad[3] = -1L; + + prefixKeyBuf.clear(); + Varint.writeUnsigned(prefixKeyBuf, prefixSubj); + Varint.writeUnsigned(prefixKeyBuf, prefixPred); + prefixKeyBuf.flip(); + } - this.dupBuf = null; - this.dupPos = 0; - this.dupLimit = 0; - this.lastResult = MDB_SUCCESS; - this.closed = false; + } - RecordIterator fallbackIterator = null; + this.dupBuf = null; + this.dupPos = 0; + this.dupLimit = 0; + this.lastResult = MDB_SUCCESS; + this.wasEmpty = false; - long readStamp; - try { - readStamp = txnLockManager.readLock(); - } catch (InterruptedException e) { - throw new SailException(e); - } - try { - this.txnRefVersion = txnRef.version(); - this.txn = txnRef.get(); + if (closed) { + long readStamp; + try { + readStamp = txnLockManager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + this.txnRefVersion = txnRef.version(); + this.txn = txnRef.get(); - cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); + cursor = openCursor(txn, dupDbi, txnRef.isReadOnly()); - boolean positioned = positionOnPrefix(); - if (positioned) { - positioned = primeDuplicateBlock(); - } - if (!positioned) { - closeInternal(false); - // TODO: We must be empty???? + boolean positioned = positionOnPrefix(); + if (positioned) { + positioned = primeDuplicateBlock(); + } + if (!positioned) { + this.exhausted = true; + this.wasEmpty = true; + // closeInternal(false); + // TODO: We must be empty???? + } + } finally { + txnLockManager.unlockRead(readStamp); + } + } else { + long readStamp; + try { + readStamp = txnLockManager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + + if (txnRefVersion != txnRef.version()) { + this.txn = txnRef.get(); + E(mdb_cursor_renew(txn, cursor)); + txnRefVersion = txnRef.version(); + } else { + E(mdb_cursor_renew(txn, cursor)); + } + + boolean positioned = positionOnPrefix(); + if (positioned) { + positioned = primeDuplicateBlock(); + } + if (!positioned) { + this.exhausted = true; + this.wasEmpty = true; + // closeInternal(false); + // TODO: We must be empty???? + } + } finally { + txnLockManager.unlockRead(readStamp); + } } + } finally { - txnLockManager.unlockRead(readStamp); + assert this.txnRef != null; + this.closed = false; + } } @Override public long[] next() { + if (exhausted) + return null; long readStamp; try { readStamp = txnLockManager.readLock(); @@ -187,7 +272,8 @@ public long[] next() { E(mdb_cursor_renew(txn, cursor)); txnRefVersion = txnRef.version(); if (!positionOnPrefix() || !primeDuplicateBlock()) { - closeInternal(false); +// closeInternal(false); + exhausted = true; return null; } } @@ -219,12 +305,14 @@ public long[] next() { do { lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); if (lastResult != MDB_SUCCESS) { - closeInternal(false); +// closeInternal(false); + exhausted = true; return null; } // Ensure we're still within the requested (subj,pred) prefix if (!currentKeyHasPrefix() && !adjustCursorToPrefix()) { - closeInternal(false); +// closeInternal(false); + exhausted = true; return null; } } while (!primeDuplicateBlock()); // skip any keys without a duplicate block (defensive) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java index 9648e5432b..830a1532fd 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/join/LmdbIdBGPQueryEvaluationStep.java @@ -571,8 +571,11 @@ public long[] next() throws QueryEvaluationException { if (next != null) { return next; } - currentRight.close(); - if (currentRight != LmdbIdJoinIterator.EMPTY_RECORD_ITERATOR) { + if (reusableRight == null && currentRight != LmdbIdJoinIterator.EMPTY_RECORD_ITERATOR) { + reusableRight = currentRight; + } else if (reusableRight != currentRight + && currentRight != LmdbIdJoinIterator.EMPTY_RECORD_ITERATOR) { + reusableRight.close(); reusableRight = currentRight; } currentRight = null; @@ -600,12 +603,51 @@ public long[] next() throws QueryEvaluationException { @Override public void close() { - if (currentRight != null) { - currentRight.close(); - currentRight = null; - } - left.close(); + // capture references and null out fields early to avoid double-closing if re-entered + RecordIterator toCloseCurrent = currentRight; + RecordIterator toCloseLeft = left; + RecordIterator toCloseReusable = reusableRight; + + currentRight = null; reusableRight = null; + + RuntimeException first = null; + + // Close currentRight + try { + if (toCloseCurrent != null) { + toCloseCurrent.close(); + } + } catch (RuntimeException e) { + first = e; + } finally { + // Always attempt to close left + try { + if (toCloseLeft != null) { + toCloseLeft.close(); + } + } catch (RuntimeException e) { + if (first == null) { + first = e; + } + } finally { + // Always attempt to close reusableRight + try { + if (toCloseReusable != null) { + toCloseReusable.close(); + } + } catch (RuntimeException e) { + if (first == null) { + first = e; + } + } + } + } + + // Rethrow the first failure after attempting all closes + if (first != null) { + throw first; + } } } From 18648e9c008f9e179d31f8caf846fac14c02e080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 13:35:05 +0900 Subject: [PATCH 74/79] working on new ID based join iterator --- .../java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java index 185bdccf63..dc18b88239 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbDupRecordIterator.java @@ -28,6 +28,7 @@ import java.nio.ByteOrder; import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; +import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.lmdb.TripleStore.DupIndex; import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; From 46974b1e198339cdbfeaecd1ddddc8eefd5c2f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 15:35:59 +0900 Subject: [PATCH 75/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 126 ++++- .../rdf4j/sail/lmdb/LmdbSailStore.java | 202 ++++---- .../eclipse/rdf4j/sail/lmdb/Statistics.java | 17 + .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 354 ++++++++----- .../TripleStoreCardinalityRefactorTest.java | 125 +++++ tmp/LmdbRecordIterator.modified.java | 490 ++++++++++++++++++ tmp/LmdbRecordIterator.original.java | 439 ++++++++++++++++ ...ecordIteratorLateBindingTest.original.java | 82 +++ 8 files changed, 1578 insertions(+), 257 deletions(-) create mode 100644 core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/TripleStoreCardinalityRefactorTest.java create mode 100644 tmp/LmdbRecordIterator.modified.java create mode 100644 tmp/LmdbRecordIterator.original.java create mode 100644 tmp/LmdbRecordIteratorLateBindingTest.original.java diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index 7f3f9671f0..c49f872aac 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -57,6 +57,7 @@ class LmdbRecordIterator implements RecordIterator { private boolean matchValues; private GroupMatcher groupMatcher; + private GroupMatcher matcherForEvaluation; /** * True when late-bound variables exist beyond the contiguous prefix of the chosen index order, requiring @@ -131,7 +132,7 @@ void initialize(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, l long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { if (initialized && !closed) { - throw new IllegalStateException("Cannot initialize LMDB record iterator while it is open"); + // prepareForReuse(); } initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, minKeyBufParam, maxKeyBufParam); @@ -141,12 +142,27 @@ void initialize(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, l private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + +// if (!initialized) { +// System.out.println(); +// } else { +// System.out.println(); +// } + this.index = index; + long prevSubj = this.subj; + long prevPred = this.pred; + long prevObj = this.obj; + long prevContext = this.context; + this.subj = subj; this.pred = pred; this.obj = obj; this.context = context; + boolean prevExternalMinKeyBuf = this.externalMinKeyBuf; + boolean prevExternalMaxKeyBuf = this.externalMaxKeyBuf; + if (quadReuse != null && quadReuse.length >= 4) { this.quad = quadReuse; } else if (this.quad == null || this.quad.length < 4) { @@ -166,12 +182,20 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea if (rangeSearch) { this.externalMinKeyBuf = minKeyBufParam != null; - this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : pool.getKeyBuffer(); + if (externalMinKeyBuf) { + this.minKeyBuf = minKeyBufParam; + } else { + if (this.minKeyBuf == null || prevExternalMinKeyBuf) { + this.minKeyBuf = pool.getKeyBuffer(); + } else { + this.minKeyBuf.clear(); + } + } minKeyBuf.clear(); if (keyBuilder != null) { keyBuilder.writeMin(minKeyBuf); } else { - index.getMinKey(minKeyBuf, subj, pred, obj, context); + index.getMinKey(minKeyBuf, subj, pred, obj, context, prevSubj, prevPred, prevObj, prevContext); } minKeyBuf.flip(); @@ -179,7 +203,15 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea this.maxKey = pool.getVal(); } this.externalMaxKeyBuf = maxKeyBufParam != null; - this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : pool.getKeyBuffer(); + if (externalMaxKeyBuf) { + this.maxKeyBuf = maxKeyBufParam; + } else { + if (this.maxKeyBuf == null || prevExternalMaxKeyBuf) { + this.maxKeyBuf = pool.getKeyBuffer(); + } else { + this.maxKeyBuf.clear(); + } + } maxKeyBuf.clear(); if (keyBuilder != null) { keyBuilder.writeMax(maxKeyBuf); @@ -193,24 +225,38 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea pool.free(maxKey); this.maxKey = null; } - if (this.maxKeyBuf != null && !externalMaxKeyBuf) { + if (this.maxKeyBuf != null && !prevExternalMaxKeyBuf) { pool.free(maxKeyBuf); + this.maxKeyBuf = null; } this.externalMaxKeyBuf = maxKeyBufParam != null; this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : null; if (subj > 0 || pred > 0 || obj > 0 || context >= 0) { this.externalMinKeyBuf = minKeyBufParam != null; - this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : pool.getKeyBuffer(); + if (externalMinKeyBuf) { + this.minKeyBuf = minKeyBufParam; + } else { + if (this.minKeyBuf == null || prevExternalMinKeyBuf) { + this.minKeyBuf = pool.getKeyBuffer(); + } else { + this.minKeyBuf.clear(); + } + } minKeyBuf.clear(); - index.getMinKey(minKeyBuf, subj, pred, obj, context); + index.getMinKey(minKeyBuf, subj, pred, obj, context, prevSubj, prevPred, prevObj, prevContext); minKeyBuf.flip(); } else { - if (this.minKeyBuf != null && !externalMinKeyBuf) { + if (this.minKeyBuf != null && !prevExternalMinKeyBuf) { pool.free(minKeyBuf); + this.minKeyBuf = null; } this.externalMinKeyBuf = minKeyBufParam != null; - this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : null; + if (externalMinKeyBuf) { + this.minKeyBuf = minKeyBufParam; + } else { + this.minKeyBuf = null; + } } } @@ -219,13 +265,16 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea int boundCount = (subj > 0 ? 1 : 0) + (pred > 0 ? 1 : 0) + (obj > 0 ? 1 : 0) + (context >= 0 ? 1 : 0); this.needMatcher = boundCount > prefixLen; this.groupMatcher = null; + this.matcherForEvaluation = null; this.fetchNext = false; this.lastResult = MDB_SUCCESS; this.closed = false; - this.dbi = index.getDB(explicit); - this.txnRef = txnRef; - this.txnLockManager = txnRef.lockManager(); + if (!initialized) { + this.dbi = index.getDB(explicit); + this.txnRef = txnRef; + this.txnLockManager = txnRef.lockManager(); + } long readStamp; try { @@ -239,9 +288,12 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea // Try to reuse a pooled cursor only for read-only transactions; otherwise open a new one if (txnRef.isReadOnly()) { - long pooled = pool.getCursor(dbi, index); - if (pooled != 0L) { - long c = pooled; + if (cursor == 0L) { + cursor = pool.getCursor(dbi, index); + } + + if (cursor != 0L) { + long c = cursor; try { E(mdb_cursor_renew(txn, c)); } catch (IOException renewEx) { @@ -262,6 +314,11 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea } } } else { + if (cursor != 0L) { + pool.freeCursor(dbi, index, cursor); + cursor = 0L; + } + try (MemoryStack stack = MemoryStack.stackPush()) { PointerBuffer pp = stack.mallocPointer(1); E(mdb_cursor_open(txn, dbi, pp)); @@ -276,7 +333,6 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea @Override public long[] next() { if (closed) { - log.debug("Calling next() on an LmdbRecordIterator that is already closed, returning null"); return null; } StampedLongAdderLockManager manager = txnLockManager; @@ -345,7 +401,7 @@ public long[] next() { return quad; } } - closeInternal(false); +// closeInternal(false); return null; } finally { manager.unlockRead(readStamp); @@ -358,16 +414,39 @@ private boolean matches() { return false; } - if (groupMatcher != null) { - return !this.groupMatcher.matches(keyData.mv_data()); + if (matcherForEvaluation != null) { + return !matcherForEvaluation.matches(keyData.mv_data()); } else if (matchValues) { - this.groupMatcher = index.createMatcher(subj, pred, obj, context); - return !this.groupMatcher.matches(keyData.mv_data()); + matcherForEvaluation = index.createMatcher(subj, pred, obj, context); + groupMatcher = matcherForEvaluation; + return !matcherForEvaluation.matches(keyData.mv_data()); } else { return false; } } + private void prepareForReuse() { + if (cursor != 0L && txnRef != null) { + if (txnRef.isReadOnly()) { + pool.freeCursor(dbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } + } + cursor = 0L; + groupMatcher = null; + matcherForEvaluation = null; + fetchNext = false; + lastResult = MDB_SUCCESS; + matchValues = false; + needMatcher = false; + txnRef = null; + txn = 0L; + txnRefVersion = 0L; + txnLockManager = null; + closed = true; + } + private void closeInternal(boolean maybeCalledAsync) { StampedLongAdderLockManager manager = this.txnLockManager; if (closed) { @@ -414,7 +493,10 @@ private void closeInternal(boolean maybeCalledAsync) { maxKeyBuf = null; externalMinKeyBuf = false; externalMaxKeyBuf = false; - groupMatcher = null; + if (maybeCalledAsync) { + groupMatcher = null; + } + matcherForEvaluation = null; fetchNext = false; lastResult = 0; matchValues = false; 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 c6cf9947f9..7d30eea5ad 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 @@ -1488,107 +1488,6 @@ public void close() { } } - private final class BindingProjectingIterator implements RecordIterator { - private RecordIterator base; - private RecordIterator reusableBase; - private long[] binding; - private int subjIndex; - private int predIndex; - private int objIndex; - private int ctxIndex; - private long[] scratch; - - void configure(RecordIterator base, RecordIterator reusableBase, long[] binding, int subjIndex, - int predIndex, int objIndex, - int ctxIndex, long[] bindingReuse) { - this.base = base; - this.reusableBase = reusableBase; - this.binding = binding; - this.subjIndex = subjIndex; - this.predIndex = predIndex; - this.objIndex = objIndex; - this.ctxIndex = ctxIndex; - int bindingLength = binding.length; - if (bindingReuse != null && bindingReuse.length >= bindingLength) { - System.arraycopy(binding, 0, bindingReuse, 0, bindingLength); - this.scratch = bindingReuse; - } else if (this.scratch == null || this.scratch.length != bindingLength) { - this.scratch = Arrays.copyOf(binding, bindingLength); - } else { - System.arraycopy(binding, 0, this.scratch, 0, bindingLength); - } - } - - RecordIterator getReusableBase() { - return reusableBase; - } - - @Override - public long[] next() throws QueryEvaluationException { - if (base == null) { - return null; - } - try { - long[] quad; - while ((quad = base.next()) != null) { - System.arraycopy(binding, 0, scratch, 0, binding.length); - boolean conflict = false; - if (subjIndex >= 0) { - long baseVal = binding[subjIndex]; - long v = quad[TripleStore.SUBJ_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[subjIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - if (!conflict && predIndex >= 0) { - long baseVal = binding[predIndex]; - long v = quad[TripleStore.PRED_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[predIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - if (!conflict && objIndex >= 0) { - long baseVal = binding[objIndex]; - long v = quad[TripleStore.OBJ_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[objIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - if (!conflict && ctxIndex >= 0) { - long baseVal = binding[ctxIndex]; - long v = quad[TripleStore.CONTEXT_IDX]; - if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { - conflict = true; - } else { - scratch[ctxIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; - } - } - if (!conflict) { - return scratch; - } - } - return null; - } catch (QueryEvaluationException e) { - throw e; - } catch (Exception e) { - throw new QueryEvaluationException(e); - } - } - - @Override - public void close() { - if (base != null) { - base.close(); - } - } - } - @Override public RecordIterator getOrderedRecordIterator(long[] binding, int subjIndex, int predIndex, int objIndex, int ctxIndex, long[] patternIds, StatementOrder order) throws QueryEvaluationException { @@ -2141,3 +2040,104 @@ public Comparator getComparator() { } } } + +final class BindingProjectingIterator implements RecordIterator { + private RecordIterator base; + private RecordIterator reusableBase; + private long[] binding; + private int subjIndex; + private int predIndex; + private int objIndex; + private int ctxIndex; + private long[] scratch; + + void configure(RecordIterator base, RecordIterator reusableBase, long[] binding, int subjIndex, + int predIndex, int objIndex, + int ctxIndex, long[] bindingReuse) { + this.base = base; + this.reusableBase = reusableBase; + this.binding = binding; + this.subjIndex = subjIndex; + this.predIndex = predIndex; + this.objIndex = objIndex; + this.ctxIndex = ctxIndex; + int bindingLength = binding.length; + if (bindingReuse != null && bindingReuse.length >= bindingLength) { + System.arraycopy(binding, 0, bindingReuse, 0, bindingLength); + this.scratch = bindingReuse; + } else if (this.scratch == null || this.scratch.length != bindingLength) { + this.scratch = Arrays.copyOf(binding, bindingLength); + } else { + System.arraycopy(binding, 0, this.scratch, 0, bindingLength); + } + } + + RecordIterator getReusableBase() { + return reusableBase; + } + + @Override + public long[] next() throws QueryEvaluationException { + if (base == null) { + return null; + } + try { + long[] quad; + while ((quad = base.next()) != null) { + System.arraycopy(binding, 0, scratch, 0, binding.length); + boolean conflict = false; + if (subjIndex >= 0) { + long baseVal = binding[subjIndex]; + long v = quad[TripleStore.SUBJ_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[subjIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict && predIndex >= 0) { + long baseVal = binding[predIndex]; + long v = quad[TripleStore.PRED_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[predIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict && objIndex >= 0) { + long baseVal = binding[objIndex]; + long v = quad[TripleStore.OBJ_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[objIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict && ctxIndex >= 0) { + long baseVal = binding[ctxIndex]; + long v = quad[TripleStore.CONTEXT_IDX]; + if (baseVal != LmdbValue.UNKNOWN_ID && baseVal != v) { + conflict = true; + } else { + scratch[ctxIndex] = (baseVal != LmdbValue.UNKNOWN_ID) ? baseVal : v; + } + } + if (!conflict) { + return scratch; + } + } + return null; + } catch (QueryEvaluationException e) { + throw e; + } catch (Exception e) { + throw new QueryEvaluationException(e); + } + } + + @Override + public void close() { + if (base != null) { + base.close(); + } + } +} diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Statistics.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Statistics.java index b0c96d8b8c..0c9b6b8894 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Statistics.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Statistics.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.lmdb; +import java.util.Arrays; + /** * Helper class for computing cardinalities within triple store. */ @@ -33,4 +35,19 @@ class Statistics { final double[] avgRowsPerValue = new double[4]; final long[] avgRowsPerValueCounts = new long[4]; final long[] counts = new long[4]; + + void reset() { + for (long[] startValue : startValues) { + Arrays.fill(startValue, 0); + } + for (long[] lastValue : lastValues) { + Arrays.fill(lastValue, 0); + } + Arrays.fill(values, 0); + Arrays.fill(minValues, 0); + Arrays.fill(maxValues, 0); + Arrays.fill(avgRowsPerValue, 1.0); + Arrays.fill(avgRowsPerValueCounts, 0); + Arrays.fill(counts, 0); + } } 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 91ac53605a..bcb8380ac9 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 @@ -812,7 +812,7 @@ protected void filterUsedIds(Collection ids) throws IOException { maxKey.mv_data(maxKeyBuf); keyBuf.clear(); - index.getMinKey(keyBuf, subj, pred, obj, context); + index.getMinKey(keyBuf, subj, pred, obj, context, 0, 0, 0, 0); keyBuf.flip(); // set cursor to min key @@ -849,13 +849,38 @@ protected void filterUsedIds(Collection ids) throws IOException { protected double cardinality(long subj, long pred, long obj, long context) throws IOException { TripleIndex index = getBestIndex(subj, pred, obj, context); + CardinalityEstimator estimator = new CardinalityEstimator(this, index, subj, pred, obj, context); int relevantParts = index.getPatternScore(subj, pred, obj, context); if (relevantParts == 0) { - // it's worthless to use the index, just retrieve all entries in the db - return txnManager.doWith((stack, txn) -> { + return estimator.countAllEntries(); + } + return estimator.estimateWithSampling(); + } + + private static final class CardinalityEstimator { + + private final TripleStore store; + private final TripleIndex index; + private final long subj; + private final long pred; + private final long obj; + private final long context; + + CardinalityEstimator(TripleStore store, TripleIndex index, long subj, long pred, long obj, long context) { + this.store = store; + this.index = index; + this.subj = subj; + this.pred = pred; + this.obj = obj; + this.context = context; + } + + double countAllEntries() throws IOException { + return store.txnManager.doWith((stack, txn) -> { double cardinality = 0; - for (boolean explicit : new boolean[] { true, false }) { + for (int i = 0; i < 2; i++) { + boolean explicit = i == 0; int dbi = index.getDB(explicit); MDBStat stat = MDBStat.mallocStack(stack); mdb_stat(txn, dbi, stat); @@ -865,152 +890,212 @@ protected double cardinality(long subj, long pred, long obj, long context) throw }); } - return txnManager.doWith((stack, txn) -> { + double estimateWithSampling() throws IOException { Pool pool = Pool.get(); - final Statistics s = pool.getStatistics(); + Statistics statistics = pool.getStatistics(); try { - MDBVal maxKey = MDBVal.malloc(stack); - ByteBuffer maxKeyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); - index.getMaxKey(maxKeyBuf, subj, pred, obj, context); - maxKeyBuf.flip(); - maxKey.mv_data(maxKeyBuf); + return store.txnManager.doWith((stack, txn) -> estimateWithTransaction(pool, statistics, stack, txn)); + } finally { + pool.free(statistics); + } + } - PointerBuffer pp = stack.mallocPointer(1); + private double estimateWithTransaction(Pool pool, Statistics statistics, MemoryStack stack, long txn) + throws IOException { + MDBVal maxKey = MDBVal.malloc(stack); + ByteBuffer maxKeyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); + index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + maxKeyBuf.flip(); + maxKey.mv_data(maxKeyBuf); - MDBVal keyData = MDBVal.mallocStack(stack); - ByteBuffer keyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); - MDBVal valueData = MDBVal.mallocStack(stack); + MDBVal keyData = MDBVal.mallocStack(stack); + ByteBuffer keyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); + MDBVal valueData = MDBVal.mallocStack(stack); - double cardinality = 0; - for (boolean explicit : new boolean[] { true, false }) { - Arrays.fill(s.avgRowsPerValue, 1.0); - Arrays.fill(s.avgRowsPerValueCounts, 0); + double cardinality = 0; + for (int i = 0; i < 2; i++) { + boolean explicit = i == 0; + statistics.reset(); - keyBuf.clear(); - index.getMinKey(keyBuf, subj, pred, obj, context); - keyBuf.flip(); + keyBuf.clear(); + index.getMinKey(keyBuf, subj, pred, obj, context, 0, 0, 0, 0); + keyBuf.flip(); - int dbi = index.getDB(explicit); + int dbi = index.getDB(explicit); - int pos = 0; - long cursor = 0; + try (CursorHandle cursor = CursorHandle.open(pool, this, stack, txn, dbi)) { + if (!positionRangeStart(txn, dbi, cursor.cursor(), keyData, keyBuf, valueData, maxKey, + statistics)) { + break; + } + if (!positionRangeEnd(txn, dbi, cursor.cursor(), keyData, valueData, maxKeyBuf, statistics)) { + break; + } - try { - E(mdb_cursor_open(txn, dbi, pp)); - cursor = pp.get(0); + BucketSummary summary = sampleBuckets(txn, dbi, cursor.cursor(), keyData, valueData, keyBuf, maxKey, + statistics); + cardinality += summary.totalSamples; + cardinality += interpolateBuckets(summary.bucketCount, statistics); + } + } - // set cursor to min key - keyData.mv_data(keyBuf); - int rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); - if (rc != MDB_SUCCESS || mdb_cmp(txn, dbi, keyData, maxKey) >= 0) { - break; - } else { - Varint.readQuadUnsigned(keyData.mv_data(), s.minValues); - } + return cardinality; + } - // set cursor to max key - keyData.mv_data(maxKeyBuf); - rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); - if (rc != MDB_SUCCESS) { - // directly go to last value - rc = mdb_cursor_get(cursor, keyData, valueData, MDB_LAST); - } else { - // go to previous value of selected key - rc = mdb_cursor_get(cursor, keyData, valueData, MDB_PREV); - } - if (rc == MDB_SUCCESS) { - Varint.readQuadUnsigned(keyData.mv_data(), s.maxValues); - // this is required to correctly estimate the range size at a later point - s.startValues[s.MAX_BUCKETS] = s.maxValues; - } else { - break; - } + private boolean positionRangeStart(long txn, int dbi, long cursor, MDBVal keyData, ByteBuffer keyBuf, + MDBVal valueData, MDBVal maxKey, Statistics statistics) { + keyData.mv_data(keyBuf); + int rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + if (rc != MDB_SUCCESS || mdb_cmp(txn, dbi, keyData, maxKey) >= 0) { + return false; + } + Varint.readQuadUnsigned(keyData.mv_data(), statistics.minValues); + return true; + } - long allSamplesCount = 0; - int bucket = 0; - boolean endOfRange = false; - for (; bucket < s.MAX_BUCKETS && !endOfRange; bucket++) { - if (bucket != 0) { - bucketStart((double) bucket / s.MAX_BUCKETS, s.minValues, s.maxValues, s.values); - keyBuf.clear(); - Varint.writeQuadUnsigned(keyBuf, s.values); - keyBuf.flip(); - } - // this is the min key for the first iteration - keyData.mv_data(keyBuf); - - int currentSamplesCount = 0; - rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); - while (rc == MDB_SUCCESS && currentSamplesCount < s.MAX_SAMPLES_PER_BUCKET) { - if (mdb_cmp(txn, dbi, keyData, maxKey) >= 0) { - endOfRange = true; - break; - } else { - allSamplesCount++; - currentSamplesCount++; - - System.arraycopy(s.values, 0, s.lastValues[bucket], 0, s.values.length); - Varint.readQuadUnsigned(keyData.mv_data(), s.values); - - if (currentSamplesCount == 1) { - Arrays.fill(s.counts, 1); - System.arraycopy(s.values, 0, s.startValues[bucket], 0, s.values.length); - } else { - for (int i = 0; i < s.values.length; i++) { - if (s.values[i] == s.lastValues[bucket][i]) { - s.counts[i]++; - } else { - long diff = s.values[i] - s.lastValues[bucket][i]; - s.avgRowsPerValueCounts[i]++; - s.avgRowsPerValue[i] = (s.avgRowsPerValue[i] - * (s.avgRowsPerValueCounts[i] - 1) + - (double) s.counts[i] / diff) / s.avgRowsPerValueCounts[i]; - s.counts[i] = 0; - } - } - } - rc = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); - if (rc != MDB_SUCCESS) { - // no more elements are available - endOfRange = true; - } - } - } - } + private boolean positionRangeEnd(long txn, int dbi, long cursor, MDBVal keyData, MDBVal valueData, + ByteBuffer maxKeyBuf, Statistics statistics) { + keyData.mv_data(maxKeyBuf); + int rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + if (rc != MDB_SUCCESS) { + rc = mdb_cursor_get(cursor, keyData, valueData, MDB_LAST); + } else { + rc = mdb_cursor_get(cursor, keyData, valueData, MDB_PREV); + } + if (rc != MDB_SUCCESS) { + return false; + } + Varint.readQuadUnsigned(keyData.mv_data(), statistics.maxValues); + statistics.startValues[Statistics.MAX_BUCKETS] = statistics.maxValues; + return true; + } - // at least the seen samples must be counted - cardinality += allSamplesCount; - - // the actual number of buckets (bucket - 1 "real" buckets and one for the last element within - // the range) - int buckets = bucket; - for (bucket = 1; bucket < buckets; bucket++) { - // find first element that has been changed - pos = 0; - while (pos < s.lastValues[bucket].length - && s.startValues[bucket][pos] == s.lastValues[bucket - 1][pos]) { - pos++; - } - if (pos < s.lastValues[bucket].length) { - // this may be < 0 if two groups are overlapping - long diffBetweenGroups = Math - .max(s.startValues[bucket][pos] - s.lastValues[bucket - 1][pos], 0); - // estimate number of elements between last element of previous bucket and first element - // of current bucket - cardinality += s.avgRowsPerValue[pos] * diffBetweenGroups; + private BucketSummary sampleBuckets(long txn, int dbi, long cursor, MDBVal keyData, MDBVal valueData, + ByteBuffer keyBuf, MDBVal maxKey, Statistics statistics) { + long allSamplesCount = 0; + int bucket = 0; + boolean endOfRange = false; + for (; bucket < Statistics.MAX_BUCKETS && !endOfRange; bucket++) { + if (bucket != 0) { + store.bucketStart((double) bucket / Statistics.MAX_BUCKETS, statistics.minValues, + statistics.maxValues, statistics.values); + keyBuf.clear(); + Varint.writeQuadUnsigned(keyBuf, statistics.values); + keyBuf.flip(); + } + + keyData.mv_data(keyBuf); + + int currentSamplesCount = 0; + int rc = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + while (rc == MDB_SUCCESS && currentSamplesCount < Statistics.MAX_SAMPLES_PER_BUCKET) { + if (mdb_cmp(txn, dbi, keyData, maxKey) >= 0) { + endOfRange = true; + break; + } + allSamplesCount++; + currentSamplesCount++; + + System.arraycopy(statistics.values, 0, statistics.lastValues[bucket], 0, + statistics.values.length); + Varint.readQuadUnsigned(keyData.mv_data(), statistics.values); + + if (currentSamplesCount == 1) { + Arrays.fill(statistics.counts, 1); + System.arraycopy(statistics.values, 0, statistics.startValues[bucket], 0, + statistics.values.length); + } else { + for (int i = 0; i < statistics.values.length; i++) { + if (statistics.values[i] == statistics.lastValues[bucket][i]) { + statistics.counts[i]++; + } else { + long diff = statistics.values[i] - statistics.lastValues[bucket][i]; + statistics.avgRowsPerValueCounts[i]++; + statistics.avgRowsPerValue[i] = (statistics.avgRowsPerValue[i] + * (statistics.avgRowsPerValueCounts[i] - 1) + + (double) statistics.counts[i] / diff) / statistics.avgRowsPerValueCounts[i]; + statistics.counts[i] = 0; } } - } finally { - if (cursor != 0) { - mdb_cursor_close(cursor); - } + } + rc = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + if (rc != MDB_SUCCESS) { + endOfRange = true; } } - return cardinality; - } finally { - pool.free(s); } - }); + return new BucketSummary(allSamplesCount, bucket); + } + + private double interpolateBuckets(int bucketCount, Statistics statistics) { + double cardinality = 0; + for (int bucket = 1; bucket < bucketCount; bucket++) { + int pos = 0; + while (pos < statistics.lastValues[bucket].length + && statistics.startValues[bucket][pos] == statistics.lastValues[bucket - 1][pos]) { + pos++; + } + if (pos < statistics.lastValues[bucket].length) { + long diffBetweenGroups = Math.max( + statistics.startValues[bucket][pos] - statistics.lastValues[bucket - 1][pos], 0); + cardinality += statistics.avgRowsPerValue[pos] * diffBetweenGroups; + } + } + return cardinality; + } + + private static final class BucketSummary { + private final long totalSamples; + private final int bucketCount; + + BucketSummary(long totalSamples, int bucketCount) { + this.totalSamples = totalSamples; + this.bucketCount = bucketCount; + } + } + + private static final class CursorHandle implements AutoCloseable { + private final Pool pool; + private final Object owner; + private final int dbi; + private final boolean pooled; + private long cursor; + + private CursorHandle(Pool pool, Object owner, int dbi, long cursor, boolean pooled) { + this.pool = pool; + this.owner = owner; + this.dbi = dbi; + this.cursor = cursor; + this.pooled = pooled; + } + + static CursorHandle open(Pool pool, Object owner, MemoryStack stack, long txn, int dbi) throws IOException { + long pooledCursor = pool.getCursor(dbi, owner); + if (pooledCursor != 0) { + return new CursorHandle(pool, owner, dbi, pooledCursor, true); + } + PointerBuffer pointer = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pointer)); + return new CursorHandle(pool, owner, dbi, pointer.get(0), false); + } + + long cursor() { + return cursor; + } + + @Override + public void close() { + if (cursor == 0) { + return; + } + if (pooled) { + pool.freeCursor(dbi, owner, cursor); + } else { + mdb_cursor_close(cursor); + } + cursor = 0; + } + } } protected TripleIndex getBestIndex(long subj, long pred, long obj, long context) { @@ -1803,7 +1888,7 @@ KeyBuilder keyBuilder(long subj, long pred, long obj, long context) { @Override public void writeMin(ByteBuffer buffer) { - getMinKey(buffer, subj, pred, obj, context); + getMinKey(buffer, subj, pred, obj, context, 0, 0, 0, 0); } @Override @@ -1813,7 +1898,8 @@ public void writeMax(ByteBuffer buffer) { }; } - void getMinKey(ByteBuffer bb, long subj, long pred, long obj, long context) { + void getMinKey(ByteBuffer bb, long subj, long pred, long obj, long context, long prevSubj, long prevPred, + long prevObj, long prevContext) { subj = subj <= 0 ? 0 : subj; pred = pred <= 0 ? 0 : pred; obj = obj <= 0 ? 0 : obj; diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/TripleStoreCardinalityRefactorTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/TripleStoreCardinalityRefactorTest.java new file mode 100644 index 0000000000..1aa5af74f9 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/TripleStoreCardinalityRefactorTest.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TripleStoreCardinalityRefactorTest { + + private static final long PRED_A = 100; + private static final long PRED_B = 200; + private static final long CONTEXT_ONE = 10; + private static final long CONTEXT_TWO = 20; + + @TempDir + File tempFolder; + + private TripleStore tripleStore; + private int explicitStatements; + + @BeforeEach + void setUp() throws Exception { + File dataDir = new File(tempFolder, "triplestore"); + dataDir.mkdir(); + tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"), null); + populateData(); + } + + @AfterEach + void tearDown() throws Exception { + tripleStore.close(); + } + + @Test + void predicateAndContextMatchIteratorCounts() throws Exception { + assertEstimatorBounds(LmdbValue.UNKNOWN_ID, PRED_A, LmdbValue.UNKNOWN_ID, CONTEXT_ONE); + } + + @Test + void subjectAndPredicateMatchIteratorCounts() throws Exception { + assertEstimatorBounds(1, PRED_B, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID); + } + + @Test + void fullScanMatchesTotalTripleCount() throws Exception { + double estimate = tripleStore.cardinality(LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, LmdbValue.UNKNOWN_ID, + LmdbValue.UNKNOWN_ID); + assertEquals(explicitStatements, estimate, 0.0); + } + + @Test + void moreSpecificPatternDoesNotExceedBroaderEstimate() throws Exception { + double general = tripleStore.cardinality(LmdbValue.UNKNOWN_ID, PRED_A, LmdbValue.UNKNOWN_ID, CONTEXT_ONE); + double specific = tripleStore.cardinality(1, PRED_A, LmdbValue.UNKNOWN_ID, CONTEXT_ONE); + assertTrue(specific <= general); + } + + private void populateData() throws IOException { + tripleStore.startTransaction(); + explicitStatements = 0; + for (long subj = 1; subj <= 3; subj++) { + for (int i = 0; i < 120; i++) { + long pred = (i % 2 == 0) ? PRED_A : PRED_B; + long obj = subj * 1000 + i; + long context = (i % 3 == 0) ? CONTEXT_ONE : CONTEXT_TWO; + storeTriple(subj, pred, obj, context); + } + } + tripleStore.commit(); + } + + private void storeTriple(long subj, long pred, long obj, long context) throws IOException { + tripleStore.storeTriple(subj, pred, obj, context, true); + explicitStatements++; + } + + private void assertEstimatorBounds(long subj, long pred, long obj, long context) throws Exception { + double estimate = tripleStore.cardinality(subj, pred, obj, context); + int expected = count(subj, pred, obj, context, true) + count(subj, pred, obj, context, false); + assertTrue(estimate >= expected, + () -> "Estimator should not undercount actual matches for pattern: " + patternLabel(subj, pred, obj, + context) + " expected=" + expected + " actual=" + estimate); + assertTrue(estimate <= explicitStatements, + () -> "Estimator should not exceed total explicit statements in dataset for pattern: " + + patternLabel(subj, pred, obj, context)); + } + + private int count(long subj, long pred, long obj, long context, boolean explicit) throws Exception { + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + RecordIterator iterator = tripleStore.getTriples(txn, subj, pred, obj, context, explicit)) { + return count(iterator); + } + } + + private int count(RecordIterator iterator) throws IOException { + int count = 0; + while (iterator.next() != null) { + count++; + } + return count; + } + + private String patternLabel(long subj, long pred, long obj, long context) { + return "subj=" + subj + ", pred=" + pred + ", obj=" + obj + ", context=" + context; + } +} diff --git a/tmp/LmdbRecordIterator.modified.java b/tmp/LmdbRecordIterator.modified.java new file mode 100644 index 0000000000..219730ebd8 --- /dev/null +++ b/tmp/LmdbRecordIterator.modified.java @@ -0,0 +1,490 @@ +/******************************************************************************* + * Copyright (c) 2021 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.sail.lmdb; + +import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; +import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; +import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; +import static org.lwjgl.util.lmdb.LMDB.MDB_SET; +import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; +import static org.lwjgl.util.lmdb.LMDB.MDB_SUCCESS; +import static org.lwjgl.util.lmdb.LMDB.mdb_cmp; +import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; +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_cursor_renew; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.KeyBuilder; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.TripleIndex; +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A record iterator that wraps a native LMDB iterator. + */ +class LmdbRecordIterator implements RecordIterator { + private static final Logger log = LoggerFactory.getLogger(LmdbRecordIterator.class); + private final Pool pool = Pool.get(); + + private TripleIndex index; + + private long subj; + private long pred; + private long obj; + private long context; + + private long cursor; + + private MDBVal maxKey; + + private boolean matchValues; + private GroupMatcher groupMatcher; + + /** + * True when late-bound variables exist beyond the contiguous prefix of the chosen index order, requiring + * value-level filtering. When false, range bounds already guarantee that every visited key matches and the + * GroupMatcher is redundant. + */ + private boolean needMatcher; + + private Txn txnRef; + + private long txnRefVersion; + + private long txn; + + private int dbi; + + private volatile boolean closed = false; + + private MDBVal keyData; + + private MDBVal valueData; + + private ByteBuffer minKeyBuf; + + private ByteBuffer maxKeyBuf; + private boolean externalMinKeyBuf; + private boolean externalMaxKeyBuf; + + private int lastResult; + + private long[] quad; + + private boolean fetchNext = false; + + private StampedLongAdderLockManager txnLockManager; + + private final Thread ownerThread = Thread.currentThread(); + + private boolean initialized = false; + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, null, null, null); + } + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, null, null); + } + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, + ByteBuffer maxKeyBufParam) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, minKeyBufParam, + maxKeyBufParam); + } + + LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { + this(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, null, null, null); + } + + LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, + ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, + minKeyBufParam, maxKeyBufParam); + initialized = true; + } + + void initialize(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, + ByteBuffer maxKeyBufParam) throws IOException { + if (initialized && !closed) { + prepareForReuse(); + } + initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, + minKeyBufParam, maxKeyBufParam); + initialized = true; + } + + private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, + ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + this.index = index; + this.subj = subj; + this.pred = pred; + this.obj = obj; + this.context = context; + + boolean prevExternalMinKeyBuf = this.externalMinKeyBuf; + boolean prevExternalMaxKeyBuf = this.externalMaxKeyBuf; + + if (quadReuse != null && quadReuse.length >= 4) { + this.quad = quadReuse; + } else if (this.quad == null || this.quad.length < 4) { + this.quad = new long[] { subj, pred, obj, context }; + } + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = obj; + this.quad[3] = context; + + if (this.keyData == null) { + this.keyData = pool.getVal(); + } + if (this.valueData == null) { + this.valueData = pool.getVal(); + } + + if (rangeSearch) { + this.externalMinKeyBuf = minKeyBufParam != null; + if (externalMinKeyBuf) { + this.minKeyBuf = minKeyBufParam; + } else { + if (this.minKeyBuf == null || prevExternalMinKeyBuf) { + this.minKeyBuf = pool.getKeyBuffer(); + } else { + this.minKeyBuf.clear(); + } + } + if (keyBuilder != null) { + keyBuilder.writeMin(minKeyBuf); + } else { + index.getMinKey(minKeyBuf, subj, pred, obj, context); + } + minKeyBuf.flip(); + + if (this.maxKey == null) { + this.maxKey = pool.getVal(); + } + this.externalMaxKeyBuf = maxKeyBufParam != null; + if (externalMaxKeyBuf) { + this.maxKeyBuf = maxKeyBufParam; + } else { + if (this.maxKeyBuf == null || prevExternalMaxKeyBuf) { + this.maxKeyBuf = pool.getKeyBuffer(); + } else { + this.maxKeyBuf.clear(); + } + } + if (keyBuilder != null) { + keyBuilder.writeMax(maxKeyBuf); + } else { + index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + } + maxKeyBuf.flip(); + this.maxKey.mv_data(maxKeyBuf); + } else { + if (this.maxKey != null) { + pool.free(maxKey); + this.maxKey = null; + } + if (this.maxKeyBuf != null && !prevExternalMaxKeyBuf) { + pool.free(maxKeyBuf); + this.maxKeyBuf = null; + } + this.externalMaxKeyBuf = maxKeyBufParam != null; + this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : null; + + if (subj > 0 || pred > 0 || obj > 0 || context >= 0) { + this.externalMinKeyBuf = minKeyBufParam != null; + if (externalMinKeyBuf) { + this.minKeyBuf = minKeyBufParam; + } else { + if (this.minKeyBuf == null || prevExternalMinKeyBuf) { + this.minKeyBuf = pool.getKeyBuffer(); + } else { + this.minKeyBuf.clear(); + } + } + index.getMinKey(minKeyBuf, subj, pred, obj, context); + minKeyBuf.flip(); + } else { + if (this.minKeyBuf != null && !prevExternalMinKeyBuf) { + pool.free(minKeyBuf); + this.minKeyBuf = null; + } + this.externalMinKeyBuf = minKeyBufParam != null; + if (externalMinKeyBuf) { + this.minKeyBuf = minKeyBufParam; + } else { + this.minKeyBuf = null; + } + } + } + + this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; + int prefixLen = index.getPatternScore(subj, pred, obj, context); + int boundCount = (subj > 0 ? 1 : 0) + (pred > 0 ? 1 : 0) + (obj > 0 ? 1 : 0) + (context >= 0 ? 1 : 0); + this.needMatcher = boundCount > prefixLen; + this.groupMatcher = null; + this.fetchNext = false; + this.lastResult = MDB_SUCCESS; + this.closed = false; + + this.dbi = index.getDB(explicit); + this.txnRef = txnRef; + this.txnLockManager = txnRef.lockManager(); + + long readStamp; + try { + readStamp = txnLockManager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + this.txnRefVersion = txnRef.version(); + this.txn = txnRef.get(); + + // Try to reuse a pooled cursor only for read-only transactions; otherwise open a new one + if (txnRef.isReadOnly()) { + long pooled = pool.getCursor(dbi, index); + if (pooled != 0L) { + long c = pooled; + try { + E(mdb_cursor_renew(txn, c)); + } catch (IOException renewEx) { + // Renewal failed (e.g., incompatible txn). Close pooled cursor and open a fresh one. + mdb_cursor_close(c); + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + c = pp.get(0); + } + } + cursor = c; + } else { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + cursor = pp.get(0); + } + } + } else { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + cursor = pp.get(0); + } + } + } finally { + txnLockManager.unlockRead(readStamp); + } + } + + @Override + public long[] next() { + if (closed) { + log.debug("Calling next() on an LmdbRecordIterator that is already closed, returning null"); + return null; + } + StampedLongAdderLockManager manager = txnLockManager; + if (manager == null) { + throw new SailException("Iterator not initialized"); + } + long readStamp; + try { + readStamp = manager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + if (txnRefVersion != txnRef.version()) { + // TODO: None of the tests in the LMDB Store cover this case! + // cursor must be renewed + mdb_cursor_renew(txn, cursor); + if (fetchNext) { + // cursor must be positioned on last item, reuse minKeyBuf if available + if (minKeyBuf == null) { + minKeyBuf = pool.getKeyBuffer(); + } + minKeyBuf.clear(); + index.toKey(minKeyBuf, quad[0], quad[1], quad[2], quad[3]); + minKeyBuf.flip(); + keyData.mv_data(minKeyBuf); + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET); + if (lastResult != MDB_SUCCESS) { + // use MDB_SET_RANGE if key was deleted + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + } + if (lastResult != MDB_SUCCESS) { + closeInternal(false); + return null; + } + } + // update version of txn ref + this.txnRefVersion = txnRef.version(); + } + + if (fetchNext) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + fetchNext = false; + } else { + if (minKeyBuf != null) { + // set cursor to min key + keyData.mv_data(minKeyBuf); + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + } else { + // set cursor to first item + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + } + } + + while (lastResult == MDB_SUCCESS) { + // if (maxKey != null && TripleStore.COMPARATOR.compare(keyData.mv_data(), maxKey.mv_data()) > 0) { + if (maxKey != null && mdb_cmp(txn, dbi, keyData, maxKey) > 0) { + lastResult = MDB_NOTFOUND; + } else if (matches()) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + } else { + // Matching value found + index.keyToQuad(keyData.mv_data(), subj, pred, obj, context, quad); + // fetch next value + fetchNext = true; + return quad; + } + } + closeInternal(false); + return null; + } finally { + manager.unlockRead(readStamp); + } + } + + private boolean matches() { + // When there are no late-bound variables beyond the contiguous prefix, range bounds fully determine matches. + if (!needMatcher) { + return false; + } + + if (groupMatcher != null) { + return !this.groupMatcher.matches(keyData.mv_data()); + } else if (matchValues) { + this.groupMatcher = index.createMatcher(subj, pred, obj, context); + return !this.groupMatcher.matches(keyData.mv_data()); + } else { + return false; + } + } + + private void prepareForReuse() { + if (cursor != 0L && txnRef != null) { + if (txnRef.isReadOnly()) { + pool.freeCursor(dbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } + } + cursor = 0L; + groupMatcher = null; + fetchNext = false; + lastResult = MDB_SUCCESS; + matchValues = false; + needMatcher = false; + txnRef = null; + txn = 0L; + txnRefVersion = 0L; + txnLockManager = null; + closed = true; + } + + private void closeInternal(boolean maybeCalledAsync) { + StampedLongAdderLockManager manager = this.txnLockManager; + if (closed) { + return; + } + long writeStamp = 0L; + boolean writeLocked = false; + if (maybeCalledAsync && ownerThread != Thread.currentThread() && manager != null) { + try { + writeStamp = manager.writeLock(); + writeLocked = true; + } catch (InterruptedException e) { + throw new SailException(e); + } + } + try { + if (cursor != 0L && txnRef != null) { + if (txnRef.isReadOnly()) { + pool.freeCursor(dbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } + cursor = 0L; + } + if (keyData != null) { + pool.free(keyData); + keyData = null; + } + if (valueData != null) { + pool.free(valueData); + valueData = null; + } + if (minKeyBuf != null && !externalMinKeyBuf) { + pool.free(minKeyBuf); + } + if (maxKeyBuf != null && !externalMaxKeyBuf) { + pool.free(maxKeyBuf); + } + if (maxKey != null) { + pool.free(maxKey); + maxKey = null; + } + minKeyBuf = null; + maxKeyBuf = null; + externalMinKeyBuf = false; + externalMaxKeyBuf = false; + groupMatcher = null; + fetchNext = false; + lastResult = 0; + matchValues = false; + needMatcher = false; + txnRef = null; + txn = 0L; + dbi = 0; + index = null; + } finally { + closed = true; + if (writeLocked) { + manager.unlockWrite(writeStamp); + } + txnLockManager = null; + } + } + + @Override + public void close() { + closeInternal(true); + } +} diff --git a/tmp/LmdbRecordIterator.original.java b/tmp/LmdbRecordIterator.original.java new file mode 100644 index 0000000000..7f3f9671f0 --- /dev/null +++ b/tmp/LmdbRecordIterator.original.java @@ -0,0 +1,439 @@ +/******************************************************************************* + * Copyright (c) 2021 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.sail.lmdb; + +import static org.eclipse.rdf4j.sail.lmdb.LmdbUtil.E; +import static org.lwjgl.util.lmdb.LMDB.MDB_NEXT; +import static org.lwjgl.util.lmdb.LMDB.MDB_NOTFOUND; +import static org.lwjgl.util.lmdb.LMDB.MDB_SET; +import static org.lwjgl.util.lmdb.LMDB.MDB_SET_RANGE; +import static org.lwjgl.util.lmdb.LMDB.MDB_SUCCESS; +import static org.lwjgl.util.lmdb.LMDB.mdb_cmp; +import static org.lwjgl.util.lmdb.LMDB.mdb_cursor_close; +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_cursor_renew; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.rdf4j.common.concurrent.locks.StampedLongAdderLockManager; +import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.KeyBuilder; +import org.eclipse.rdf4j.sail.lmdb.TripleStore.TripleIndex; +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A record iterator that wraps a native LMDB iterator. + */ +class LmdbRecordIterator implements RecordIterator { + private static final Logger log = LoggerFactory.getLogger(LmdbRecordIterator.class); + private final Pool pool = Pool.get(); + + private TripleIndex index; + + private long subj; + private long pred; + private long obj; + private long context; + + private long cursor; + + private MDBVal maxKey; + + private boolean matchValues; + private GroupMatcher groupMatcher; + + /** + * True when late-bound variables exist beyond the contiguous prefix of the chosen index order, requiring + * value-level filtering. When false, range bounds already guarantee that every visited key matches and the + * GroupMatcher is redundant. + */ + private boolean needMatcher; + + private Txn txnRef; + + private long txnRefVersion; + + private long txn; + + private int dbi; + + private volatile boolean closed = false; + + private MDBVal keyData; + + private MDBVal valueData; + + private ByteBuffer minKeyBuf; + + private ByteBuffer maxKeyBuf; + private boolean externalMinKeyBuf; + private boolean externalMaxKeyBuf; + + private int lastResult; + + private long[] quad; + + private boolean fetchNext = false; + + private StampedLongAdderLockManager txnLockManager; + + private final Thread ownerThread = Thread.currentThread(); + + private boolean initialized = false; + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, null, null, null); + } + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, null, null); + } + + LmdbRecordIterator(TripleIndex index, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, + ByteBuffer maxKeyBufParam) throws IOException { + this(index, null, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, minKeyBufParam, + maxKeyBufParam); + } + + LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef) throws IOException { + this(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, null, null, null); + } + + LmdbRecordIterator(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, + ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, + minKeyBufParam, maxKeyBufParam); + initialized = true; + } + + void initialize(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, long pred, long obj, + long context, boolean explicit, Txn txnRef, long[] quadReuse, ByteBuffer minKeyBufParam, + ByteBuffer maxKeyBufParam) throws IOException { + if (initialized && !closed) { + throw new IllegalStateException("Cannot initialize LMDB record iterator while it is open"); + } + initializeInternal(index, keyBuilder, rangeSearch, subj, pred, obj, context, explicit, txnRef, quadReuse, + minKeyBufParam, maxKeyBufParam); + initialized = true; + } + + private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolean rangeSearch, long subj, + long pred, long obj, long context, boolean explicit, Txn txnRef, long[] quadReuse, + ByteBuffer minKeyBufParam, ByteBuffer maxKeyBufParam) throws IOException { + this.index = index; + this.subj = subj; + this.pred = pred; + this.obj = obj; + this.context = context; + + if (quadReuse != null && quadReuse.length >= 4) { + this.quad = quadReuse; + } else if (this.quad == null || this.quad.length < 4) { + this.quad = new long[] { subj, pred, obj, context }; + } + this.quad[0] = subj; + this.quad[1] = pred; + this.quad[2] = obj; + this.quad[3] = context; + + if (this.keyData == null) { + this.keyData = pool.getVal(); + } + if (this.valueData == null) { + this.valueData = pool.getVal(); + } + + if (rangeSearch) { + this.externalMinKeyBuf = minKeyBufParam != null; + this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : pool.getKeyBuffer(); + minKeyBuf.clear(); + if (keyBuilder != null) { + keyBuilder.writeMin(minKeyBuf); + } else { + index.getMinKey(minKeyBuf, subj, pred, obj, context); + } + minKeyBuf.flip(); + + if (this.maxKey == null) { + this.maxKey = pool.getVal(); + } + this.externalMaxKeyBuf = maxKeyBufParam != null; + this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : pool.getKeyBuffer(); + maxKeyBuf.clear(); + if (keyBuilder != null) { + keyBuilder.writeMax(maxKeyBuf); + } else { + index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + } + maxKeyBuf.flip(); + this.maxKey.mv_data(maxKeyBuf); + } else { + if (this.maxKey != null) { + pool.free(maxKey); + this.maxKey = null; + } + if (this.maxKeyBuf != null && !externalMaxKeyBuf) { + pool.free(maxKeyBuf); + } + this.externalMaxKeyBuf = maxKeyBufParam != null; + this.maxKeyBuf = externalMaxKeyBuf ? maxKeyBufParam : null; + + if (subj > 0 || pred > 0 || obj > 0 || context >= 0) { + this.externalMinKeyBuf = minKeyBufParam != null; + this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : pool.getKeyBuffer(); + minKeyBuf.clear(); + index.getMinKey(minKeyBuf, subj, pred, obj, context); + minKeyBuf.flip(); + } else { + if (this.minKeyBuf != null && !externalMinKeyBuf) { + pool.free(minKeyBuf); + } + this.externalMinKeyBuf = minKeyBufParam != null; + this.minKeyBuf = externalMinKeyBuf ? minKeyBufParam : null; + } + } + + this.matchValues = subj > 0 || pred > 0 || obj > 0 || context >= 0; + int prefixLen = index.getPatternScore(subj, pred, obj, context); + int boundCount = (subj > 0 ? 1 : 0) + (pred > 0 ? 1 : 0) + (obj > 0 ? 1 : 0) + (context >= 0 ? 1 : 0); + this.needMatcher = boundCount > prefixLen; + this.groupMatcher = null; + this.fetchNext = false; + this.lastResult = MDB_SUCCESS; + this.closed = false; + + this.dbi = index.getDB(explicit); + this.txnRef = txnRef; + this.txnLockManager = txnRef.lockManager(); + + long readStamp; + try { + readStamp = txnLockManager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + this.txnRefVersion = txnRef.version(); + this.txn = txnRef.get(); + + // Try to reuse a pooled cursor only for read-only transactions; otherwise open a new one + if (txnRef.isReadOnly()) { + long pooled = pool.getCursor(dbi, index); + if (pooled != 0L) { + long c = pooled; + try { + E(mdb_cursor_renew(txn, c)); + } catch (IOException renewEx) { + // Renewal failed (e.g., incompatible txn). Close pooled cursor and open a fresh one. + mdb_cursor_close(c); + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + c = pp.get(0); + } + } + cursor = c; + } else { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + cursor = pp.get(0); + } + } + } else { + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pp = stack.mallocPointer(1); + E(mdb_cursor_open(txn, dbi, pp)); + cursor = pp.get(0); + } + } + } finally { + txnLockManager.unlockRead(readStamp); + } + } + + @Override + public long[] next() { + if (closed) { + log.debug("Calling next() on an LmdbRecordIterator that is already closed, returning null"); + return null; + } + StampedLongAdderLockManager manager = txnLockManager; + if (manager == null) { + throw new SailException("Iterator not initialized"); + } + long readStamp; + try { + readStamp = manager.readLock(); + } catch (InterruptedException e) { + throw new SailException(e); + } + try { + if (txnRefVersion != txnRef.version()) { + // TODO: None of the tests in the LMDB Store cover this case! + // cursor must be renewed + mdb_cursor_renew(txn, cursor); + if (fetchNext) { + // cursor must be positioned on last item, reuse minKeyBuf if available + if (minKeyBuf == null) { + minKeyBuf = pool.getKeyBuffer(); + } + minKeyBuf.clear(); + index.toKey(minKeyBuf, quad[0], quad[1], quad[2], quad[3]); + minKeyBuf.flip(); + keyData.mv_data(minKeyBuf); + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET); + if (lastResult != MDB_SUCCESS) { + // use MDB_SET_RANGE if key was deleted + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + } + if (lastResult != MDB_SUCCESS) { + closeInternal(false); + return null; + } + } + // update version of txn ref + this.txnRefVersion = txnRef.version(); + } + + if (fetchNext) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + fetchNext = false; + } else { + if (minKeyBuf != null) { + // set cursor to min key + keyData.mv_data(minKeyBuf); + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_SET_RANGE); + } else { + // set cursor to first item + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + } + } + + while (lastResult == MDB_SUCCESS) { + // if (maxKey != null && TripleStore.COMPARATOR.compare(keyData.mv_data(), maxKey.mv_data()) > 0) { + if (maxKey != null && mdb_cmp(txn, dbi, keyData, maxKey) > 0) { + lastResult = MDB_NOTFOUND; + } else if (matches()) { + lastResult = mdb_cursor_get(cursor, keyData, valueData, MDB_NEXT); + } else { + // Matching value found + index.keyToQuad(keyData.mv_data(), subj, pred, obj, context, quad); + // fetch next value + fetchNext = true; + return quad; + } + } + closeInternal(false); + return null; + } finally { + manager.unlockRead(readStamp); + } + } + + private boolean matches() { + // When there are no late-bound variables beyond the contiguous prefix, range bounds fully determine matches. + if (!needMatcher) { + return false; + } + + if (groupMatcher != null) { + return !this.groupMatcher.matches(keyData.mv_data()); + } else if (matchValues) { + this.groupMatcher = index.createMatcher(subj, pred, obj, context); + return !this.groupMatcher.matches(keyData.mv_data()); + } else { + return false; + } + } + + private void closeInternal(boolean maybeCalledAsync) { + StampedLongAdderLockManager manager = this.txnLockManager; + if (closed) { + return; + } + long writeStamp = 0L; + boolean writeLocked = false; + if (maybeCalledAsync && ownerThread != Thread.currentThread() && manager != null) { + try { + writeStamp = manager.writeLock(); + writeLocked = true; + } catch (InterruptedException e) { + throw new SailException(e); + } + } + try { + if (cursor != 0L && txnRef != null) { + if (txnRef.isReadOnly()) { + pool.freeCursor(dbi, index, cursor); + } else { + mdb_cursor_close(cursor); + } + cursor = 0L; + } + if (keyData != null) { + pool.free(keyData); + keyData = null; + } + if (valueData != null) { + pool.free(valueData); + valueData = null; + } + if (minKeyBuf != null && !externalMinKeyBuf) { + pool.free(minKeyBuf); + } + if (maxKeyBuf != null && !externalMaxKeyBuf) { + pool.free(maxKeyBuf); + } + if (maxKey != null) { + pool.free(maxKey); + maxKey = null; + } + minKeyBuf = null; + maxKeyBuf = null; + externalMinKeyBuf = false; + externalMaxKeyBuf = false; + groupMatcher = null; + fetchNext = false; + lastResult = 0; + matchValues = false; + needMatcher = false; + txnRef = null; + txn = 0L; + dbi = 0; + index = null; + } finally { + closed = true; + if (writeLocked) { + manager.unlockWrite(writeStamp); + } + txnLockManager = null; + } + } + + @Override + public void close() { + closeInternal(true); + } +} diff --git a/tmp/LmdbRecordIteratorLateBindingTest.original.java b/tmp/LmdbRecordIteratorLateBindingTest.original.java new file mode 100644 index 0000000000..4f0ce6f9f9 --- /dev/null +++ b/tmp/LmdbRecordIteratorLateBindingTest.original.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.sail.lmdb.TxnManager.Txn; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbRecordIteratorLateBindingTest { + + @TempDir + File dataDir; + + private TripleStore tripleStore; + + @BeforeEach + void setUp() throws Exception { + tripleStore = new TripleStore(dataDir, new LmdbStoreConfig("spoc,posc"), null); + + tripleStore.startTransaction(); + tripleStore.storeTriple(1, 2, 3, 0, true); + tripleStore.storeTriple(1, 5, 6, 0, true); + tripleStore.storeTriple(1, 6, 9, 0, true); + tripleStore.storeTriple(2, 5, 6, 0, true); + tripleStore.commit(); + } + + @AfterEach + void tearDown() throws Exception { + if (tripleStore != null) { + tripleStore.close(); + } + } + + @Test + void subjectObjectPatternStillFiltersWithMatcher() throws Exception { + try (Txn txn = tripleStore.getTxnManager().createReadTxn(); + LmdbRecordIterator iter = (LmdbRecordIterator) tripleStore.getTriples(txn, 1, -1, 6, -1, true)) { + assertThat(iter).isInstanceOf(LmdbRecordIterator.class); + assertThat(getBooleanField(iter, "needMatcher")).isTrue(); + + List seen = new ArrayList<>(); + long[] next; + while ((next = iter.next()) != null) { + seen.add(next.clone()); + } + + assertThat(seen).containsExactly(new long[] { 1, 5, 6, 0 }); + assertThat(getField(iter, "groupMatcher")).isNotNull(); + } + } + + private static boolean getBooleanField(Object instance, String name) throws Exception { + Field field = LmdbRecordIterator.class.getDeclaredField(name); + field.setAccessible(true); + return field.getBoolean(instance); + } + + private static Object getField(Object instance, String name) throws Exception { + Field field = LmdbRecordIterator.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(instance); + } +} From 17395e8a8d4977baf8ba5bca4e4bc2a76ba4d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 21:31:56 +0900 Subject: [PATCH 76/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 8 +- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 26 +- .../org/eclipse/rdf4j/sail/lmdb/Varint.java | 12 + .../rdf4j/sail/lmdb/util/IndexKeyWriters.java | 402 ++++++++++-------- .../rdf4j/sail/lmdb/QueryBenchmarkTest.java | 3 +- 5 files changed, 270 insertions(+), 181 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index c49f872aac..a3ee26eec2 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -150,10 +150,10 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea // } this.index = index; - long prevSubj = this.subj; - long prevPred = this.pred; - long prevObj = this.obj; - long prevContext = this.context; + long prevSubj = minKeyBuf != null ? this.subj : TripleStore.NO_PREVIOUS_ID; + long prevPred = minKeyBuf != null ? this.pred : TripleStore.NO_PREVIOUS_ID; + long prevObj = minKeyBuf != null ? this.obj : TripleStore.NO_PREVIOUS_ID; + long prevContext = minKeyBuf != null ? this.context : TripleStore.NO_PREVIOUS_ID; this.subj = subj; this.pred = pred; 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 bcb8380ac9..e47929ae17 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 @@ -123,6 +123,7 @@ public class TripleStore implements Closeable { public static final int CONTEXT_IDX = 3; static final int MAX_KEY_LENGTH = 4 * 9; + static final long NO_PREVIOUS_ID = Long.MIN_VALUE; /** * The default triple indexes. @@ -812,7 +813,8 @@ protected void filterUsedIds(Collection ids) throws IOException { maxKey.mv_data(maxKeyBuf); keyBuf.clear(); - index.getMinKey(keyBuf, subj, pred, obj, context, 0, 0, 0, 0); + index.getMinKey(keyBuf, subj, pred, obj, context, NO_PREVIOUS_ID, NO_PREVIOUS_ID, + NO_PREVIOUS_ID, NO_PREVIOUS_ID); keyBuf.flip(); // set cursor to min key @@ -918,7 +920,8 @@ private double estimateWithTransaction(Pool pool, Statistics statistics, MemoryS statistics.reset(); keyBuf.clear(); - index.getMinKey(keyBuf, subj, pred, obj, context, 0, 0, 0, 0); + index.getMinKey(keyBuf, subj, pred, obj, context, NO_PREVIOUS_ID, NO_PREVIOUS_ID, NO_PREVIOUS_ID, + NO_PREVIOUS_ID); keyBuf.flip(); int dbi = index.getDB(explicit); @@ -1888,7 +1891,8 @@ KeyBuilder keyBuilder(long subj, long pred, long obj, long context) { @Override public void writeMin(ByteBuffer buffer) { - getMinKey(buffer, subj, pred, obj, context, 0, 0, 0, 0); + getMinKey(buffer, subj, pred, obj, context, NO_PREVIOUS_ID, NO_PREVIOUS_ID, NO_PREVIOUS_ID, + NO_PREVIOUS_ID); } @Override @@ -1904,7 +1908,12 @@ void getMinKey(ByteBuffer bb, long subj, long pred, long obj, long context, long pred = pred <= 0 ? 0 : pred; obj = obj <= 0 ? 0 : obj; context = context <= 0 ? 0 : context; - toKey(bb, subj, pred, obj, context); + long prevSubjNorm = prevSubj == NO_PREVIOUS_ID ? NO_PREVIOUS_ID : (prevSubj <= 0 ? 0 : prevSubj); + long prevPredNorm = prevPred == NO_PREVIOUS_ID ? NO_PREVIOUS_ID : (prevPred <= 0 ? 0 : prevPred); + long prevObjNorm = prevObj == NO_PREVIOUS_ID ? NO_PREVIOUS_ID : (prevObj <= 0 ? 0 : prevObj); + long prevContextNorm = prevContext == NO_PREVIOUS_ID ? NO_PREVIOUS_ID + : (prevContext <= 0 ? 0 : prevContext); + toKey(bb, subj, pred, obj, context, prevSubjNorm, prevPredNorm, prevObjNorm, prevContextNorm); } void getMaxKey(ByteBuffer bb, long subj, long pred, long obj, long context) { @@ -1996,6 +2005,11 @@ public void print() { } void toKey(ByteBuffer bb, long subj, long pred, long obj, long context) { + toKey(bb, subj, pred, obj, context, NO_PREVIOUS_ID, NO_PREVIOUS_ID, NO_PREVIOUS_ID, NO_PREVIOUS_ID); + } + + void toKey(ByteBuffer bb, long subj, long pred, long obj, long context, long prevSubj, long prevPred, + long prevObj, long prevContext) { boolean shouldCache = threeOfFourAreZeroOrMax(subj, pred, obj, context); if (shouldCache) { @@ -2012,7 +2026,9 @@ void toKey(ByteBuffer bb, long subj, long pred, long obj, long context) { } // Pass through to the keyWriter with caching hint - keyWriter.write(bb, subj, pred, obj, context, shouldCache); + boolean hasPrev = prevSubj != NO_PREVIOUS_ID; + keyWriter.write(bb, subj, pred, obj, context, shouldCache, hasPrev, prevSubj, prevPred, prevObj, + prevContext); } void keyToQuad(ByteBuffer key, long[] quad) { diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java index 8ee5494f8d..b926fbf9fd 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/Varint.java @@ -246,6 +246,18 @@ public static int calcListLengthUnsigned(long a, long b, long c, long d) { return calcLengthUnsigned(a) + calcLengthUnsigned(b) + calcLengthUnsigned(c) + calcLengthUnsigned(d); } + /** + * Advances the {@link ByteBuffer#position()} by the number of bytes required to encode the supplied value using the + * unsigned variable-length format, without writing any data. This assumes the buffer already contains the encoded + * representation at the current position. + * + * @param bb buffer whose position should be advanced + * @param value value whose encoded length should be skipped + */ + public static void advanceUnsigned(ByteBuffer bb, long value) { + bb.position(bb.position() + calcLengthUnsigned(value)); + } + /** * The number of bytes required to represent the given number minus one. The descriptor can be encoded in 3 bits. * diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyWriters.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyWriters.java index bfd81aae41..fb9a01fde1 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyWriters.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyWriters.java @@ -24,12 +24,14 @@ private IndexKeyWriters() { @FunctionalInterface public interface KeyWriter { - void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean shouldCache); + void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean shouldCache, boolean hasPrev, + long prevSubj, long prevPred, long prevObj, long prevContext); } @FunctionalInterface interface BasicWriter { - void write(ByteBuffer bb, long subj, long pred, long obj, long context); + void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext); } @FunctionalInterface @@ -119,6 +121,15 @@ public static KeyWriter forFieldSeq(String fieldSeq) { return new CachingKeyWriter(basic); } + private static boolean writeOrSkip(ByteBuffer bb, long value, long prevValue, boolean rewrite) { + if (rewrite || value != prevValue) { + Varint.writeUnsigned(bb, value); + return true; + } + Varint.advanceUnsigned(bb, value); + return false; + } + // Simple array-based cache keyed by a masked index computed from a hashCode. private static final class CachingKeyWriter implements KeyWriter { @@ -149,9 +160,10 @@ private static final class Entry { } @Override - public void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean shouldCache) { + public void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean shouldCache, + boolean hasPrev, long prevSubj, long prevPred, long prevObj, long prevContext) { if (!shouldCache) { - basic.write(bb, subj, pred, obj, context); + basic.write(bb, subj, pred, obj, context, hasPrev, prevSubj, prevPred, prevObj, prevContext); return; } @@ -169,7 +181,7 @@ public void write(ByteBuffer bb, long subj, long pred, long obj, long context, b int len = Varint.calcListLengthUnsigned(subj, pred, obj, context); byte[] bytes = new byte[len]; ByteBuffer out = ByteBuffer.wrap(bytes); - basic.write(out, subj, pred, obj, context); + basic.write(out, subj, pred, obj, context, false, 0, 0, 0, 0); out.flip(); bb.put(out); cache[slot] = new Entry(hashCode, subj, pred, obj, context, bytes); @@ -231,172 +243,220 @@ public static MatcherFactory matcherFactory(String fieldSeq) { } } - static void spoc(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, context); - } - - static void spco(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, obj); - } - - static void sopc(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, context); - } - - static void socp(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, pred); - } - - static void scpo(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, obj); - } - - static void scop(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, pred); - } - - static void psoc(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, context); - } - - static void psco(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, obj); - } - - static void posc(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, context); - } - - static void pocs(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, subj); - } - - static void pcso(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, obj); - } - - static void pcos(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, subj); - } - - static void ospc(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, context); - } - - static void oscp(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, pred); - } - - static void opsc(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, context); - } - - static void opcs(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, subj); - } - - static void ocsp(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, pred); - } - - static void ocps(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, subj); - } - - static void cspo(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, obj); - } - - static void csop(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, pred); - } - - static void cpso(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, obj); - } - - static void cpos(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, subj); - } - - static void cosp(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, subj); - Varint.writeUnsigned(bb, pred); - } - - static void cops(ByteBuffer bb, long subj, long pred, long obj, long context) { - Varint.writeUnsigned(bb, context); - Varint.writeUnsigned(bb, obj); - Varint.writeUnsigned(bb, pred); - Varint.writeUnsigned(bb, subj); + static void spoc(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + writeOrSkip(bb, context, prevContext, rewrite); + } + + static void spco(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + writeOrSkip(bb, obj, prevObj, rewrite); + } + + static void sopc(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + writeOrSkip(bb, context, prevContext, rewrite); + } + + static void socp(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + writeOrSkip(bb, pred, prevPred, rewrite); + } + + static void scpo(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + writeOrSkip(bb, obj, prevObj, rewrite); + } + + static void scop(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + writeOrSkip(bb, pred, prevPred, rewrite); + } + + static void psoc(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + writeOrSkip(bb, context, prevContext, rewrite); + } + + static void psco(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + writeOrSkip(bb, obj, prevObj, rewrite); + } + + static void posc(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + writeOrSkip(bb, context, prevContext, rewrite); + } + + static void pocs(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + writeOrSkip(bb, subj, prevSubj, rewrite); + } + + static void pcso(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + writeOrSkip(bb, obj, prevObj, rewrite); + } + + static void pcos(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + writeOrSkip(bb, subj, prevSubj, rewrite); + } + + static void ospc(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + writeOrSkip(bb, context, prevContext, rewrite); + } + + static void oscp(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + writeOrSkip(bb, pred, prevPred, rewrite); + } + + static void opsc(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + writeOrSkip(bb, context, prevContext, rewrite); + } + + static void opcs(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + writeOrSkip(bb, subj, prevSubj, rewrite); + } + + static void ocsp(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + writeOrSkip(bb, pred, prevPred, rewrite); + } + + static void ocps(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + writeOrSkip(bb, subj, prevSubj, rewrite); + } + + static void cspo(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + writeOrSkip(bb, obj, prevObj, rewrite); + } + + static void csop(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + writeOrSkip(bb, pred, prevPred, rewrite); + } + + static void cpso(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + writeOrSkip(bb, obj, prevObj, rewrite); + } + + static void cpos(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + writeOrSkip(bb, subj, prevSubj, rewrite); + } + + static void cosp(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, subj, prevSubj, rewrite); + writeOrSkip(bb, pred, prevPred, rewrite); + } + + static void cops(ByteBuffer bb, long subj, long pred, long obj, long context, boolean hasPrev, long prevSubj, + long prevPred, long prevObj, long prevContext) { + boolean rewrite = !hasPrev; + rewrite = writeOrSkip(bb, context, prevContext, rewrite); + rewrite = writeOrSkip(bb, obj, prevObj, rewrite); + rewrite = writeOrSkip(bb, pred, prevPred, rewrite); + writeOrSkip(bb, subj, prevSubj, rewrite); } static boolean[] spocShouldMatch(long subj, long pred, long obj, long context) { diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java index 7d0c035d58..a7c165e84e 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/QueryBenchmarkTest.java @@ -166,10 +166,11 @@ public void groupByQuery() { } @Test - @Timeout(30) +// @Timeout(30) public void complexQuery() { try (SailRepositoryConnection connection = repository.getConnection()) { long count; + System.out.println(); try (var stream = connection.prepareTupleQuery(query4).evaluate().stream()) { count = stream.count(); } From 10f1631ba904355e482356ba0ccc171a7695082f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 6 Nov 2025 22:00:30 +0900 Subject: [PATCH 77/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/LmdbRecordIterator.java | 2 +- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java index a3ee26eec2..48c9b5d67c 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbRecordIterator.java @@ -216,7 +216,7 @@ private void initializeInternal(TripleIndex index, KeyBuilder keyBuilder, boolea if (keyBuilder != null) { keyBuilder.writeMax(maxKeyBuf); } else { - index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + index.getMaxKey(maxKeyBuf, subj, pred, obj, context, prevSubj, prevPred, prevObj, prevContext); } maxKeyBuf.flip(); this.maxKey.mv_data(maxKeyBuf); 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 e47929ae17..6ec4475fd1 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 @@ -808,7 +808,7 @@ protected void filterUsedIds(Collection ids) throws IOException { GroupMatcher matcher = index.createMatcher(subj, pred, obj, context); maxKeyBuf.clear(); - index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + index.getMaxKey(maxKeyBuf, subj, pred, obj, context, -1, -1, -1, -1); maxKeyBuf.flip(); maxKey.mv_data(maxKeyBuf); @@ -906,7 +906,7 @@ private double estimateWithTransaction(Pool pool, Statistics statistics, MemoryS throws IOException { MDBVal maxKey = MDBVal.malloc(stack); ByteBuffer maxKeyBuf = stack.malloc(TripleStore.MAX_KEY_LENGTH); - index.getMaxKey(maxKeyBuf, subj, pred, obj, context); + index.getMaxKey(maxKeyBuf, subj, pred, obj, context, -1, -1, -1, -1); maxKeyBuf.flip(); maxKey.mv_data(maxKeyBuf); @@ -1897,7 +1897,7 @@ public void writeMin(ByteBuffer buffer) { @Override public void writeMax(ByteBuffer buffer) { - getMaxKey(buffer, subj, pred, obj, context); + getMaxKey(buffer, subj, pred, obj, context, -1, -1, -1, -1); } }; } @@ -1916,12 +1916,20 @@ void getMinKey(ByteBuffer bb, long subj, long pred, long obj, long context, long toKey(bb, subj, pred, obj, context, prevSubjNorm, prevPredNorm, prevObjNorm, prevContextNorm); } - void getMaxKey(ByteBuffer bb, long subj, long pred, long obj, long context) { + void getMaxKey(ByteBuffer bb, long subj, long pred, long obj, long context, long prevSubj, long prevPred, + long prevObj, long prevContext) { subj = subj <= 0 ? Long.MAX_VALUE : subj; pred = pred <= 0 ? Long.MAX_VALUE : pred; obj = obj <= 0 ? Long.MAX_VALUE : obj; context = context < 0 ? Long.MAX_VALUE : context; - toKey(bb, subj, pred, obj, context); + long prevSubjNorm = prevSubj == NO_PREVIOUS_ID ? NO_PREVIOUS_ID + : (prevSubj <= 0 ? Long.MAX_VALUE : prevSubj); + long prevPredNorm = prevPred == NO_PREVIOUS_ID ? NO_PREVIOUS_ID + : (prevPred <= 0 ? Long.MAX_VALUE : prevPred); + long prevObjNorm = prevObj == NO_PREVIOUS_ID ? NO_PREVIOUS_ID : (prevObj <= 0 ? Long.MAX_VALUE : prevObj); + long prevContextNorm = prevContext == NO_PREVIOUS_ID ? NO_PREVIOUS_ID + : (prevContext <= 0 ? Long.MAX_VALUE : prevContext); + toKey(bb, subj, pred, obj, context, prevSubjNorm, prevPredNorm, prevObjNorm, prevContextNorm); } GroupMatcher createMatcher(long subj, long pred, long obj, long context) { From e1f28f50715210b707eda67bc41d186ae15e11af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 7 Nov 2025 03:02:36 +0900 Subject: [PATCH 78/79] working on new ID based join iterator --- .../eclipse/rdf4j/sail/lmdb/TripleStore.java | 31 +- .../rdf4j/sail/lmdb/util/IndexKeyReaders.java | 264 ++++++++++++++++++ 2 files changed, 268 insertions(+), 27 deletions(-) create mode 100644 core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java 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 6ec4475fd1..df3c87f861 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 @@ -88,6 +88,7 @@ import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; import org.eclipse.rdf4j.sail.lmdb.util.GroupMatcher; +import org.eclipse.rdf4j.sail.lmdb.util.IndexKeyReaders; import org.eclipse.rdf4j.sail.lmdb.util.IndexKeyWriters; import org.lwjgl.PointerBuffer; import org.lwjgl.system.MemoryStack; @@ -1725,6 +1726,7 @@ class TripleIndex implements DupIndex { private final char[] fieldSeq; private final IndexKeyWriters.KeyWriter keyWriter; + private final IndexKeyReaders.KeyToQuadReader keyReader; private final IndexKeyWriters.MatcherFactory matcherFactory; private final int dbiExplicit, dbiInferred; private final boolean dupsortEnabled; @@ -1737,6 +1739,7 @@ public TripleIndex(String fieldSeq, boolean dupsortEnabled) throws IOException { this.fieldSeq = fieldSeq.toCharArray(); this.keyWriter = IndexKeyWriters.forFieldSeq(fieldSeq); this.matcherFactory = IndexKeyWriters.matcherFactory(fieldSeq); + this.keyReader = IndexKeyReaders.forFieldSeq(fieldSeq); this.indexMap = getIndexes(this.fieldSeq); this.patternScoreFunction = PatternScoreFunctions.forFieldSeq(fieldSeq); // open database and use native sort order without comparator @@ -2044,33 +2047,7 @@ void keyToQuad(ByteBuffer key, long[] quad) { } void keyToQuad(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - for (int i = 0; i < indexMap.length; i++) { - int component = indexMap[i]; - long bound; - switch (component) { - case SUBJ_IDX: - bound = subj; - break; - case PRED_IDX: - bound = pred; - break; - case OBJ_IDX: - bound = obj; - break; - case CONTEXT_IDX: - bound = context; - break; - default: - bound = LmdbValue.UNKNOWN_ID; - break; - } - if (bound != LmdbValue.UNKNOWN_ID) { - Varint.skipUnsigned(key); - quad[component] = bound; - } else { - quad[component] = Varint.readUnsigned(key); - } - } + keyReader.read(key, subj, pred, obj, context, quad); } @Override diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java new file mode 100644 index 0000000000..fe536dc4a3 --- /dev/null +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java @@ -0,0 +1,264 @@ +/******************************************************************************* + * Copyright (c) 2025 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.sail.lmdb.util; + +import java.nio.ByteBuffer; + +import org.eclipse.rdf4j.sail.lmdb.Varint; +import org.eclipse.rdf4j.sail.lmdb.model.LmdbValue; + +public final class IndexKeyReaders { + + private static final int SUBJ_IDX = 0; + private static final int PRED_IDX = 1; + private static final int OBJ_IDX = 2; + private static final int CONTEXT_IDX = 3; + + private IndexKeyReaders() { + } + + @FunctionalInterface + public interface KeyToQuadReader { + void read(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad); + } + + public static KeyToQuadReader forFieldSeq(String fieldSeq) { + switch (fieldSeq) { + case "spoc": + return IndexKeyReaders::spoc; + case "spco": + return IndexKeyReaders::spco; + case "sopc": + return IndexKeyReaders::sopc; + case "socp": + return IndexKeyReaders::socp; + case "scpo": + return IndexKeyReaders::scpo; + case "scop": + return IndexKeyReaders::scop; + case "psoc": + return IndexKeyReaders::psoc; + case "psco": + return IndexKeyReaders::psco; + case "posc": + return IndexKeyReaders::posc; + case "pocs": + return IndexKeyReaders::pocs; + case "pcso": + return IndexKeyReaders::pcso; + case "pcos": + return IndexKeyReaders::pcos; + case "ospc": + return IndexKeyReaders::ospc; + case "oscp": + return IndexKeyReaders::oscp; + case "opsc": + return IndexKeyReaders::opsc; + case "opcs": + return IndexKeyReaders::opcs; + case "ocsp": + return IndexKeyReaders::ocsp; + case "ocps": + return IndexKeyReaders::ocps; + case "cspo": + return IndexKeyReaders::cspo; + case "csop": + return IndexKeyReaders::csop; + case "cpso": + return IndexKeyReaders::cpso; + case "cpos": + return IndexKeyReaders::cpos; + case "cosp": + return IndexKeyReaders::cosp; + case "cops": + return IndexKeyReaders::cops; + default: + throw new IllegalArgumentException("Unsupported field sequence: " + fieldSeq); + } + } + + private static void readAndStore(ByteBuffer key, long[] quad, int componentIndex, long bound) { + if (bound != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[componentIndex] = bound; + } else { + quad[componentIndex] = Varint.readUnsigned(key); + } + } + + private static void spoc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, CONTEXT_IDX, context); + } + + private static void spco(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, OBJ_IDX, obj); + } + + private static void sopc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, CONTEXT_IDX, context); + } + + private static void socp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, PRED_IDX, pred); + } + + private static void scpo(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, OBJ_IDX, obj); + } + + private static void scop(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, PRED_IDX, pred); + } + + private static void psoc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, CONTEXT_IDX, context); + } + + private static void psco(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, OBJ_IDX, obj); + } + + private static void posc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, CONTEXT_IDX, context); + } + + private static void pocs(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, SUBJ_IDX, subj); + } + + private static void pcso(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, OBJ_IDX, obj); + } + + private static void pcos(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, SUBJ_IDX, subj); + } + + private static void ospc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, CONTEXT_IDX, context); + } + + private static void oscp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, PRED_IDX, pred); + } + + private static void opsc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, CONTEXT_IDX, context); + } + + private static void opcs(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, SUBJ_IDX, subj); + } + + private static void ocsp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, PRED_IDX, pred); + } + + private static void ocps(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, SUBJ_IDX, subj); + } + + private static void cspo(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, OBJ_IDX, obj); + } + + private static void csop(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, PRED_IDX, pred); + } + + private static void cpso(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, OBJ_IDX, obj); + } + + private static void cpos(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, SUBJ_IDX, subj); + } + + private static void cosp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, SUBJ_IDX, subj); + readAndStore(key, quad, PRED_IDX, pred); + } + + private static void cops(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + readAndStore(key, quad, CONTEXT_IDX, context); + readAndStore(key, quad, OBJ_IDX, obj); + readAndStore(key, quad, PRED_IDX, pred); + readAndStore(key, quad, SUBJ_IDX, subj); + } +} From 9852af0be71b1b9f5afed086a0b2978fad13be4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Fri, 7 Nov 2025 03:16:26 +0900 Subject: [PATCH 79/79] working on new ID based join iterator --- .../rdf4j/sail/lmdb/util/IndexKeyReaders.java | 677 +++++++++++++++--- 1 file changed, 574 insertions(+), 103 deletions(-) diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java index fe536dc4a3..b7da9a980e 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/util/IndexKeyReaders.java @@ -85,180 +85,651 @@ public static KeyToQuadReader forFieldSeq(String fieldSeq) { } } - private static void readAndStore(ByteBuffer key, long[] quad, int componentIndex, long bound) { - if (bound != LmdbValue.UNKNOWN_ID) { + private static void spoc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { + if (subj != LmdbValue.UNKNOWN_ID) { Varint.skipUnsigned(key); - quad[componentIndex] = bound; + quad[SUBJ_IDX] = subj; } else { - quad[componentIndex] = Varint.readUnsigned(key); + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); } - } - - private static void spoc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, CONTEXT_IDX, context); } private static void spco(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, OBJ_IDX, obj); + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } } private static void sopc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, CONTEXT_IDX, context); + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } } private static void socp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, PRED_IDX, pred); + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } } private static void scpo(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, OBJ_IDX, obj); + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } } private static void scop(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, PRED_IDX, pred); + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } } private static void psoc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, CONTEXT_IDX, context); + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } } private static void psco(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, OBJ_IDX, obj); + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } } private static void posc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, CONTEXT_IDX, context); + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } } private static void pocs(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, SUBJ_IDX, subj); + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } } private static void pcso(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, OBJ_IDX, obj); + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } } private static void pcos(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, SUBJ_IDX, subj); + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } } private static void ospc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, CONTEXT_IDX, context); + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } } private static void oscp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, PRED_IDX, pred); + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } } private static void opsc(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, CONTEXT_IDX, context); + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } } private static void opcs(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, SUBJ_IDX, subj); + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } } private static void ocsp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, PRED_IDX, pred); + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } } private static void ocps(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, SUBJ_IDX, subj); + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } } private static void cspo(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, OBJ_IDX, obj); + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } } private static void csop(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, PRED_IDX, pred); + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } } private static void cpso(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, OBJ_IDX, obj); + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } } private static void cpos(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, SUBJ_IDX, subj); + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } } private static void cosp(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, SUBJ_IDX, subj); - readAndStore(key, quad, PRED_IDX, pred); + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } } private static void cops(ByteBuffer key, long subj, long pred, long obj, long context, long[] quad) { - readAndStore(key, quad, CONTEXT_IDX, context); - readAndStore(key, quad, OBJ_IDX, obj); - readAndStore(key, quad, PRED_IDX, pred); - readAndStore(key, quad, SUBJ_IDX, subj); + if (context != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[CONTEXT_IDX] = context; + } else { + quad[CONTEXT_IDX] = Varint.readUnsigned(key); + } + if (obj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[OBJ_IDX] = obj; + } else { + quad[OBJ_IDX] = Varint.readUnsigned(key); + } + if (pred != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[PRED_IDX] = pred; + } else { + quad[PRED_IDX] = Varint.readUnsigned(key); + } + if (subj != LmdbValue.UNKNOWN_ID) { + Varint.skipUnsigned(key); + quad[SUBJ_IDX] = subj; + } else { + quad[SUBJ_IDX] = Varint.readUnsigned(key); + } } }