Skip to content

Commit 954d5b2

Browse files
committed
GH-5123 Ensure NativeStore remains usable after RDF*-star rejection; guard txnLock release
1 parent a014e92 commit 954d5b2

3 files changed

Lines changed: 130 additions & 9 deletions

File tree

core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeSailStore.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,12 @@ public void clear(Resource... contexts) throws SailException {
445445

446446
@Override
447447
public void approve(Resource subj, IRI pred, Value obj, Resource ctx) throws SailException {
448-
addStatement(subj, pred, obj, explicit, ctx);
448+
try {
449+
addStatement(subj, pred, obj, explicit, ctx);
450+
} catch (IllegalArgumentException e) {
451+
// Ensure upstream handles this as a SailException so branch flush clears pending changes
452+
throw new SailException(e);
453+
}
449454
}
450455

451456
@Override
@@ -477,9 +482,10 @@ public void approveAll(Set<Statement> approved, Set<Resource> approvedContexts)
477482
}
478483
} catch (IOException e) {
479484
throw new SailException(e);
480-
} catch (RuntimeException e) {
485+
} catch (IllegalArgumentException e) {
486+
// Wrap invalid value types (e.g., RDF* Triple value) so upstream can rollback/clear changes
481487
logger.error("Encountered an unexpected problem while trying to add a statement", e);
482-
throw e;
488+
throw new SailException(e);
483489
} finally {
484490
sinkStoreAccessLock.unlock();
485491
}
@@ -539,9 +545,10 @@ private boolean addStatement(Resource subj, IRI pred, Value obj, boolean explici
539545
}
540546
} catch (IOException e) {
541547
throw new SailException(e);
542-
} catch (RuntimeException e) {
548+
} catch (IllegalArgumentException e) {
549+
// Wrap invalid value types (e.g., RDF* Triple value) so upstream can rollback/clear changes
543550
logger.error("Encountered an unexpected problem while trying to add a statement", e);
544-
throw e;
551+
throw new SailException(e);
545552
} finally {
546553
sinkStoreAccessLock.unlock();
547554
}

core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreConnection.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ protected void commitInternal() throws SailException {
7676
try {
7777
super.commitInternal();
7878
} finally {
79-
txnLock.release();
80-
txnLock = null;
79+
if (txnLock != null) {
80+
txnLock.release();
81+
txnLock = null;
82+
}
8183
}
8284

8385
nativeStore.notifySailChanged(sailChangedEvent);
@@ -91,15 +93,19 @@ protected void rollbackInternal() throws SailException {
9193
try {
9294
super.rollbackInternal();
9395
} finally {
94-
txnLock.release();
95-
txnLock = null;
96+
if (txnLock != null) {
97+
txnLock.release();
98+
txnLock = null;
99+
}
96100
}
97101
// create a fresh event object.
98102
sailChangedEvent = new DefaultSailChangedEvent(nativeStore);
99103
}
100104

101105
@Override
102106
protected void addStatementInternal(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException {
107+
// mark that statements were added so reads can flush pending updates pre-emptively
108+
setStatementsAdded();
103109
// assume the triple is not yet present in the triple store
104110
sailChangedEvent.setStatementsAdded(true);
105111

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.sail.nativerdf;
12+
13+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
14+
15+
import java.io.ByteArrayInputStream;
16+
import java.io.File;
17+
import java.nio.charset.StandardCharsets;
18+
19+
import org.eclipse.rdf4j.common.transaction.IsolationLevel;
20+
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
21+
import org.eclipse.rdf4j.model.Statement;
22+
import org.eclipse.rdf4j.repository.Repository;
23+
import org.eclipse.rdf4j.repository.RepositoryConnection;
24+
import org.eclipse.rdf4j.repository.RepositoryResult;
25+
import org.eclipse.rdf4j.repository.sail.SailRepository;
26+
import org.eclipse.rdf4j.rio.RDFFormat;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.api.io.TempDir;
29+
30+
/**
31+
* Reproduces a reported issue where attempting to add Turtle-star data to a NativeStore throws, and subsequently the
32+
* repository becomes unusable for normal operations. After the rejection, the repository should remain usable.
33+
*/
34+
public class NativeStoreRDFStarRejectionTest {
35+
36+
@TempDir
37+
public File dataDir;
38+
39+
@Test
40+
public void nativeStoreRejectsTurtleStarButRemainsUsable() {
41+
String data = "@prefix ex: <http://example.org/> .\n" +
42+
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
43+
"\n" +
44+
"# Basic triple\n" +
45+
"ex:JohnDoe ex:worksAt ex:CompanyX .\n" +
46+
"\n" +
47+
"# RDF* triple (unsupported by NativeStore)\n" +
48+
"<<ex:JohnDoe ex:worksAt ex:CompanyX>> ex:since \"2022-01-01\"^^xsd:date .\n";
49+
50+
Repository repo = new SailRepository(new NativeStore(dataDir));
51+
52+
// First: attempt to add data that includes RDF*-star. Expect an exception (rejection).
53+
try (RepositoryConnection conn = repo.getConnection()) {
54+
try {
55+
conn.add(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), null, RDFFormat.TURTLE);
56+
} catch (Exception expected) {
57+
// Expected: Turtle-star should be rejected by NativeStore
58+
}
59+
}
60+
61+
// Then: repository should still be usable. Getting statements must not throw.
62+
assertDoesNotThrow(() -> {
63+
try (RepositoryConnection conn = repo.getConnection();
64+
RepositoryResult<Statement> result = conn.getStatements(null, null, null)) {
65+
// iterate to fully exercise the result set
66+
while (result.hasNext()) {
67+
result.next();
68+
}
69+
}
70+
}, "Repository became unusable after rejecting Turtle-star input");
71+
}
72+
73+
@Test
74+
public void nativeStoreRejectsTurtleStarButRemainsUsableSnapshot() {
75+
String data = "@prefix ex: <http://example.org/> .\n" +
76+
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n" +
77+
"\n" +
78+
"# Basic triple\n" +
79+
"ex:JohnDoe ex:worksAt ex:CompanyX .\n" +
80+
"\n" +
81+
"# RDF* triple (unsupported by NativeStore)\n" +
82+
"<<ex:JohnDoe ex:worksAt ex:CompanyX>> ex:since \"2022-01-01\"^^xsd:date .\n";
83+
84+
Repository repo = new SailRepository(new NativeStore(dataDir));
85+
86+
// First: attempt to add data that includes RDF*-star. Expect an exception (rejection).
87+
try (RepositoryConnection conn = repo.getConnection()) {
88+
try {
89+
conn.begin(IsolationLevels.SNAPSHOT);
90+
conn.add(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), null, RDFFormat.TURTLE);
91+
conn.commit();
92+
} catch (Exception expected) {
93+
// Expected: Turtle-star should be rejected by NativeStore
94+
}
95+
}
96+
97+
// Then: repository should still be usable. Getting statements must not throw.
98+
assertDoesNotThrow(() -> {
99+
try (RepositoryConnection conn = repo.getConnection();
100+
RepositoryResult<Statement> result = conn.getStatements(null, null, null)) {
101+
// iterate to fully exercise the result set
102+
while (result.hasNext()) {
103+
result.next();
104+
}
105+
}
106+
}, "Repository became unusable after rejecting Turtle-star input");
107+
}
108+
}

0 commit comments

Comments
 (0)