test_1_0_lmdb_store.py 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. import pdb
  2. import pytest
  3. from os import path
  4. from shutil import rmtree
  5. from rdflib import Namespace, URIRef
  6. from rdflib.graph import DATASET_DEFAULT_GRAPH_ID as RDFLIB_DEFAULT_GRAPH_URI
  7. from rdflib.namespace import RDF, RDFS
  8. from lakesuperior.model.rdf.graph import Graph
  9. from lakesuperior.store.base_lmdb_store import LmdbError
  10. from lakesuperior.store.ldp_rs.lmdb_store import LmdbStore
  11. @pytest.fixture(scope='class')
  12. def store():
  13. """
  14. Test LMDB store.
  15. This store has a different life cycle than the one used for tests in higher
  16. levels of the stack and is not bootstrapped (i.e. starts completely empty).
  17. """
  18. env_path = '/tmp/test_lmdbstore'
  19. # Remove previous test DBs
  20. rmtree(env_path, ignore_errors=True)
  21. store = LmdbStore(env_path)
  22. yield store
  23. store.close()
  24. # Leave store around for post-test inspection.
  25. #store.destroy()
  26. @pytest.fixture
  27. def bogus_trp():
  28. """
  29. 1000 bogus triples.
  30. With 2048-sized pages, even 2-bound indices with 5-byte values such as
  31. sp:o should yield more than one page (2048 bytes // 5 = 409 values).
  32. """
  33. return {
  34. (
  35. URIRef('urn:test_mp:s1'),
  36. URIRef(f'urn:test_mp:p{i // 10}'),
  37. URIRef(f'urn:test_mp:o{i}'))
  38. for i in range(1000)
  39. }
  40. def _clean(res):
  41. return {r[0] for r in res}
  42. @pytest.mark.usefixtures('store')
  43. class TestStoreInit:
  44. '''
  45. Tests for intializing and shutting down store and transactions.
  46. '''
  47. def test_open_close(self):
  48. '''
  49. Test opening, closing and destroying a store.
  50. '''
  51. env_path = '/tmp/test_lmdbstore_init'
  52. tmpstore = LmdbStore(env_path)
  53. assert tmpstore.is_open
  54. tmpstore.close()
  55. assert not tmpstore.is_open
  56. tmpstore.destroy()
  57. assert not path.exists(env_path)
  58. assert not path.exists(env_path + '-lock')
  59. @pytest.mark.usefixtures('store')
  60. class TestTransactionContext:
  61. '''
  62. Tests for intializing and shutting down store and transactions.
  63. '''
  64. def test_txn(self, store):
  65. '''
  66. Test opening and closing the main transaction.
  67. '''
  68. store.begin(True)
  69. assert store.is_txn_open
  70. store.commit()
  71. assert not store.is_txn_open
  72. store.begin(True)
  73. store.abort()
  74. assert not store.is_txn_open
  75. def test_ctx_mgr(self, store):
  76. '''
  77. Test enclosing a transaction in a context.
  78. '''
  79. with store.txn_ctx():
  80. assert store.is_txn_open
  81. assert not store.is_txn_rw
  82. assert not store.is_txn_open
  83. with store.txn_ctx(True):
  84. assert store.is_txn_open
  85. assert store.is_txn_rw
  86. assert not store.is_txn_open
  87. assert not store.is_txn_rw
  88. try:
  89. with store.txn_ctx():
  90. raise RuntimeError()
  91. except RuntimeError:
  92. assert not store.is_txn_open
  93. def test_rollback(self, store):
  94. '''
  95. Test rolling back a transaction.
  96. '''
  97. trp = (
  98. URIRef('urn:nogo:s'), URIRef('urn:nogo:p'), URIRef('urn:nogo:o')
  99. )
  100. try:
  101. with store.txn_ctx(True):
  102. store.add(trp)
  103. raise RuntimeError() # This should roll back the transaction.
  104. except RuntimeError:
  105. pass
  106. with store.txn_ctx():
  107. res = set(store.triples(trp))
  108. assert len(res) == 0
  109. def test_nested_ro_txn(self, store):
  110. """
  111. Test two nested RO transactions.
  112. """
  113. trp = (URIRef('urn:s:0'), URIRef('urn:p:0'), URIRef('urn:o:0'))
  114. with store.txn_ctx(True):
  115. store.add(trp)
  116. with store.txn_ctx():
  117. with store.txn_ctx():
  118. res = {*store.triples(trp)}
  119. assert trp in {q[0] for q in res}
  120. assert trp in {q[0] for q in res}
  121. def test_nested_ro_txn_nowrite(self, store):
  122. """
  123. Test two nested RO transactions.
  124. """
  125. trp = (URIRef('urn:s:0'), URIRef('urn:p:0'), URIRef('urn:o:0'))
  126. with pytest.raises(LmdbError):
  127. with store.txn_ctx():
  128. with store.txn_ctx():
  129. store.add(trp)
  130. def test_nested_ro_rw_txn(self, store):
  131. """
  132. Test a RO transaction nested into a RW one.
  133. """
  134. trp = (URIRef('urn:s:1'), URIRef('urn:p:1'), URIRef('urn:o:1'))
  135. with store.txn_ctx():
  136. with store.txn_ctx(True):
  137. store.add(trp)
  138. # Outer txn should now see the new triple.
  139. assert trp in {q[0] for q in store.triples(trp)}
  140. def test_nested_rw_ro_txn(self, store):
  141. """
  142. Test that a RO transaction nested in a RW transaction can write.
  143. """
  144. trp = (URIRef('urn:s:2'), URIRef('urn:p:2'), URIRef('urn:o:2'))
  145. with store.txn_ctx(True):
  146. with store.txn_ctx():
  147. store.add(trp)
  148. assert trp in {q[0] for q in store.triples(trp)}
  149. def test_nested_rw_rw_txn(self, store):
  150. """
  151. Test that a RW transaction nested in a RW transaction can write.
  152. """
  153. trp = (URIRef('urn:s:3'), URIRef('urn:p:3'), URIRef('urn:o:3'))
  154. with store.txn_ctx(True):
  155. with store.txn_ctx():
  156. store.add(trp)
  157. assert trp in {q[0] for q in store.triples(trp)}
  158. @pytest.mark.usefixtures('store')
  159. class TestBasicOps:
  160. '''
  161. High-level tests for basic operations.
  162. '''
  163. def test_create_triple(self, store):
  164. '''
  165. Test creation of a single triple.
  166. '''
  167. trp = (
  168. URIRef('urn:test:s'), URIRef('urn:test:p'), URIRef('urn:test:o'))
  169. with store.txn_ctx(True):
  170. store.add(trp)
  171. with store.txn_ctx():
  172. res1 = set(store.triples((None, None, None)))
  173. res2 = set(store.triples(trp))
  174. assert len(res1) == 1
  175. assert len(res2) == 1
  176. clean_res1 = _clean(res1)
  177. clean_res2 = _clean(res2)
  178. assert trp in clean_res1 & clean_res2
  179. def test_triple_match_1bound(self, store):
  180. '''
  181. Test triple patterns matching one bound term.
  182. '''
  183. with store.txn_ctx():
  184. res1 = set(store.triples((URIRef('urn:test:s'), None, None)))
  185. res2 = set(store.triples((None, URIRef('urn:test:p'), None)))
  186. res3 = set(store.triples((None, None, URIRef('urn:test:o'))))
  187. assert _clean(res1) == {(
  188. URIRef('urn:test:s'), URIRef('urn:test:p'),
  189. URIRef('urn:test:o'))}
  190. assert _clean(res2) == _clean(res1)
  191. assert _clean(res3) == _clean(res2)
  192. def test_triple_match_2bound(self, store):
  193. '''
  194. Test triple patterns matching two bound terms.
  195. '''
  196. with store.txn_ctx():
  197. res1 = set(store.triples(
  198. (URIRef('urn:test:s'), URIRef('urn:test:p'), None)))
  199. res2 = set(store.triples(
  200. (URIRef('urn:test:s'), None, URIRef('urn:test:o'))))
  201. res3 = set(store.triples(
  202. (None, URIRef('urn:test:p'), URIRef('urn:test:o'))))
  203. assert _clean(res1) == {(
  204. URIRef('urn:test:s'),
  205. URIRef('urn:test:p'), URIRef('urn:test:o'))}
  206. assert _clean(res2) == _clean(res1)
  207. assert _clean(res3) == _clean(res2)
  208. def test_triple_match_3bound(self, store):
  209. '''
  210. Test triple patterns matching 3 bound terms (exact match).
  211. '''
  212. with store.txn_ctx():
  213. pattern = (
  214. URIRef('urn:test:s'), URIRef('urn:test:p'),
  215. URIRef('urn:test:o'))
  216. res1 = set(store.triples(pattern))
  217. assert _clean(res1) == {pattern}
  218. def test_triple_no_match_1bound(self, store):
  219. '''
  220. Test empty matches with 1 bound term.
  221. '''
  222. with store.txn_ctx(True):
  223. store.add((
  224. URIRef('urn:test:s'),
  225. URIRef('urn:test:p2'), URIRef('urn:test:o2')))
  226. store.add((
  227. URIRef('urn:test:s3'),
  228. URIRef('urn:test:p3'), URIRef('urn:test:o3')))
  229. res1 = set(store.triples((None, None, None)))
  230. assert len(res1) == 3
  231. res1 = set(store.triples((URIRef('urn:test:s2'), None, None)))
  232. res2 = set(store.triples((None, URIRef('urn:test:p4'), None)))
  233. res3 = set(store.triples((None, None, URIRef('urn:test:o4'))))
  234. assert len(res1) == len(res2) == len(res3) == 0
  235. def test_triple_no_match_2bound(self, store):
  236. '''
  237. Test empty matches with 2 bound terms.
  238. '''
  239. with store.txn_ctx(True):
  240. res1 = set(store.triples(
  241. (URIRef('urn:test:s2'), URIRef('urn:test:p'), None)))
  242. res2 = set(store.triples(
  243. (URIRef('urn:test:s3'), None, URIRef('urn:test:o'))))
  244. res3 = set(store.triples(
  245. (None, URIRef('urn:test:p3'), URIRef('urn:test:o2'))))
  246. assert len(res1) == len(res2) == len(res3) == 0
  247. def test_triple_no_match_3bound(self, store):
  248. '''
  249. Test empty matches with 3 bound terms.
  250. '''
  251. with store.txn_ctx(True):
  252. res1 = set(store.triples((
  253. URIRef('urn:test:s2'), URIRef('urn:test:p3'),
  254. URIRef('urn:test:o2'))))
  255. assert len(res1) == 0
  256. def test_remove(self, store):
  257. '''
  258. Test removing one or more triples.
  259. '''
  260. with store.txn_ctx(True):
  261. store.remove((URIRef('urn:test:s3'),
  262. URIRef('urn:test:p3'), URIRef('urn:test:o3')))
  263. with store.txn_ctx():
  264. res1 = set(store.triples((None, None, None)))
  265. assert len(res1) == 2
  266. with store.txn_ctx(True):
  267. store.remove((URIRef('urn:test:s'), None, None))
  268. res2 = set(store.triples((None, None, None)))
  269. assert len(res2) == 0
  270. @pytest.mark.usefixtures('store', 'bogus_trp')
  271. class TestExtendedOps:
  272. '''
  273. Test additional store operations.
  274. '''
  275. def test_all_terms(self, store, bogus_trp):
  276. """
  277. Test the "all terms" mehods.
  278. """
  279. with store.txn_ctx(True):
  280. for trp in bogus_trp:
  281. store.add(trp)
  282. with store.txn_ctx():
  283. all_s = store.all_terms('s')
  284. all_p = store.all_terms('p')
  285. all_o = store.all_terms('o')
  286. assert len(all_s) == 1
  287. assert len(all_p) == 100
  288. assert len(all_o) == 1000
  289. assert URIRef('urn:test_mp:s1') in all_s
  290. assert URIRef('urn:test_mp:s1') not in all_p
  291. assert URIRef('urn:test_mp:s1') not in all_o
  292. assert URIRef('urn:test_mp:p10') not in all_s
  293. assert URIRef('urn:test_mp:p10') in all_p
  294. assert URIRef('urn:test_mp:p10') not in all_o
  295. assert URIRef('urn:test_mp:o99') not in all_s
  296. assert URIRef('urn:test_mp:o99') not in all_p
  297. assert URIRef('urn:test_mp:o99') in all_o
  298. @pytest.mark.usefixtures('store', 'bogus_trp')
  299. class TestEntryCount:
  300. '''
  301. Count number of entries in databases.
  302. '''
  303. @pytest.fixture
  304. def indices(self):
  305. """
  306. List of index labels.
  307. """
  308. return [
  309. 's:po',
  310. 'p:so',
  311. 'o:sp',
  312. 'po:s',
  313. 'so:p',
  314. 'sp:o',
  315. 'spo:c',
  316. 'c:spo',
  317. ]
  318. def test_init(self, store, bogus_trp):
  319. """
  320. Insert initial data and verify store length.
  321. """
  322. with store.txn_ctx(True):
  323. for trp in bogus_trp:
  324. store.add(trp)
  325. with store.txn_ctx():
  326. assert len(store) == 1000
  327. def test_entries_full(self, store, indices):
  328. """
  329. Count entries for the full data set.
  330. """
  331. with store.txn_ctx():
  332. stat = store.stats()
  333. for idxlabel in indices:
  334. assert stat['db_stats'][idxlabel]['ms_entries'] == 1000
  335. # 1 subject, 100 predicates, 1000 objects, 1 context
  336. assert stat['db_stats']['t:st']['ms_entries'] == 1102
  337. assert stat['db_stats']['th:t']['ms_entries'] == 1102
  338. def test_entries_partial(self, store, indices):
  339. """
  340. Delete some triples and verify numbers.
  341. """
  342. with store.txn_ctx(True):
  343. store.remove((URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p0'), None))
  344. with store.txn_ctx():
  345. stat = store.stats()
  346. assert stat['db_stats']['t:st']['ms_entries'] == 1102
  347. assert stat['db_stats']['th:t']['ms_entries'] == 1102
  348. def test_entries_empty(self, store, indices):
  349. """
  350. Delete all remainng triples and verify that indices are empty.
  351. """
  352. with store.txn_ctx(True):
  353. store.remove((None, None, None))
  354. with store.txn_ctx():
  355. stat = store.stats()
  356. for idxlabel in indices:
  357. assert stat['db_stats'][idxlabel]['ms_entries'] == 0
  358. assert stat['db_stats']['t:st']['ms_entries'] == 1102
  359. assert stat['db_stats']['th:t']['ms_entries'] == 1102
  360. @pytest.mark.usefixtures('store', 'bogus_trp')
  361. class TestMultiPageLookup:
  362. '''
  363. Tests looking up results retrieved in multiple pages.
  364. '''
  365. def test_init(self, store, bogus_trp):
  366. """
  367. Insert initial data and verify store length.
  368. """
  369. with store.txn_ctx(True):
  370. for trp in bogus_trp:
  371. store.add(trp)
  372. with store.txn_ctx():
  373. assert len(store) == 1000
  374. def test_mp_lookup_0bound(self, store):
  375. """
  376. Lookup all triples in a multi-page setup.
  377. """
  378. with store.txn_ctx(True):
  379. res = store.triples((None, None, None))
  380. res_set = set(res)
  381. assert len(res_set) == 1000
  382. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p99'),
  383. URIRef('urn:test_mp:o999')) in _clean(res_set)
  384. def test_mp_lookup_1bound(self, store):
  385. """
  386. Lookup 1-bound triples in a multi-page setup.
  387. """
  388. with store.txn_ctx(True):
  389. res1 = store.triples((URIRef('urn:test_mp:s1'), None, None))
  390. res_set1 = set(res1)
  391. res2 = store.triples((None, URIRef('urn:test_mp:p69'), None))
  392. res_set2 = set(res2)
  393. res3 = store.triples((None, None, URIRef('urn:test_mp:o699')))
  394. res_set3 = set(res3)
  395. assert len(res_set1) == 1000
  396. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p99'),
  397. URIRef('urn:test_mp:o999')) in _clean(res_set1)
  398. assert len(res_set2) == 10
  399. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p69'),
  400. URIRef('urn:test_mp:o699')) in _clean(res_set2)
  401. assert len(res_set3) == 1
  402. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p69'),
  403. URIRef('urn:test_mp:o699')) in _clean(res_set3)
  404. def test_mp_lookup_2bound(self, store):
  405. """
  406. Lookup 2-bound triples in a multi-page setup.
  407. """
  408. with store.txn_ctx(True):
  409. res1 = store.triples((URIRef('urn:test_mp:s1'),
  410. URIRef('urn:test_mp:p96'), None))
  411. res_set1 = set(res1)
  412. res2 = store.triples((URIRef('urn:test_mp:s1'),
  413. None, URIRef('urn:test_mp:o966')))
  414. res_set2 = set(res2)
  415. res3 = store.triples((None,
  416. URIRef('urn:test_mp:p96'), URIRef('urn:test_mp:o966')))
  417. res_set3 = set(res3)
  418. assert len(res_set1) == 10
  419. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p96'),
  420. URIRef('urn:test_mp:o966')) in _clean(res_set1)
  421. assert len(res_set2) == 1
  422. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p96'),
  423. URIRef('urn:test_mp:o966')) in _clean(res_set2)
  424. assert len(res_set3) == 1
  425. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p96'),
  426. URIRef('urn:test_mp:o966')) in _clean(res_set3)
  427. @pytest.mark.usefixtures('store')
  428. class TestRemoveMulti:
  429. '''
  430. Tests for proper removal of multiple combinations of triple and context.
  431. '''
  432. @pytest.fixture
  433. def data(self):
  434. return {
  435. 'spo1': (
  436. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o1')),
  437. 'spo2': (
  438. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o2')),
  439. 'spo3': (
  440. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o3')),
  441. 'c1': URIRef('urn:test:c1'),
  442. 'c2': URIRef('urn:test:c2'),
  443. 'c3': URIRef('urn:test:c3'),
  444. }
  445. def test_init(self, store, data):
  446. """
  447. Initialize the store with test data.
  448. """
  449. with store.txn_ctx(True):
  450. store.add(data['spo1'], data['c1'])
  451. store.add(data['spo2'], data['c1'])
  452. store.add(data['spo3'], data['c1'])
  453. store.add(data['spo1'], data['c2'])
  454. store.add(data['spo2'], data['c2'])
  455. store.add(data['spo3'], data['c2'])
  456. store.add(data['spo1'], data['c3'])
  457. store.add(data['spo2'], data['c3'])
  458. store.add(data['spo3'], data['c3'])
  459. with store.txn_ctx():
  460. assert len(store) == 9
  461. assert len(set(store.triples((None, None, None)))) == 3
  462. assert len(set(store.triples((None, None, None), data['c1']))) == 3
  463. assert len(set(store.triples((None, None, None), data['c2']))) == 3
  464. assert len(set(store.triples((None, None, None), data['c3']))) == 3
  465. def test_remove_1ctx(self, store, data):
  466. """
  467. Test removing all triples from a context.
  468. """
  469. with store.txn_ctx(True):
  470. store.remove((None, None, None), data['c1'])
  471. with store.txn_ctx():
  472. assert len(store) == 6
  473. assert len(set(store.triples((None, None, None)))) == 3
  474. assert len(set(store.triples((None, None, None), data['c1']))) == 0
  475. assert len(set(store.triples((None, None, None), data['c2']))) == 3
  476. assert len(set(store.triples((None, None, None), data['c3']))) == 3
  477. def test_remove_1subj(self, store, data):
  478. """
  479. Test removing one subject from all contexts.
  480. """
  481. with store.txn_ctx(True):
  482. store.remove((data['spo1'][0], None, None))
  483. assert len(store) == 0
  484. @pytest.mark.usefixtures('store')
  485. class TestCleanup:
  486. '''
  487. Tests for proper cleanup on resource deletion.
  488. '''
  489. @pytest.fixture
  490. def data(self):
  491. return {
  492. 'spo1': (
  493. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o1')),
  494. 'spo2': (
  495. URIRef('urn:test:s2'), URIRef('urn:test:p2'), URIRef('urn:test:o2')),
  496. 'c1': URIRef('urn:test:c1'),
  497. 'c2': URIRef('urn:test:c2'),
  498. }
  499. def _is_empty(self, store):
  500. stats = store.stats()['db_stats']
  501. for dblabel in ('spo:c', 'c:spo', 's:po', 'p:so', 'o:sp',):
  502. if stats[dblabel]['ms_entries'] > 0:
  503. return False
  504. return True
  505. def test_cleanup_spo(self, store, data):
  506. with store.txn_ctx(True):
  507. store.add(data['spo1'])
  508. with store.txn_ctx():
  509. assert not self._is_empty(store)
  510. with store.txn_ctx(True):
  511. store.remove(data['spo1'])
  512. with store.txn_ctx():
  513. assert self._is_empty(store)
  514. def test_cleanup_spoc1(self, store, data):
  515. with store.txn_ctx(True):
  516. store.add(data['spo1'], data['c1'])
  517. with store.txn_ctx():
  518. assert not self._is_empty(store)
  519. with store.txn_ctx(True):
  520. store.remove(data['spo1'], data['c1'])
  521. with store.txn_ctx():
  522. assert self._is_empty(store)
  523. def test_cleanup_spoc2(self, store, data):
  524. with store.txn_ctx(True):
  525. store.add(data['spo1'], data['c1'])
  526. with store.txn_ctx(True):
  527. store.remove((None, None, None), data['c1'])
  528. with store.txn_ctx():
  529. assert self._is_empty(store)
  530. def test_cleanup_spoc3(self, store, data):
  531. with store.txn_ctx(True):
  532. store.add(data['spo1'], data['c1'])
  533. store.add(data['spo2'], data['c1'])
  534. with store.txn_ctx(True):
  535. store.remove((data['spo1'][0], None, None), data['c1'])
  536. with store.txn_ctx():
  537. assert not self._is_empty(store)
  538. with store.txn_ctx(True):
  539. store.remove((data['spo2'][0], None, None), data['c1'])
  540. with store.txn_ctx():
  541. assert self._is_empty(store)
  542. def test_cleanup_spoc4(self, store, data):
  543. with store.txn_ctx(True):
  544. store.add(data['spo1'], data['c1'])
  545. store.add(data['spo2'], data['c1'])
  546. store.add(data['spo2'], data['c2'])
  547. with store.txn_ctx(True):
  548. store.remove((None, None, None))
  549. with store.txn_ctx():
  550. assert self._is_empty(store)
  551. @pytest.mark.usefixtures('store')
  552. class TestBindings:
  553. '''
  554. Tests for namespace bindings.
  555. '''
  556. @pytest.fixture
  557. def bindings(self):
  558. return (
  559. ('ns1', Namespace('http://test.org/ns#')),
  560. ('ns2', Namespace('http://my_org.net/ns#')),
  561. ('ns3', Namespace('urn:test:')),
  562. ('ns4', Namespace('info:myinst/graph#')),
  563. )
  564. def test_ns(self, store, bindings):
  565. '''
  566. Test namespace bindings.
  567. '''
  568. with store.txn_ctx(True):
  569. for b in bindings:
  570. store.bind(*b)
  571. nslist = list(store.namespaces())
  572. assert len(nslist) == len(bindings)
  573. for i in range(len(bindings)):
  574. assert nslist[i] == bindings[i]
  575. def test_ns2pfx(self, store, bindings):
  576. '''
  577. Test namespace to prefix conversion.
  578. '''
  579. with store.txn_ctx(True):
  580. for b in bindings:
  581. pfx, ns = b
  582. assert store.namespace(pfx) == ns
  583. def test_pfx2ns(self, store, bindings):
  584. '''
  585. Test namespace to prefix conversion.
  586. '''
  587. with store.txn_ctx(True):
  588. for b in bindings:
  589. pfx, ns = b
  590. assert store.prefix(ns) == pfx
  591. @pytest.mark.usefixtures('store')
  592. class TestContext:
  593. '''
  594. Tests for context handling.
  595. '''
  596. def test_add_empty_graph(self, store):
  597. '''
  598. Test creating an empty and a non-empty graph.
  599. '''
  600. gr_uri = URIRef('urn:bogus:graph#a')
  601. with store.txn_ctx(True):
  602. store.add_graph(gr_uri)
  603. assert gr_uri in store.contexts()
  604. def test_add_graph_with_triple(self, store):
  605. '''
  606. Test creating an empty and a non-empty graph.
  607. '''
  608. trp = (URIRef('urn:test:s123'),
  609. URIRef('urn:test:p123'), URIRef('urn:test:o123'))
  610. ctx_uri = URIRef('urn:bogus:graph#b')
  611. with store.txn_ctx(True):
  612. store.add(trp, ctx_uri)
  613. with store.txn_ctx():
  614. assert ctx_uri in store.contexts(trp)
  615. def test_empty_context(self, store):
  616. '''
  617. Test creating and deleting empty contexts.
  618. '''
  619. gr_uri = URIRef('urn:bogus:empty#a')
  620. with store.txn_ctx(True):
  621. store.add_graph(gr_uri)
  622. assert gr_uri in store.contexts()
  623. with store.txn_ctx(True):
  624. store.remove_graph(gr_uri)
  625. assert gr_uri not in store.contexts()
  626. def test_context_ro_txn(self, store):
  627. '''
  628. Test creating a context within a read-only transaction.
  629. '''
  630. gr_uri = URIRef('urn:bogus:empty#b')
  631. with store.txn_ctx():
  632. store.add_graph(gr_uri)
  633. a = 5 #bogus stuff for debugger
  634. # The lookup must happen in a separate transaction. The first
  635. # transaction opens a separate (non-child) R/W transaction while it is
  636. # already isolated so even after the RW txn is committed, the RO one
  637. # doesn't know anything about the changes.
  638. # If the RW transaction could be nested inside the RO one that would
  639. # allow a lookup in the same transaction, but this does not seem to be
  640. # possible.
  641. with store.txn_ctx():
  642. assert gr_uri in store.contexts()
  643. with store.txn_ctx(True):
  644. store.remove_graph(gr_uri)
  645. assert gr_uri not in store.contexts()
  646. def test_add_trp_to_ctx(self, store):
  647. '''
  648. Test adding triples to a graph.
  649. '''
  650. gr_uri = URIRef('urn:bogus:graph#a') # From previous test
  651. gr2_uri = URIRef('urn:bogus:graph#z') # Never created before
  652. trp1 = (URIRef('urn:s:1'), URIRef('urn:p:1'), URIRef('urn:o:1'))
  653. trp2 = (URIRef('urn:s:2'), URIRef('urn:p:2'), URIRef('urn:o:2'))
  654. trp3 = (URIRef('urn:s:3'), URIRef('urn:p:3'), URIRef('urn:o:3'))
  655. trp4 = (URIRef('urn:s:4'), URIRef('urn:p:4'), URIRef('urn:o:4'))
  656. with store.txn_ctx(True):
  657. store.add(trp1, gr_uri)
  658. store.add(trp2, gr_uri)
  659. store.add(trp2, gr_uri) # Duplicate; dropped.
  660. store.add(trp2, None) # Goes to the default graph.
  661. store.add(trp3, gr2_uri)
  662. store.add(trp3, gr_uri)
  663. store.add(trp4) # Goes to the default graph.
  664. # Quick size checks.
  665. with store.txn_ctx():
  666. assert len(set(store.triples((None, None, None)))) == 5
  667. assert len(set(store.triples((None, None, None),
  668. RDFLIB_DEFAULT_GRAPH_URI))) == 2
  669. assert len(set(store.triples((None, None, None), gr_uri))) == 3
  670. assert len(set(store.triples((None, None, None), gr2_uri))) == 1
  671. assert gr2_uri in store.contexts()
  672. assert trp1 in _clean(store.triples((None, None, None)))
  673. assert trp1 not in _clean(store.triples((None, None, None),
  674. RDFLIB_DEFAULT_GRAPH_URI))
  675. assert trp2 in _clean(store.triples((None, None, None), gr_uri))
  676. assert trp2 in _clean(store.triples((None, None, None)))
  677. assert trp3 in _clean(store.triples((None, None, None), gr2_uri))
  678. assert trp3 not in _clean(store.triples((None, None, None),
  679. RDFLIB_DEFAULT_GRAPH_URI))
  680. # Verify that contexts are in the right place.
  681. with store.txn_ctx():
  682. # trp3 is in both graphs.
  683. res_no_ctx = store.triples(trp3)
  684. res_ctx = store.triples(trp3, gr2_uri)
  685. for res in res_no_ctx:
  686. assert Graph(uri=gr_uri) in res[1]
  687. assert Graph(uri=gr2_uri) in res[1]
  688. for res in res_ctx:
  689. assert Graph(uri=gr_uri) in res[1]
  690. assert Graph(uri=gr2_uri) in res[1]
  691. def test_delete_from_ctx(self, store):
  692. '''
  693. Delete triples from a named graph and from the default graph.
  694. '''
  695. gr_uri = URIRef('urn:bogus:graph#a')
  696. gr2_uri = URIRef('urn:bogus:graph#b')
  697. with store.txn_ctx(True):
  698. store.remove((None, None, None), gr2_uri)
  699. assert len(set(store.triples((None, None, None), gr2_uri))) == 0
  700. assert len(set(store.triples((None, None, None), gr_uri))) == 3
  701. with store.txn_ctx(True):
  702. store.remove((URIRef('urn:s:1'), None, None))
  703. assert len(set(store.triples(
  704. (URIRef('urn:s:1'), None, None), gr_uri))) == 0
  705. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  706. assert len(set(store.triples((None, None, None)))) == 3
  707. # This should result in no change because the graph does not exist.
  708. with store.txn_ctx(True):
  709. store.remove((None, None, None), URIRef('urn:phony:graph#xyz'))
  710. store.remove(
  711. (URIRef('urn:s:1'), None, None),
  712. URIRef('urn:phony:graph#xyz'))
  713. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  714. assert len(set(store.triples((None, None, None)))) == 3
  715. with store.txn_ctx(True):
  716. store.remove((URIRef('urn:s:4'), None, None),
  717. RDFLIB_DEFAULT_GRAPH_URI)
  718. assert len(set(store.triples((None, None, None)))) == 2
  719. with store.txn_ctx(True):
  720. store.remove((None, None, None))
  721. assert len(set(store.triples((None, None, None)))) == 0
  722. assert len(set(store.triples((None, None, None), gr_uri))) == 0
  723. assert len(store) == 0
  724. def test_remove_shared_ctx(self, store):
  725. """
  726. Remove a context that shares triples with another one.
  727. """
  728. trp1 = (
  729. URIRef('urn:bogus:shared_s:1'), URIRef('urn:bogus:shared_p:1'),
  730. URIRef('urn:bogus:shared_o:1'))
  731. trp2 = (
  732. URIRef('urn:bogus:shared_s:2'), URIRef('urn:bogus:shared_p:2'),
  733. URIRef('urn:bogus:shared_o:2'))
  734. trp3 = (
  735. URIRef('urn:bogus:shared_s:3'), URIRef('urn:bogus:shared_p:3'),
  736. URIRef('urn:bogus:shared_o:3'))
  737. ctx1 = URIRef('urn:bogus:shared_graph#a')
  738. ctx2 = URIRef('urn:bogus:shared_graph#b')
  739. with store.txn_ctx(True):
  740. store.add(trp1, ctx1)
  741. store.add(trp2, ctx1)
  742. store.add(trp2, ctx2)
  743. store.add(trp3, ctx2)
  744. with store.txn_ctx(True):
  745. store.remove_graph(ctx1)
  746. with store.txn_ctx():
  747. assert len(set(store.triples(trp1))) == 0
  748. assert len(set(store.triples(trp2))) == 1
  749. assert len(set(store.triples(trp3))) == 1
  750. #@pytest.mark.usefixtures('store')
  751. #class TestRdflib:
  752. # '''
  753. # Test case adapted from
  754. # http://rdflib.readthedocs.io/en/stable/univrdfstore.html#interface-test-cases
  755. # '''
  756. #
  757. # @pytest.fixture
  758. # def sample_gr(self):
  759. # from rdflib import Graph, plugin
  760. # from rdflib.store import Store
  761. # store = plugin.get('Lmdb', Store)('/tmp/rdflibtest')
  762. # return Graph(store).parse('''
  763. # @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
  764. # @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  765. # @prefix : <http://test/> .
  766. # {:a :b :c; a :foo} => {:a :d :c} .
  767. # _:foo a rdfs:Class .
  768. # :a :d :c .
  769. # ''', format='n3')
  770. #
  771. # def test_basic(self, sample_gr):
  772. # from rdflib.namespace import RDF
  773. # with sample_gr.store.txn_ctx():
  774. # implies = URIRef("http://www.w3.org/2000/10/swap/log#implies")
  775. # a = URIRef('http://test/a')
  776. # b = URIRef('http://test/b')
  777. # c = URIRef('http://test/c')
  778. # d = URIRef('http://test/d')
  779. # for s,p,o in g.triples((None,implies,None)):
  780. # formulaA = s
  781. # formulaB = o
  782. #
  783. # #contexts test
  784. # assert len(list(g.contexts()))==3
  785. #
  786. # #contexts (with triple) test
  787. # assert len(list(g.contexts((a,d,c))))==2
  788. #
  789. # #triples test cases
  790. # assert type(list(g.triples(
  791. # (None,RDF.type,RDFS.Class)))[0][0]) == BNode
  792. # assert len(list(g.triples((None,implies,None))))==1
  793. # assert len(list(g.triples((None,RDF.type,None))))==3
  794. # assert len(list(g.triples((None,RDF.type,None),formulaA)))==1
  795. # assert len(list(g.triples((None,None,None),formulaA)))==2
  796. # assert len(list(g.triples((None,None,None),formulaB)))==1
  797. # assert len(list(g.triples((None,None,None))))==5
  798. # assert len(list(g.triples(
  799. # (None,URIRef('http://test/d'),None),formulaB)))==1
  800. # assert len(list(g.triples(
  801. # (None,URIRef('http://test/d'),None))))==1
  802. #
  803. # #Remove test cases
  804. # g.remove((None,implies,None))
  805. # assert len(list(g.triples((None,implies,None))))==0
  806. # assert len(list(g.triples((None,None,None),formulaA)))==2
  807. # assert len(list(g.triples((None,None,None),formulaB)))==1
  808. # g.remove((None,b,None),formulaA)
  809. # assert len(list(g.triples((None,None,None),formulaA)))==1
  810. # g.remove((None,RDF.type,None),formulaA)
  811. # assert len(list(g.triples((None,None,None),formulaA)))==0
  812. # g.remove((None,RDF.type,RDFS.Class))
  813. #
  814. # #remove_context tests
  815. # formulaBContext=Context(g,formulaB)
  816. # g.remove_context(formulaB)
  817. # assert len(list(g.triples((None,RDF.type,None))))==2
  818. # assert len(g)==3
  819. # assert len(formulaBContext)==0
  820. # g.remove((None,None,None))
  821. # assert len(g)==0