test_lmdb_store.py 29 KB

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