-- SPDX-FileCopyrightText: © 2026 Vladimir Zorin -- SPDX-License-Identifier: OWL-1.0 or later local testimony = require("testimony") local std = require("std") local mneme = require("mneme") local testify = testimony.new("== mneme: keyspaces and high-level API ==") local tmp_path = function() return "/tmp/mneme_ks_" .. std.nanoid() .. ".mneme" end testify:that("set and get a key in a keyspace", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("test") testimony.assert_not_nil(ks) local ok, err = ks:set("hello", "world") testimony.assert_not_nil(ok, err) local val = ks:get("hello") testimony.assert_equal("world", val) db:close() std.fs.remove(path) end) testify:that("get non-existent key returns nil", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("test") local val, err = ks:get("missing") testimony.assert_nil(val) db:close() std.fs.remove(path) end) testify:that("del removes a key", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("test") ks:set("a", "1") ks:set("b", "2") ks:del("a") testimony.assert_nil(ks:get("a")) testimony.assert_equal("2", ks:get("b")) db:close() std.fs.remove(path) end) testify:that("exists returns true/false", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("test") ks:set("present", "yes") testimony.assert_true(ks:exists("present")) testimony.assert_true(not ks:exists("absent")) db:close() std.fs.remove(path) end) testify:that("two keyspaces are isolated", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks1 = db:keyspace("alpha") local ks2 = db:keyspace("beta") ks1:set("key", "from_alpha") ks2:set("key", "from_beta") testimony.assert_equal("from_alpha", ks1:get("key")) testimony.assert_equal("from_beta", ks2:get("key")) -- Keys in one don't appear in the other ks1:set("only_in_alpha", "yes") testimony.assert_nil(ks2:get("only_in_alpha")) db:close() std.fs.remove(path) end) testify:that("db:keyspaces() lists created keyspaces", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) db:keyspace("first"):set("x", "1") db:keyspace("second"):set("y", "2") db:keyspace("third"):set("z", "3") local names = db:keyspaces() testimony.assert_equal(3, #names) -- Should be sorted (B-tree order) testimony.assert_equal("first", names[1]) testimony.assert_equal("second", names[2]) testimony.assert_equal("third", names[3]) db:close() std.fs.remove(path) end) testify:that("db:drop_keyspace() removes a keyspace", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) db:keyspace("keep"):set("a", "1") db:keyspace("drop"):set("b", "2") db:drop_keyspace("drop") local names = db:keyspaces() testimony.assert_equal(1, #names) testimony.assert_equal("keep", names[1]) -- Dropped keyspace's data is inaccessible testimony.assert_nil(db:keyspace("drop"):get("b")) db:close() std.fs.remove(path) end) testify:that("scan with prefix returns matching keys", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("scan_test") ks:set("user:001", "alice") ks:set("user:002", "bob") ks:set("user:003", "carol") ks:set("item:001", "widget") ks:set("item:002", "gadget") local users = {} for k, v in ks:scan("user:") do users[k] = v end testimony.assert_equal("alice", users["user:001"]) testimony.assert_equal("bob", users["user:002"]) testimony.assert_equal("carol", users["user:003"]) testimony.assert_nil(users["item:001"]) -- Count testimony.assert_equal(3, ks:count("user:")) testimony.assert_equal(2, ks:count("item:")) testimony.assert_equal(5, ks:count()) db:close() std.fs.remove(path) end) testify:that("scan without prefix returns all keys", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("all") ks:set("a", "1") ks:set("b", "2") ks:set("c", "3") local count = 0 for k, v in ks:scan() do count = count + 1 end testimony.assert_equal(3, count) db:close() std.fs.remove(path) end) testify:that("range scan returns keys in [start, end)", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("range_test") for i = 1, 20 do ks:set(string.format("k:%02d", i), "v" .. i) end local results = {} for k, v in ks:range("k:05", "k:10") do table.insert(results, k) end testimony.assert_equal(5, #results) testimony.assert_equal("k:05", results[1]) testimony.assert_equal("k:09", results[5]) db:close() std.fs.remove(path) end) testify:that("data persists across close and reopen", function() local path = tmp_path() do local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("persist") ks:set("survived", "yes") db:close() end do local db = mneme.open(path) local ks = db:keyspace("persist") testimony.assert_equal("yes", ks:get("survived")) db:close() end std.fs.remove(path) end) testify:that("multiple keys in a keyspace work correctly", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("multi") for i = 1, 100 do ks:set(string.format("k:%04d", i), "v" .. i) end for i = 1, 100 do local val = ks:get(string.format("k:%04d", i)) testimony.assert_equal("v" .. i, val) end testimony.assert_equal(100, ks:count()) db:close() std.fs.remove(path) end) testify:conclude()