store_mdb.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275
  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,
  172. LSUP_TripleKey spok, LSUP_Key ck);
  173. inline static LSUP_rc lookup_0bound(
  174. MDBStore *store, MDBIterator *it, size_t *ct);
  175. inline static LSUP_rc lookup_1bound(
  176. MDBStore *store, uint8_t idx0, MDBIterator *it, size_t *ct);
  177. inline static LSUP_rc lookup_2bound(
  178. MDBStore *store, uint8_t idx0, uint8_t idx1,
  179. MDBIterator *it, size_t *ct);
  180. inline static LSUP_rc lookup_3bound(
  181. MDBStore *store, MDBIterator *it, size_t *ct);
  182. /* TODO
  183. inline static int check_txn_open(MDB_txn *txn, bool write);
  184. */
  185. /* TODO
  186. static int unlink_cb(
  187. const char *fpath, const struct stat *sb,
  188. int typeflag, struct FTW *ftwbuf);
  189. static int rmrf(char *path);
  190. */
  191. /**
  192. * API.
  193. */
  194. LSUP_rc
  195. LSUP_mdbstore_setup(char **path/*, bool clear*/) // TODO clear
  196. {
  197. int rc;
  198. // Set environment path.
  199. if (path == NULL && (*path = getenv("LSUP_STORE_PATH")) == NULL) {
  200. // FIXME This won't work for multiple graphs with different disk
  201. // back ends. A random path generator needs to be used.
  202. *path = DEFAULT_ENV_PATH;
  203. fprintf(
  204. stderr,
  205. "WARNING: `LSUP_STORE_PATH' environment variable is not set. "
  206. "The default location %s will be used as the graph store.\n",
  207. *path);
  208. }
  209. // TODO Verify that a writable directory exists or can be created.
  210. //struct stat path_stat;
  211. /*
  212. // TODO clear
  213. if (clear) {
  214. rmrf(*path);
  215. if (mkdir(*path, ENV_DIR_MODE) != 0) abort();
  216. }
  217. */
  218. if (mkdir_p(*path, ENV_DIR_MODE) != 0) abort();
  219. // Open a temporary environment and txn to create the DBs.
  220. MDB_env *env;
  221. mdb_env_create(&env);
  222. mdb_env_set_maxdbs(env, N_DB);
  223. mdb_env_open(env, *path, 0, ENV_FILE_MODE);
  224. MDB_txn *txn;
  225. mdb_txn_begin(env, NULL, 0, &txn);
  226. for (int i = 0; i < N_DB; i++) {
  227. TRACE("Creating DB %s", db_labels[i]);
  228. MDB_dbi dbi;
  229. rc = mdb_dbi_open(txn, db_labels[i], db_flags[i] | MDB_CREATE, &dbi);
  230. if (rc != MDB_SUCCESS) return rc;
  231. }
  232. mdb_txn_commit(txn);
  233. mdb_env_close(env);
  234. return rc;
  235. }
  236. MDBStore *
  237. LSUP_mdbstore_new(const char *path, const LSUP_Buffer *default_ctx)
  238. {
  239. int rc;
  240. LSUP_MDBStore *store;
  241. CRITICAL(store = malloc(sizeof(LSUP_MDBStore)));
  242. rc = mdb_env_create(&store->env);
  243. TRACE("create rc: %d", rc);
  244. store->default_ctx = (
  245. default_ctx ?
  246. LSUP_buffer_new(default_ctx->size, default_ctx->addr) : NULL);
  247. // Set map size.
  248. size_t mapsize;
  249. char *env_mapsize = getenv("LSUP_MDB_MAPSIZE");
  250. if (env_mapsize == NULL) mapsize = DEFAULT_MAPSIZE;
  251. else sscanf(env_mapsize, "%lu", &mapsize);
  252. rc = mdb_env_set_maxdbs(store->env, N_DB);
  253. if (UNLIKELY (rc != MDB_SUCCESS)) return NULL;
  254. rc = mdb_env_open(store->env, path, 0, ENV_FILE_MODE);
  255. if (UNLIKELY (rc != MDB_SUCCESS)) return NULL;
  256. // Assign DB handles to store->dbi.
  257. MDB_txn *txn;
  258. mdb_txn_begin(store->env, NULL, 0, &txn);
  259. for (int i = 0; i < N_DB; i++) {
  260. rc = mdb_dbi_open(txn, db_labels[i], db_flags[i], store->dbi + i);
  261. if (UNLIKELY (rc != MDB_SUCCESS)) {
  262. mdb_txn_abort(txn);
  263. return NULL;
  264. }
  265. }
  266. mdb_txn_commit(txn);
  267. store->state |= LSSTORE_OPEN;
  268. store->txn = NULL;
  269. return store;
  270. }
  271. void
  272. LSUP_mdbstore_free(LSUP_MDBStore *store)
  273. {
  274. if (store->state & LSSTORE_OPEN) {
  275. TRACE(STR, "Closing MDB env.\n");
  276. mdb_env_close(store->env);
  277. }
  278. if (store->default_ctx) {
  279. LSUP_buffer_done(store->default_ctx);
  280. free(store->default_ctx);
  281. }
  282. free(store);
  283. }
  284. LSUP_rc
  285. LSUP_mdbstore_stats(LSUP_MDBStore *store)
  286. {
  287. // TODO
  288. // MDB_stat env_stat, db_stats[N_DB];
  289. return 0;
  290. }
  291. size_t
  292. LSUP_mdbstore_size(LSUP_MDBStore *store)
  293. {
  294. if(!(store->state & LSSTORE_INIT)) return 0;
  295. bool txn_pending = false;
  296. if (!store->txn) {
  297. mdb_txn_begin(store->env, NULL, 0, &store->txn);
  298. txn_pending = true;
  299. }
  300. MDB_stat stat;
  301. int rc = mdb_stat(store->txn, store->dbi[IDX_SPO_C], &stat);
  302. TRACE("Stat rc: %d\n", rc);
  303. // TODO error handling.
  304. if(txn_pending) {
  305. mdb_txn_abort(store->txn);
  306. store->txn = NULL;
  307. }
  308. return stat.ms_entries;
  309. }
  310. MDBIterator *
  311. LSUP_mdbstore_add_init(LSUP_MDBStore *store, const LSUP_Buffer *sc)
  312. {
  313. /* An iterator is used here. Some members are a bit misused but it does
  314. * its job without having to define a very similar struct.
  315. */
  316. MDBIterator *it;
  317. CRITICAL (it = malloc (sizeof (*it)));
  318. it->i = 0;
  319. if (!store->txn) {
  320. mdb_txn_begin(store->env, NULL, 0, &store->txn);
  321. // We are starting the main DB txn and we need to close it afterwards.
  322. it->state = LSSTORE_DIRTY_TXN;
  323. }
  324. // Take care of context first.
  325. // Serialize and hash.
  326. it->ck = NULL_KEY;
  327. if (store->default_ctx != NULL) {
  328. if (sc == NULL) sc = store->default_ctx;
  329. it->ck = LSUP_sterm_to_key(sc);
  330. // Insert t:st for context.
  331. //TRACE("Adding context: %s", sc);
  332. it->key.mv_data = &it->ck;
  333. it->key.mv_size = KLEN;
  334. it->data.mv_data = sc->addr;
  335. it->data.mv_size = sc->size;
  336. if (mdb_put(
  337. store->txn, store->dbi[IDX_T_ST],
  338. &it->key, &it->data, MDB_NOOVERWRITE) != MDB_SUCCESS)
  339. it->rc = LSUP_DB_ERR;
  340. }
  341. return it;
  342. }
  343. LSUP_rc
  344. LSUP_mdbstore_add_iter(MDBIterator *it, const LSUP_SerTriple *sspo)
  345. {
  346. int db_rc;
  347. LSUP_TripleKey spok = NULL_TRP;
  348. // Add triple.
  349. for (int j = 0; j < 3; j++) {
  350. LSUP_Buffer *st = LSUP_striple_pos(sspo, j);
  351. printf("Inserting term: ");
  352. LSUP_buffer_print(st);
  353. printf("\n");
  354. spok[j] = LSUP_sterm_to_key(st);
  355. it->key.mv_data = spok + j;
  356. it->key.mv_size = KLEN;
  357. it->data.mv_data = st->addr;
  358. it->data.mv_size = st->size;
  359. db_rc = mdb_put(
  360. it->store->txn, it->store->dbi[IDX_T_ST],
  361. &it->key, &it->data, MDB_NOOVERWRITE);
  362. if (db_rc == MDB_SUCCESS) it->rc = LSUP_OK;
  363. else if (db_rc != MDB_KEYEXIST) {
  364. it->rc = LSUP_DB_ERR;
  365. return it->rc;
  366. }
  367. }
  368. TRACE("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
  369. // Insert spo:c.
  370. it->key.mv_data = spok;
  371. it->key.mv_size = TRP_KLEN;
  372. // In triple mode, data is empty (= NULL_KEY).
  373. it->data.mv_data = &it->ck;
  374. it->data.mv_size = it->ck == NULL_KEY ? 0 : KLEN;
  375. db_rc = mdb_put(
  376. it->store->txn, it->store->dbi[IDX_SPO_C],
  377. &it->key, &it->data, MDB_NODUPDATA);
  378. if (db_rc == MDB_SUCCESS) it->rc = LSUP_OK;
  379. else if (db_rc != MDB_KEYEXIST) {
  380. it->rc = LSUP_DB_ERR;
  381. return it->rc;
  382. }
  383. // Index.
  384. it->rc = index_triple(it->store, OP_ADD, spok, it->ck);
  385. if(it->rc == LSUP_OK) it->i++;
  386. return it->rc;
  387. }
  388. LSUP_rc
  389. LSUP_mdbstore_add_done(MDBIterator *it, size_t *inserted)
  390. {
  391. // Only return commit rc if it fails.
  392. if (it->state & LSSTORE_DIRTY_TXN) {
  393. if (it->rc == LSUP_OK) {
  394. if(mdb_txn_commit(it->store->txn) != MDB_SUCCESS) {
  395. mdb_txn_abort(it->store->txn);
  396. it->rc = LSUP_DB_ERR;
  397. }
  398. } else mdb_txn_abort(it->store->txn);
  399. it->store->txn = NULL;
  400. }
  401. return it->rc;
  402. }
  403. LSUP_rc
  404. LSUP_mdbstore_add(
  405. LSUP_MDBStore *store, const LSUP_Buffer *sc,
  406. const LSUP_SerTriple strp[], const size_t ct, size_t *inserted)
  407. {
  408. MDBIterator *it = LSUP_mdbstore_add_init(store, sc);
  409. if (it->rc < 0) {
  410. for (size_t i = 0; i < ct; i++)
  411. if (LSUP_mdbstore_add_iter(it, strp + i) < 0) break;
  412. }
  413. return LSUP_mdbstore_add_done(it, inserted);
  414. }
  415. LSUP_Key
  416. LSUP_mdbstore_sterm_to_key(
  417. LSUP_MDBStore *store, const LSUP_Buffer *sterm)
  418. {
  419. // TODO this will be replaced by a lookup when 128-bit hash is introduced.
  420. return LSUP_sterm_to_key(sterm);
  421. }
  422. LSUP_rc
  423. LSUP_mdbstore_key_to_sterm(
  424. LSUP_MDBStore *store, const LSUP_Key key, LSUP_Buffer *sterm)
  425. {
  426. LSUP_rc rc = LSUP_NORESULT;
  427. MDB_txn *txn;
  428. if (store->txn) txn = store->txn;
  429. else mdb_txn_begin(store->env, NULL, MDB_RDONLY, &txn);
  430. MDB_val key_v, data_v;
  431. key_v.mv_data = (void*)&key;
  432. key_v.mv_size = KLEN;
  433. int mdb_rc = mdb_get(txn, store->dbi[IDX_T_ST], &key_v, &data_v);
  434. if (mdb_rc == MDB_SUCCESS) {
  435. sterm->addr = data_v.mv_data;
  436. sterm->size = data_v.mv_size;
  437. rc = LSUP_OK;
  438. }
  439. else if (UNLIKELY(mdb_rc != MDB_NOTFOUND)) rc = LSUP_ERROR;
  440. if (txn != store->txn) mdb_txn_abort(txn);
  441. return rc;
  442. }
  443. MDBIterator *
  444. LSUP_mdbstore_lookup(
  445. LSUP_MDBStore *store, const LSUP_SerTriple *sspo,
  446. const LSUP_Buffer *sc, size_t *ct)
  447. {
  448. LSUP_TripleKey spok = {
  449. LSUP_sterm_to_key(sspo->s),
  450. LSUP_sterm_to_key(sspo->p),
  451. LSUP_sterm_to_key(sspo->o),
  452. };
  453. LSUP_MDBIterator *it;
  454. CRITICAL(it = malloc (sizeof (*it)));
  455. it->store = store;
  456. it->ck = store->default_ctx ? LSUP_sterm_to_key(sc) : NULL_KEY;
  457. if(ct) *ct = 0;
  458. uint8_t idx0, idx1;
  459. // s p o (all terms bound)
  460. if (spok[0] != NULL_KEY && spok[1] != NULL_KEY && spok[2] != NULL_KEY) {
  461. it->luk[0] = spok[0];
  462. it->luk[1] = spok[1];
  463. it->luk[2] = spok[2];
  464. RCNL (lookup_3bound (store, it, ct));
  465. } else if (spok[0] != NULL_KEY) {
  466. it->luk[0] = spok[0];
  467. idx0 = 0;
  468. // s p ?
  469. if (spok[1] != NULL_KEY) {
  470. it->luk[1] = spok[1];
  471. idx1 = 1;
  472. RCNL (lookup_2bound (store, idx0, idx1, it, ct));
  473. // s ? o
  474. } else if (spok[2] != NULL_KEY) {
  475. it->luk[1] = spok[2];
  476. idx1 = 2;
  477. RCNL (lookup_2bound (store, idx0, idx1, it, ct));
  478. // s ? ?
  479. } else RCNL (lookup_1bound (store, idx0, it, ct));
  480. } else if (spok[1] != NULL_KEY) {
  481. it->luk[0] = spok[1];
  482. idx0 = 1;
  483. // ? p o
  484. if (spok[2] != NULL_KEY) {
  485. it->luk[1] = spok[2];
  486. idx1 = 2;
  487. RCNL (lookup_2bound (store, idx0, idx1, it, ct));
  488. // ? p ?
  489. } else RCNL (lookup_1bound (store, idx0, it, ct));
  490. // ? ? o
  491. } else if (spok[2] != NULL_KEY) {
  492. it->luk[0] = spok[2];
  493. idx0 = 2;
  494. RCNL (lookup_1bound(store, idx0, it, ct));
  495. // ? ? ? (all terms unbound)
  496. } else RCNL (lookup_0bound(store, it, ct));
  497. return it;
  498. }
  499. LSUP_rc
  500. mdbiter_next_key(LSUP_MDBIterator *it)
  501. {
  502. if (UNLIKELY (!it)) return LSUP_DB_ERR;
  503. // Only advance if the previous it->rc wasn't already at the end.
  504. if(it->rc == MDB_NOTFOUND) return LSUP_END;
  505. if (UNLIKELY (it->rc != MDB_SUCCESS)) {
  506. fprintf(stderr, mdb_strerror(it->rc));
  507. return LSUP_DB_ERR;
  508. }
  509. LSUP_rc rc;
  510. it->iter_op_fn(it);
  511. if (it->ck) {
  512. rc = LSUP_NORESULT; // Intermediary value, will never be returned.
  513. MDB_cursor *cur;
  514. MDB_val key, data;
  515. mdb_cursor_open
  516. (mdb_cursor_txn(it->cur), it->store->dbi[IDX_SPO_C], &cur);
  517. key.mv_size = TRP_KLEN;
  518. data.mv_data = &it->ck;
  519. data.mv_size = KLEN;
  520. while (rc == LSUP_NORESULT) {
  521. TRACE(STR, "begin ctx loop.");
  522. // If ctx is specified, look if the matching triple is associated
  523. // with it. If not, move on to the next triple.
  524. // The loop normally exits when a triple with matching ctx is found
  525. // (LSUP_OK), if there are no more triples (LSUP_END), or if there
  526. // is an error (LSUPP_DB_ERR).
  527. key.mv_data = it->spok;
  528. int db_rc = mdb_cursor_get(cur, &key, &data, MDB_GET_BOTH);
  529. if (db_rc == MDB_SUCCESS) {
  530. rc = LSUP_OK;
  531. TRACE(STR, "Triple found for context.");
  532. }
  533. else if (db_rc == MDB_NOTFOUND) {
  534. TRACE(STR, "No triples found for context.");
  535. if (it->rc == MDB_NOTFOUND) rc = LSUP_END;
  536. else it->iter_op_fn(it);
  537. } else {
  538. fprintf(stderr, mdb_strerror(it->rc));
  539. rc = LSUP_DB_ERR;
  540. }
  541. }
  542. mdb_cursor_close(cur);
  543. } else rc = LSUP_OK;
  544. return rc;
  545. }
  546. LSUP_rc
  547. LSUP_mdbiter_next(LSUP_MDBIterator *it, LSUP_SerTriple *sspo)
  548. {
  549. LSUP_rc rc = mdbiter_next_key(it);
  550. if (sspo && rc == LSUP_OK) {
  551. LSUP_mdbstore_key_to_sterm(it->store, it->spok[0], sspo->s);
  552. LSUP_mdbstore_key_to_sterm(it->store, it->spok[1], sspo->p);
  553. LSUP_mdbstore_key_to_sterm(it->store, it->spok[2], sspo->o);
  554. // TODO error handling.
  555. }
  556. return rc;
  557. }
  558. void
  559. LSUP_mdbiter_free(MDBIterator *it)
  560. {
  561. if (it) {
  562. mdb_cursor_close(it->cur);
  563. if(it->store->txn != it->txn) mdb_txn_abort(it->txn);
  564. free(it);
  565. it = NULL;
  566. }
  567. }
  568. LSUP_rc
  569. LSUP_mdbstore_remove(
  570. MDBStore *store, const LSUP_SerTriple *sspo,
  571. const LSUP_Buffer *sc, size_t *ct)
  572. {
  573. LSUP_rc rc = LSUP_NOACTION;
  574. LSUP_Key ck = NULL_KEY;
  575. if (store->default_ctx != NULL) {
  576. if (sc == NULL) sc = store->default_ctx;
  577. ck = LSUP_sterm_to_key(sc);
  578. }
  579. MDB_txn *txn;
  580. mdb_txn_begin(store->env, NULL, 0, &txn);
  581. MDB_cursor *dcur, *icur;
  582. mdb_cursor_open(txn, store->dbi[IDX_SPO_C], &dcur);
  583. mdb_cursor_open(txn, store->dbi[IDX_C_SPO], &icur);
  584. MDB_val spok_v, ck_v;
  585. spok_v.mv_size = TRP_KLEN;
  586. ck_v.mv_size = KLEN;
  587. LSUP_MDBIterator *it = LSUP_mdbstore_lookup(store, sspo, sc, ct);
  588. if (UNLIKELY (!it)) return LSUP_DB_ERR;
  589. while (mdbiter_next_key(it)) {
  590. spok_v.mv_data = it->spok;
  591. rc = mdb_cursor_get(dcur, &spok_v, &ck_v, MDB_GET_BOTH);
  592. if (rc == MDB_NOTFOUND) continue;
  593. if (UNLIKELY(rc != MDB_SUCCESS)) goto _remove_abort;
  594. // Delete spo:c entry.
  595. mdb_cursor_del(dcur, 0);
  596. // Restore ck address after each delete.
  597. ck_v.mv_data = &ck;
  598. // Delete c::spo entry.
  599. rc = mdb_cursor_get(icur, &ck_v, &spok_v, MDB_GET_BOTH);
  600. if (rc == MDB_NOTFOUND) continue;
  601. if (UNLIKELY(rc != MDB_SUCCESS)) goto _remove_abort;
  602. mdb_cursor_del(icur, 0);
  603. spok_v.mv_data = it->spok;
  604. // If there are no more contexts associated with this triple,
  605. // remove from indices.
  606. rc = mdb_cursor_get(dcur, &spok_v, NULL, MDB_SET);
  607. if (rc == MDB_SUCCESS) continue;
  608. if (UNLIKELY(rc != MDB_NOTFOUND)) goto _remove_abort;
  609. index_triple(store, OP_REMOVE, it->spok, ck);
  610. }
  611. if(UNLIKELY(mdb_txn_commit(txn) != MDB_SUCCESS)) {
  612. rc = LSUP_TXN_ERR;
  613. goto _remove_abort;
  614. }
  615. return rc;
  616. _remove_abort:
  617. mdb_txn_abort(txn);
  618. return rc;
  619. }
  620. /* * * Static functions. * * */
  621. /* TODO
  622. static int
  623. unlink_cb(
  624. const char *fpath, const struct stat *sb,
  625. int typeflag, struct FTW *ftwbuf)
  626. {
  627. int rv = remove(fpath);
  628. if (rv)
  629. perror(fpath);
  630. return rv;
  631. }
  632. static int rmrf(char *path)
  633. { return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS); }
  634. */
  635. static LSUP_rc
  636. index_triple(
  637. LSUP_MDBStore *store, StoreOp op,
  638. LSUP_TripleKey spok, LSUP_Key ck)
  639. {
  640. int rc = LSUP_NOACTION;
  641. MDB_val v1, v2;
  642. printf("Indexing triple: %lx %lx %lx\n", spok[0], spok[1], spok[2]);
  643. // Index c:spo.
  644. if (op == OP_REMOVE) {
  645. if (ck != NULL_KEY) {
  646. MDB_cursor *cur;
  647. v1.mv_data = &ck;
  648. v1.mv_size = KLEN;
  649. v2.mv_data = spok;
  650. v2.mv_size = TRP_KLEN;
  651. mdb_cursor_open(store->txn, store->dbi[IDX_C_SPO], &cur);
  652. rc = mdb_cursor_get(cur, &v1, &v2, MDB_GET_BOTH);
  653. if(rc == MDB_SUCCESS) mdb_cursor_del(cur, 0);
  654. mdb_cursor_close(cur);
  655. }
  656. } else if (op == OP_ADD) {
  657. if (ck != NULL_KEY) {
  658. v1.mv_data = &ck;
  659. v1.mv_size = KLEN;
  660. v2.mv_data = spok;
  661. v2.mv_size = TRP_KLEN;
  662. mdb_put(
  663. store->txn, store->dbi[IDX_C_SPO],
  664. &v1, &v2, MDB_NODUPDATA);
  665. }
  666. } else return LSUP_VALUE_ERR;
  667. LSUP_DoubleKey dbl_keys[3] = {
  668. {spok[1], spok[2]}, // po
  669. {spok[0], spok[2]}, // so
  670. {spok[0], spok[1]}, // sp
  671. };
  672. // Add terms to index.
  673. v1.mv_size = KLEN;
  674. v2.mv_size = DBL_KLEN;
  675. int db_rc;
  676. for (int i = 0; i < 3; i++) {
  677. MDB_dbi db1 = store->dbi[lookup_indices[i]]; // s:po, p:so, o:sp
  678. MDB_dbi db2 = store->dbi[lookup_indices[i + 3]]; // po:s, so:p, sp:o
  679. v1.mv_data = spok + i;
  680. v2.mv_data = dbl_keys[i];
  681. if (op == OP_REMOVE) {
  682. MDB_cursor *cur1, *cur2;
  683. mdb_cursor_open(store->txn, store->dbi[lookup_indices[i]], &cur1);
  684. rc = mdb_cursor_get(cur1, &v1, &v2, MDB_GET_BOTH);
  685. if (rc == MDB_SUCCESS) mdb_cursor_del(cur1, 0);
  686. mdb_cursor_close(cur1);
  687. // Restore pointers invalidated after delete.
  688. v1.mv_data = spok + i;
  689. v2.mv_data = dbl_keys[i];
  690. mdb_cursor_open(
  691. store->txn, store->dbi[lookup_indices[i + 3]], &cur2);
  692. rc = mdb_cursor_get(cur2, &v2, &v1, MDB_GET_BOTH);
  693. if (rc == MDB_SUCCESS) mdb_cursor_del(cur2, 0);
  694. mdb_cursor_close(cur2);
  695. } else { // OP_ADD is guaranteed.
  696. printf("Indexing in %s: ", db_labels[lookup_indices[i]]);
  697. printf(
  698. "%lx: %lx %lx\n", *(size_t*)(v1.mv_data),
  699. *(size_t*)(v2.mv_data), *(size_t*)(v2.mv_data) + 1);
  700. db_rc = mdb_put(store->txn, db1, &v1, &v2, MDB_NODUPDATA);
  701. if (db_rc != MDB_SUCCESS && db_rc != MDB_KEYEXIST)
  702. return LSUP_DB_ERR;
  703. printf("Indexing in %s: ", db_labels[lookup_indices[i + 3]]);
  704. printf(
  705. "%lx %lx: %lx\n", *(size_t*)(v2.mv_data),
  706. *(size_t*)(v2.mv_data) + 1, *(size_t*)(v1.mv_data));
  707. db_rc = mdb_put(store->txn, db2, &v2, &v1, MDB_NODUPDATA);
  708. if (db_rc != MDB_SUCCESS && db_rc != MDB_KEYEXIST)
  709. return LSUP_DB_ERR;
  710. }
  711. }
  712. return rc;
  713. }
  714. /* * * Term-specific iterators. * * */
  715. /** @brief Advance 0-bound iterator.
  716. *
  717. * Cursor: spo:c
  718. */
  719. inline static void
  720. it_next_0bound(MDBIterator *it)
  721. {
  722. memcpy(it->spok, it->data.mv_data, sizeof(LSUP_TripleKey));
  723. it->rc = mdb_cursor_get(it->cur, &it->key, NULL, MDB_NEXT);
  724. }
  725. /** @brief Advance 1-bound iterator.
  726. *
  727. * Uses paged data in a nested loop.
  728. *
  729. * Cursor: s:po, p:so, or o:sp.
  730. */
  731. inline static void
  732. it_next_1bound(MDBIterator *it)
  733. {
  734. LSUP_DoubleKey *lu_dset = it->data.mv_data;
  735. it->spok[it->term_order[0]] = it->luk[0];
  736. it->spok[it->term_order[1]] = lu_dset[it->i][0];
  737. it->spok[it->term_order[2]] = lu_dset[it->i][1];
  738. TRACE(
  739. "Composed triple: {%lu %lu %lu}",
  740. it->spok[0], it->spok[1], it->spok[2]);
  741. // Ensure next block within the same page is not beyond the last.
  742. if(it->i < it->data.mv_size / DBL_KLEN - 1) {
  743. it->i ++;
  744. TRACE("Increasing page cursor to %lu.", it->i);
  745. TRACE("it->rc: %d", it->rc);
  746. } else {
  747. // If the last block in the page is being yielded,
  748. // move cursor to beginning of next page.
  749. it->i = 0;
  750. TRACE("Reset page cursor to %lu.", it->i);
  751. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_NEXT_MULTIPLE);
  752. TRACE("it->rc: %d", it->rc);
  753. }
  754. }
  755. /** @brief Advance 2-bound iterator.
  756. *
  757. * Uses paged data in a nested loop.
  758. *
  759. * Cursor: po:s, so:p, or sp:o.
  760. */
  761. inline static void
  762. it_next_2bound(MDBIterator *it)
  763. {
  764. LSUP_Key *lu_dset = it->data.mv_data;
  765. it->spok[it->term_order[0]] = it->luk[0];
  766. it->spok[it->term_order[1]] = it->luk[1];
  767. it->spok[it->term_order[2]] = lu_dset[it->i];
  768. // Ensure next block within the same page is not beyond the last.
  769. if(it->i < it->data.mv_size / KLEN - 1)
  770. it->i ++;
  771. else {
  772. // If the last block in the page is being yielded,
  773. // move cursor to beginning of next page.
  774. it->i = 0;
  775. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_NEXT_MULTIPLE);
  776. }
  777. }
  778. /** @brief Advance 3-bound iterator.
  779. *
  780. * This is a special case of 0÷1 results; either there was one matching triple,
  781. * which was already set in the first result, or there was none, i.e. it->rc is
  782. * already MDB_NOTFOUND and this function will not be called.
  783. */
  784. inline static void
  785. it_next_3bound(MDBIterator *it)
  786. {
  787. it->rc = MDB_NOTFOUND;
  788. }
  789. /* * * Term-specific lookups. * * */
  790. inline static LSUP_rc
  791. lookup_0bound(MDBStore *store, MDBIterator *it, size_t *ct)
  792. {
  793. if(store->txn) it->txn = store->txn;
  794. else {
  795. it->rc = mdb_txn_begin(store->env, NULL, MDB_RDONLY, &it->txn);
  796. if (it->rc != MDB_SUCCESS) abort();
  797. }
  798. if(ct) {
  799. if(it->ck != NULL_KEY) {
  800. // Look up by given context.
  801. it->rc = mdb_cursor_open(it->txn, store->dbi[IDX_C_SPO], &it->cur);
  802. it->key.mv_data = &it->ck;
  803. it->key.mv_size = KLEN;
  804. it->rc = mdb_cursor_get(it->cur, &it->key, NULL, MDB_SET);
  805. if (it->rc == MDB_SUCCESS) mdb_cursor_count(it->cur, ct);
  806. mdb_cursor_close(it->cur);
  807. } else {
  808. // Look up all contexts.
  809. MDB_stat stat;
  810. mdb_stat(it->txn, store->dbi[IDX_S_PO], &stat);
  811. *ct = stat.ms_entries;
  812. }
  813. }
  814. mdb_cursor_open(it->txn, store->dbi[IDX_SPO_C], &it->cur);
  815. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_FIRST);
  816. it->iter_op_fn = it_next_0bound;
  817. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  818. fprintf(stderr, "Database error: %s", mdb_strerror(it->rc));
  819. return LSUP_DB_ERR;
  820. }
  821. return LSUP_OK;
  822. }
  823. inline static LSUP_rc
  824. lookup_1bound(MDBStore *store, uint8_t idx0, MDBIterator *it, size_t *ct)
  825. {
  826. it->term_order = (const uint8_t*)lookup_ordering_1bound[idx0];
  827. TRACE("Looking up 1 bound term: %lu\n", it->luk[0]);
  828. if(!it->txn) {
  829. if(store->txn) it->txn = store->txn;
  830. else {
  831. it->rc = mdb_txn_begin(store->env, NULL, MDB_RDONLY, &it->txn);
  832. if (it->rc != MDB_SUCCESS) abort();
  833. }
  834. }
  835. mdb_cursor_open(it->txn, store->dbi[lookup_indices[idx0]], &it->cur);
  836. it->key.mv_data = it->luk;
  837. it->key.mv_size = KLEN;
  838. if(ct) {
  839. // If a context is specified, the only way to count triples matching
  840. // the context is to loop over them.
  841. if (it->ck != NULL_KEY) {
  842. MDBIterator *ct_it;
  843. CRITICAL(ct_it = malloc(sizeof(MDBIterator)));
  844. ct_it->luk[0] = it->luk[0];
  845. LSUP_TripleKey ct_spok;
  846. memcpy(ct_it->spok, ct_spok, sizeof(LSUP_TripleKey));
  847. ct_it->ck = it->ck;
  848. ct_it->store = it->store;
  849. ct_it->txn = it->txn;
  850. ct_it->key = it->key;
  851. ct_it->data = it->data;
  852. ct_it->i = 0;
  853. lookup_1bound(store, idx0, ct_it, NULL);
  854. while (LSUP_mdbiter_next(ct_it, NULL) != LSUP_END) {
  855. ct[0] ++;
  856. TRACE("Counter increased to %lu.", *ct);
  857. }
  858. mdb_cursor_close(ct_it->cur);
  859. free(ct_it);
  860. } else {
  861. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_SET);
  862. if (it->rc == MDB_SUCCESS) mdb_cursor_count(it->cur, ct);
  863. }
  864. }
  865. it->i = 0;
  866. it->iter_op_fn = it_next_1bound;
  867. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_SET);
  868. if (it->rc == MDB_SUCCESS)
  869. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_GET_MULTIPLE);
  870. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  871. fprintf(stderr, "Database error: %s", mdb_strerror(it->rc));
  872. return LSUP_DB_ERR;
  873. }
  874. return LSUP_OK;
  875. }
  876. inline static LSUP_rc
  877. lookup_2bound(
  878. MDBStore *store, uint8_t idx0, uint8_t idx1,
  879. MDBIterator *it, size_t *ct)
  880. {
  881. uint8_t luk1_offset, luk2_offset;
  882. MDB_dbi dbi = 0;
  883. // Establish lookup ordering with some awkward offset math.
  884. for(int i = 0; i < 3; i++) {
  885. if (
  886. (
  887. idx0 == lookup_ordering_2bound[i][0] &&
  888. idx1 == lookup_ordering_2bound[i][1]
  889. ) || (
  890. idx0 == lookup_ordering_2bound[i][1] &&
  891. idx1 == lookup_ordering_2bound[i][0]
  892. )
  893. ) {
  894. it->term_order = (const uint8_t*)lookup_ordering_2bound[i];
  895. if (it->term_order[0] == idx0) {
  896. luk1_offset = 0;
  897. luk2_offset = 1;
  898. } else {
  899. luk1_offset = 1;
  900. luk2_offset = 0;
  901. }
  902. dbi = store->dbi[lookup_indices[i + 3]];
  903. TRACE(
  904. "Looking up 2 bound in %s\n",
  905. db_labels[lookup_indices[i + 3]]);
  906. break;
  907. }
  908. }
  909. if (dbi == 0) {
  910. TRACE(
  911. "Values %d and %d not found in lookup keys.",
  912. idx0, idx1);
  913. return LSUP_VALUE_ERR;
  914. }
  915. // Compose term keys in lookup key.
  916. LSUP_DoubleKey luk;
  917. luk[luk1_offset] = it->luk[0];
  918. luk[luk2_offset] = it->luk[1];
  919. if(!it->txn) {
  920. if(store->txn) it->txn = store->txn;
  921. else {
  922. it->rc = mdb_txn_begin(store->env, NULL, MDB_RDONLY, &it->txn);
  923. if (it->rc != MDB_SUCCESS) abort();
  924. }
  925. }
  926. it->key.mv_data = luk;
  927. it->key.mv_size = DBL_KLEN;
  928. mdb_cursor_open(it->txn, dbi, &it->cur);
  929. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_SET);
  930. if(ct) {
  931. // If a context is specified, the only way to count triples matching
  932. // the context is to loop over them.
  933. if (it->ck != NULL_KEY) {
  934. MDBIterator *ct_it;
  935. CRITICAL(ct_it = malloc(sizeof(MDBIterator)));
  936. ct_it->luk[0] = it->luk[0];
  937. ct_it->luk[1] = it->luk[1];
  938. LSUP_TripleKey ct_spok;
  939. memcpy(ct_it->spok, ct_spok, sizeof(LSUP_TripleKey));
  940. ct_it->ck = it->ck;
  941. ct_it->store = it->store;
  942. ct_it->txn = it->txn;
  943. lookup_2bound(store, idx0, idx1, ct_it, NULL);
  944. while (LSUP_mdbiter_next(ct_it, NULL) != LSUP_END) {
  945. ct[0] ++;
  946. }
  947. if (ct_it->cur) mdb_cursor_close(ct_it->cur);
  948. free(ct_it);
  949. } else {
  950. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_SET);
  951. if (it->rc == MDB_SUCCESS) mdb_cursor_count(it->cur, ct);
  952. }
  953. }
  954. it->i = 0;
  955. it->iter_op_fn = it_next_2bound;
  956. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_SET);
  957. if (it->rc == MDB_SUCCESS)
  958. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_GET_MULTIPLE);
  959. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  960. fprintf(stderr, "Database error: %s", mdb_strerror(it->rc));
  961. return LSUP_DB_ERR;
  962. }
  963. return LSUP_OK;
  964. }
  965. inline static LSUP_rc
  966. lookup_3bound(MDBStore *store, MDBIterator *it, size_t *ct)
  967. {
  968. TRACE(
  969. "Looking up 3 bound: {%lx, %lx, %lx}",
  970. it->luk[0], it->luk[1], it->luk[2]);
  971. if(store->txn) it->txn = store->txn;
  972. else mdb_txn_begin(store->env, NULL, MDB_RDONLY, &it->txn);
  973. it->key.mv_data = it->luk;
  974. if(it->ck != NULL_KEY) {
  975. it->rc = mdb_cursor_open(it->txn, store->dbi[IDX_SPO_C], &it->cur);
  976. it->key.mv_size = TRP_KLEN;
  977. it->data.mv_data = &it->ck;
  978. it->data.mv_size = KLEN;
  979. } else {
  980. it->rc = mdb_cursor_open(it->txn, store->dbi[IDX_S_PO], &it->cur);
  981. it->key.mv_size = KLEN;
  982. it->data.mv_data = it->luk + 1;
  983. it->data.mv_size = DBL_KLEN;
  984. }
  985. it->rc = mdb_cursor_get(it->cur, &it->key, &it->data, MDB_GET_BOTH);
  986. mdb_cursor_close(it->cur);
  987. if(ct && it->rc == MDB_SUCCESS) *ct = 1;
  988. it->iter_op_fn = it_next_3bound;
  989. memcpy(it->spok, it->luk, sizeof(LSUP_TripleKey));
  990. if (it->rc != MDB_SUCCESS && it->rc != MDB_NOTFOUND) {
  991. fprintf(stderr, "Database error: %s", mdb_strerror(it->rc));
  992. return LSUP_DB_ERR;
  993. }
  994. return LSUP_OK;
  995. }