graph.pyx 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  1. import logging
  2. from functools import wraps
  3. from rdflib import Graph
  4. from rdflib.term import Node
  5. from lakesuperior import env
  6. from libc.stdint cimport uint32_t, uint64_t
  7. from libc.string cimport memcmp, memcpy
  8. from libc.stdlib cimport free
  9. from cymem.cymem cimport Pool
  10. from lakesuperior.cy_include cimport cylmdb as lmdb
  11. from lakesuperior.cy_include cimport collections as cc
  12. from lakesuperior.cy_include cimport spookyhash as sph
  13. from lakesuperior.model.graph cimport term
  14. from lakesuperior.store.ldp_rs.lmdb_triplestore cimport (
  15. KLEN, DBL_KLEN, TRP_KLEN, TripleKey)
  16. from lakesuperior.model.structures.hash cimport term_hash_seed32
  17. from lakesuperior.model.structures.keyset cimport Keyset
  18. from lakesuperior.model.base cimport Buffer
  19. from lakesuperior.model.graph.triple cimport BufferTriple
  20. from lakesuperior.model.structures.hash cimport hash64
  21. cdef extern from 'spookyhash_api.h':
  22. uint64_t spookyhash_64(const void *input, size_t input_size, uint64_t seed)
  23. logger = logging.getLogger(__name__)
  24. cdef int term_cmp_fn(const void* key1, const void* key2):
  25. """
  26. Compare function for two Buffer objects.
  27. :rtype: int
  28. :return: 0 if the byte streams are the same, another integer otherwise.
  29. """
  30. b1 = <Buffer *>key1
  31. b2 = <Buffer *>key2
  32. if b1.sz != b2.sz:
  33. logger.info(f'Sizes differ: {b1.sz} != {b2.sz}. Return 1.')
  34. return 1
  35. cdef int cmp = memcmp(b1.addr, b2.addr, b1.sz)
  36. logger.info(f'term memcmp: {cmp}')
  37. return cmp
  38. cdef int trp_lit_cmp_fn(const void* key1, const void* key2):
  39. """
  40. Compare function for two triples in a set.
  41. s, p, o byte data are compared literally.
  42. :rtype: int
  43. :return: 0 if all three terms point to byte-wise identical data in both
  44. triples.
  45. """
  46. t1 = <BufferTriple *>key1
  47. t2 = <BufferTriple *>key2
  48. print('Comparing terms: {} {} {}'.format(
  49. (<unsigned char*>t1.s.addr)[:t1.s.sz],
  50. (<unsigned char*>t1.p.addr)[:t1.p.sz],
  51. (<unsigned char*>t1.o.addr)[:t1.o.sz]
  52. ))
  53. print('With: {} {} {}'.format(
  54. (<unsigned char*>t2.s.addr)[:t2.s.sz],
  55. (<unsigned char*>t2.p.addr)[:t2.p.sz],
  56. (<unsigned char*>t2.o.addr)[:t2.o.sz]
  57. ))
  58. print('Term comparison results: {}, {}, {}'.format(
  59. term_cmp_fn(t1.o, t2.o),
  60. term_cmp_fn(t1.s, t2.s),
  61. term_cmp_fn(t1.p, t2.p)
  62. ))
  63. diff = (
  64. term_cmp_fn(t1.o, t2.o) or
  65. term_cmp_fn(t1.s, t2.s) or
  66. term_cmp_fn(t1.p, t2.p)
  67. )
  68. logger.info(f'Triples match: {not(diff)}')
  69. return diff
  70. cdef int trp_cmp_fn(const void* key1, const void* key2):
  71. """
  72. Compare function for two triples in a set.
  73. Here, pointers to terms are compared for s, p, o. The pointers should be
  74. guaranteed to point to unique values (i.e. no two pointers have the same
  75. term value within a graph).
  76. :rtype: int
  77. :return: 0 if the addresses of all terms are the same, 1 otherwise.
  78. """
  79. t1 = <BufferTriple *>key1
  80. t2 = <BufferTriple *>key2
  81. print('Comparing terms: {} {} {}'.format(
  82. (<unsigned char*>t1.s.addr)[:t1.s.sz],
  83. (<unsigned char*>t1.p.addr)[:t1.p.sz],
  84. (<unsigned char*>t1.o.addr)[:t1.o.sz]
  85. ))
  86. print('With: {} {} {}'.format(
  87. (<unsigned char*>t2.s.addr)[:t2.s.sz],
  88. (<unsigned char*>t2.p.addr)[:t2.p.sz],
  89. (<unsigned char*>t2.o.addr)[:t2.o.sz]
  90. ))
  91. print('Comparing addresses: <0x{:02x}> <0x{:02x}> <0x{:02x}>'.format(
  92. <size_t>t1.s, <size_t>t1.p, <size_t>t1.o))
  93. print('With: <0x{:02x}> <0x{:02x}> <0x{:02x}>'.format(
  94. <size_t>t2.s, <size_t>t2.p, <size_t>t2.o))
  95. cdef int is_not_equal = (
  96. t1.s.addr != t2.s.addr or
  97. t1.p.addr != t2.p.addr or
  98. t1.o.addr != t2.o.addr
  99. )
  100. logger.info(f'Triples match: {not(is_not_equal)}')
  101. return is_not_equal
  102. cdef bint graph_eq_fn(SimpleGraph g1, SimpleGraph g2):
  103. """
  104. Compare 2 graphs for equality.
  105. Note that this returns the opposite value than the triple and term
  106. compare functions: 1 (True) if equal, 0 (False) if not.
  107. """
  108. cdef:
  109. void* el
  110. cc.HashSetIter it
  111. cc.hashset_iter_init(&it, g1._triples)
  112. while cc.hashset_iter_next(&it, &el) != cc.CC_ITER_END:
  113. if cc.hashset_contains(g2._triples, el):
  114. return False
  115. return True
  116. cdef size_t term_hash_fn(const void* key, int l, uint32_t seed):
  117. """
  118. Hash function for serialized terms (:py:class:`Buffer` objects)
  119. """
  120. return <size_t>spookyhash_64((<Buffer*>key).addr, (<Buffer*>key).sz, seed)
  121. cdef size_t trp_lit_hash_fn(const void* key, int l, uint32_t seed):
  122. """
  123. Hash function for sets of (serialized) triples.
  124. This function concatenates the literal terms of the triple as bytes
  125. and computes their hash.
  126. """
  127. trp = <BufferTriple*>key
  128. seed64 = <uint64_t>seed
  129. seed_dummy = seed64
  130. cdef sph.spookyhash_context ctx
  131. sph.spookyhash_context_init(&ctx, seed64, seed_dummy)
  132. sph.spookyhash_update(&ctx, trp.s.addr, trp.s.sz)
  133. sph.spookyhash_update(&ctx, trp.s.addr, trp.p.sz)
  134. sph.spookyhash_update(&ctx, trp.s.addr, trp.o.sz)
  135. sph.spookyhash_final(&ctx, &seed64, &seed_dummy)
  136. return <size_t>seed64
  137. cdef size_t trp_hash_fn(const void* key, int l, uint32_t seed):
  138. """
  139. Hash function for sets of (serialized) triples.
  140. This function computes the hash of the concatenated pointer values in the
  141. s, p, o members of the triple. The triple structure is treated as a byte
  142. string. This is safe in spite of byte-wise struct evaluation being a
  143. frowned-upon practice (due to padding issues), because it is assumed that
  144. the input value is always the same type of structure.
  145. """
  146. return <size_t>spookyhash_64(key, l, seed)
  147. cdef size_t hash_ptr_passthrough(const void* key, int l, uint32_t seed):
  148. """
  149. No-op function that takes a pointer and does *not* hash it.
  150. The pointer value is used as the "hash".
  151. """
  152. return <size_t>key
  153. cdef inline bint lookup_none_cmp_fn(
  154. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  155. ):
  156. """
  157. Dummy callback for queries with all parameters unbound.
  158. This function always returns ``True``
  159. """
  160. return True
  161. cdef inline bint lookup_s_cmp_fn(
  162. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  163. ):
  164. """
  165. Lookup callback compare function for a given s in a triple.
  166. The function returns ``True`` if ``t1`` matches the first term.
  167. ``t2`` is not used and is declared only for compatibility with the
  168. other interchangeable functions.
  169. """
  170. return term_cmp_fn(t1, trp[0].s)
  171. cdef inline bint lookup_p_cmp_fn(
  172. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  173. ):
  174. return term_cmp_fn(t1, trp[0].p)
  175. cdef inline bint lookup_o_cmp_fn(
  176. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  177. ):
  178. return term_cmp_fn(t1, trp[0].o)
  179. cdef inline bint lookup_sp_cmp_fn(
  180. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  181. ):
  182. return (
  183. term_cmp_fn(t1, trp[0].s)
  184. and term_cmp_fn(t2, trp[0].p))
  185. cdef inline bint lookup_so_cmp_fn(
  186. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  187. ):
  188. return (
  189. term_cmp_fn(t1, trp[0].s)
  190. and term_cmp_fn(t2, trp[0].o))
  191. cdef inline bint lookup_po_cmp_fn(
  192. const BufferTriple *trp, const Buffer *t1, const Buffer *t2
  193. ):
  194. return (
  195. term_cmp_fn(t1, trp[0].p)
  196. and term_cmp_fn(t2, trp[0].o))
  197. cdef class SimpleGraph:
  198. """
  199. Fast and simple implementation of a graph.
  200. Most functions should mimic RDFLib's graph with less overhead. It uses
  201. the same funny but functional slicing notation. No lookup functions within
  202. the graph are available at this time.
  203. Instances of this class hold a set of
  204. :py:class:`~lakesuperior.store.ldp_rs.term.Term` structures that stores
  205. unique terms within the graph, and a set of
  206. :py:class:`~lakesuperior.store.ldp_rs.triple.Triple` structures referencing
  207. those terms. Therefore, no data duplication occurs and the storage is quite
  208. sparse.
  209. A graph can be instantiated from a store lookup.
  210. A SimpleGraph can also be obtained from a
  211. :py:class:`lakesuperior.store.keyset.Keyset` which is convenient bacause
  212. a Keyset can be obtained very efficiently from querying a store, then also
  213. very efficiently filtered and eventually converted into a set of meaningful
  214. terms.
  215. An instance of this class can also be converted to and from a
  216. ``rdflib.Graph`` instance. TODO verify that this frees Cython pointers.
  217. """
  218. def __cinit__(
  219. self, Keyset keyset=None, store=None, set data=set(), *args, **kwargs):
  220. """
  221. Initialize the graph with pre-existing data or by looking up a store.
  222. One of ``keyset``, or ``data`` can be provided. If more than
  223. one of these is provided, precedence is given in the mentioned order.
  224. If none of them is specified, an empty graph is initialized.
  225. :param rdflib.URIRef uri: The graph URI.
  226. This will serve as the subject for some queries.
  227. :param Keyset keyset: Keyset to create the graph from. Keys will be
  228. converted to set elements.
  229. :param lakesuperior.store.ldp_rs.LmdbTripleStore store: store to
  230. look up the keyset. Only used if ``keyset`` is specified. If not
  231. set, the environment store is used.
  232. :param set data: Initial data as a set of 3-tuples of RDFLib terms.
  233. :param tuple lookup: tuple of a 3-tuple of lookup terms, and a context.
  234. E.g. ``((URIRef('urn:ns:a'), None, None), URIRef('urn:ns:ctx'))``.
  235. Any and all elements may be ``None``.
  236. :param lmdbStore store: the store to look data up.
  237. """
  238. cdef:
  239. cc.HashSetConf terms_conf, trp_conf
  240. self.term_cmp_fn = &term_cmp_fn
  241. self.trp_cmp_fn = &trp_lit_cmp_fn
  242. cc.hashset_conf_init(&terms_conf)
  243. terms_conf.load_factor = 0.85
  244. terms_conf.hash = &term_hash_fn
  245. terms_conf.hash_seed = term_hash_seed32
  246. terms_conf.key_compare = self.term_cmp_fn
  247. terms_conf.key_length = sizeof(Buffer*)
  248. cc.hashset_conf_init(&trp_conf)
  249. trp_conf.load_factor = 0.75
  250. trp_conf.hash = &trp_lit_hash_fn
  251. trp_conf.hash_seed = term_hash_seed32
  252. trp_conf.key_compare = self.trp_cmp_fn
  253. trp_conf.key_length = sizeof(BufferTriple)
  254. cc.hashset_new_conf(&terms_conf, &self._terms)
  255. cc.hashset_new_conf(&trp_conf, &self._triples)
  256. self.store = store or env.app_globals.rdf_store
  257. self._pool = Pool()
  258. # Initialize empty data set.
  259. if keyset:
  260. # Populate with triples extracted from provided key set.
  261. self._data_from_keyset(keyset)
  262. elif data:
  263. # Populate with provided Python set.
  264. self.add(data)
  265. def __dealloc__(self):
  266. """
  267. Free the triple pointers. TODO use a Cymem pool
  268. """
  269. free(self._triples)
  270. free(self._terms)
  271. @property
  272. def data(self):
  273. """
  274. Triple data as a Python set.
  275. :rtype: set
  276. """
  277. return self._to_pyset()
  278. @property
  279. def terms(self):
  280. return self._get_terms()
  281. # # # BASIC SET OPERATIONS # # #
  282. cpdef SimpleGraph union(self, SimpleGraph other):
  283. """
  284. Perform set union resulting in a new SimpleGraph instance.
  285. TODO Allow union of multiple graphs at a time.
  286. :param SimpleGraph other: The other graph to merge.
  287. :rtype: SimpleGraph
  288. :return: A new SimpleGraph instance.
  289. """
  290. cdef:
  291. void *cur
  292. cc.HashSetIter it
  293. SimpleGraph new_gr = SimpleGraph()
  294. BufferTriple *trp
  295. new_gr.store = self.store
  296. for gr in (self, other):
  297. cc.hashset_iter_init(&it, gr._triples)
  298. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  299. bt = <BufferTriple*>cur
  300. new_gr._add_triple(bt)
  301. return new_gr
  302. cdef void ip_union(self, SimpleGraph other) except *:
  303. """
  304. Perform an in-place set union that adds triples to this instance
  305. TODO Allow union of multiple graphs at a time.
  306. :param SimpleGraph other: The other graph to merge.
  307. :rtype: void
  308. """
  309. cdef:
  310. void *cur
  311. cc.HashSetIter it
  312. cc.hashset_iter_init(&it, other._triples)
  313. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  314. bt = <BufferTriple*>cur
  315. self._add_triple(bt)
  316. cpdef SimpleGraph intersection(self, SimpleGraph other):
  317. """
  318. Graph intersection.
  319. :param SimpleGraph other: The other graph to intersect.
  320. :rtype: SimpleGraph
  321. :return: A new SimpleGraph instance.
  322. """
  323. cdef:
  324. void *cur
  325. cc.HashSetIter it
  326. SimpleGraph new_gr = SimpleGraph()
  327. cc.hashset_iter_init(&it, self._triples)
  328. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  329. bt = <BufferTriple*>cur
  330. print('Checking: <0x{:02x}> <0x{:02x}> <0x{:02x}>'.format(
  331. <size_t>bt.s, <size_t>bt.p, <size_t>bt.o))
  332. if other._trp_contains(bt):
  333. print('Adding.')
  334. new_gr._add_triple(bt)
  335. return new_gr
  336. cdef void ip_intersection(self, SimpleGraph other) except *:
  337. """
  338. In-place graph intersection.
  339. Triples in common with another graph are removed from the current one.
  340. :param SimpleGraph other: The other graph to intersect.
  341. :rtype: void
  342. """
  343. cdef:
  344. void *cur
  345. cc.HashSetIter it
  346. cc.hashset_iter_init(&it, self._triples)
  347. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  348. bt = <BufferTriple*>cur
  349. if not other._trp_contains(bt):
  350. self._remove_triple(bt)
  351. cpdef SimpleGraph xor(self, SimpleGraph other):
  352. """
  353. Graph Exclusive disjunction (XOR).
  354. :param SimpleGraph other: The other graph to perform XOR with.
  355. :rtype: SimpleGraph
  356. :return: A new SimpleGraph instance.
  357. """
  358. cdef:
  359. void *cur_self
  360. void *cur_other
  361. cc.HashSetIter it_self, it_other
  362. SimpleGraph new_gr = SimpleGraph()
  363. BufferTriple* bt
  364. # Add triples in this and not in other.
  365. print('Comparing with this.')
  366. cc.hashset_iter_init(&it_self, self._triples)
  367. while cc.hashset_iter_next(&it_self, &cur_self) != cc.CC_ITER_END:
  368. bt = <BufferTriple*>cur_self
  369. if not other._trp_contains(bt):
  370. print('Adding from this.')
  371. new_gr._add_triple(bt)
  372. # Other way around.
  373. print('Comparing with that.')
  374. cc.hashset_iter_init(&it_other, other._triples)
  375. while cc.hashset_iter_next(&it_other, &cur_other) != cc.CC_ITER_END:
  376. bt = <BufferTriple*>cur_other
  377. print('Checking on that.')
  378. if not self._trp_contains(bt):
  379. print('Adding from that.')
  380. new_gr._add_triple(bt)
  381. else:
  382. print('Triple exists. Not adding.')
  383. return new_gr
  384. cdef void ip_xor(self, SimpleGraph other) except *:
  385. """
  386. In-place graph XOR.
  387. Triples in common with another graph are removed from the current one,
  388. and triples not in common will be added from the other one.
  389. :param SimpleGraph other: The other graph to perform XOR with.
  390. :rtype: void
  391. """
  392. cdef:
  393. void *cur
  394. cc.HashSetIter it
  395. # Add triples in other graph and not in this graph.
  396. cc.hashset_iter_init(&it, self._triples)
  397. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  398. bt = <BufferTriple*>cur
  399. if other._trp_contains(bt):
  400. self._remove_triple(bt)
  401. # Remove triples in common.
  402. cc.hashset_iter_init(&it, other._triples)
  403. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  404. bt = <BufferTriple*>cur
  405. if not self._trp_contains(bt):
  406. self._add_triple(bt)
  407. cdef void _data_from_lookup(self, tuple trp_ptn, ctx=None) except *:
  408. """
  409. Look up triples in the triplestore and load them into ``data``.
  410. :param tuple lookup: 3-tuple of RDFlib terms or ``None``.
  411. :param LmdbTriplestore store: Reference to a LMDB triplestore. This
  412. is normally set to ``lakesuperior.env.app_globals.rdf_store``.
  413. """
  414. cdef:
  415. size_t i
  416. unsigned char spok[TRP_KLEN]
  417. with self.store.txn_ctx():
  418. keyset = self.store.triple_keys(trp_ptn, ctx)
  419. self.data_from_keyset(keyset)
  420. cdef void _data_from_keyset(self, Keyset data) except *:
  421. """Populate a graph from a Keyset."""
  422. cdef TripleKey spok
  423. while data.next(spok):
  424. self._add_from_spok(spok)
  425. cdef inline void _add_from_spok(self, TripleKey spok) except *:
  426. """
  427. Add a triple from a TripleKey of term keys.
  428. """
  429. s_spo = <SPOBuffer>self._pool.alloc(3, sizeof(Buffer))
  430. self.store.lookup_term(spok, s_spo)
  431. self.store.lookup_term(spok + KLEN, s_spo + 1)
  432. self.store.lookup_term(spok + DBL_KLEN, s_spo + 2)
  433. trp = <BufferTriple *>self._pool.alloc(1, sizeof(BufferTriple))
  434. trp.s = s_spo
  435. trp.p = s_spo + 1
  436. trp.o = s_spo + 2
  437. self._add_triple(trp)
  438. cdef inline void _add_triple(self, BufferTriple* trp) except *:
  439. """
  440. Add a triple from 3 (TPL) serialized terms.
  441. Each of the terms is added to the term set if not existing. The triple
  442. also is only added if not existing.
  443. """
  444. logger.info('Inserting terms.')
  445. cc.hashset_add(self._terms, trp.s)
  446. cc.hashset_add(self._terms, trp.p)
  447. cc.hashset_add(self._terms, trp.o)
  448. logger.info('inserted terms.')
  449. logger.info(f'Terms set size: {cc.hashset_size(self._terms)}')
  450. cdef size_t trp_sz = cc.hashset_size(self._triples)
  451. logger.info(f'Triples set size before adding: {trp_sz}')
  452. r = cc.hashset_add(self._triples, trp)
  453. print('Insert triple result:')
  454. print(r)
  455. trp_sz = cc.hashset_size(self._triples)
  456. logger.info(f'Triples set size after adding: {trp_sz}')
  457. cdef:
  458. cc.HashSetIter ti
  459. void *cur
  460. cdef int _remove_triple(self, BufferTriple* trp_buf) except -1:
  461. """
  462. Remove one triple from the graph.
  463. """
  464. return cc.hashset_remove(self._triples, trp_buf, NULL)
  465. cdef bint _trp_contains(self, BufferTriple* btrp):
  466. cdef:
  467. cc.HashSetIter it
  468. void* cur
  469. cc.hashset_iter_init(&it, self._triples)
  470. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  471. if self.trp_cmp_fn(cur, btrp) == 0:
  472. return True
  473. return False
  474. cdef _get_terms(self):
  475. """
  476. Get all terms in the graph.
  477. """
  478. cdef:
  479. cc.HashSetIter it
  480. void *cur
  481. terms = [] # This is intentionally a list to spot issues with the set.
  482. cc.hashset_iter_init(&it, self._terms)
  483. while cc.hashset_iter_next(&it, &cur) != cc.CC_ITER_END:
  484. s_term = <Buffer*>cur
  485. terms.append((f'0x{<size_t>cur:02x}', term.deserialize_to_rdflib(s_term)))
  486. return terms
  487. cdef set _to_pyset(self):
  488. """
  489. Convert triple data to a Python set.
  490. :rtype: set
  491. """
  492. cdef:
  493. void *void_p
  494. cc.HashSetIter ti
  495. term.Term s, p, o
  496. graph_set = set()
  497. # Looping over an empty HashSet results in a segfault. Exit early in
  498. # that case.
  499. #if not cc.hashset_size(self._triples):
  500. # return graph_set
  501. cc.hashset_iter_init(&ti, self._triples)
  502. while cc.hashset_iter_next(&ti, &void_p) != cc.CC_ITER_END:
  503. if void_p == NULL:
  504. logger.warn('Triple is NULL!')
  505. break
  506. trp = <BufferTriple *>void_p
  507. graph_set.add((
  508. term.deserialize_to_rdflib(trp.s),
  509. term.deserialize_to_rdflib(trp.p),
  510. term.deserialize_to_rdflib(trp.o),
  511. ))
  512. return graph_set
  513. # Basic set operations.
  514. def add(self, trp):
  515. """
  516. Add triples to the graph.
  517. :param iterable triples: Set, list or tuple of 3-tuple triples.
  518. """
  519. cdef size_t cur = 0, trp_cur = 0
  520. trp_ct = len(trp)
  521. term_buf = <Buffer*>self._pool.alloc(3 * trp_ct, sizeof(Buffer))
  522. trp_buf = <BufferTriple*>self._pool.alloc(trp_ct, sizeof(BufferTriple))
  523. for s, p, o in trp:
  524. term.serialize_from_rdflib(s, term_buf + cur, self._pool)
  525. term.serialize_from_rdflib(p, term_buf + cur + 1, self._pool)
  526. term.serialize_from_rdflib(o, term_buf + cur + 2, self._pool)
  527. (trp_buf + trp_cur).s = term_buf + cur
  528. (trp_buf + trp_cur).p = term_buf + cur + 1
  529. (trp_buf + trp_cur).o = term_buf + cur + 2
  530. self._add_triple(trp_buf + trp_cur)
  531. trp_cur += 1
  532. cur += 3
  533. def len_terms(self):
  534. """ Number of triples in the graph. """
  535. return cc.hashset_size(self._terms)
  536. def remove(self, trp):
  537. """
  538. Remove one item from the graph.
  539. :param tuple item: A 3-tuple of RDFlib terms. Only exact terms, i.e.
  540. wildcards are not accepted.
  541. """
  542. cdef:
  543. Buffer ss, sp, so
  544. BufferTriple trp_buf
  545. term.serialize_from_rdflib(trp[0], &ss, self._pool)
  546. term.serialize_from_rdflib(trp[1], &sp, self._pool)
  547. term.serialize_from_rdflib(trp[2], &so, self._pool)
  548. trp_buf.s = &ss
  549. trp_buf.p = &sp
  550. trp_buf.o = &so
  551. self._remove_triple(&trp_buf)
  552. def __len__(self):
  553. """ Number of triples in the graph. """
  554. return cc.hashset_size(self._triples)
  555. def __eq__(self, other):
  556. """ Equality operator between ``SimpleGraph`` instances. """
  557. return graph_eq_fn(self, other)
  558. def __repr__(self):
  559. """
  560. String representation of the graph.
  561. It provides the number of triples in the graph and memory address of
  562. the instance.
  563. """
  564. return (
  565. f'<{self.__class__.__name__} @{hex(id(self))} '
  566. f'length={len(self.data)}>'
  567. )
  568. def __str__(self):
  569. """ String dump of the graph triples. """
  570. return str(self.data)
  571. def __sub__(self, other):
  572. """ Set subtraction. """
  573. return self.subtract(other)
  574. def __isub__(self, other):
  575. """ In-place set subtraction. """
  576. self.ip_subtract(other)
  577. return self
  578. def __and__(self, other):
  579. """ Set intersection. """
  580. return self.intersection(other)
  581. def __iand__(self, other):
  582. """ In-place set intersection. """
  583. self.ip_intersection(other)
  584. return self
  585. def __or__(self, other):
  586. """ Set union. """
  587. return self.union(other)
  588. def __ior__(self, other):
  589. """ In-place set union. """
  590. self.ip_union(other)
  591. return self
  592. def __xor__(self, other):
  593. """ Set exclusive disjunction (XOR). """
  594. return self.xor(other)
  595. def __ixor__(self, other):
  596. """ In-place set exclusive disjunction (XOR). """
  597. self.ip_xor(other)
  598. return self
  599. def __contains__(self, trp):
  600. """
  601. Whether the graph contains a triple.
  602. :rtype: boolean
  603. """
  604. cdef:
  605. Buffer ss, sp, so
  606. BufferTriple btrp
  607. btrp.s = &ss
  608. btrp.p = &sp
  609. btrp.o = &so
  610. s, p, o = trp
  611. term.serialize_from_rdflib(s, &ss)
  612. term.serialize_from_rdflib(p, &sp)
  613. term.serialize_from_rdflib(o, &so)
  614. return self._trp_contains(&btrp)
  615. def __iter__(self):
  616. """ Graph iterator. It iterates over the set triples. """
  617. raise NotImplementedError()
  618. # Slicing.
  619. def __getitem__(self, item):
  620. """
  621. Slicing function.
  622. It behaves similarly to `RDFLib graph slicing
  623. <https://rdflib.readthedocs.io/en/stable/utilities.html#slicing-graphs>`__
  624. """
  625. if isinstance(item, slice):
  626. s, p, o = item.start, item.stop, item.step
  627. return self._slice(s, p, o)
  628. else:
  629. raise TypeError(f'Wrong slice format: {item}.')
  630. cpdef void set(self, tuple trp) except *:
  631. """
  632. Set a single value for subject and predicate.
  633. Remove all triples matching ``s`` and ``p`` before adding ``s p o``.
  634. """
  635. if None in trp:
  636. raise ValueError(f'Invalid triple: {trp}')
  637. self.remove_triples((trp[0], trp[1], None))
  638. self.add((trp,))
  639. cpdef void remove_triples(self, pattern) except *:
  640. """
  641. Remove triples by pattern.
  642. The pattern used is similar to :py:meth:`LmdbTripleStore.delete`.
  643. """
  644. s, p, o = pattern
  645. for match in self.lookup(s, p, o):
  646. logger.debug(f'Removing from graph: {match}.')
  647. self.data.remove(match)
  648. cpdef object as_rdflib(self):
  649. """
  650. Return the data set as an RDFLib Graph.
  651. :rtype: rdflib.Graph
  652. """
  653. gr = Graph()
  654. for trp in self.data:
  655. gr.add(trp)
  656. return gr
  657. def _slice(self, s, p, o):
  658. """
  659. Return terms filtered by other terms.
  660. This behaves like the rdflib.Graph slicing policy.
  661. """
  662. _data = self.data
  663. logger.debug(f'Slicing graph by: {s}, {p}, {o}.')
  664. if s is None and p is None and o is None:
  665. return _data
  666. elif s is None and p is None:
  667. return {(r[0], r[1]) for r in _data if r[2] == o}
  668. elif s is None and o is None:
  669. return {(r[0], r[2]) for r in _data if r[1] == p}
  670. elif p is None and o is None:
  671. return {(r[1], r[2]) for r in _data if r[0] == s}
  672. elif s is None:
  673. return {r[0] for r in _data if r[1] == p and r[2] == o}
  674. elif p is None:
  675. return {r[1] for r in _data if r[0] == s and r[2] == o}
  676. elif o is None:
  677. return {r[2] for r in _data if r[0] == s and r[1] == p}
  678. else:
  679. # all given
  680. return (s,p,o) in _data
  681. def lookup(self, s, p, o):
  682. """
  683. Look up triples by a pattern.
  684. This function converts RDFLib terms into the serialized format stored
  685. in the graph's internal structure and compares them bytewise.
  686. Any and all of the lookup terms can be ``None``.
  687. """
  688. cdef:
  689. void *void_p
  690. BufferTriple trp
  691. BufferTriple *trp_p
  692. cc.HashSetIter ti
  693. Buffer t1
  694. Buffer t2
  695. lookup_fn_t fn
  696. res = set()
  697. # Decide comparison logic outside the loop.
  698. if s is not None and p is not None and o is not None:
  699. # Return immediately if 3-term match is requested.
  700. term.serialize_from_rdflib(s, trp.s, self._pool)
  701. term.serialize_from_rdflib(p, trp.p, self._pool)
  702. term.serialize_from_rdflib(o, trp.o, self._pool)
  703. if cc.hashset_contains(self._triples, &trp):
  704. res.add((s, p, o))
  705. return res
  706. elif s is not None:
  707. term.serialize_from_rdflib(s, &t1)
  708. if p is not None:
  709. fn = lookup_sp_cmp_fn
  710. term.serialize_from_rdflib(p, &t2)
  711. elif o is not None:
  712. fn = lookup_so_cmp_fn
  713. term.serialize_from_rdflib(o, &t2)
  714. else:
  715. fn = lookup_s_cmp_fn
  716. elif p is not None:
  717. term.serialize_from_rdflib(p, &t1)
  718. if o is not None:
  719. fn = lookup_po_cmp_fn
  720. term.serialize_from_rdflib(o, &t2)
  721. else:
  722. fn = lookup_p_cmp_fn
  723. elif o is not None:
  724. fn = lookup_o_cmp_fn
  725. term.serialize_from_rdflib(o, &t1)
  726. else:
  727. fn = lookup_none_cmp_fn
  728. # Iterate over serialized triples.
  729. cc.hashset_iter_init(&ti, self._triples)
  730. while cc.hashset_iter_next(&ti, &void_p) != cc.CC_ITER_END:
  731. if void_p == NULL:
  732. trp_p = <BufferTriple *>void_p
  733. res.add((
  734. term.deserialize_to_rdflib(trp_p[0].s),
  735. term.deserialize_to_rdflib(trp_p[0].p),
  736. term.deserialize_to_rdflib(trp_p[0].o),
  737. ))
  738. return res
  739. #cpdef set terms(self, str type):
  740. # """
  741. # Get all terms of a type: subject, predicate or object.
  742. # :param str type: One of ``s``, ``p`` or ``o``.
  743. # """
  744. # i = 'spo'.index(type)
  745. # return {r[i] for r in self.data}
  746. cdef class Imr(SimpleGraph):
  747. """
  748. In-memory resource data container.
  749. This is an extension of :py:class:`~SimpleGraph` that adds a subject URI to
  750. the data set and some convenience methods.
  751. An instance of this class can be converted to a ``rdflib.Resource``
  752. instance.
  753. Some set operations that produce a new object (``-``, ``|``, ``&``, ``^``)
  754. will create a new ``Imr`` instance with the same subject URI.
  755. """
  756. def __init__(self, uri, *args, **kwargs):
  757. """
  758. Initialize the graph with pre-existing data or by looking up a store.
  759. Either ``data``, or ``lookup`` *and* ``store``, can be provide.
  760. ``lookup`` and ``store`` have precedence. If none of them is specified,
  761. an empty graph is initialized.
  762. :param rdflib.URIRef uri: The graph URI.
  763. This will serve as the subject for some queries.
  764. :param set data: Initial data as a set of 3-tuples of RDFLib terms.
  765. :param tuple lookup: tuple of a 3-tuple of lookup terms, and a context.
  766. E.g. ``((URIRef('urn:ns:a'), None, None), URIRef('urn:ns:ctx'))``.
  767. Any and all elements may be ``None``.
  768. :param lmdbStore store: the store to look data up.
  769. """
  770. print(len(self))
  771. self.uri = str(uri)
  772. @property
  773. def identifier(self):
  774. """
  775. IMR URI. For compatibility with RDFLib Resource.
  776. :rtype: string
  777. """
  778. return self.uri
  779. @property
  780. def graph(self):
  781. """
  782. Return a SimpleGraph with the same data.
  783. :rtype: SimpleGraph
  784. """
  785. return SimpleGraph(self.data)
  786. def __repr__(self):
  787. """
  788. String representation of an Imr.
  789. This includes the subject URI, number of triples contained and the
  790. memory address of the instance.
  791. """
  792. return (f'<{self.__class__.__name__} @{hex(id(self))} uri={self.uri}, '
  793. f'length={len(self.data)}>')
  794. def __sub__(self, other):
  795. """
  796. Set difference. This creates a new Imr with the same subject URI.
  797. """
  798. return self.__class__(uri=self.uri, data=self.data - other)
  799. def __and__(self, other):
  800. """
  801. Set intersection. This creates a new Imr with the same subject URI.
  802. """
  803. return self.__class__(uri=self.uri, data=self.data & other)
  804. def __or__(self, other):
  805. """
  806. Set union. This creates a new Imr with the same subject URI.
  807. """
  808. return self.__class__(uri=self.uri, data=self.data | other)
  809. def __xor__(self, other):
  810. """
  811. Set exclusive OR (XOR). This creates a new Imr with the same subject
  812. URI.
  813. """
  814. return self.__class__(uri=self.uri, data=self.data ^ other)
  815. def __getitem__(self, item):
  816. """
  817. Supports slicing notation.
  818. """
  819. if isinstance(item, slice):
  820. s, p, o = item.start, item.stop, item.step
  821. return self._slice(s, p, o)
  822. elif isinstance(item, Node):
  823. # If a Node is given, return all values for that predicate.
  824. return {
  825. r[2] for r in self.data
  826. if r[0] == self.uri and r[1] == item}
  827. else:
  828. raise TypeError(f'Wrong slice format: {item}.')
  829. def value(self, p, strict=False):
  830. """
  831. Get an individual value.
  832. :param rdflib.termNode p: Predicate to search for.
  833. :param bool strict: If set to ``True`` the method raises an error if
  834. more than one value is found. If ``False`` (the default) only
  835. the first found result is returned.
  836. :rtype: rdflib.term.Node
  837. """
  838. values = self[p]
  839. if strict and len(values) > 1:
  840. raise RuntimeError('More than one value found for {}, {}.'.format(
  841. self.uri, p))
  842. for ret in values:
  843. return ret
  844. return None
  845. cpdef as_rdflib(self):
  846. """
  847. Return the IMR as a RDFLib Resource.
  848. :rtype: rdflib.Resource
  849. """
  850. gr = Graph()
  851. for trp in self.data:
  852. gr.add(trp)
  853. return gr.resource(identifier=self.uri)