test_lmdb_store.py 29 KB

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