store_mdb.c 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277
  1. #include <ftw.h>
  2. #include "store_mdb.h"
  3. /**
  4. * Number of DBs defined.
  5. */
  6. #define N_DB 12
  7. /**
  8. * Memory map size.
  9. */
  10. #if !(defined __LP64__ || defined __LLP64__) || \
  11. defined _WIN32 && !defined _WIN64
  12. #define DEFAULT_MAPSIZE 1<<31 // 2Gb (limit for 32-bit systems)
  13. #else
  14. #define DEFAULT_MAPSIZE 1UL<<40 // 1Tb
  15. #endif
  16. #define DEFAULT_ENV_PATH "./mdb_store"
  17. #define ENV_DIR_MODE 0750
  18. #define ENV_FILE_MODE 0640
  19. typedef char DbLabel[8];
  20. // TODO Most of these are no longer used. Clean up.
  21. typedef enum {
  22. LSSTORE_INIT = 1, // Is the store environment set up on disk?
  23. LSSTORE_OPEN = 3, // Is the environment open? Assumes init is set.
  24. LSSTORE_DIRTY_TXN = 4, // Main txn was opened in a subroutine.
  25. } StoreState;
  26. typedef enum {
  27. OP_ADD,
  28. OP_REMOVE,
  29. } StoreOp;
  30. typedef struct MDBStore {
  31. MDB_env * env; // Environment handle.
  32. MDB_txn * txn; // Current transaction.
  33. MDB_dbi dbi[N_DB]; // DB handles. Refer to DbIdx enum.
  34. LSUP_Buffer * default_ctx;// Default ctx as a serialized URI.
  35. StoreState state; // Store state.
  36. } MDBStore;
  37. /** @brief Iterator operation.
  38. *
  39. * Function executed for each iteration of a #MDBIterator. It assumes that a
  40. * result triple has already been found and is ready to be composed and
  41. * yielded.
  42. *
  43. * Upon call, the rc value of the iterator structure is set to the MDB_* rc
  44. * value for the next result. It is up to the caller to evaluate this value
  45. * and decide whether to call the function again.
  46. */
  47. typedef void (*iter_op_fn_t)(struct MDBIterator *it);
  48. /** @brief Triple iterator.
  49. */
  50. typedef struct MDBIterator {
  51. MDBStore * store; // MDB store pointer.
  52. MDB_txn * txn; // MDB transaction.
  53. MDB_cursor * cur; // MDB cursor.
  54. MDB_val key, data; // Internal data handlers.
  55. LSUP_TripleKey spok; // Triple to be populated with match.
  56. LSUP_Key ck; // Ctx key to filter by. May be NULL_TRP.
  57. iter_op_fn_t iter_op_fn; // Function used to look up next match.
  58. const uint8_t * term_order; // Term order used in 1-2bound look-ups.
  59. LSUP_Key luk[3]; // 0÷3 lookup keys.
  60. size_t i; // Internal counter for paged lookups.
  61. int rc; // MDB_* return code for the next result.
  62. StoreState state; // State flags.
  63. } MDBIterator;
  64. /*
  65. * TODO At the moment up to 64-bit key / hash values are allowed. Later on,
  66. * 128-bit keys should be allowed by compile options, and that will no longer
  67. * be compatible with integer keys and data. When 128-bit keys are supported,
  68. * integer keys should remain available for code compiled with 64-bit keys.
  69. */
  70. #define DUPSORT_MASK MDB_DUPSORT
  71. #define DUPFIXED_MASK MDB_DUPSORT | MDB_DUPFIXED
  72. #define INT_KEY_MASK MDB_INTEGERKEY
  73. #define INT_DUP_KEY_MASK MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERKEY
  74. #define INT_DUPDATA_MASK MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERDUP
  75. /**
  76. * Main DBs. These are the master information containers.
  77. *
  78. * Data columns are: identifier prefix, DB label, flags.
  79. */
  80. #define MAIN_TABLE \
  81. ENTRY( T_ST, "t:st", 0 ) /* Key to ser. term */ \
  82. ENTRY( SPO_C, "spo:c", DUPFIXED_MASK ) /* Triple to context */ \
  83. ENTRY( C_, "c:", 0 ) /* Track empty ctx */ \
  84. ENTRY( PFX_NS, "pfx:ns", 0 ) /* Prefix to NS */ \
  85. /**
  86. * Lookup DBs. These are indices and may be destroyed and rebuilt.
  87. */
  88. #define LOOKUP_TABLE \
  89. ENTRY( S_PO, "s:po", DUPFIXED_MASK ) /* 1-bound lookup */ \
  90. ENTRY( P_SO, "p:so", DUPFIXED_MASK ) /* 1-bound lookup */ \
  91. ENTRY( O_SP, "o:sp", DUPFIXED_MASK ) /* 1-bound lookup */ \
  92. ENTRY( PO_S, "po:s", DUPFIXED_MASK ) /* 2-bound lookup */ \
  93. ENTRY( SO_P, "so:p", DUPFIXED_MASK ) /* 2-bound lookup */ \
  94. ENTRY( SP_O, "sp:o", DUPFIXED_MASK ) /* 2-bound lookup */ \
  95. ENTRY( C_SPO, "c:spo", DUPFIXED_MASK ) /* Context lookup */ \
  96. ENTRY( NS_PFX, "ns:pfx", 0 ) /* NS to prefix */ \
  97. /**
  98. * DB labels. They are prefixed with DB_
  99. */
  100. #define ENTRY(a, b, c) static const DbLabel DB_##a = b;
  101. MAIN_TABLE
  102. LOOKUP_TABLE
  103. #undef ENTRY
  104. /**
  105. * Numeric index of each DB. Prefixed with IDX_
  106. *
  107. * These index numbers are referred to in all the arrays defeined below. They
  108. * are independent from the LMDB dbi values which are considered opaque here.
  109. */
  110. typedef enum {
  111. #define ENTRY(a, b, c) IDX_##a,
  112. MAIN_TABLE
  113. LOOKUP_TABLE
  114. #undef ENTRY
  115. } DBIdx;
  116. /**
  117. * DB labels.
  118. */
  119. static const char *db_labels[N_DB] = {
  120. #define ENTRY(a, b, c) DB_##a,
  121. MAIN_TABLE
  122. LOOKUP_TABLE
  123. #undef ENTRY
  124. };
  125. /**
  126. * DB flags. These are aligned with the dbi_labels index.
  127. */
  128. static const unsigned int db_flags[N_DB] = {
  129. #define ENTRY(a, b, c) c,
  130. MAIN_TABLE
  131. LOOKUP_TABLE
  132. #undef ENTRY
  133. };
  134. /**
  135. * 1-bound and 2-bound lookup indices.
  136. *
  137. * N.B. Only the first 6 (1-bound and 2-bound term lookup) are used.
  138. * The others are added just because they belong logically to the lookup table.
  139. */
  140. static DBIdx lookup_indices[9] = {
  141. #define ENTRY(a, b, c) IDX_##a,
  142. LOOKUP_TABLE
  143. #undef ENTRY
  144. };
  145. /**
  146. * Order in which keys are looked up if two terms are bound.
  147. * The indices with the smallest average number of values per key should be
  148. * looked up first.
  149. *
  150. * TODO deprecate?
  151. *
  152. * 0 = s:po
  153. * 1 = p:so
  154. * 2 = o:sp
  155. */
  156. // static const uint8_t lookup_rank[3] = {0, 2, 1};
  157. static const uint8_t lookup_ordering_1bound[3][3] = {
  158. {0, 1, 2}, // s:po
  159. {1, 0, 2}, // p:so
  160. {2, 0, 1}, // o:sp
  161. };
  162. static const uint8_t lookup_ordering_2bound[3][3] = {
  163. {1, 2, 0}, // po:s
  164. {0, 2, 1}, // so:p
  165. {0, 1, 2}, // sp:o
  166. };
  167. /**
  168. * Static prototypes.
  169. */
  170. static int index_triple(
  171. LSUP_MDBStore *store, StoreOp op, LSUP_TripleKey spok, LSUP_Key ck);
  172. inline static LSUP_rc lookup_0bound(
  173. MDBStore *store, MDBIterator *it, size_t *ct);
  174. inline static LSUP_rc lookup_1bound(
  175. MDBStore *store, uint8_t idx0, MDBIterator *it, size_t *ct);
  176. inline static LSUP_rc lookup_2bound(
  177. MDBStore *store, uint8_t idx0, uint8_t idx1,
  178. MDBIterator *it, size_t *ct);
  179. inline static LSUP_rc lookup_3bound(
  180. MDBStore *store, MDBIterator *it, size_t *ct);
  181. /**
  182. * API.
  183. */
  184. LSUP_rc
  185. LSUP_mdbstore_setup (char **path, bool clear)
  186. {
  187. int rc;
  188. // Set environment path.
  189. if (path == NULL && (*path = getenv ("LSUP_STORE_PATH")) == NULL) {
  190. // FIXME This won't work for multiple graphs with different disk
  191. // back ends. A random path generator needs to be used.
  192. *path = DEFAULT_ENV_PATH;
  193. fprintf(
  194. stderr,
  195. "WARNING: `LSUP_STORE_PATH' environment variable is not set. "
  196. "The default location %s will be used as the graph store.\n",
  197. *path);
  198. }
  199. // TODO Verify that a writable directory exists or can be created.
  200. //struct stat path_stat;
  201. if (clear) rm_r (*path);
  202. if (mkdir_p (*path, ENV_DIR_MODE) != 0) return LSUP_IO_ERR;
  203. // Open a temporary environment and txn to create the DBs.
  204. MDB_env *env;
  205. mdb_env_create (&env);
  206. mdb_env_set_maxdbs (env, N_DB);
  207. mdb_env_open (env, *path, 0, ENV_FILE_MODE);
  208. MDB_txn *txn;
  209. mdb_txn_begin (env, NULL, 0, &txn);
  210. for (int i = 0; i < N_DB; i++) {
  211. TRACE ("Creating DB %s", db_labels[i]);
  212. MDB_dbi dbi;
  213. rc = mdb_dbi_open (txn, db_labels[i], db_flags[i] | MDB_CREATE, &dbi);
  214. if (rc != MDB_SUCCESS) return rc;
  215. }
  216. mdb_txn_commit (txn);
  217. mdb_env_close (env);
  218. return rc;
  219. }
  220. MDBStore *
  221. LSUP_mdbstore_new (const char *path, const LSUP_Buffer *default_ctx)
  222. {
  223. int db_rc;
  224. LSUP_MDBStore *store;
  225. CRITICAL (store = malloc (sizeof (LSUP_MDBStore)));
  226. db_rc = mdb_env_create (&store->env);
  227. TRACE ("create rc: %d", db_rc);
  228. store->default_ctx = (
  229. default_ctx ?
  230. LSUP_buffer_new (default_ctx->size, default_ctx->addr) : NULL);
  231. // Set map size.
  232. size_t mapsize;
  233. char *env_mapsize = getenv ("LSUP_MDB_MAPSIZE");
  234. if (env_mapsize == NULL) mapsize = DEFAULT_MAPSIZE;
  235. else sscanf (env_mapsize, "%lu", &mapsize);
  236. db_rc = mdb_env_set_maxdbs (store->env, N_DB);
  237. if (UNLIKELY (db_rc != MDB_SUCCESS)) return NULL;
  238. db_rc = mdb_env_open (store->env, path, 0, ENV_FILE_MODE);
  239. if (UNLIKELY (db_rc != MDB_SUCCESS)) return NULL;
  240. // Assign DB handles to store->dbi.
  241. MDB_txn *txn;
  242. mdb_txn_begin (store->env, NULL, 0, &txn);
  243. for (int i = 0; i < N_DB; i++) {
  244. db_rc = mdb_dbi_open (txn, db_labels[i], db_flags[i], store->dbi + i);
  245. if (UNLIKELY (db_rc != MDB_SUCCESS)) {
  246. mdb_txn_abort (txn);
  247. return NULL;
  248. }
  249. }
  250. mdb_txn_commit (txn);
  251. store->state |= LSSTORE_OPEN;
  252. store->txn = NULL;
  253. return store;
  254. }
  255. void
  256. LSUP_mdbstore_free (LSUP_MDBStore *store)
  257. {
  258. if (store->state & LSSTORE_OPEN) {
  259. TRACE (STR, "Closing MDB env.\n");
  260. mdb_env_close (store->env);
  261. }
  262. if (store->default_ctx) {
  263. LSUP_buffer_done (store->default_ctx);
  264. free (store->default_ctx);
  265. }
  266. free (store);
  267. }
  268. LSUP_rc
  269. LSUP_mdbstore_stat (LSUP_MDBStore *store, MDB_stat *stat)
  270. {
  271. if (!(store->state & LSSTORE_INIT)) return 0;
  272. MDB_txn *txn;
  273. mdb_txn_begin (store->env, NULL, MDB_RDONLY, &txn);
  274. if (mdb_stat (txn, store->dbi[IDX_SPO_C], stat) != MDB_SUCCESS)
  275. return LSUP_DB_ERR;
  276. mdb_txn_abort (txn);
  277. return LSUP_OK;
  278. }
  279. size_t
  280. LSUP_mdbstore_size (LSUP_MDBStore *store)
  281. {
  282. // Size is calculated outside of any pending write txn.
  283. MDB_stat stat;
  284. if (LSUP_mdbstore_stat (store, &stat) != LSUP_OK) return 0;
  285. return stat.ms_entries;
  286. }
  287. MDBIterator *
  288. LSUP_mdbstore_add_init (LSUP_MDBStore *store, const LSUP_Buffer *sc)
  289. {
  290. /* An iterator is used here. Some members are a bit misused but it does
  291. * its job without having to define a very similar struct.
  292. */
  293. MDBIterator *it = malloc (sizeof (*it));
  294. if (!it) return NULL;
  295. it->store = store;
  296. it->i = 0;
  297. // No other write transaction may be open.
  298. mdb_txn_begin (store->env, NULL, 0, &it->store->txn);
  299. // Take care of context first.
  300. // Serialize and hash.
  301. it->ck = NULL_KEY;
  302. if (store->default_ctx != NULL) {
  303. if (sc == NULL) sc = store->default_ctx;
  304. it->ck = LSUP_sterm_to_key (sc);
  305. // Insert t:st for context.
  306. //TRACE ("Adding context: %s", sc);
  307. it->key.mv_data = &it->ck;
  308. it->key.mv_size = KLEN;
  309. it->data.mv_data = sc->addr;
  310. it->data.mv_size = sc->size;
  311. if (mdb_put(
  312. it->store->txn, it->store->dbi[IDX_T_ST],
  313. &it->key, &it->data, MDB_NOOVERWRITE) != MDB_SUCCESS)
  314. return NULL;
  315. }
  316. return it;
  317. }
  318. LSUP_rc
  319. LSUP_mdbstore_add_iter (MDBIterator *it, const LSUP_SerTriple *sspo)
  320. {
  321. int db_rc;
  322. LSUP_rc rc;
  323. LSUP_TripleKey spok = NULL_TRP;
  324. // Add triple.
  325. for (int i = 0; i < 3; i++) {
  326. LSUP_Buffer *st = LSUP_striple_pos (sspo, i);
  327. printf ("Inserting term: ");
  328. LSUP_buffer_print (st);
  329. printf ("\n");
  330. spok[i] = LSUP_sterm_to_key (st);
  331. it->key.mv_data = spok + i;
  332. it->key.mv_size = KLEN;
  333. it->data.mv_data = st->addr;
  334. it->data.mv_size = st->size;
  335. db_rc = mdb_put(
  336. it->store->txn, it->store->dbi[IDX_T_ST],
  337. &it->key, &it->data, MDB_NOOVERWRITE);
  338. if (db_rc != MDB_SUCCESS && db_rc != MDB_KEYEXIST) {
  339. return LSUP_DB_ERR;
  340. }
  341. }
  342. TRACE ("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
  343. // Insert spo:c.
  344. it->key.mv_data = spok;
  345. it->key.mv_size = TRP_KLEN;
  346. // In triple mode, data is empty (= NULL_KEY).
  347. it->data.mv_data = &it->ck;
  348. it->data.mv_size = it->ck == NULL_KEY ? 0 : KLEN;
  349. db_rc = mdb_put(
  350. it->store->txn, it->store->dbi[IDX_SPO_C],
  351. &it->key, &it->data, MDB_NODUPDATA);
  352. if (db_rc == MDB_KEYEXIST) return LSUP_NOACTION;
  353. if (db_rc != MDB_SUCCESS) return LSUP_DB_ERR;
  354. // Index.
  355. rc = index_triple (it->store, OP_ADD, spok, it->ck);
  356. if (rc == LSUP_OK) it->i++;
  357. return rc;
  358. }
  359. LSUP_rc
  360. LSUP_mdbstore_add_done (MDBIterator *it)
  361. {
  362. LSUP_rc rc = LSUP_OK;
  363. if (mdb_txn_commit (it->store->txn) != MDB_SUCCESS) {
  364. mdb_txn_abort (it->store->txn);
  365. rc = LSUP_DB_ERR;
  366. }
  367. it->store->txn = NULL;
  368. free (it);
  369. return rc;
  370. }
  371. void
  372. LSUP_mdbstore_add_abort (MDBIterator *it)
  373. {
  374. mdb_txn_abort (it->store->txn);
  375. it->store->txn = NULL;
  376. free (it);
  377. }
  378. LSUP_rc
  379. LSUP_mdbstore_add (
  380. LSUP_MDBStore *store, const LSUP_Buffer *sc,
  381. const LSUP_SerTriple strp[], const size_t ct, size_t *inserted)
  382. {
  383. MDBIterator *it = LSUP_mdbstore_add_init (store, sc);
  384. if (UNLIKELY (!it)) return LSUP_DB_ERR;
  385. for (size_t i = 0; i < ct; i++) {
  386. LSUP_rc rc = LSUP_mdbstore_add_iter (it, strp + i);
  387. if (UNLIKELY (rc < 0)) {
  388. LSUP_mdbstore_add_abort (it);
  389. return rc;
  390. }
  391. }
  392. *inserted = it->i;
  393. return LSUP_mdbstore_add_done (it);
  394. }
  395. static LSUP_Key __attribute__ ((unused))
  396. sterm_to_key (
  397. LSUP_MDBStore *store, const LSUP_Buffer *sterm)
  398. {
  399. // TODO this will be replaced by a lookup when 128-bit hash is introduced.
  400. return LSUP_sterm_to_key (sterm);
  401. }
  402. static LSUP_rc
  403. key_to_sterm(
  404. LSUP_MDBStore *store, const LSUP_Key key, LSUP_Buffer *sterm,
  405. MDB_txn *txn)
  406. {
  407. LSUP_rc rc = LSUP_NORESULT;
  408. int db_rc;
  409. bool txn_dirty = false;
  410. if (!txn) {
  411. db_rc = mdb_txn_begin (store->env, NULL, MDB_RDONLY, &txn);
  412. if (db_rc != MDB_SUCCESS) return LSUP_DB_ERR;
  413. txn_dirty = true;
  414. }
  415. MDB_val key_v, data_v;
  416. key_v.mv_data = (void*)&key;
  417. key_v.mv_size = KLEN;
  418. db_rc = mdb_get (txn, store->dbi[IDX_T_ST], &key_v, &data_v);
  419. if (db_rc == MDB_SUCCESS) {
  420. sterm->addr = data_v.mv_data;
  421. sterm->size = data_v.mv_size;
  422. rc = LSUP_OK;
  423. } else if (db_rc == MDB_NOTFOUND) {
  424. free (sterm->addr);
  425. sterm->addr = NULL;
  426. sterm->size = 0;
  427. } else rc = LSUP_ERROR;
  428. if (txn_dirty) mdb_txn_abort (txn);
  429. return rc;
  430. }
  431. MDBIterator *
  432. LSUP_mdbstore_lookup(
  433. LSUP_MDBStore *store, const LSUP_SerTriple *sspo,
  434. const LSUP_Buffer *sc, size_t *ct)
  435. {
  436. LSUP_TripleKey spok = {
  437. LSUP_sterm_to_key (sspo->s),
  438. LSUP_sterm_to_key (sspo->p),
  439. LSUP_sterm_to_key (sspo->o),
  440. };
  441. LSUP_MDBIterator *it;
  442. CRITICAL (it = malloc (sizeof (*it)));
  443. it->store = store;
  444. it->ck = store->default_ctx ? LSUP_sterm_to_key (sc) : NULL_KEY;
  445. if (ct) *ct = 0;
  446. uint8_t idx0, idx1;
  447. // s p o (all terms bound)
  448. if (spok[0] != NULL_KEY && spok[1] != NULL_KEY && spok[2] != NULL_KEY) {
  449. it->luk[0] = spok[0];
  450. it->luk[1] = spok[1];
  451. it->luk[2] = spok[2];
  452. RCNL (lookup_3bound (store, it, ct));
  453. } else if (spok[0] != NULL_KEY) {
  454. it->luk[0] = spok[0];
  455. idx0 = 0;
  456. // s p ?
  457. if (spok[1] != NULL_KEY) {
  458. it->luk[1] = spok[1];
  459. idx1 = 1;
  460. RCNL (lookup_2bound (store, idx0, idx1, it, ct));
  461. // s ? o
  462. } else if (spok[2] != NULL_KEY) {
  463. it->luk[1] = spok[2];
  464. idx1 = 2;
  465. RCNL (lookup_2bound (store, idx0, idx1, it, ct));
  466. // s ? ?
  467. } else RCNL (lookup_1bound (store, idx0, it, ct));
  468. } else if (spok[1] != NULL_KEY) {
  469. it->luk[0] = spok[1];
  470. idx0 = 1;
  471. // ? p o
  472. if (spok[2] != NULL_KEY) {
  473. it->luk[1] = spok[2];
  474. idx1 = 2;
  475. RCNL (lookup_2bound (store, idx0, idx1, it, ct));
  476. // ? p ?
  477. } else RCNL (lookup_1bound (store, idx0, it, ct));
  478. // ? ? o
  479. } else if (spok[2] != NULL_KEY) {
  480. it->luk[0] = spok[2];
  481. idx0 = 2;
  482. RCNL (lookup_1bound (store, idx0, it, ct));
  483. // ? ? ? (all terms unbound)
  484. } else RCNL (lookup_0bound (store, it, ct));
  485. return it;
  486. }
  487. inline static LSUP_rc
  488. mdbiter_next_key (LSUP_MDBIterator *it)
  489. {
  490. if (UNLIKELY (!it)) return LSUP_DB_ERR;
  491. // Only advance if the previous it->rc wasn't already at the end.
  492. if (it->rc == MDB_NOTFOUND) return LSUP_END;
  493. if (UNLIKELY (it->rc != MDB_SUCCESS)) {
  494. fprintf (stderr, mdb_strerror (it->rc));
  495. return LSUP_DB_ERR;
  496. }
  497. LSUP_rc rc;
  498. it->iter_op_fn (it);
  499. if (it->ck) {
  500. rc = LSUP_NORESULT; // Intermediary value, will never be returned.
  501. MDB_cursor *cur;
  502. MDB_val key, data;
  503. mdb_cursor_open
  504. (mdb_cursor_txn (it->cur), it->store->dbi[IDX_SPO_C], &cur);
  505. key.mv_size = TRP_KLEN;
  506. data.mv_data = &it->ck;
  507. data.mv_size = KLEN;
  508. while (rc == LSUP_NORESULT) {
  509. TRACE (STR, "begin ctx loop.");
  510. // If ctx is specified, look if the matching triple is associated
  511. // with it. If not, move on to the next triple.
  512. // The loop normally exits when a triple with matching ctx is found
  513. // (LSUP_OK), if there are no more triples (LSUP_END), or if there
  514. // is an error (LSUPP_DB_ERR).
  515. key.mv_data = it->spok;
  516. int db_rc = mdb_cursor_get (cur, &key, &data, MDB_GET_BOTH);
  517. if (db_rc == MDB_SUCCESS) {
  518. rc = LSUP_OK;
  519. TRACE (STR, "Triple found for context.");
  520. }
  521. else if (db_rc == MDB_NOTFOUND) {
  522. TRACE (STR, "No triples found for context.");
  523. if (it->rc == MDB_NOTFOUND) rc = LSUP_END;
  524. else it->iter_op_fn (it);
  525. } else {
  526. fprintf (stderr, mdb_strerror (it->rc));
  527. rc = LSUP_DB_ERR;
  528. }
  529. }
  530. mdb_cursor_close (cur);
  531. } else rc = LSUP_OK;
  532. return rc;
  533. }
  534. LSUP_rc
  535. LSUP_mdbiter_next (LSUP_MDBIterator *it, LSUP_SerTriple *sspo)
  536. {
  537. LSUP_rc rc = mdbiter_next_key (it);
  538. if (sspo && rc == LSUP_OK) {
  539. key_to_sterm (it->store, it->spok[0], sspo->s, it->txn);
  540. key_to_sterm (it->store, it->spok[1], sspo->p, it->txn);
  541. key_to_sterm (it->store, it->spok[2], sspo->o, it->txn);
  542. // TODO error handling.
  543. }
  544. return rc;
  545. }
  546. size_t
  547. LSUP_mdbiter_i (LSUP_MDBIterator *it)
  548. { return it->i; }
  549. void
  550. LSUP_mdbiter_free (MDBIterator *it)
  551. {
  552. if (it) {
  553. mdb_cursor_close (it->cur);
  554. if (it->store->txn != it->txn) mdb_txn_abort (it->txn);
  555. free (it);
  556. it = NULL;
  557. }
  558. }
  559. LSUP_rc
  560. LSUP_mdbstore_remove(
  561. MDBStore *store, const LSUP_SerTriple *sspo,
  562. const LSUP_Buffer *sc, size_t *ct)
  563. {
  564. LSUP_rc rc = LSUP_NOACTION;
  565. LSUP_Key ck = NULL_KEY;
  566. if (store->default_ctx != NULL) {
  567. if (sc == NULL) sc = store->default_ctx;
  568. ck = LSUP_sterm_to_key (sc);
  569. }
  570. MDB_txn *txn;
  571. mdb_txn_begin (store->env, NULL, 0, &txn);
  572. MDB_cursor *dcur, *icur;
  573. mdb_cursor_open (txn, store->dbi[IDX_SPO_C], &dcur);
  574. mdb_cursor_open (txn, store->dbi[IDX_C_SPO], &icur);
  575. MDB_val spok_v, ck_v;
  576. spok_v.mv_size = TRP_KLEN;
  577. ck_v.mv_size = KLEN;
  578. LSUP_MDBIterator *it = LSUP_mdbstore_lookup (store, sspo, sc, ct);
  579. if (UNLIKELY (!it)) return LSUP_DB_ERR;
  580. while (mdbiter_next_key (it)) {
  581. spok_v.mv_data = it->spok;
  582. rc = mdb_cursor_get (dcur, &spok_v, &ck_v, MDB_GET_BOTH);
  583. if (rc == MDB_NOTFOUND) continue;
  584. if (UNLIKELY (rc != MDB_SUCCESS)) goto _remove_abort;
  585. // Delete spo:c entry.
  586. mdb_cursor_del (dcur, 0);
  587. // Restore ck address after each delete.
  588. ck_v.mv_data = &ck;
  589. // Delete c::spo entry.
  590. rc = mdb_cursor_get (icur, &ck_v, &spok_v, MDB_GET_BOTH);
  591. if (rc == MDB_NOTFOUND) continue;
  592. if (UNLIKELY (rc != MDB_SUCCESS)) goto _remove_abort;
  593. mdb_cursor_del (icur, 0);
  594. spok_v.mv_data = it->spok;
  595. // If there are no more contexts associated with this triple,
  596. // remove from indices.
  597. rc = mdb_cursor_get (dcur, &spok_v, NULL, MDB_SET);
  598. if (rc == MDB_SUCCESS) continue;
  599. if (UNLIKELY (rc != MDB_NOTFOUND)) goto _remove_abort;
  600. index_triple (store, OP_REMOVE, it->spok, ck);
  601. }
  602. if (UNLIKELY (mdb_txn_commit (txn) != MDB_SUCCESS)) {
  603. rc = LSUP_TXN_ERR;
  604. goto _remove_abort;
  605. }
  606. return rc;
  607. _remove_abort:
  608. mdb_txn_abort (txn);
  609. return rc;
  610. }
  611. /* * * Static functions. * * */
  612. /** @brief Index an added or removed triple.
  613. *
  614. * @param store[in] MDB store to index.
  615. * @param op[in] Store operation. One of OP_ADD or OP_REMOVE.
  616. * @param spok[in] Triple key to index.
  617. * @param ck[in] Context to index, may be NULL.
  618. */
  619. static LSUP_rc
  620. index_triple(
  621. LSUP_MDBStore *store, StoreOp op, LSUP_TripleKey spok, LSUP_Key ck)
  622. {
  623. int db_rc;
  624. LSUP_rc rc = LSUP_NOACTION;
  625. MDB_val v1, v2;
  626. printf ("Indexing triple: %lx %lx %lx\n", spok[0], spok[1], spok[2]);
  627. // Index c:spo.
  628. if (op == OP_REMOVE) {
  629. if (ck != NULL_KEY) {
  630. MDB_cursor *cur;
  631. v1.mv_data = &ck;
  632. v1.mv_size = KLEN;
  633. v2.mv_data = spok;
  634. v2.mv_size = TRP_KLEN;
  635. mdb_cursor_open (store->txn, store->dbi[IDX_C_SPO], &cur);
  636. if (mdb_cursor_get (cur, &v1, &v2, MDB_GET_BOTH) == MDB_SUCCESS) {
  637. db_rc = mdb_cursor_del (cur, 0);
  638. if (db_rc != MDB_SUCCESS) return LSUP_DB_ERR;
  639. rc = LSUP_OK;
  640. }
  641. mdb_cursor_close (cur);
  642. }
  643. } else if (op == OP_ADD) {
  644. if (ck != NULL_KEY) {
  645. v1.mv_data = &ck;
  646. v1.mv_size = KLEN;
  647. v2.mv_data = spok;
  648. v2.mv_size = TRP_KLEN;
  649. db_rc = mdb_put(
  650. store->txn, store->dbi[IDX_C_SPO],
  651. &v1, &v2, MDB_NODUPDATA);
  652. if (db_rc != MDB_SUCCESS) return LSUP_DB_ERR;
  653. if (db_rc != MDB_KEYEXIST) rc = LSUP_OK;
  654. }
  655. } else return LSUP_VALUE_ERR;
  656. LSUP_DoubleKey dbl_keys[3] = {
  657. {spok[1], spok[2]}, // po
  658. {spok[0], spok[2]}, // so
  659. {spok[0], spok[1]}, // sp
  660. };
  661. // Add terms to index.
  662. v1.mv_size = KLEN;
  663. v2.mv_size = DBL_KLEN;
  664. for (int i = 0; i < 3; i++) {
  665. MDB_dbi db1 = store->dbi[lookup_indices[i]]; // s:po, p:so, o:sp
  666. MDB_dbi db2 = store->dbi[lookup_indices[i + 3]]; // po:s, so:p, sp:o
  667. v1.mv_data = spok + i;
  668. v2.mv_data = dbl_keys[i];
  669. if (op == OP_REMOVE) {
  670. MDB_cursor *cur1, *cur2;
  671. mdb_cursor_open(
  672. store->txn, store->dbi[lookup_indices[i]], &cur1);
  673. db_rc = mdb_cursor_get (cur1, &v1, &v2, MDB_GET_BOTH);
  674. if (db_rc == MDB_SUCCESS) mdb_cursor_del (cur1, 0);
  675. mdb_cursor_close (cur1);
  676. // Restore pointers invalidated after delete.
  677. v1.mv_data = spok + i;
  678. v2.mv_data = dbl_keys[i];
  679. mdb_cursor_open(
  680. store->txn, store->dbi[lookup_indices[i + 3]], &cur2);
  681. db_rc = mdb_cursor_get (cur2, &v2, &v1, MDB_GET_BOTH);
  682. if (db_rc == MDB_SUCCESS) mdb_cursor_del (cur2, 0);
  683. // TODO error handling.
  684. rc = LSUP_OK;
  685. mdb_cursor_close (cur2);
  686. } else { // OP_ADD is guaranteed.
  687. // 1-bound index.
  688. TRACE ("Indexing in %s: ", db_labels[lookup_indices[i]]);
  689. TRACE(
  690. "%lx: %lx %lx\n", *(size_t*)(v1.mv_data),
  691. *(size_t*)(v2.mv_data), *(size_t*)(v2.mv_data) + 1);
  692. db_rc = mdb_put (store->txn, db1, &v1, &v2, MDB_NODUPDATA);
  693. if (db_rc == MDB_SUCCESS) rc = LSUP_OK;
  694. else if (db_rc != MDB_KEYEXIST) return LSUP_DB_ERR;
  695. // 2-bound index.
  696. TRACE ("Indexing in %s: ", db_labels[lookup_indices[i + 3]]);
  697. TRACE(
  698. "%lx %lx: %lx\n", *(size_t*)(v2.mv_data),
  699. *(size_t*)(v2.mv_data) + 1, *(size_t*)(v1.mv_data));
  700. db_rc = mdb_put (store->txn, db2, &v2, &v1, MDB_NODUPDATA);
  701. if (db_rc == MDB_SUCCESS) rc = LSUP_OK;
  702. else if (db_rc != MDB_KEYEXIST) return LSUP_DB_ERR;
  703. }
  704. }
  705. return rc;
  706. }
  707. /* * * Term-specific iterators. * * */
  708. /** @brief Advance 0-bound iterator.
  709. *
  710. * Cursor: spo:c
  711. */
  712. inline static void
  713. it_next_0bound (MDBIterator *it)
  714. {
  715. memcpy (it->spok, it->data.mv_data, sizeof (LSUP_TripleKey));
  716. it->rc = mdb_cursor_get (it->cur, &it->key, NULL, MDB_NEXT);
  717. }
  718. /** @brief Advance 1-bound iterator.
  719. *
  720. * Uses paged data in a nested loop.
  721. *
  722. * Cursor: s:po, p:so, or o:sp.
  723. */
  724. inline static void
  725. it_next_1bound (MDBIterator *it)
  726. {
  727. LSUP_DoubleKey *lu_dset = it->data.mv_data;
  728. it->spok[it->term_order[0]] = it->luk[0];
  729. it->spok[it->term_order[1]] = lu_dset[it->i][0];
  730. it->spok[it->term_order[2]] = lu_dset[it->i][1];
  731. TRACE(
  732. "Composed triple: {%lu %lu %lu}",
  733. it->spok[0], it->spok[1], it->spok[2]);
  734. // Ensure next block within the same page is not beyond the last.
  735. if (it->i < it->data.mv_size / DBL_KLEN - 1) {
  736. it->i ++;
  737. TRACE ("Increasing page cursor to %lu.", it->i);
  738. TRACE ("it->rc: %d", it->rc);
  739. } else {
  740. // If the last block in the page is being yielded,
  741. // move cursor to beginning of next page.
  742. it->i = 0;
  743. TRACE ("Reset page cursor to %lu.", it->i);
  744. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_NEXT_MULTIPLE);
  745. TRACE ("it->rc: %d", it->rc);
  746. }
  747. }
  748. /** @brief Advance 2-bound iterator.
  749. *
  750. * Uses paged data in a nested loop.
  751. *
  752. * Cursor: po:s, so:p, or sp:o.
  753. */
  754. inline static void
  755. it_next_2bound (MDBIterator *it)
  756. {
  757. LSUP_Key *lu_dset = it->data.mv_data;
  758. it->spok[it->term_order[0]] = it->luk[0];
  759. it->spok[it->term_order[1]] = it->luk[1];
  760. it->spok[it->term_order[2]] = lu_dset[it->i];
  761. // Ensure next block within the same page is not beyond the last.
  762. if (it->i < it->data.mv_size / KLEN - 1)
  763. it->i ++;
  764. else {
  765. // If the last block in the page is being yielded,
  766. // move cursor to beginning of next page.
  767. it->i = 0;
  768. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_NEXT_MULTIPLE);
  769. }
  770. }
  771. /** @brief Advance 3-bound iterator.
  772. *
  773. * This is a special case of 0÷1 results; either there was one matching triple,
  774. * which was already set in the first result, or there was none, i.e. it->rc is
  775. * already MDB_NOTFOUND and this function will not be called.
  776. */
  777. inline static void
  778. it_next_3bound (MDBIterator *it)
  779. {
  780. it->rc = MDB_NOTFOUND;
  781. }
  782. /* * * Term-specific lookups. * * */
  783. inline static LSUP_rc
  784. lookup_0bound (MDBStore *store, MDBIterator *it, size_t *ct)
  785. {
  786. if (store->txn) it->txn = store->txn;
  787. else {
  788. it->rc = mdb_txn_begin (store->env, NULL, MDB_RDONLY, &it->txn);
  789. if (it->rc != MDB_SUCCESS) abort(); // TODO handle error
  790. }
  791. if (ct) {
  792. if (it->ck != NULL_KEY) {
  793. // Look up by given context.
  794. it->rc = mdb_cursor_open (it->txn, store->dbi[IDX_C_SPO], &it->cur);
  795. it->key.mv_data = &it->ck;
  796. it->key.mv_size = KLEN;
  797. it->rc = mdb_cursor_get (it->cur, &it->key, NULL, MDB_SET);
  798. if (it->rc == MDB_SUCCESS) mdb_cursor_count (it->cur, ct);
  799. mdb_cursor_close (it->cur);
  800. } else {
  801. // Look up all contexts.
  802. MDB_stat stat;
  803. mdb_stat (it->txn, store->dbi[IDX_S_PO], &stat);
  804. *ct = stat.ms_entries;
  805. }
  806. }
  807. mdb_cursor_open (it->txn, store->dbi[IDX_SPO_C], &it->cur);
  808. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_FIRST);
  809. it->iter_op_fn = it_next_0bound;
  810. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  811. fprintf (stderr, "Database error: %s", mdb_strerror (it->rc));
  812. return LSUP_DB_ERR;
  813. }
  814. return LSUP_OK;
  815. }
  816. inline static LSUP_rc
  817. lookup_1bound (MDBStore *store, uint8_t idx0, MDBIterator *it, size_t *ct)
  818. {
  819. it->term_order = (const uint8_t*)lookup_ordering_1bound[idx0];
  820. TRACE ("Looking up 1 bound term: %lu\n", it->luk[0]);
  821. if (!it->txn) {
  822. if (store->txn) it->txn = store->txn;
  823. else {
  824. it->rc = mdb_txn_begin (store->env, NULL, MDB_RDONLY, &it->txn);
  825. if (it->rc != MDB_SUCCESS) abort();
  826. }
  827. }
  828. mdb_cursor_open (it->txn, store->dbi[lookup_indices[idx0]], &it->cur);
  829. it->key.mv_data = it->luk;
  830. it->key.mv_size = KLEN;
  831. if (ct) {
  832. // If a context is specified, the only way to count triples matching
  833. // the context is to loop over them.
  834. if (it->ck != NULL_KEY) {
  835. MDBIterator *ct_it;
  836. CRITICAL (ct_it = malloc (sizeof (MDBIterator)));
  837. ct_it->luk[0] = it->luk[0];
  838. LSUP_TripleKey ct_spok;
  839. memcpy (ct_it->spok, ct_spok, sizeof (LSUP_TripleKey));
  840. ct_it->ck = it->ck;
  841. ct_it->store = it->store;
  842. ct_it->txn = it->txn;
  843. ct_it->key = it->key;
  844. ct_it->data = it->data;
  845. ct_it->i = 0;
  846. lookup_1bound (store, idx0, ct_it, NULL);
  847. while (LSUP_mdbiter_next (ct_it, NULL) != LSUP_END) {
  848. ct[0] ++;
  849. TRACE ("Counter increased to %lu.", *ct);
  850. }
  851. mdb_cursor_close (ct_it->cur);
  852. free (ct_it);
  853. } else {
  854. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
  855. if (it->rc == MDB_SUCCESS) mdb_cursor_count (it->cur, ct);
  856. }
  857. }
  858. it->i = 0;
  859. it->iter_op_fn = it_next_1bound;
  860. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
  861. if (it->rc == MDB_SUCCESS)
  862. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_GET_MULTIPLE);
  863. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  864. fprintf (stderr, "Database error: %s", mdb_strerror (it->rc));
  865. return LSUP_DB_ERR;
  866. }
  867. return LSUP_OK;
  868. }
  869. inline static LSUP_rc
  870. lookup_2bound(
  871. MDBStore *store, uint8_t idx0, uint8_t idx1,
  872. MDBIterator *it, size_t *ct)
  873. {
  874. uint8_t luk1_offset, luk2_offset;
  875. MDB_dbi dbi = 0;
  876. // Establish lookup ordering with some awkward offset math.
  877. for (int i = 0; i < 3; i++) {
  878. if (
  879. (
  880. idx0 == lookup_ordering_2bound[i][0] &&
  881. idx1 == lookup_ordering_2bound[i][1]
  882. ) || (
  883. idx0 == lookup_ordering_2bound[i][1] &&
  884. idx1 == lookup_ordering_2bound[i][0]
  885. )
  886. ) {
  887. it->term_order = (const uint8_t*)lookup_ordering_2bound[i];
  888. if (it->term_order[0] == idx0) {
  889. luk1_offset = 0;
  890. luk2_offset = 1;
  891. } else {
  892. luk1_offset = 1;
  893. luk2_offset = 0;
  894. }
  895. dbi = store->dbi[lookup_indices[i + 3]];
  896. TRACE(
  897. "Looking up 2 bound in %s\n",
  898. db_labels[lookup_indices[i + 3]]);
  899. break;
  900. }
  901. }
  902. if (dbi == 0) {
  903. TRACE(
  904. "Values %d and %d not found in lookup keys.",
  905. idx0, idx1);
  906. return LSUP_VALUE_ERR;
  907. }
  908. // Compose term keys in lookup key.
  909. LSUP_DoubleKey luk;
  910. luk[luk1_offset] = it->luk[0];
  911. luk[luk2_offset] = it->luk[1];
  912. if (!it->txn) {
  913. if (store->txn) it->txn = store->txn;
  914. else {
  915. it->rc = mdb_txn_begin (store->env, NULL, MDB_RDONLY, &it->txn);
  916. if (it->rc != MDB_SUCCESS) abort();
  917. }
  918. }
  919. it->key.mv_data = luk;
  920. it->key.mv_size = DBL_KLEN;
  921. mdb_cursor_open (it->txn, dbi, &it->cur);
  922. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
  923. if (ct) {
  924. // If a context is specified, the only way to count triples matching
  925. // the context is to loop over them.
  926. if (it->ck != NULL_KEY) {
  927. MDBIterator *ct_it;
  928. CRITICAL (ct_it = malloc (sizeof (MDBIterator)));
  929. ct_it->luk[0] = it->luk[0];
  930. ct_it->luk[1] = it->luk[1];
  931. LSUP_TripleKey ct_spok;
  932. memcpy (ct_it->spok, ct_spok, sizeof (LSUP_TripleKey));
  933. ct_it->ck = it->ck;
  934. ct_it->store = it->store;
  935. ct_it->txn = it->txn;
  936. lookup_2bound (store, idx0, idx1, ct_it, NULL);
  937. while (LSUP_mdbiter_next (ct_it, NULL) != LSUP_END) {
  938. ct[0] ++;
  939. }
  940. if (ct_it->cur) mdb_cursor_close (ct_it->cur);
  941. free (ct_it);
  942. } else {
  943. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
  944. if (it->rc == MDB_SUCCESS) mdb_cursor_count (it->cur, ct);
  945. }
  946. }
  947. it->i = 0;
  948. it->iter_op_fn = it_next_2bound;
  949. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
  950. if (it->rc == MDB_SUCCESS)
  951. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_GET_MULTIPLE);
  952. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  953. fprintf (stderr, "Database error: %s", mdb_strerror (it->rc));
  954. return LSUP_DB_ERR;
  955. }
  956. return LSUP_OK;
  957. }
  958. inline static LSUP_rc
  959. lookup_3bound (MDBStore *store, MDBIterator *it, size_t *ct)
  960. {
  961. TRACE(
  962. "Looking up 3 bound: {%lx, %lx, %lx}",
  963. it->luk[0], it->luk[1], it->luk[2]);
  964. if (store->txn) it->txn = store->txn;
  965. else mdb_txn_begin (store->env, NULL, MDB_RDONLY, &it->txn);
  966. it->key.mv_data = it->luk;
  967. if (it->ck != NULL_KEY) {
  968. it->rc = mdb_cursor_open (it->txn, store->dbi[IDX_SPO_C], &it->cur);
  969. it->key.mv_size = TRP_KLEN;
  970. it->data.mv_data = &it->ck;
  971. it->data.mv_size = KLEN;
  972. } else {
  973. it->rc = mdb_cursor_open (it->txn, store->dbi[IDX_S_PO], &it->cur);
  974. it->key.mv_size = KLEN;
  975. it->data.mv_data = it->luk + 1;
  976. it->data.mv_size = DBL_KLEN;
  977. }
  978. it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_GET_BOTH);
  979. mdb_cursor_close (it->cur);
  980. if (ct && it->rc == MDB_SUCCESS) *ct = 1;
  981. it->iter_op_fn = it_next_3bound;
  982. memcpy (it->spok, it->luk, sizeof (LSUP_TripleKey));
  983. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  984. fprintf (stderr, "Database error: %s", mdb_strerror (it->rc));
  985. return LSUP_DB_ERR;
  986. }
  987. return LSUP_OK;
  988. }