commit 61d003518867c27d7a535cdcd2acaab36467f4a5
parent 2bd167406b72212d9ba13aa046bdd20934182ff4
Author: William Casarin <jb55@jb55.com>
Date: Mon, 7 Aug 2023 10:05:21 -0700
lmdb: add db scan helper
This is similar to the one in strfry, but converted to C
Needs tests!
Diffstat:
A | lmdb_util.h | | | 100 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 100 insertions(+), 0 deletions(-)
diff --git a/lmdb_util.h b/lmdb_util.h
@@ -0,0 +1,100 @@
+
+// Define callback function type
+typedef bool (*lmdb_callback_t)(const MDB_val*, const MDB_val*);
+
+
+int lmdb_foreach(MDB_txn *txn, MDB_dbi dbi, MDB_val *start, MDB_val *start_dup, callback_t cb, bool reverse) {
+ int success = 0;
+ MDB_cursor *cursor;
+
+ // Open a cursor on the provided transaction and database
+ int rc = mdb_cursor_open(txn, dbi, &cursor);
+
+ if (rc != 0)
+ return 0;
+
+ MDB_val k = *start, v = *start_dup;
+ MDB_cursor_op op = reverse ? MDB_PREV : MDB_NEXT;
+
+ // If we're scanning in reverse...
+ if (reverse) {
+ // Try to position the cursor at the first key-value pair where
+ // both the key and the value are greater than or equal to our
+ // starting point.
+ rc = mdb_cursor_get(cursor, &k, &v, MDB_GET_BOTH_RANGE);
+ if (rc == 0) {
+ if (v.mv_size != start_dup->mv_size ||
+ memcmp(v.mv_data, start_dup->mv_data, v.mv_size) != 0) {
+ // If the value doesn't match our starting
+ // point, step back to the previous record.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_PREV) != 0)
+ goto cleanup;
+ }
+ } else {
+ // If we couldn't find a record that matches both our
+ // starting key and value, try to find a record that
+ // matches just our starting key.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_SET) == 0) {
+ // If we find a match, move to the last value
+ // for this key, since we're scanning in
+ // reverse.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_LAST_DUP) != 0)
+ goto cleanup;
+ } else {
+ // If we can't find a record with our starting
+ // key, try to find the first record with a key
+ // greater than our starting key.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_SET_RANGE) == 0) {
+ // If we find such a record, step back
+ // to the previous record.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_PREV) != 0)
+ goto cleanup;
+ } else {
+ // If we can't even find a record with
+ // a key greater than our starting key,
+ // fall back to starting from the last
+ // record in the database.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_LAST) != 0)
+ goto cleanup;
+ }
+ }
+ }
+ // If we're not scanning in reverse...
+ else {
+ // Try to position the cursor at the first key-value
+ // pair where both the key and the value are greater
+ // than or equal to our starting point.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_SET) != 0) {
+ // If we couldn't find a record that matches
+ // both our starting key and value, try to find
+ // a record that matches just our starting key.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_SET_RANGE) != 0)
+ goto cleanup;
+
+ // If we can't find a record with our starting
+ // key, try to find the first record with a key
+ // greater than our starting key.
+ if (mdb_cursor_get(cursor, &k, &v, MDB_FIRST_DUP) != 0)
+ goto cleanup;
+ }
+ }
+ }
+
+ // Whether we're scanning forward or backward, start the actual
+ // iteration, moving one step at a time in the appropriate direction
+ // and calling the provided callback for each record.
+ do {
+ if (!cb(&k, &v))
+ goto cleanup;
+ } while (mdb_cursor_get(cursor, &k, &v, op) == 0);
+
+ // If we make it through the entire iteration without the callback
+ // returning false, return true to signal success.
+ success = 1;
+
+cleanup:
+ mdb_cursor_close(cursor);
+ return success;
+}
+
+