Skip to content

Commit 797e7bb

Browse files
committed
fix: SPARQL LOAD ... INTO GRAPH handling
`LOAD ... INTO GRAPH` stopped working correctly after the change to handling of the `publicID` `Graph.parse` parameter in RDFLib 7.0.0 (<RDFLib#2406>). This is because `LOAD` evaluation relied on `publicID` to select the graph name. So after <RDFLib#2406> data would be loaded into the default graph even if a named graph is specified. This change adds tests for `LOAD ... INTO GRAPH` and fixes the load evaluation.
1 parent 079f388 commit 797e7bb

File tree

4 files changed

+112
-7
lines changed

4 files changed

+112
-7
lines changed

rdflib/plugins/sparql/sparql.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,14 +311,23 @@ def dataset(self) -> ConjunctiveGraph:
311311
)
312312
return self._dataset
313313

314-
def load(self, source: URIRef, default: bool = False, **kwargs: Any) -> None:
314+
def load(
315+
self,
316+
source: URIRef,
317+
default: bool = False,
318+
into: Optional[Identifier] = None,
319+
**kwargs: Any,
320+
) -> None:
315321
"""
316322
Load data from the source into the query context's.
317323
318324
:param source: The source to load from.
319-
:param default: If `True`, triples from the source will be added to the
320-
default graph, otherwise it will be loaded into a graph with
321-
``source`` URI as its name.
325+
:param default: If `True`, triples from the source will be added
326+
to the default graph, otherwise it will be loaded into a
327+
graph with ``source`` URI as its name.
328+
:param into: The name of the graph to load the data into. If
329+
`None`, the source URI will be used as as the name of the
330+
graph.
322331
:param kwargs: Keyword arguments to pass to
323332
:meth:`rdflib.graph.Graph.parse`.
324333
"""
@@ -353,7 +362,9 @@ def _load(graph, source):
353362
if default:
354363
_load(self.graph, source)
355364
else:
356-
_load(self.dataset.get_context(source), source)
365+
if into is None:
366+
into = source
367+
_load(self.dataset.get_context(into), source)
357368

358369
def __getitem__(self, key: Union[str, Path]) -> Optional[Union[str, Path]]:
359370
# in SPARQL BNodes are just labels

rdflib/plugins/sparql/update.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def evalLoad(ctx: QueryContext, u: CompValue) -> None:
5151
assert isinstance(u.iri, URIRef)
5252

5353
if u.graphiri:
54-
ctx.load(u.iri, default=False, publicID=u.graphiri)
54+
ctx.load(u.iri, default=False, into=u.graphiri)
5555
else:
5656
ctx.load(u.iri, default=True)
5757

test/test_sparql/test_update.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import logging
2+
from test.data import TEST_DATA_DIR
3+
from test.utils import GraphHelper
4+
from test.utils.graph import load_sources
5+
from test.utils.namespace import EGDO
6+
from typing import Callable
7+
8+
import pytest
9+
10+
from rdflib.graph import ConjunctiveGraph, Dataset, Graph
11+
12+
13+
@pytest.mark.parametrize("graph_factory", [Graph, ConjunctiveGraph, Dataset])
14+
def test_load_into_default(graph_factory: Callable[[], Graph]) -> None:
15+
source_path = TEST_DATA_DIR / "variants" / "simple_triple.ttl"
16+
17+
expected_graph = graph_factory()
18+
load_sources(source_path, graph=expected_graph)
19+
20+
actual_graph = graph_factory()
21+
actual_graph.update(f"LOAD <{source_path}>")
22+
23+
if logging.getLogger().isEnabledFor(logging.DEBUG):
24+
debug_format = (
25+
"trig" if isinstance(expected_graph, ConjunctiveGraph) else "turtle"
26+
)
27+
logging.debug(
28+
"expected_graph = \n%s", expected_graph.serialize(format=debug_format)
29+
)
30+
logging.debug(
31+
"actual_graph = \n%s", actual_graph.serialize(format=debug_format)
32+
)
33+
34+
if isinstance(expected_graph, ConjunctiveGraph):
35+
assert isinstance(actual_graph, ConjunctiveGraph)
36+
GraphHelper.assert_collection_graphs_equal(expected_graph, actual_graph)
37+
else:
38+
GraphHelper.assert_triple_sets_equals(expected_graph, actual_graph)
39+
40+
41+
@pytest.mark.parametrize("graph_factory", [ConjunctiveGraph, Dataset])
42+
def test_load_into_named(graph_factory: Callable[[], ConjunctiveGraph]) -> None:
43+
source_path = TEST_DATA_DIR / "variants" / "simple_triple.ttl"
44+
45+
expected_graph = graph_factory()
46+
load_sources(source_path, graph=expected_graph.get_context(EGDO.graph))
47+
48+
actual_graph = graph_factory()
49+
50+
actual_graph.update(f"LOAD <{source_path}> INTO GRAPH <{EGDO.graph}>")
51+
52+
if logging.getLogger().isEnabledFor(logging.DEBUG):
53+
debug_format = "trig"
54+
logging.debug(
55+
"expected_graph = \n%s", expected_graph.serialize(format=debug_format)
56+
)
57+
logging.debug(
58+
"actual_graph = \n%s", actual_graph.serialize(format=debug_format)
59+
)
60+
61+
GraphHelper.assert_collection_graphs_equal(expected_graph, actual_graph)

test/utils/__init__.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Iterable,
2020
List,
2121
Optional,
22+
Sequence,
2223
Set,
2324
Tuple,
2425
Type,
@@ -35,7 +36,7 @@
3536
from rdflib import BNode, ConjunctiveGraph, Graph
3637
from rdflib.graph import Dataset
3738
from rdflib.plugin import Plugin
38-
from rdflib.term import Identifier, Literal, Node, URIRef
39+
from rdflib.term import IdentifiedNode, Identifier, Literal, Node, URIRef
3940

4041
PluginT = TypeVar("PluginT")
4142

@@ -257,6 +258,23 @@ def assert_quad_sets_equals(
257258
else:
258259
assert lhs_set != rhs_set
259260

261+
@classmethod
262+
def assert_collection_graphs_equal(
263+
cls, lhs: ConjunctiveGraph, rhs: ConjunctiveGraph
264+
) -> None:
265+
"""
266+
Assert that all graphs in provides collections are equal,
267+
comparing named graphs with identically named graphs.
268+
"""
269+
cls.assert_triple_sets_equals(lhs.default_context, rhs.default_context)
270+
graph_names = cls.non_default_graph_names(lhs) | cls.non_default_graph_names(
271+
rhs
272+
)
273+
for identifier in graph_names:
274+
cls.assert_triple_sets_equals(
275+
lhs.get_context(identifier), rhs.get_context(identifier)
276+
)
277+
260278
@classmethod
261279
def assert_sets_equals(
262280
cls,
@@ -381,6 +399,21 @@ def strip_literal_datatypes(cls, graph: Graph, datatypes: Set[URIRef]) -> None:
381399
if object.datatype in datatypes:
382400
object._datatype = None
383401

402+
@classmethod
403+
def non_default_graph_names(
404+
cls, container: ConjunctiveGraph
405+
) -> Set[IdentifiedNode]:
406+
return set(context.identifier for context in container.contexts()) - {
407+
container.default_context.identifier
408+
}
409+
410+
@classmethod
411+
def non_default_graphs(cls, container: ConjunctiveGraph) -> Sequence[Graph]:
412+
result = []
413+
for name in cls.non_default_graph_names(container):
414+
result.append(container.get_context(name))
415+
return result
416+
384417

385418
def eq_(lhs, rhs, msg=None):
386419
"""

0 commit comments

Comments
 (0)