test_lmdb_store.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  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.rdf.graph import Graph
  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 TestExtendedOps:
  215. '''
  216. Test additional store operations.
  217. '''
  218. def test_all_terms(self, store, bogus_trp):
  219. """
  220. Test the "all terms" mehods.
  221. """
  222. with store.txn_ctx(True):
  223. for trp in bogus_trp:
  224. store.add(trp)
  225. with store.txn_ctx():
  226. all_s = store.all_terms('s')
  227. all_p = store.all_terms('p')
  228. all_o = store.all_terms('o')
  229. assert len(all_s) == 1
  230. assert len(all_p) == 100
  231. assert len(all_o) == 1000
  232. assert URIRef('urn:test_mp:s1') in all_s
  233. assert URIRef('urn:test_mp:s1') not in all_p
  234. assert URIRef('urn:test_mp:s1') not in all_o
  235. assert URIRef('urn:test_mp:p10') not in all_s
  236. assert URIRef('urn:test_mp:p10') in all_p
  237. assert URIRef('urn:test_mp:p10') not in all_o
  238. assert URIRef('urn:test_mp:o99') not in all_s
  239. assert URIRef('urn:test_mp:o99') not in all_p
  240. assert URIRef('urn:test_mp:o99') in all_o
  241. @pytest.mark.usefixtures('store', 'bogus_trp')
  242. class TestEntryCount:
  243. '''
  244. Count number of entries in databases.
  245. '''
  246. @pytest.fixture
  247. def indices(self):
  248. """
  249. List of index labels.
  250. """
  251. return [
  252. 's:po',
  253. 'p:so',
  254. 'o:sp',
  255. 'po:s',
  256. 'so:p',
  257. 'sp:o',
  258. 'spo:c',
  259. 'c:spo',
  260. ]
  261. def test_init(self, store, bogus_trp):
  262. """
  263. Insert initial data and verify store length.
  264. """
  265. with store.txn_ctx(True):
  266. for trp in bogus_trp:
  267. store.add(trp)
  268. with store.txn_ctx():
  269. assert len(store) == 1000
  270. def test_entries_full(self, store, indices):
  271. """
  272. Count entries for the full data set.
  273. """
  274. with store.txn_ctx():
  275. stat = store.stats()
  276. for idxlabel in indices:
  277. assert stat['db_stats'][idxlabel]['ms_entries'] == 1000
  278. # 1 subject, 100 predicates, 1000 objects, 1 context
  279. assert stat['db_stats']['t:st']['ms_entries'] == 1102
  280. assert stat['db_stats']['th:t']['ms_entries'] == 1102
  281. def test_entries_partial(self, store, indices):
  282. """
  283. Delete some triples and verify numbers.
  284. """
  285. with store.txn_ctx(True):
  286. store.remove((URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p0'), None))
  287. with store.txn_ctx():
  288. stat = store.stats()
  289. assert stat['db_stats']['t:st']['ms_entries'] == 1102
  290. assert stat['db_stats']['th:t']['ms_entries'] == 1102
  291. def test_entries_empty(self, store, indices):
  292. """
  293. Delete all remainng triples and verify that indices are empty.
  294. """
  295. with store.txn_ctx(True):
  296. store.remove((None, None, None))
  297. with store.txn_ctx():
  298. stat = store.stats()
  299. for idxlabel in indices:
  300. assert stat['db_stats'][idxlabel]['ms_entries'] == 0
  301. assert stat['db_stats']['t:st']['ms_entries'] == 1102
  302. assert stat['db_stats']['th:t']['ms_entries'] == 1102
  303. @pytest.mark.usefixtures('store', 'bogus_trp')
  304. class TestMultiPageLookup:
  305. '''
  306. Tests looking up results retrieved in multiple pages.
  307. '''
  308. def test_init(self, store, bogus_trp):
  309. """
  310. Insert initial data and verify store length.
  311. """
  312. with store.txn_ctx(True):
  313. for trp in bogus_trp:
  314. store.add(trp)
  315. with store.txn_ctx():
  316. assert len(store) == 1000
  317. def test_mp_lookup_0bound(self, store):
  318. """
  319. Lookup all triples in a multi-page setup.
  320. """
  321. with store.txn_ctx(True):
  322. res = store.triples((None, None, None))
  323. res_set = set(res)
  324. assert len(res_set) == 1000
  325. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p99'),
  326. URIRef('urn:test_mp:o999')) in _clean(res_set)
  327. def test_mp_lookup_1bound(self, store):
  328. """
  329. Lookup 1-bound triples in a multi-page setup.
  330. """
  331. with store.txn_ctx(True):
  332. res1 = store.triples((URIRef('urn:test_mp:s1'), None, None))
  333. res_set1 = set(res1)
  334. res2 = store.triples((None, URIRef('urn:test_mp:p69'), None))
  335. res_set2 = set(res2)
  336. res3 = store.triples((None, None, URIRef('urn:test_mp:o699')))
  337. res_set3 = set(res3)
  338. assert len(res_set1) == 1000
  339. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p99'),
  340. URIRef('urn:test_mp:o999')) in _clean(res_set1)
  341. assert len(res_set2) == 10
  342. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p69'),
  343. URIRef('urn:test_mp:o699')) in _clean(res_set2)
  344. assert len(res_set3) == 1
  345. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p69'),
  346. URIRef('urn:test_mp:o699')) in _clean(res_set3)
  347. def test_mp_lookup_2bound(self, store):
  348. """
  349. Lookup 2-bound triples in a multi-page setup.
  350. """
  351. with store.txn_ctx(True):
  352. res1 = store.triples((URIRef('urn:test_mp:s1'),
  353. URIRef('urn:test_mp:p96'), None))
  354. res_set1 = set(res1)
  355. res2 = store.triples((URIRef('urn:test_mp:s1'),
  356. None, URIRef('urn:test_mp:o966')))
  357. res_set2 = set(res2)
  358. res3 = store.triples((None,
  359. URIRef('urn:test_mp:p96'), URIRef('urn:test_mp:o966')))
  360. res_set3 = set(res3)
  361. assert len(res_set1) == 10
  362. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p96'),
  363. URIRef('urn:test_mp:o966')) in _clean(res_set1)
  364. assert len(res_set2) == 1
  365. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p96'),
  366. URIRef('urn:test_mp:o966')) in _clean(res_set2)
  367. assert len(res_set3) == 1
  368. assert (URIRef('urn:test_mp:s1'), URIRef('urn:test_mp:p96'),
  369. URIRef('urn:test_mp:o966')) in _clean(res_set3)
  370. @pytest.mark.usefixtures('store')
  371. class TestRemoveMulti:
  372. '''
  373. Tests for proper removal of multiple combinations of triple and context.
  374. '''
  375. @pytest.fixture
  376. def data(self):
  377. return {
  378. 'spo1': (
  379. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o1')),
  380. 'spo2': (
  381. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o2')),
  382. 'spo3': (
  383. URIRef('urn:test:s1'), URIRef('urn:test:p1'), URIRef('urn:test:o3')),
  384. 'c1': URIRef('urn:test:c1'),
  385. 'c2': URIRef('urn:test:c2'),
  386. 'c3': URIRef('urn:test:c3'),
  387. }
  388. def test_init(self, store, data):
  389. """
  390. Initialize the store with test data.
  391. """
  392. with store.txn_ctx(True):
  393. store.add(data['spo1'], data['c1'])
  394. store.add(data['spo2'], data['c1'])
  395. store.add(data['spo3'], data['c1'])
  396. store.add(data['spo1'], data['c2'])
  397. store.add(data['spo2'], data['c2'])
  398. store.add(data['spo3'], data['c2'])
  399. store.add(data['spo1'], data['c3'])
  400. store.add(data['spo2'], data['c3'])
  401. store.add(data['spo3'], data['c3'])
  402. with store.txn_ctx():
  403. assert len(store) == 9
  404. assert len(set(store.triples((None, None, None)))) == 3
  405. assert len(set(store.triples((None, None, None), data['c1']))) == 3
  406. assert len(set(store.triples((None, None, None), data['c2']))) == 3
  407. assert len(set(store.triples((None, None, None), data['c3']))) == 3
  408. def test_remove_1ctx(self, store, data):
  409. """
  410. Test removing all triples from a context.
  411. """
  412. with store.txn_ctx(True):
  413. store.remove((None, None, None), data['c1'])
  414. with store.txn_ctx():
  415. assert len(store) == 6
  416. assert len(set(store.triples((None, None, None)))) == 3
  417. assert len(set(store.triples((None, None, None), data['c1']))) == 0
  418. assert len(set(store.triples((None, None, None), data['c2']))) == 3
  419. assert len(set(store.triples((None, None, None), data['c3']))) == 3
  420. def test_remove_1subj(self, store, data):
  421. """
  422. Test removing one subject from all contexts.
  423. """
  424. with store.txn_ctx(True):
  425. store.remove((data['spo1'][0], None, None))
  426. assert len(store) == 0
  427. @pytest.mark.usefixtures('store')
  428. class TestCleanup:
  429. '''
  430. Tests for proper cleanup on resource deletion.
  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:s2'), URIRef('urn:test:p2'), URIRef('urn:test:o2')),
  439. 'c1': URIRef('urn:test:c1'),
  440. 'c2': URIRef('urn:test:c2'),
  441. }
  442. def _is_empty(self, store):
  443. stats = store.stats()['db_stats']
  444. for dblabel in ('spo:c', 'c:spo', 's:po', 'p:so', 'o:sp',):
  445. if stats[dblabel]['ms_entries'] > 0:
  446. return False
  447. return True
  448. def test_cleanup_spo(self, store, data):
  449. with store.txn_ctx(True):
  450. store.add(data['spo1'])
  451. with store.txn_ctx():
  452. assert not self._is_empty(store)
  453. with store.txn_ctx(True):
  454. store.remove(data['spo1'])
  455. with store.txn_ctx():
  456. assert self._is_empty(store)
  457. def test_cleanup_spoc1(self, store, data):
  458. with store.txn_ctx(True):
  459. store.add(data['spo1'], data['c1'])
  460. with store.txn_ctx():
  461. assert not self._is_empty(store)
  462. with store.txn_ctx(True):
  463. store.remove(data['spo1'], data['c1'])
  464. with store.txn_ctx():
  465. assert self._is_empty(store)
  466. def test_cleanup_spoc2(self, store, data):
  467. with store.txn_ctx(True):
  468. store.add(data['spo1'], data['c1'])
  469. with store.txn_ctx(True):
  470. store.remove((None, None, None), data['c1'])
  471. with store.txn_ctx():
  472. assert self._is_empty(store)
  473. def test_cleanup_spoc3(self, store, data):
  474. with store.txn_ctx(True):
  475. store.add(data['spo1'], data['c1'])
  476. store.add(data['spo2'], data['c1'])
  477. with store.txn_ctx(True):
  478. store.remove((data['spo1'][0], None, None), data['c1'])
  479. with store.txn_ctx():
  480. assert not self._is_empty(store)
  481. with store.txn_ctx(True):
  482. store.remove((data['spo2'][0], None, None), data['c1'])
  483. with store.txn_ctx():
  484. assert self._is_empty(store)
  485. def test_cleanup_spoc4(self, store, data):
  486. with store.txn_ctx(True):
  487. store.add(data['spo1'], data['c1'])
  488. store.add(data['spo2'], data['c1'])
  489. store.add(data['spo2'], data['c2'])
  490. with store.txn_ctx(True):
  491. store.remove((None, None, None))
  492. with store.txn_ctx():
  493. assert self._is_empty(store)
  494. @pytest.mark.usefixtures('store')
  495. class TestBindings:
  496. '''
  497. Tests for namespace bindings.
  498. '''
  499. @pytest.fixture
  500. def bindings(self):
  501. return (
  502. ('ns1', Namespace('http://test.org/ns#')),
  503. ('ns2', Namespace('http://my_org.net/ns#')),
  504. ('ns3', Namespace('urn:test:')),
  505. ('ns4', Namespace('info:myinst/graph#')),
  506. )
  507. def test_ns(self, store, bindings):
  508. '''
  509. Test namespace bindings.
  510. '''
  511. with store.txn_ctx(True):
  512. for b in bindings:
  513. store.bind(*b)
  514. nslist = list(store.namespaces())
  515. assert len(nslist) == len(bindings)
  516. for i in range(len(bindings)):
  517. assert nslist[i] == bindings[i]
  518. def test_ns2pfx(self, store, bindings):
  519. '''
  520. Test namespace to prefix conversion.
  521. '''
  522. with store.txn_ctx(True):
  523. for b in bindings:
  524. pfx, ns = b
  525. assert store.namespace(pfx) == ns
  526. def test_pfx2ns(self, store, bindings):
  527. '''
  528. Test namespace to prefix conversion.
  529. '''
  530. with store.txn_ctx(True):
  531. for b in bindings:
  532. pfx, ns = b
  533. assert store.prefix(ns) == pfx
  534. @pytest.mark.usefixtures('store')
  535. class TestContext:
  536. '''
  537. Tests for context handling.
  538. '''
  539. def test_add_empty_graph(self, store):
  540. '''
  541. Test creating an empty and a non-empty graph.
  542. '''
  543. gr_uri = URIRef('urn:bogus:graph#a')
  544. with store.txn_ctx(True):
  545. store.add_graph(gr_uri)
  546. assert gr_uri in store.contexts()
  547. def test_add_graph_with_triple(self, store):
  548. '''
  549. Test creating an empty and a non-empty graph.
  550. '''
  551. trp = (URIRef('urn:test:s123'),
  552. URIRef('urn:test:p123'), URIRef('urn:test:o123'))
  553. ctx_uri = URIRef('urn:bogus:graph#b')
  554. with store.txn_ctx(True):
  555. store.add(trp, ctx_uri)
  556. with store.txn_ctx():
  557. assert ctx_uri in store.contexts(trp)
  558. def test_empty_context(self, store):
  559. '''
  560. Test creating and deleting empty contexts.
  561. '''
  562. gr_uri = URIRef('urn:bogus:empty#a')
  563. with store.txn_ctx(True):
  564. store.add_graph(gr_uri)
  565. assert gr_uri in store.contexts()
  566. with store.txn_ctx(True):
  567. store.remove_graph(gr_uri)
  568. assert gr_uri not in store.contexts()
  569. def test_context_ro_txn(self, store):
  570. '''
  571. Test creating a context within a read-only transaction.
  572. '''
  573. gr_uri = URIRef('urn:bogus:empty#b')
  574. with store.txn_ctx():
  575. store.add_graph(gr_uri)
  576. a = 5 #bogus stuff for debugger
  577. # The lookup must happen in a separate transaction. The first
  578. # transaction opens a separate (non-child) R/W transaction while it is
  579. # already isolated so even after the RW txn is committed, the RO one
  580. # doesn't know anything about the changes.
  581. # If the RW transaction could be nested inside the RO one that would
  582. # allow a lookup in the same transaction, but this does not seem to be
  583. # possible.
  584. with store.txn_ctx():
  585. assert gr_uri in store.contexts()
  586. with store.txn_ctx(True):
  587. store.remove_graph(gr_uri)
  588. assert gr_uri not in store.contexts()
  589. def test_add_trp_to_ctx(self, store):
  590. '''
  591. Test adding triples to a graph.
  592. '''
  593. gr_uri = URIRef('urn:bogus:graph#a') # From previous test
  594. gr2_uri = URIRef('urn:bogus:graph#z') # Never created before
  595. trp1 = (URIRef('urn:s:1'), URIRef('urn:p:1'), URIRef('urn:o:1'))
  596. trp2 = (URIRef('urn:s:2'), URIRef('urn:p:2'), URIRef('urn:o:2'))
  597. trp3 = (URIRef('urn:s:3'), URIRef('urn:p:3'), URIRef('urn:o:3'))
  598. trp4 = (URIRef('urn:s:4'), URIRef('urn:p:4'), URIRef('urn:o:4'))
  599. with store.txn_ctx(True):
  600. store.add(trp1, gr_uri)
  601. store.add(trp2, gr_uri)
  602. store.add(trp2, gr_uri) # Duplicate; dropped.
  603. store.add(trp2, None) # Goes to the default graph.
  604. store.add(trp3, gr2_uri)
  605. store.add(trp3, gr_uri)
  606. store.add(trp4) # Goes to the default graph.
  607. # Quick size checks.
  608. with store.txn_ctx():
  609. assert len(set(store.triples((None, None, None)))) == 5
  610. assert len(set(store.triples((None, None, None),
  611. RDFLIB_DEFAULT_GRAPH_URI))) == 2
  612. assert len(set(store.triples((None, None, None), gr_uri))) == 3
  613. assert len(set(store.triples((None, None, None), gr2_uri))) == 1
  614. assert gr2_uri in store.contexts()
  615. assert trp1 in _clean(store.triples((None, None, None)))
  616. assert trp1 not in _clean(store.triples((None, None, None),
  617. RDFLIB_DEFAULT_GRAPH_URI))
  618. assert trp2 in _clean(store.triples((None, None, None), gr_uri))
  619. assert trp2 in _clean(store.triples((None, None, None)))
  620. assert trp3 in _clean(store.triples((None, None, None), gr2_uri))
  621. assert trp3 not in _clean(store.triples((None, None, None),
  622. RDFLIB_DEFAULT_GRAPH_URI))
  623. # Verify that contexts are in the right place.
  624. with store.txn_ctx():
  625. # trp3 is in both graphs.
  626. res_no_ctx = store.triples(trp3)
  627. res_ctx = store.triples(trp3, gr2_uri)
  628. for res in res_no_ctx:
  629. assert Graph(uri=gr_uri) in res[1]
  630. assert Graph(uri=gr2_uri) in res[1]
  631. for res in res_ctx:
  632. assert Graph(uri=gr_uri) in res[1]
  633. assert Graph(uri=gr2_uri) in res[1]
  634. def test_delete_from_ctx(self, store):
  635. '''
  636. Delete triples from a named graph and from the default graph.
  637. '''
  638. gr_uri = URIRef('urn:bogus:graph#a')
  639. gr2_uri = URIRef('urn:bogus:graph#b')
  640. with store.txn_ctx(True):
  641. store.remove((None, None, None), gr2_uri)
  642. assert len(set(store.triples((None, None, None), gr2_uri))) == 0
  643. assert len(set(store.triples((None, None, None), gr_uri))) == 3
  644. with store.txn_ctx(True):
  645. store.remove((URIRef('urn:s:1'), None, None))
  646. assert len(set(store.triples(
  647. (URIRef('urn:s:1'), None, None), gr_uri))) == 0
  648. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  649. assert len(set(store.triples((None, None, None)))) == 3
  650. # This should result in no change because the graph does not exist.
  651. with store.txn_ctx(True):
  652. store.remove((None, None, None), URIRef('urn:phony:graph#xyz'))
  653. store.remove(
  654. (URIRef('urn:s:1'), None, None),
  655. URIRef('urn:phony:graph#xyz'))
  656. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  657. assert len(set(store.triples((None, None, None)))) == 3
  658. with store.txn_ctx(True):
  659. store.remove((URIRef('urn:s:4'), None, None),
  660. RDFLIB_DEFAULT_GRAPH_URI)
  661. assert len(set(store.triples((None, None, None)))) == 2
  662. with store.txn_ctx(True):
  663. store.remove((None, None, None))
  664. assert len(set(store.triples((None, None, None)))) == 0
  665. assert len(set(store.triples((None, None, None), gr_uri))) == 0
  666. assert len(store) == 0
  667. def test_remove_shared_ctx(self, store):
  668. """
  669. Remove a context that shares triples with another one.
  670. """
  671. trp1 = (
  672. URIRef('urn:bogus:shared_s:1'), URIRef('urn:bogus:shared_p:1'),
  673. URIRef('urn:bogus:shared_o:1'))
  674. trp2 = (
  675. URIRef('urn:bogus:shared_s:2'), URIRef('urn:bogus:shared_p:2'),
  676. URIRef('urn:bogus:shared_o:2'))
  677. trp3 = (
  678. URIRef('urn:bogus:shared_s:3'), URIRef('urn:bogus:shared_p:3'),
  679. URIRef('urn:bogus:shared_o:3'))
  680. ctx1 = URIRef('urn:bogus:shared_graph#a')
  681. ctx2 = URIRef('urn:bogus:shared_graph#b')
  682. with store.txn_ctx(True):
  683. store.add(trp1, ctx1)
  684. store.add(trp2, ctx1)
  685. store.add(trp2, ctx2)
  686. store.add(trp3, ctx2)
  687. with store.txn_ctx(True):
  688. store.remove_graph(ctx1)
  689. with store.txn_ctx():
  690. assert len(set(store.triples(trp1))) == 0
  691. assert len(set(store.triples(trp2))) == 1
  692. assert len(set(store.triples(trp3))) == 1
  693. @pytest.mark.usefixtures('store')
  694. class TestTransactions:
  695. '''
  696. Tests for transaction handling.
  697. '''
  698. # @TODO Test concurrent reads and writes.
  699. pass
  700. #@pytest.mark.usefixtures('store')
  701. #class TestRdflib:
  702. # '''
  703. # Test case adapted from
  704. # http://rdflib.readthedocs.io/en/stable/univrdfstore.html#interface-test-cases
  705. # '''
  706. #
  707. # @pytest.fixture
  708. # def sample_gr(self):
  709. # from rdflib import Graph, plugin
  710. # from rdflib.store import Store
  711. # store = plugin.get('Lmdb', Store)('/tmp/rdflibtest')
  712. # return Graph(store).parse('''
  713. # @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
  714. # @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  715. # @prefix : <http://test/> .
  716. # {:a :b :c; a :foo} => {:a :d :c} .
  717. # _:foo a rdfs:Class .
  718. # :a :d :c .
  719. # ''', format='n3')
  720. #
  721. # def test_basic(self, sample_gr):
  722. # from rdflib.namespace import RDF
  723. # with sample_gr.store.txn_ctx():
  724. # implies = URIRef("http://www.w3.org/2000/10/swap/log#implies")
  725. # a = URIRef('http://test/a')
  726. # b = URIRef('http://test/b')
  727. # c = URIRef('http://test/c')
  728. # d = URIRef('http://test/d')
  729. # for s,p,o in g.triples((None,implies,None)):
  730. # formulaA = s
  731. # formulaB = o
  732. #
  733. # #contexts test
  734. # assert len(list(g.contexts()))==3
  735. #
  736. # #contexts (with triple) test
  737. # assert len(list(g.contexts((a,d,c))))==2
  738. #
  739. # #triples test cases
  740. # assert type(list(g.triples(
  741. # (None,RDF.type,RDFS.Class)))[0][0]) == BNode
  742. # assert len(list(g.triples((None,implies,None))))==1
  743. # assert len(list(g.triples((None,RDF.type,None))))==3
  744. # assert len(list(g.triples((None,RDF.type,None),formulaA)))==1
  745. # assert len(list(g.triples((None,None,None),formulaA)))==2
  746. # assert len(list(g.triples((None,None,None),formulaB)))==1
  747. # assert len(list(g.triples((None,None,None))))==5
  748. # assert len(list(g.triples(
  749. # (None,URIRef('http://test/d'),None),formulaB)))==1
  750. # assert len(list(g.triples(
  751. # (None,URIRef('http://test/d'),None))))==1
  752. #
  753. # #Remove test cases
  754. # g.remove((None,implies,None))
  755. # assert len(list(g.triples((None,implies,None))))==0
  756. # assert len(list(g.triples((None,None,None),formulaA)))==2
  757. # assert len(list(g.triples((None,None,None),formulaB)))==1
  758. # g.remove((None,b,None),formulaA)
  759. # assert len(list(g.triples((None,None,None),formulaA)))==1
  760. # g.remove((None,RDF.type,None),formulaA)
  761. # assert len(list(g.triples((None,None,None),formulaA)))==0
  762. # g.remove((None,RDF.type,RDFS.Class))
  763. #
  764. # #remove_context tests
  765. # formulaBContext=Context(g,formulaB)
  766. # g.remove_context(formulaB)
  767. # assert len(list(g.triples((None,RDF.type,None))))==2
  768. # assert len(g)==3
  769. # assert len(formulaBContext)==0
  770. # g.remove((None,None,None))
  771. # assert len(g)==0