// SPDX-FileCopyrightText: © 2026 Vladimir Zorin // SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later // Licensed under OWL v1.0+. See LICENSE. #include "mneme.h" #include #include /* Use shared inline helpers from mneme.h */ #define keycmp mneme_keycmp #define cptr_get mneme_cptr_get /* ── Leaf page cache helper ───────────────────────────────────────── */ static inline const uint8_t *cursor_leaf_page(mneme_cursor_t *cur) { int ll = cur->depth - 1; uint32_t pgno = cur->stack[ll].pgno; if (cur->leaf_page && cur->leaf_pgno == pgno) return cur->leaf_page; cur->leaf_page = mneme_page_resolve(cur->txn, pgno); cur->leaf_pgno = pgno; return cur->leaf_page; } static inline void cursor_invalidate_cache(mneme_cursor_t *cur) { cur->leaf_page = NULL; } /* Use shared page search helpers from mneme.h */ #define cur_branch_search mneme_branch_search #define cur_branch_child mneme_branch_child #define cur_leaf_search mneme_leaf_search /* ── Cursor open ───────────────────────────────────────────────────── */ int mneme_cursor_open(mneme_txn_t *txn, uint32_t root_pgno, mneme_cursor_t **out) { mneme_cursor_t *cur = (mneme_cursor_t *)calloc(1, sizeof(*cur)); if (!cur) return -1; cur->txn = txn; cur->root_pgno = root_pgno; cur->depth = 0; cur->valid = 0; cur->leaf_page = NULL; cur->leaf_pgno = 0; *out = cur; return 0; } /* ── Descend to leftmost leaf from a given page ────────────────────── */ static int descend_left(mneme_cursor_t *cur, uint32_t pgno) { for (;;) { const uint8_t *page = mneme_page_resolve(cur->txn, pgno); if (!page) return -1; const mneme_page_hdr_t *hdr = (const mneme_page_hdr_t *)page; if (cur->depth >= MNEME_CURSOR_STACK_DEPTH) return -1; cur->stack[cur->depth].pgno = pgno; if (hdr->page_type == MNEME_PAGE_LEAF) { cur->stack[cur->depth].idx = 0; cur->depth++; cur->valid = (hdr->key_count > 0) ? 1 : 0; cur->leaf_page = page; cur->leaf_pgno = pgno; return 0; } /* Branch: follow leftmost child (cell[0].child) */ cur->stack[cur->depth].idx = 0; cur->depth++; pgno = cur_branch_child(page, 0); } } /* ── Descend to rightmost leaf from a given page ───────────────────── */ static int descend_right(mneme_cursor_t *cur, uint32_t pgno) { for (;;) { const uint8_t *page = mneme_page_resolve(cur->txn, pgno); if (!page) return -1; const mneme_page_hdr_t *hdr = (const mneme_page_hdr_t *)page; if (cur->depth >= MNEME_CURSOR_STACK_DEPTH) return -1; cur->stack[cur->depth].pgno = pgno; if (hdr->page_type == MNEME_PAGE_LEAF) { cur->stack[cur->depth].idx = (hdr->key_count > 0) ? (int)hdr->key_count - 1 : 0; cur->depth++; cur->valid = (hdr->key_count > 0) ? 1 : 0; cur->leaf_page = page; cur->leaf_pgno = pgno; return 0; } /* Branch: follow rightmost child (right_ptr) */ cur->stack[cur->depth].idx = (int)hdr->key_count; cur->depth++; pgno = hdr->right_ptr; } } /* ── Cursor first ──────────────────────────────────────────────────── */ int mneme_cursor_first(mneme_cursor_t *cur) { cur->depth = 0; cur->valid = 0; cursor_invalidate_cache(cur); int rc = descend_left(cur, cur->root_pgno); if (rc < 0) return rc; /* If we landed on an empty leaf (e.g. after deletions), advance */ if (!cur->valid && cur->depth > 0) return mneme_cursor_next(cur); return 0; } /* ── Cursor last ───────────────────────────────────────────────────── */ int mneme_cursor_last(mneme_cursor_t *cur) { cur->depth = 0; cur->valid = 0; cursor_invalidate_cache(cur); int rc = descend_right(cur, cur->root_pgno); if (rc < 0) return rc; if (!cur->valid && cur->depth > 0) return mneme_cursor_prev(cur); return 0; } /* ── Cursor seek ───────────────────────────────────────────────────── */ int mneme_cursor_seek(mneme_cursor_t *cur, const uint8_t *key, uint16_t key_len) { cur->depth = 0; cur->valid = 0; cursor_invalidate_cache(cur); uint32_t pgno = cur->root_pgno; for (;;) { const uint8_t *page = mneme_page_resolve(cur->txn, pgno); if (!page) return -1; const mneme_page_hdr_t *hdr = (const mneme_page_hdr_t *)page; if (cur->depth >= MNEME_CURSOR_STACK_DEPTH) return -1; cur->stack[cur->depth].pgno = pgno; if (hdr->page_type == MNEME_PAGE_LEAF) { int found; int idx = cur_leaf_search(page, key, key_len, &found); if (idx >= (int)hdr->key_count) { /* Past end of this leaf. Need to advance to next leaf by going up and finding the next sibling. */ cur->stack[cur->depth].idx = (int)hdr->key_count; cur->depth++; cur->valid = 0; /* Use cursor_next logic to find the next valid position */ return mneme_cursor_next(cur); } cur->stack[cur->depth].idx = idx; cur->depth++; cur->valid = 1; cur->leaf_page = page; cur->leaf_pgno = pgno; return 0; } /* Branch: descend */ int cidx = cur_branch_search(page, key, key_len); cur->stack[cur->depth].idx = cidx; cur->depth++; pgno = cur_branch_child(page, cidx); } } /* ── Cursor next ───────────────────────────────────────────────────── */ int mneme_cursor_next(mneme_cursor_t *cur) { for (;;) { if (cur->depth == 0) { cur->valid = 0; cursor_invalidate_cache(cur); return 0; } /* Try advancing in the current leaf */ int leaf_level = cur->depth - 1; cur->stack[leaf_level].idx++; const uint8_t *page = cursor_leaf_page(cur); if (!page) { cur->valid = 0; cursor_invalidate_cache(cur); return -1; } const mneme_page_hdr_t *hdr = (const mneme_page_hdr_t *)page; if (cur->stack[leaf_level].idx < (int)hdr->key_count) { cur->valid = 1; return 0; } /* Leaf exhausted. Pop up to find next subtree. */ cur->depth = leaf_level; /* remove leaf from stack */ cursor_invalidate_cache(cur); while (cur->depth > 0) { cur->depth--; /* pop to parent branch */ int parent_level = cur->depth; /* Advance to next child in this branch */ cur->stack[parent_level].idx++; page = mneme_page_resolve(cur->txn, cur->stack[parent_level].pgno); if (!page) { cur->valid = 0; return -1; } hdr = (const mneme_page_hdr_t *)page; if (cur->stack[parent_level].idx <= (int)hdr->key_count) { /* Valid child exists. Descend to its leftmost leaf. */ uint32_t child = cur_branch_child(page, cur->stack[parent_level].idx); cur->depth = parent_level + 1; /* restore this branch level */ int rc = descend_left(cur, child); if (rc < 0) return rc; if (!cur->valid) break; return 0; } /* This branch is also exhausted. Continue popping. */ } if (cur->depth == 0) { /* Popped past root: cursor exhausted */ cur->valid = 0; return 0; } /* Descended into an empty leaf; continue outer loop. */ } } /* ── Cursor prev ───────────────────────────────────────────────────── */ int mneme_cursor_prev(mneme_cursor_t *cur) { for (;;) { if (cur->depth == 0) { cur->valid = 0; cursor_invalidate_cache(cur); return 0; } int leaf_level = cur->depth - 1; cur->stack[leaf_level].idx--; if (cur->stack[leaf_level].idx >= 0) { cur->valid = 1; return 0; } /* Before first cell. Pop up to find previous subtree. */ cur->depth = leaf_level; cursor_invalidate_cache(cur); while (cur->depth > 0) { cur->depth--; int parent_level = cur->depth; cur->stack[parent_level].idx--; if (cur->stack[parent_level].idx >= 0) { const uint8_t *page = mneme_page_resolve(cur->txn, cur->stack[parent_level].pgno); if (!page) { cur->valid = 0; return -1; } uint32_t child = cur_branch_child(page, cur->stack[parent_level].idx); cur->depth = parent_level + 1; int rc = descend_right(cur, child); if (rc < 0) return rc; if (!cur->valid) break; return 0; } } if (cur->depth == 0) { cur->valid = 0; return 0; } /* Descended into an empty leaf; continue outer loop. */ } } /* ── Cursor key ────────────────────────────────────────────────────── */ int mneme_cursor_key(mneme_cursor_t *cur, const uint8_t **key_out, uint16_t *key_len_out) { if (!cur->valid || cur->depth == 0) return -1; const uint8_t *page = cursor_leaf_page(cur); if (!page) return -1; int idx = cur->stack[cur->depth - 1].idx; uint16_t off = cptr_get(page, idx); uint16_t kl; memcpy(&kl, page + off, 2); *key_out = page + off + MNEME_LCELL_HDR; *key_len_out = kl; return 0; } /* ── Cursor val ────────────────────────────────────────────────────── */ int mneme_cursor_val(mneme_cursor_t *cur, const uint8_t **val_out, uint32_t *val_len_out, uint8_t *type_tag_out, int64_t *ttl_out) { if (!cur->valid || cur->depth == 0) return -1; const uint8_t *page = cursor_leaf_page(cur); if (!page) return -1; int idx = cur->stack[cur->depth - 1].idx; uint16_t off = cptr_get(page, idx); const uint8_t *c = page + off; uint16_t kl; uint32_t vl; int64_t ttl; uint8_t tag; memcpy(&kl, c, 2); c += 2; memcpy(&vl, c, 4); c += 4; memcpy(&ttl, c, 8); c += 8; tag = *c; c += 1; c += kl; /* skip key */ *val_out = c; *val_len_out = vl; *type_tag_out = tag; *ttl_out = ttl; return 0; } /* ── Cursor entry (combined key + val in one call) ────────────────── */ int mneme_cursor_entry(mneme_cursor_t *cur, const uint8_t **key_out, uint16_t *key_len_out, const uint8_t **val_out, uint32_t *val_len_out, uint8_t *type_tag_out, int64_t *ttl_out) { if (!cur->valid || cur->depth == 0) return -1; const uint8_t *page = cursor_leaf_page(cur); if (!page) return -1; int idx = cur->stack[cur->depth - 1].idx; uint16_t off = cptr_get(page, idx); const uint8_t *c = page + off; uint16_t kl; uint32_t vl; int64_t ttl; uint8_t tag; memcpy(&kl, c, 2); c += 2; memcpy(&vl, c, 4); c += 4; memcpy(&ttl, c, 8); c += 8; tag = *c; c += 1; *key_out = c; *key_len_out = kl; c += kl; *val_out = c; *val_len_out = vl; *type_tag_out = tag; *ttl_out = ttl; return 0; } /* ── Cursor close ──────────────────────────────────────────────────── */ void mneme_cursor_close(mneme_cursor_t *cur) { if (cur) free(cur); }