-- 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: sorted sets ==") local tmp_path = function() return "/tmp/mneme_zs_" .. std.nanoid() .. ".mneme" end testify:that("add and range_by_score returns score-ordered results", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") local zs = ks:sorted_set("board") zs:add(100.0, "alice") zs:add(85.5, "bob") zs:add(92.3, "carol") local results = zs:range_by_score(-math.huge, math.huge) testimony.assert_equal(3, #results) testimony.assert_equal("bob", results[1].member) testimony.assert_true(math.abs(results[1].score - 85.5) < 0.01) testimony.assert_equal("carol", results[2].member) testimony.assert_equal("alice", results[3].member) db:close() std.fs.remove(path) end) testify:that("score lookup returns correct score", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(42.5, "x") local s = zs:score("x") testimony.assert_true(math.abs(s - 42.5) < 0.01) testimony.assert_nil(zs:score("missing")) db:close() std.fs.remove(path) end) testify:that("rem removes a member", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(1, "a", 2, "b", 3, "c") testimony.assert_equal(3, zs:card()) zs:rem("b") testimony.assert_equal(2, zs:card()) testimony.assert_nil(zs:score("b")) local results = zs:range_by_score(-math.huge, math.huge) testimony.assert_equal(2, #results) testimony.assert_equal("a", results[1].member) testimony.assert_equal("c", results[2].member) db:close() std.fs.remove(path) end) testify:that("update score re-adds same member with new score", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(10, "x") testimony.assert_true(math.abs(zs:score("x") - 10) < 0.01) zs:add(20, "x") testimony.assert_true(math.abs(zs:score("x") - 20) < 0.01) testimony.assert_equal(1, zs:card()) -- still 1 member db:close() std.fs.remove(path) end) testify:that("range_by_rank returns correct slice", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(10, "a", 20, "b", 30, "c", 40, "d", 50, "e") local results = zs:range_by_rank(1, 3) -- ranks 1,2,3 (0-based) testimony.assert_equal(3, #results) testimony.assert_equal("b", results[1].member) testimony.assert_equal("c", results[2].member) testimony.assert_equal("d", results[3].member) db:close() std.fs.remove(path) end) testify:that("reverse range returns descending order", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(10, "a", 20, "b", 30, "c") local results = zs:range_by_score(30, 10, { reverse = true }) testimony.assert_equal(3, #results) testimony.assert_equal("c", results[1].member) testimony.assert_equal("b", results[2].member) testimony.assert_equal("a", results[3].member) db:close() std.fs.remove(path) end) testify:that("rank returns correct 0-based position", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(10, "first", 20, "second", 30, "third") testimony.assert_equal(0, zs:rank("first")) testimony.assert_equal(1, zs:rank("second")) testimony.assert_equal(2, zs:rank("third")) testimony.assert_nil(zs:rank("missing")) db:close() std.fs.remove(path) end) testify:that("iter yields all entries in order", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(30, "c", 10, "a", 20, "b") local entries = {} for score, member in zs:iter() do table.insert(entries, { s = score, m = member }) end testimony.assert_equal(3, #entries) testimony.assert_equal("a", entries[1].m) testimony.assert_equal("b", entries[2].m) testimony.assert_equal("c", entries[3].m) db:close() std.fs.remove(path) end) testify:that("iter reverse yields descending order", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(10, "a", 20, "b", 30, "c") local entries = {} for score, member in zs:iter({ reverse = true }) do table.insert(entries, { s = score, m = member }) end testimony.assert_equal(3, #entries) testimony.assert_equal("c", entries[1].m) testimony.assert_equal("b", entries[2].m) testimony.assert_equal("a", entries[3].m) db:close() std.fs.remove(path) end) testify:that("iter reverse handles control-byte members", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(1, "\x01member", 2, "\x02member", 3, "\xffmember") local entries = {} for score, member in zs:iter({ reverse = true }) do table.insert(entries, { s = score, m = member }) end testimony.assert_equal(3, #entries) testimony.assert_equal("\xffmember", entries[1].m) testimony.assert_equal("\x02member", entries[2].m) testimony.assert_equal("\x01member", entries[3].m) db:close() std.fs.remove(path) end) testify:that("negative scores sort correctly", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(-10, "neg", 0, "zero", 10, "pos") local results = zs:range_by_score(-math.huge, math.huge) testimony.assert_equal(3, #results) testimony.assert_equal("neg", results[1].member) testimony.assert_true(results[1].score < 0) testimony.assert_equal("zero", results[2].member) testimony.assert_equal("pos", results[3].member) db:close() std.fs.remove(path) end) testify:that("1000 members with range queries", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("big") for i = 1, 1000 do zs:add(i * 0.1, "m" .. string.format("%04d", i)) end testimony.assert_equal(1000, zs:card()) -- Range query: scores 50.0 to 60.0 (members 500-600) local results = zs:range_by_score(50.0, 60.0) testimony.assert_true(#results >= 99 and #results <= 101) -- With limit local limited = zs:range_by_score(0, math.huge, { limit = 5 }) testimony.assert_equal(5, #limited) db:close() std.fs.remove(path) end) testify:that("multiple add in single call", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local zs = db:keyspace("t"):sorted_set("s") zs:add(100, "alice", 85.5, "bob", 92.3, "carol") testimony.assert_equal(3, zs:card()) testimony.assert_true(math.abs(zs:score("alice") - 100) < 0.01) testimony.assert_true(math.abs(zs:score("bob") - 85.5) < 0.01) db:close() std.fs.remove(path) end) testify:conclude()