Skip to content

Commit 293840a

Browse files
Eric Prud'hommeauxclaude
andcommitted
~ BNodeResolver: harden recursive neighbor identification
Sibling bnode neighbors reached through the same link predicate used to all take the FIRST candidate of the recursive resolution, collapsing distinct neighbors (and thus their holes) onto one identity. Now, within one interrogation response, neighbor labels resolve once (labels are response-consistent) and siblings sharing a link pin draw successive candidates from a single recursive resolution - a bijection, correct up to automorphism. The neighbor pin is also narrowed by the arcs already accumulated for the hole, discriminating it where the outer pin alone does not, and pooling drops the duplicate recursion round trips. test: siblings distinguishable only through a second bnode hop must get distinct proxies whose mentions cover both leaf values (fails on the pre-hardening code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 16dd1e8 commit 293840a

2 files changed

Lines changed: 80 additions & 12 deletions

File tree

‎lib/BNodeResolver.cpp‎

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,15 @@ namespace bnr {
192192
* us there are N nodes - collapsing to one witness would lose the
193193
* others. Distinct candidates get distinct proxies; structurally
194194
* identical ones dedup onto one witness via the fragment key. */
195+
/* neighbor identification is shared across candidates: labels are
196+
* consistent within this one response, so a neighbor reached from two
197+
* candidates resolves once (nbrAssigned), and sibling neighbors
198+
* sharing a link pin draw successive candidates from one recursive
199+
* resolution (nbrPools) - a bijection, correct up to automorphism,
200+
* instead of collapsing them all onto the first candidate */
201+
std::map<const TTerm*, const TTerm*> nbrAssigned;
202+
std::map<std::string, std::pair<std::vector<const TTerm*>, size_t> > nbrPools;
203+
195204
std::vector<const TTerm*> proxies;
196205
for (std::vector<const TTerm*>::const_iterator cand = order.begin();
197206
cand != order.end(); ++cand) {
@@ -204,19 +213,37 @@ namespace bnr {
204213
for (std::vector<Arc>::const_iterator a = arcs.begin(); a != arcs.end(); ++a) {
205214
Arc arc = *a;
206215
if (dynamic_cast<const BNode*>(arc.other) != NULL) {
207-
/* neighbor bnode: identify it through its link to the hole */
208-
std::string nbrPin = pinWhere
209-
+ (arc.holeAtSubject
210-
? "?bnrH9 " + sparqlTerm(arc.p) + " ?bnrHn9 . "
211-
: "?bnrHn9 " + sparqlTerm(arc.p) + " ?bnrH9 . ");
212-
/* re-pin with the neighbor as the new hole */
213-
std::string renamed;
214-
for (size_t i = 0; i < nbrPin.size(); ) {
215-
if (nbrPin.compare(i, 7, "?bnrHn9") == 0) { renamed += "?bnrH9"; i += 7; }
216-
else if (nbrPin.compare(i, 6, "?bnrH9") == 0) { renamed += "?bnrPrev9"; i += 6; }
217-
else renamed += nbrPin[i++];
216+
std::map<const TTerm*, const TTerm*>::const_iterator known
217+
= nbrAssigned.find(arc.other);
218+
if (known != nbrAssigned.end()) {
219+
arc.other = known->second;
220+
} else {
221+
/* pin the neighbor through its link to the hole,
222+
* narrowed by the arcs already accumulated for the
223+
* hole (they discriminate it where the outer pin
224+
* alone does not) */
225+
std::string nbrPin = pinWhere;
226+
size_t nctr = (depth + 1) * 100; // mention vars disjoint per level
227+
nbrPin += fragmentWhere(frag, "bnrH9", nctr);
228+
nbrPin += (arc.holeAtSubject
229+
? "?bnrH9 " + sparqlTerm(arc.p) + " ?bnrHn9 . "
230+
: "?bnrHn9 " + sparqlTerm(arc.p) + " ?bnrH9 . ");
231+
/* re-pin with the neighbor as the new hole */
232+
std::string renamed;
233+
for (size_t i = 0; i < nbrPin.size(); ) {
234+
if (nbrPin.compare(i, 7, "?bnrHn9") == 0) { renamed += "?bnrH9"; i += 7; }
235+
else if (nbrPin.compare(i, 6, "?bnrH9") == 0) { renamed += "?bnrPrev9"; i += 6; }
236+
else renamed += nbrPin[i++];
237+
}
238+
if (nbrPools.find(renamed) == nbrPools.end())
239+
nbrPools[renamed] = std::make_pair(
240+
resolveBNode(renamed, inFlight, depth + 1), (size_t)0);
241+
std::pair<std::vector<const TTerm*>, size_t>& pool = nbrPools[renamed];
242+
const TTerm* nbr = pool.first[std::min(pool.second, pool.first.size() - 1)];
243+
++pool.second;
244+
nbrAssigned[arc.other] = nbr;
245+
arc.other = nbr;
218246
}
219-
arc.other = resolveBNode(renamed, inFlight, depth + 1)[0];
220247
}
221248
frag.arcs.push_back(arc);
222249
if (uniquelyIdentifies(frag)) {

‎tests/test_BNodeResolver.cpp‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,47 @@ BOOST_AUTO_TEST_CASE( mention_rejoins_the_right_bnode ) {
120120
}
121121
}
122122

123+
BOOST_AUTO_TEST_CASE( sibling_neighbors_distinguished_by_recursion ) {
124+
/* two anonymous neighbors of x:s via the SAME predicate are themselves
125+
* only distinguishable through THEIR neighbors (a second bnode hop):
126+
* identification must recurse, and sibling neighbors must draw distinct
127+
* candidates from the recursive resolution instead of all collapsing
128+
* onto its first candidate */
129+
sw::RdfDB db;
130+
{
131+
sw::TurtleDriver parser("", &F);
132+
std::string ttl =
133+
"@prefix x: <http://a.example/x#> .\n"
134+
"x:s x:p _:n1, _:n2 .\n"
135+
"_:n1 x:q _:c1 . _:c1 x:r x:v1 .\n"
136+
"_:n2 x:q _:c2 . _:c2 x:r x:v2 .\n";
137+
sw::IStreamContext istr(ttl, sw::IStreamContext::STRING);
138+
parser.parse(istr, db.ensureGraph(sw::DefaultGraph));
139+
}
140+
sw::bnr::RelabelingLocalClient client(&F, &db);
141+
sw::bnr::BNodeResolver resolver(&F, &client);
142+
143+
std::string where = "<http://a.example/x#s> <http://a.example/x#p> ?n . ";
144+
sw::bnr::Table t = resolver.select("SELECT ?n WHERE { " + where + "}", where);
145+
BOOST_REQUIRE_EQUAL(t.size(), 2u);
146+
const sw::TTerm* ns[] = { t[0]["n"], t[1]["n"] };
147+
BOOST_CHECK(ns[0] != ns[1]); // siblings must not collapse
148+
149+
/* each proxy re-queries to exactly one node, and between them the two
150+
* reach both x:r values - the bijection covers both siblings */
151+
std::set<std::string> vs;
152+
for (int i = 0; i < 2; ++i) {
153+
std::string w2;
154+
size_t ctr = 0;
155+
std::string m = resolver.mention(ns[i], w2, ctr);
156+
w2 += m + " <http://a.example/x#q> ?c . ?c <http://a.example/x#r> ?v . ";
157+
sw::bnr::Table t2 = resolver.select("SELECT ?v WHERE { " + w2 + "}", w2);
158+
BOOST_REQUIRE_EQUAL(t2.size(), 1u);
159+
vs.insert(t2[0].find("v")->second->getLexicalValue());
160+
}
161+
BOOST_CHECK_EQUAL(vs.size(), 2u);
162+
}
163+
123164
BOOST_AUTO_TEST_CASE( provider_caches_and_subsumes ) {
124165
Endpoint ep;
125166
sw::RdfDB cacheDB;

0 commit comments

Comments
 (0)