Skip to content

Commit 30dc9de

Browse files
authored
GH-5380 Allow deleting invalid/corrupt LMDB repositories (#5430)
2 parents 8ae9307 + 62de570 commit 30dc9de

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ e2e/node_modules
5353
e2e/playwright-report
5454
e2e/test-results
5555
.aider*
56+
/tools/server/.lwjgl/
57+
/tools/server/.lwjgl/

AGENTS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,18 @@ Do **not** modify existing headers’ years.
591591
* Entire repo:
592592
593593
* `mvn -o verify` (long; only when appropriate)
594+
* Slow tests (entire repo):
595+
596+
* `mvn -o verify -PslowTestsOnly,-skipSlowTests,-formatting -Dmaven.javadoc.skip -Djapicmp.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500`
597+
* Slow tests (by module):
598+
599+
* `mvn -o -pl <module> verify -PslowTestsOnly,-skipSlowTests,-formatting -Dmaven.javadoc.skip -Djapicmp.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500`
600+
* Integration tests (entire repo):
601+
602+
* `mvn -o verify -PskipUnitTests,-formatting -Dmaven.javadoc.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500`
603+
* Integration tests (by module):
604+
605+
* `mvn -o -pl <module> verify -PskipUnitTests,-formatting -Dmaven.javadoc.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500`
594606
* Useful flags:
595607
596608
* `-Dtest=ClassName`

tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/repository/RepositoryInterceptor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ protected void setRequestAttributes(HttpServletRequest request) throws ClientHTT
105105
request.setAttribute(REPOSITORY_KEY, new RepositoryConfigRepository(repositoryManager));
106106
} else if (nextRepositoryID != null) {
107107
try {
108+
// For requests to delete a repository, we must not attempt to initialize the repository. Otherwise a
109+
// corrupt/invalid configuration can block deletion.
110+
if ("DELETE".equals(request.getMethod()) && request.getPathInfo().equals("/" + nextRepositoryID)) {
111+
request.setAttribute(REPOSITORY_ID_KEY, nextRepositoryID);
112+
return;
113+
}
114+
108115
Repository repository = repositoryManager.getRepository(nextRepositoryID);
109116
if (repository == null && !"PUT".equals(request.getMethod())) {
110117
throw new ClientHTTPException(SC_NOT_FOUND, "Unknown repository: " + nextRepositoryID);
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*/
11+
package org.eclipse.rdf4j.http.server;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
import org.eclipse.rdf4j.http.protocol.Protocol;
16+
import org.eclipse.rdf4j.model.IRI;
17+
import org.eclipse.rdf4j.model.Model;
18+
import org.eclipse.rdf4j.model.Resource;
19+
import org.eclipse.rdf4j.model.ValueFactory;
20+
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
21+
import org.eclipse.rdf4j.repository.Repository;
22+
import org.eclipse.rdf4j.repository.RepositoryConnection;
23+
import org.eclipse.rdf4j.repository.RepositoryException;
24+
import org.eclipse.rdf4j.repository.config.RepositoryConfig;
25+
import org.eclipse.rdf4j.repository.http.HTTPRepository;
26+
import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager;
27+
import org.eclipse.rdf4j.repository.sail.config.SailRepositoryConfig;
28+
import org.eclipse.rdf4j.sail.config.AbstractSailImplConfig;
29+
import org.junit.jupiter.api.AfterAll;
30+
import org.junit.jupiter.api.BeforeAll;
31+
import org.junit.jupiter.api.Test;
32+
33+
/**
34+
* Jetty-based integration test that reproduces a user report: Creating an LMDB repository with an invalid triple index
35+
* (e.g., "cposc") then attempting to delete it via HTTP fails.
36+
*
37+
* Expected behavior: either reject creation upfront or allow deletion. This test asserts deletion succeeds; it
38+
* currently fails, exposing the bug.
39+
*/
40+
public class LmdbInvalidIndexDeletionIT {
41+
42+
private static TestServer server;
43+
44+
@BeforeAll
45+
public static void startServer() throws Exception {
46+
server = new TestServer();
47+
try {
48+
server.start();
49+
} catch (Exception e) {
50+
server.stop();
51+
throw e;
52+
}
53+
}
54+
55+
@AfterAll
56+
public static void stopServer() throws Exception {
57+
server.stop();
58+
}
59+
60+
@Test
61+
void deletionSucceedsAfterInvalidLmdbInit() throws Exception {
62+
String id = "badlmdb-server";
63+
64+
// Build a minimal LMDB Sail config without depending on LMDB classes
65+
// by exporting with the LMDB sail type and the tripleIndexes property.
66+
class GenericLmdbConfig extends AbstractSailImplConfig {
67+
private final String tripleIndexes;
68+
69+
GenericLmdbConfig(String type, String tripleIndexes) {
70+
super(type);
71+
this.tripleIndexes = tripleIndexes;
72+
}
73+
74+
@Override
75+
public Resource export(Model m) {
76+
Resource node = super.export(m);
77+
ValueFactory vf = SimpleValueFactory.getInstance();
78+
IRI tripleIdx = vf.createIRI("http://rdf4j.org/config/sail/lmdb#tripleIndexes");
79+
m.add(node, tripleIdx, vf.createLiteral(tripleIndexes));
80+
return node;
81+
}
82+
}
83+
84+
GenericLmdbConfig lmdbConfig = new GenericLmdbConfig("rdf4j:LmdbStore", "cposc");
85+
RepositoryConfig repoConfig = new RepositoryConfig(id, new SailRepositoryConfig(lmdbConfig));
86+
87+
RemoteRepositoryManager manager = RemoteRepositoryManager.getInstance(TestServer.SERVER_URL);
88+
try {
89+
try {
90+
// Create config on server (does not initialize the underlying store yet)
91+
manager.addRepositoryConfig(repoConfig);
92+
} catch (Exception ignored) {
93+
}
94+
95+
// Trigger initialization by opening a connection; expected to fail due to invalid index
96+
Repository httpRepo = new HTTPRepository(
97+
Protocol.getRepositoryLocation(TestServer.SERVER_URL, id));
98+
try (RepositoryConnection conn = httpRepo.getConnection()) {
99+
// attempt a trivial call to ensure init
100+
conn.size();
101+
} catch (RepositoryException expected) {
102+
// initialization fails as LMDB rejects invalid index spec
103+
}
104+
105+
// Now attempt to delete the repository; expected to succeed
106+
boolean removed = manager.removeRepository(id);
107+
assertThat(removed).isTrue();
108+
} finally {
109+
// best-effort cleanup if assertion failed
110+
try {
111+
manager.removeRepository(id);
112+
} catch (Exception ignore) {
113+
} finally {
114+
manager.shutDown();
115+
}
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)