update.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. """
  2. Code for carrying out Update Operations
  3. """
  4. from __future__ import annotations
  5. from typing import TYPE_CHECKING, Iterator, Mapping, Optional, Sequence
  6. from rdflib.graph import Graph
  7. from rdflib.plugins.sparql.evaluate import evalBGP, evalPart
  8. from rdflib.plugins.sparql.evalutils import _fillTemplate, _join
  9. from rdflib.plugins.sparql.parserutils import CompValue
  10. from rdflib.plugins.sparql.sparql import FrozenDict, QueryContext, Update
  11. from rdflib.term import Identifier, URIRef, Variable
  12. def _graphOrDefault(ctx: QueryContext, g: str) -> Optional[Graph]:
  13. if g == "DEFAULT":
  14. return ctx.graph
  15. else:
  16. return ctx.dataset.get_context(g)
  17. def _graphAll(ctx: QueryContext, g: str) -> Sequence[Graph]:
  18. """
  19. return a list of graphs
  20. """
  21. if g == "DEFAULT":
  22. # type error: List item 0 has incompatible type "Optional[Graph]"; expected "Graph"
  23. return [ctx.graph] # type: ignore[list-item]
  24. elif g == "NAMED":
  25. return [
  26. # type error: Item "None" of "Optional[Graph]" has no attribute "identifier"
  27. c
  28. for c in ctx.dataset.contexts()
  29. if c.identifier != ctx.graph.identifier # type: ignore[union-attr]
  30. ]
  31. elif g == "ALL":
  32. return list(ctx.dataset.contexts())
  33. else:
  34. return [ctx.dataset.get_context(g)]
  35. def evalLoad(ctx: QueryContext, u: CompValue) -> None:
  36. """
  37. http://www.w3.org/TR/sparql11-update/#load
  38. """
  39. if TYPE_CHECKING:
  40. assert isinstance(u.iri, URIRef)
  41. if u.graphiri:
  42. ctx.load(u.iri, default=False, into=u.graphiri)
  43. else:
  44. ctx.load(u.iri, default=True)
  45. def evalCreate(ctx: QueryContext, u: CompValue) -> None:
  46. """
  47. http://www.w3.org/TR/sparql11-update/#create
  48. """
  49. g = ctx.dataset.get_context(u.graphiri)
  50. if len(g) > 0:
  51. raise Exception("Graph %s already exists." % g.identifier)
  52. raise Exception("Create not implemented!")
  53. def evalClear(ctx: QueryContext, u: CompValue) -> None:
  54. """
  55. http://www.w3.org/TR/sparql11-update/#clear
  56. """
  57. for g in _graphAll(ctx, u.graphiri):
  58. g.remove((None, None, None))
  59. def evalDrop(ctx: QueryContext, u: CompValue) -> None:
  60. """
  61. http://www.w3.org/TR/sparql11-update/#drop
  62. """
  63. if ctx.dataset.store.graph_aware:
  64. for g in _graphAll(ctx, u.graphiri):
  65. ctx.dataset.store.remove_graph(g)
  66. else:
  67. evalClear(ctx, u)
  68. def evalInsertData(ctx: QueryContext, u: CompValue) -> None:
  69. """
  70. http://www.w3.org/TR/sparql11-update/#insertData
  71. """
  72. # add triples
  73. g = ctx.graph
  74. g += u.triples
  75. # add quads
  76. # u.quads is a dict of graphURI=>[triples]
  77. for g in u.quads:
  78. # type error: Argument 1 to "get_context" of "ConjunctiveGraph" has incompatible type "Optional[Graph]"; expected "Union[IdentifiedNode, str, None]"
  79. cg = ctx.dataset.get_context(g) # type: ignore[arg-type]
  80. cg += u.quads[g]
  81. def evalDeleteData(ctx: QueryContext, u: CompValue) -> None:
  82. """
  83. http://www.w3.org/TR/sparql11-update/#deleteData
  84. """
  85. # remove triples
  86. g = ctx.graph
  87. g -= u.triples
  88. # remove quads
  89. # u.quads is a dict of graphURI=>[triples]
  90. for g in u.quads:
  91. # type error: Argument 1 to "get_context" of "ConjunctiveGraph" has incompatible type "Optional[Graph]"; expected "Union[IdentifiedNode, str, None]"
  92. cg = ctx.dataset.get_context(g) # type: ignore[arg-type]
  93. cg -= u.quads[g]
  94. def evalDeleteWhere(ctx: QueryContext, u: CompValue) -> None:
  95. """
  96. http://www.w3.org/TR/sparql11-update/#deleteWhere
  97. """
  98. res: Iterator[FrozenDict] = evalBGP(ctx, u.triples)
  99. for g in u.quads:
  100. cg = ctx.dataset.get_context(g)
  101. c = ctx.pushGraph(cg)
  102. res = _join(res, list(evalBGP(c, u.quads[g])))
  103. # type error: Incompatible types in assignment (expression has type "FrozenBindings", variable has type "QueryContext")
  104. for c in res: # type: ignore[assignment]
  105. g = ctx.graph
  106. g -= _fillTemplate(u.triples, c)
  107. for g in u.quads:
  108. cg = ctx.dataset.get_context(c.get(g))
  109. cg -= _fillTemplate(u.quads[g], c)
  110. def evalModify(ctx: QueryContext, u: CompValue) -> None:
  111. originalctx = ctx
  112. # Using replaces the dataset for evaluating the where-clause
  113. dg: Optional[Graph]
  114. if u.using:
  115. otherDefault = False
  116. for d in u.using:
  117. if d.default:
  118. if not otherDefault:
  119. # replace current default graph
  120. dg = Graph()
  121. ctx = ctx.pushGraph(dg)
  122. otherDefault = True
  123. ctx.load(d.default, default=True)
  124. elif d.named:
  125. g = d.named
  126. ctx.load(g, default=False)
  127. # "The WITH clause provides a convenience for when an operation
  128. # primarily refers to a single graph. If a graph name is specified
  129. # in a WITH clause, then - for the purposes of evaluating the
  130. # WHERE clause - this will define an RDF Dataset containing a
  131. # default graph with the specified name, but only in the absence
  132. # of USING or USING NAMED clauses. In the presence of one or more
  133. # graphs referred to in USING clauses and/or USING NAMED clauses,
  134. # the WITH clause will be ignored while evaluating the WHERE
  135. # clause."
  136. if not u.using and u.withClause:
  137. g = ctx.dataset.get_context(u.withClause)
  138. ctx = ctx.pushGraph(g)
  139. res = evalPart(ctx, u.where)
  140. if u.using:
  141. if otherDefault:
  142. ctx = originalctx # restore original default graph
  143. if u.withClause:
  144. g = ctx.dataset.get_context(u.withClause)
  145. ctx = ctx.pushGraph(g)
  146. for c in list(res):
  147. # TODO: Make this more intentional and without the weird type checking logic
  148. # once ConjunctiveGraph is removed and Dataset no longer inherits from
  149. # Graph.
  150. dg = ctx.graph if type(ctx.graph) is Graph else ctx.dataset.default_context
  151. if u.delete:
  152. # type error: Unsupported left operand type for - ("None")
  153. # type error: Unsupported operand types for - ("Graph" and "Generator[Tuple[Identifier, Identifier, Identifier], None, None]")
  154. dg -= _fillTemplate(u.delete.triples, c) # type: ignore[operator]
  155. for g, q in u.delete.quads.items():
  156. cg = ctx.dataset.get_context(c.get(g))
  157. cg -= _fillTemplate(q, c)
  158. if u.insert:
  159. # type error: Unsupported left operand type for + ("None")
  160. # type error: Unsupported operand types for + ("Graph" and "Generator[Tuple[Identifier, Identifier, Identifier], None, None]")
  161. dg += _fillTemplate(u.insert.triples, c) # type: ignore[operator]
  162. for g, q in u.insert.quads.items():
  163. cg = ctx.dataset.get_context(c.get(g))
  164. cg += _fillTemplate(q, c)
  165. def evalAdd(ctx: QueryContext, u: CompValue) -> None:
  166. """
  167. add all triples from src to dst
  168. http://www.w3.org/TR/sparql11-update/#add
  169. """
  170. src, dst = u.graph
  171. srcg = _graphOrDefault(ctx, src)
  172. dstg = _graphOrDefault(ctx, dst)
  173. # type error: Item "None" of "Optional[Graph]" has no attribute "identifier"
  174. if srcg.identifier == dstg.identifier: # type: ignore[union-attr]
  175. return
  176. # type error: Unsupported left operand type for + ("None")
  177. dstg += srcg # type: ignore[operator]
  178. def evalMove(ctx: QueryContext, u: CompValue) -> None:
  179. """
  180. remove all triples from dst
  181. add all triples from src to dst
  182. remove all triples from src
  183. http://www.w3.org/TR/sparql11-update/#move
  184. """
  185. src, dst = u.graph
  186. srcg = _graphOrDefault(ctx, src)
  187. dstg = _graphOrDefault(ctx, dst)
  188. # type error: Item "None" of "Optional[Graph]" has no attribute "identifier"
  189. if srcg.identifier == dstg.identifier: # type: ignore[union-attr]
  190. return
  191. # type error: Item "None" of "Optional[Graph]" has no attribute "remove"
  192. dstg.remove((None, None, None)) # type: ignore[union-attr]
  193. # type error: Unsupported left operand type for + ("None")
  194. dstg += srcg # type: ignore[operator]
  195. if ctx.dataset.store.graph_aware:
  196. # type error: Argument 1 to "remove_graph" of "Store" has incompatible type "Optional[Graph]"; expected "Graph"
  197. ctx.dataset.store.remove_graph(srcg) # type: ignore[arg-type]
  198. else:
  199. # type error: Item "None" of "Optional[Graph]" has no attribute "remove"
  200. srcg.remove((None, None, None)) # type: ignore[union-attr]
  201. def evalCopy(ctx: QueryContext, u: CompValue) -> None:
  202. """
  203. remove all triples from dst
  204. add all triples from src to dst
  205. http://www.w3.org/TR/sparql11-update/#copy
  206. """
  207. src, dst = u.graph
  208. srcg = _graphOrDefault(ctx, src)
  209. dstg = _graphOrDefault(ctx, dst)
  210. # type error: Item "None" of "Optional[Graph]" has no attribute "remove"
  211. if srcg.identifier == dstg.identifier: # type: ignore[union-attr]
  212. return
  213. # type error: Item "None" of "Optional[Graph]" has no attribute "remove"
  214. dstg.remove((None, None, None)) # type: ignore[union-attr]
  215. # type error: Unsupported left operand type for + ("None")
  216. dstg += srcg # type: ignore[operator]
  217. def evalUpdate(
  218. graph: Graph,
  219. update: Update,
  220. initBindings: Optional[Mapping[str, Identifier]] = None,
  221. ) -> None:
  222. """http://www.w3.org/TR/sparql11-update/#updateLanguage
  223. 'A request is a sequence of operations [...] Implementations MUST
  224. ensure that operations of a single request are executed in a
  225. fashion that guarantees the same effects as executing them in
  226. lexical order.
  227. Operations all result either in success or failure.
  228. If multiple operations are present in a single request, then a
  229. result of failure from any operation MUST abort the sequence of
  230. operations, causing the subsequent operations to be ignored.'
  231. This will return None on success and raise Exceptions on error
  232. !!! warning "Security Considerations"
  233. This method can access indirectly requested network endpoints, for
  234. example, query processing will attempt to access network endpoints
  235. specified in `SERVICE` directives.
  236. When processing untrusted or potentially malicious queries, measures
  237. should be taken to restrict network and file access.
  238. For information on available security measures, see the RDFLib
  239. [Security Considerations](../security_considerations.md)
  240. documentation.
  241. """
  242. for u in update.algebra:
  243. initBindings = dict((Variable(k), v) for k, v in (initBindings or {}).items())
  244. ctx = QueryContext(graph, initBindings=initBindings)
  245. ctx.prologue = u.prologue
  246. try:
  247. if u.name == "Load":
  248. evalLoad(ctx, u)
  249. elif u.name == "Clear":
  250. evalClear(ctx, u)
  251. elif u.name == "Drop":
  252. evalDrop(ctx, u)
  253. elif u.name == "Create":
  254. evalCreate(ctx, u)
  255. elif u.name == "Add":
  256. evalAdd(ctx, u)
  257. elif u.name == "Move":
  258. evalMove(ctx, u)
  259. elif u.name == "Copy":
  260. evalCopy(ctx, u)
  261. elif u.name == "InsertData":
  262. evalInsertData(ctx, u)
  263. elif u.name == "DeleteData":
  264. evalDeleteData(ctx, u)
  265. elif u.name == "DeleteWhere":
  266. evalDeleteWhere(ctx, u)
  267. elif u.name == "Modify":
  268. evalModify(ctx, u)
  269. else:
  270. raise Exception("Unknown update operation: %s" % (u,))
  271. except: # noqa: E722
  272. if not u.silent:
  273. raise