regexmatching.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. """
  2. This wrapper intercepts calls through the store interface which make use of
  3. the REGEXTerm class to represent matches by REGEX instead of literal
  4. comparison.
  5. Implemented for stores that don't support this and essentially
  6. provides the support by replacing the REGEXTerms by wildcards (None) and
  7. matching against the results from the store it's wrapping.
  8. """
  9. import re
  10. from rdflib.graph import Graph
  11. from rdflib.store import Store
  12. # Store is capable of doing its own REGEX matching
  13. NATIVE_REGEX = 0
  14. # Store uses Python's re module internally for REGEX matching
  15. PYTHON_REGEX = 1
  16. class REGEXTerm(str):
  17. """
  18. REGEXTerm can be used in any term slot and is interpreted as a request to
  19. perform a REGEX match (not a string comparison) using the value
  20. (pre-compiled) for checking rdf:type matches
  21. """
  22. def __init__(self, expr):
  23. self.compiledExpr = re.compile(expr)
  24. def __reduce__(self):
  25. return (REGEXTerm, ("",))
  26. def regexCompareQuad(quad, regexQuad): # noqa: N802, N803
  27. for index in range(4):
  28. if isinstance(regexQuad[index], REGEXTerm) and not regexQuad[
  29. index
  30. ].compiledExpr.match(quad[index]):
  31. return False
  32. return True
  33. class REGEXMatching(Store):
  34. def __init__(self, storage):
  35. self.storage = storage
  36. self.context_aware = storage.context_aware
  37. # NOTE: this store can't be formula_aware as it doesn't have enough
  38. # info to reverse the removal of a quoted statement.
  39. self.formula_aware = storage.formula_aware
  40. self.transaction_aware = storage.transaction_aware
  41. def open(self, configuration, create=True):
  42. return self.storage.open(configuration, create)
  43. def close(self, commit_pending_transaction=False):
  44. self.storage.close()
  45. def destroy(self, configuration):
  46. self.storage.destroy(configuration)
  47. def add(self, triple, context, quoted=False):
  48. (subject, predicate, object_) = triple
  49. self.storage.add((subject, predicate, object_), context, quoted)
  50. def remove(self, triple, context=None):
  51. (subject, predicate, object_) = triple
  52. if (
  53. isinstance(subject, REGEXTerm)
  54. or isinstance(predicate, REGEXTerm)
  55. or isinstance(object_, REGEXTerm)
  56. or (context is not None and isinstance(context.identifier, REGEXTerm))
  57. ):
  58. # One or more of the terms is a REGEX expression, so we must
  59. # replace it / them with wildcard(s)and match after we query.
  60. s = not isinstance(subject, REGEXTerm) and subject or None
  61. p = not isinstance(predicate, REGEXTerm) and predicate or None
  62. o = not isinstance(object_, REGEXTerm) and object_ or None
  63. c = (
  64. (context is not None and not isinstance(context.identifier, REGEXTerm))
  65. and context
  66. or None
  67. )
  68. removeQuadList = [] # noqa: N806
  69. for (s1, p1, o1), cg in self.storage.triples((s, p, o), c):
  70. for ctx in cg:
  71. ctx = ctx.identifier
  72. if regexCompareQuad(
  73. (s1, p1, o1, ctx),
  74. (
  75. subject,
  76. predicate,
  77. object_,
  78. context is not None and context.identifier or context,
  79. ),
  80. ):
  81. removeQuadList.append((s1, p1, o1, ctx))
  82. for s, p, o, c in removeQuadList:
  83. self.storage.remove((s, p, o), c and Graph(self, c) or c)
  84. else:
  85. self.storage.remove((subject, predicate, object_), context)
  86. def triples(self, triple, context=None):
  87. (subject, predicate, object_) = triple
  88. if (
  89. isinstance(subject, REGEXTerm)
  90. or isinstance(predicate, REGEXTerm)
  91. or isinstance(object_, REGEXTerm)
  92. or (context is not None and isinstance(context.identifier, REGEXTerm))
  93. ):
  94. # One or more of the terms is a REGEX expression, so we must
  95. # replace it / them with wildcard(s) and match after we query.
  96. s = not isinstance(subject, REGEXTerm) and subject or None
  97. p = not isinstance(predicate, REGEXTerm) and predicate or None
  98. o = not isinstance(object_, REGEXTerm) and object_ or None
  99. c = (
  100. (context is not None and not isinstance(context.identifier, REGEXTerm))
  101. and context
  102. or None
  103. )
  104. for (s1, p1, o1), cg in self.storage.triples((s, p, o), c):
  105. matchingCtxs = [] # noqa: N806
  106. for ctx in cg:
  107. if c is None:
  108. if context is None or context.identifier.compiledExpr.match(
  109. ctx.identifier
  110. ):
  111. matchingCtxs.append(ctx)
  112. else:
  113. matchingCtxs.append(ctx)
  114. if matchingCtxs and regexCompareQuad(
  115. (s1, p1, o1, None), (subject, predicate, object_, None)
  116. ):
  117. yield (s1, p1, o1), (c for c in matchingCtxs)
  118. else:
  119. for (s1, p1, o1), cg in self.storage.triples(
  120. (subject, predicate, object_), context
  121. ):
  122. yield (s1, p1, o1), cg
  123. def __len__(self, context=None):
  124. # NOTE: If the context is a REGEX this could be an expensive
  125. # proposition
  126. return self.storage.__len__(context)
  127. def contexts(self, triple=None):
  128. # NOTE: There is no way to control REGEX matching for this method at
  129. # this level as it only returns the contexts, not the matching
  130. # triples.
  131. for ctx in self.storage.contexts(triple):
  132. yield ctx
  133. def remove_context(self, identifier):
  134. self.storage.remove((None, None, None), identifier)
  135. def bind(self, prefix, namespace, override=True):
  136. self.storage.bind(prefix, namespace, override=override)
  137. def prefix(self, namespace):
  138. return self.storage.prefix(namespace)
  139. def namespace(self, prefix):
  140. return self.storage.namespace(prefix)
  141. def namespaces(self):
  142. return self.storage.namespaces()
  143. def commit(self):
  144. self.storage.commit()
  145. def rollback(self):
  146. self.storage.rollback()