ValueStoreWalSearchTest.java
/*******************************************************************************
* 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
*******************************************************************************/
// Some portions generated by Codex
package org.eclipse.rdf4j.sail.nativerdf.wal;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Random;
import org.eclipse.rdf4j.benchmark.common.BenchmarkResources;
import org.eclipse.rdf4j.model.Value;
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.nativerdf.NativeStore;
import org.eclipse.rdf4j.sail.nativerdf.ValueStore;
import org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreConfig;
import org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
class ValueStoreWalSearchTest {
@TempDir
File dataDir;
@Test
void findsValueByIdViaSegmentProbe() throws Exception {
// Configure NativeStore with small WAL segment size to ensure multiple segments possible
NativeStoreConfig cfg = new NativeStoreConfig("spoc,ospc,psoc");
cfg.setWalMaxSegmentBytes(64 * 1024); // 64 KiB
cfg.setWalSyncPolicy(ValueStoreWalConfig.SyncPolicy.ALWAYS.name());
NativeStore store = (NativeStore) new NativeStoreFactory().getSail(cfg);
store.setDataDir(dataDir);
SailRepository repo = new SailRepository(store);
repo.init();
try (SailRepositoryConnection conn = repo.getConnection()) {
try (var in = BenchmarkResources.openDecompressedStream("benchmarkFiles/datagovbe-valid.ttl.gz")) {
assertThat(in).isNotNull();
conn.add(in, "", RDFFormat.TURTLE);
}
}
repo.shutDown();
Path walDir = dataDir.toPath().resolve(ValueStoreWalConfig.DEFAULT_DIRECTORY_NAME);
String storeUuid = Files.readString(walDir.resolve("store.uuid"), StandardCharsets.UTF_8).trim();
ValueStoreWalConfig cfgRead = ValueStoreWalConfig.builder().walDirectory(walDir).storeUuid(storeUuid).build();
// Build dictionary of minted values from WAL and pick a random entry
Map<Integer, ValueStoreWalRecord> dict = Map.of();
boolean complete = false;
for (int attempt = 0; attempt < 10 && (!complete || dict.isEmpty()); attempt++) {
try (ValueStoreWalReader reader = ValueStoreWalReader.open(cfgRead)) {
ValueStoreWalRecovery.ReplayReport report = new ValueStoreWalRecovery().replayWithReport(reader);
dict = report.dictionary();
complete = report.complete();
}
if (!complete || dict.isEmpty()) {
Thread.sleep(100);
}
}
assertThat(dict).isNotEmpty();
assertThat(complete).as("WAL recovery should be complete before searching").isTrue();
Integer[] ids = dict.keySet().toArray(Integer[]::new);
Integer pickId = ids[new Random().nextInt(ids.length)];
ValueStoreWalSearch search = ValueStoreWalSearch.open(cfgRead);
Value found = null;
for (int attempt = 0; attempt < 100 && found == null; attempt++) {
found = search.findValueById(pickId);
if (found == null) {
Thread.sleep(100);
}
}
assertThat(found).as("ValueStoreWalSearch should find value by id").isNotNull();
// Cross-check against ValueStore
try (ValueStore vs = new ValueStore(dataDir, false)) {
Value vsValue = vs.getValue(pickId);
assertThat(vsValue).isNotNull();
assertThat(found).isEqualTo(vsValue);
}
}
}