store_mdb.c 34 KB

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