use aok::Void;
use tempfile::tempdir;
use wedb_embed::WeDb;
use wedb_embed::zset::{
Aggregate, RangeLexSpec, RangeScoreSpec, ZAdd, ZRangeSpec, ZSetMeta, decode_sortable_f64,
encode_sortable_f64,
};
#[ctor::ctor(unsafe)]
fn _log_init() {
log_init::init();
}
#[test]
fn test_zset_metadata_and_score_codec() -> Void {
let meta = ZSetMeta::new(1700000000000, 202, 88);
let enc = meta.encode();
assert_eq!(enc.len(), ZSetMeta::ENCODED_SIZE);
let dec = ZSetMeta::decode(&enc).expect("decode failed");
assert_eq!(dec.base.size, 88);
let kv_enc = meta.encode_kvrocks();
assert_eq!(kv_enc.len(), ZSetMeta::KVROCKS_ENCODED_SIZE);
let kv_dec = ZSetMeta::decode(&kv_enc).expect("decode kvrocks failed");
assert_eq!(kv_dec.base.size, 88);
let scores = [
f64::NEG_INFINITY,
-1000.5,
-100.5,
-1.0,
-0.0,
0.0,
0.5,
100.5,
1000.5,
f64::INFINITY,
];
for i in 0..scores.len() - 1 {
let enc1 = encode_sortable_f64(scores[i]);
let enc2 = encode_sortable_f64(scores[i + 1]);
assert!(
enc1 <= enc2,
"failed for {} vs {}",
scores[i],
scores[i + 1]
);
let dec = decode_sortable_f64(enc1);
if scores[i].is_nan() {
assert!(dec.is_nan());
} else {
assert_eq!(dec.to_bits(), scores[i].to_bits());
}
}
Ok(())
}
#[test]
fn test_zset_basic_ops_and_binary_safety() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
let bin_m1 = b"bin\x00key\xff";
let bin_m2 = b"\x00\x01\x02\xfe\xff";
let bin_m3 = b"regular_user";
assert_eq!(
db.zadd(
"z_bin",
&[
(10.0, bin_m1.as_slice()),
(20.0, bin_m2.as_slice()),
(15.0, bin_m3.as_slice())
],
[]
)?,
3
);
assert_eq!(db.zcard("z_bin")?, 3);
assert_eq!(db.zscore("z_bin", bin_m1)?, Some(10.0));
assert_eq!(db.zscore("z_bin", bin_m2)?, Some(20.0));
assert_eq!(db.zscore("z_bin", bin_m3)?, Some(15.0));
assert_eq!(db.zscore("z_bin", b"nonexistent")?, None);
let mget = db.zmget(
"z_bin",
&[bin_m1.as_slice(), bin_m2.as_slice(), b"not_found"],
)?;
assert_eq!(mget.len(), 2);
assert_eq!(mget.get(bin_m1.as_slice()), Some(&10.0));
assert_eq!(mget.get(bin_m2.as_slice()), Some(&20.0));
assert_eq!(
db.zmscore(
"z_bin",
&[bin_m1.as_slice(), b"not_found", bin_m2.as_slice()]
)?,
vec![Some(10.0), None, Some(20.0)]
);
let range = db.zrange("z_bin", 0, -1)?;
assert_eq!(
range,
vec![
(bin_m1.to_vec(), 10.0),
(bin_m3.to_vec(), 15.0),
(bin_m2.to_vec(), 20.0),
]
);
let revrange = db.zrevrange("z_bin", 0, 1)?;
assert_eq!(
revrange,
vec![(bin_m2.to_vec(), 20.0), (bin_m3.to_vec(), 15.0)]
);
assert_eq!(db.zrank("z_bin", bin_m1)?, Some(0));
assert_eq!(db.zrank("z_bin", bin_m3)?, Some(1));
assert_eq!(db.zrank("z_bin", bin_m2)?, Some(2));
assert_eq!(db.zrank_with_score("z_bin", bin_m2)?, Some((2, 20.0)));
assert_eq!(db.zrevrank("z_bin", bin_m2)?, Some(0));
assert_eq!(db.zrevrank_with_score("z_bin", bin_m2)?, Some((0, 20.0)));
assert_eq!(db.zincrby("z_bin", 25.0, bin_m1)?, 35.0);
assert_eq!(db.zscore("z_bin", bin_m1)?, Some(35.0));
assert_eq!(db.zrem("z_bin", &[bin_m2.as_slice(), b"nonexistent"])?, 1);
assert_eq!(db.zcard("z_bin")?, 2);
assert_eq!(db.zscore("z_bin", bin_m2)?, None);
Ok(())
}
#[test]
fn test_zset_options_nx_xx_gt_lt_ch_and_incompatible() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
assert!(
db.zadd("z_opts", &[(10.0, "m1")], [ZAdd::Nx, ZAdd::Xx])
.is_err()
);
assert!(
db.zadd("z_opts", &[(10.0, "m1")], [ZAdd::Gt, ZAdd::Lt])
.is_err()
);
assert!(
db.zadd("z_opts", &[(10.0, "m1")], [ZAdd::Nx, ZAdd::Gt])
.is_err()
);
db.zadd("z_opts", &[(100.0, "m1")], [])?;
assert_eq!(
db.zadd("z_opts", &[(200.0, "m1"), (50.0, "m2")], [ZAdd::Nx])?,
1
);
assert_eq!(db.zscore("z_opts", "m1")?, Some(100.0));
assert_eq!(db.zscore("z_opts", "m2")?, Some(50.0));
assert_eq!(
db.zadd("z_opts", &[(300.0, "m1"), (10.0, "m3")], [ZAdd::Xx])?,
0
);
assert_eq!(db.zscore("z_opts", "m1")?, Some(300.0));
assert_eq!(db.zscore("z_opts", "m3")?, None);
db.zadd("z_opts", &[(250.0, "m1")], [ZAdd::Gt])?;
assert_eq!(db.zscore("z_opts", "m1")?, Some(300.0)); db.zadd("z_opts", &[(350.0, "m1")], [ZAdd::Gt])?;
assert_eq!(db.zscore("z_opts", "m1")?, Some(350.0));
db.zadd("z_opts", &[(400.0, "m1")], [ZAdd::Lt])?;
assert_eq!(db.zscore("z_opts", "m1")?, Some(350.0)); db.zadd("z_opts", &[(150.0, "m1")], [ZAdd::Lt])?;
assert_eq!(db.zscore("z_opts", "m1")?, Some(150.0));
let ch = db.zadd("z_opts", &[(180.0, "m1"), (90.0, "m4")], [ZAdd::Ch])?;
assert_eq!(ch, 2);
let ch_dup = db.zadd(
"z_opts_dup",
&[(10.0, "dup"), (20.0, "dup"), (30.0, "dup")],
[],
)?;
assert_eq!(ch_dup, 1);
assert_eq!(db.zscore("z_opts_dup", "dup")?, Some(30.0));
Ok(())
}
#[test]
fn test_zset_ranges_and_pop_and_algebra() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.zadd(
"z_range",
&[(1.0, "a"), (2.0, "b"), (3.0, "c"), (4.0, "d"), (5.0, "e")],
[],
)?;
assert_eq!(db.zrange("z_range", 0, -6)?, Vec::new());
assert_eq!(db.zrange("z_range", 0, -5)?, vec![(b"a".to_vec(), 1.0)]);
assert_eq!(db.zrange("z_range", -2, -1)?.len(), 2);
assert_eq!(db.zrevrange("z_range", 0, -6)?, Vec::new());
assert_eq!(db.zrevrange("z_range", 0, -5)?, vec![(b"e".to_vec(), 5.0)]);
let score_spec = RangeScoreSpec {
min: 2.0,
max: 4.0,
minex: true,
maxex: false,
offset: 0,
count: None,
};
assert_eq!(db.zcount("z_range", &score_spec)?, 2); let by_score = db.zrangebyscore("z_range", &score_spec)?;
assert_eq!(by_score, vec![(b"c".to_vec(), 3.0), (b"d".to_vec(), 4.0)]);
let rev_by_score = db.zrevrangebyscore("z_range", &score_spec)?;
assert_eq!(
rev_by_score,
vec![(b"d".to_vec(), 4.0), (b"c".to_vec(), 3.0)]
);
let lex_spec = RangeLexSpec {
min: b"b".to_vec(),
max: b"d".to_vec(),
minex: false,
maxex: true,
min_infinite: false,
max_infinite: false,
offset: 0,
count: None,
};
assert_eq!(db.zlexcount("z_range", &lex_spec)?, 2); let by_lex = db.zrangebylex("z_range", &lex_spec)?;
assert_eq!(by_lex, vec![b"b".to_vec(), b"c".to_vec()]);
let rev_by_lex = db.zrevrangebylex("z_range", &lex_spec)?;
assert_eq!(rev_by_lex, vec![b"c".to_vec(), b"b".to_vec()]);
let zrange_score_spec = ZRangeSpec {
by_score: true,
rev: true,
offset: 0,
count: Some(2),
..Default::default()
};
let unified_res = db.zrange_spec("z_range", b"2.0", b"5.0", &zrange_score_spec)?;
assert_eq!(unified_res.len(), 2);
assert_eq!(unified_res[0].0, b"e");
let bzpop = db.bzpopmin(&["nonexistent_zset", "z_range"])?;
assert_eq!(bzpop, Some((b"z_range".to_vec(), b"a".to_vec(), 1.0)));
assert_eq!(db.zcard("z_range")?, 4);
let popmax = db.zpopmax("z_range", 1)?;
assert_eq!(popmax, vec![(b"e".to_vec(), 5.0)]);
assert_eq!(db.zcard("z_range")?, 3);
let rand_items = db.zrandmember("z_range", 2)?;
assert_eq!(rand_items.len(), 2);
let rand_rep = db.zrandmember("z_range", -4)?;
assert_eq!(rand_rep.len(), 4);
db.zadd("z_A", &[(10.0, "x"), (20.0, "y")], [])?;
db.zadd("z_B", &[(30.0, "y"), (40.0, "z")], [])?;
let diff = db.zdiff(&["z_A", "z_B"])?;
assert_eq!(diff, vec![(b"x".to_vec(), 10.0)]);
assert_eq!(db.zdiffstore("z_diff_dst", &["z_A", "z_B"])?, 1);
let union_res = db.zunion(&[("z_A", 1.0), ("z_B", 2.0)], Aggregate::Sum)?;
assert_eq!(union_res.len(), 3);
assert_eq!(
db.zunionstore("z_union_dst", &[("z_A", 1.0), ("z_B", 2.0)], Aggregate::Sum)?,
3
);
let inter_res = db.zinter(&[("z_A", 1.0), ("z_B", 1.0)], Aggregate::Max)?;
assert_eq!(inter_res, vec![(b"y".to_vec(), 30.0)]);
assert_eq!(
db.zinterstore("z_inter_dst", &[("z_A", 1.0), ("z_B", 1.0)], Aggregate::Max)?,
1
);
assert_eq!(db.zintercard(&["z_A", "z_B"], 0)?, 1);
assert_eq!(db.zintercard(&["z_A", "z_B"], 1)?, 1);
assert_eq!(db.zremrangebyrank("z_range", 0, 0)?, 1);
assert_eq!(db.zcard("z_range")?, 2);
let rem_score_spec = RangeScoreSpec::new(2.5, 3.5);
assert_eq!(db.zremrangebyscore("z_range", &rem_score_spec)?, 1);
assert_eq!(db.zcard("z_range")?, 1);
let rem_lex_spec = RangeLexSpec::new(b"a", b"z");
assert_eq!(db.zremrangebylex("z_range", &rem_lex_spec)?, 1);
assert_eq!(db.zcard("z_range")?, 0);
let (cur, page) = db.zscan("z_union_dst", 0, None, Some(10))?;
assert_eq!(page.len(), 3);
assert_eq!(cur, 0);
Ok(())
}
#[test]
fn test_zset_extended_edge_cases() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.zadd(
"z_equal_scores",
&[
(10.0, "charlie"),
(10.0, "alice"),
(10.0, "bob"),
(10.0, "david"),
],
[],
)?;
let all = db.zget_all("z_equal_scores")?;
assert_eq!(
all,
vec![
(b"alice".to_vec(), 10.0),
(b"bob".to_vec(), 10.0),
(b"charlie".to_vec(), 10.0),
(b"david".to_vec(), 10.0),
]
);
assert_eq!(db.zrank("z_equal_scores", "alice")?, Some(0));
assert_eq!(db.zrank("z_equal_scores", "bob")?, Some(1));
assert_eq!(db.zrank("z_equal_scores", "charlie")?, Some(2));
assert_eq!(db.zrank("z_equal_scores", "david")?, Some(3));
assert_eq!(db.zrank("z_equal_scores", "none")?, None);
assert_eq!(db.zrevrank("z_equal_scores", "david")?, Some(0));
assert_eq!(db.zrevrank("z_equal_scores", "charlie")?, Some(1));
assert_eq!(db.zrevrank("z_equal_scores", "bob")?, Some(2));
assert_eq!(db.zrevrank("z_equal_scores", "alice")?, Some(3));
assert_eq!(db.zrevrank("z_equal_scores", "none")?, None);
db.zadd(
"z_floats",
&[
(f64::NEG_INFINITY, "neg_inf"),
(-0.0, "neg_zero"),
(0.0, "pos_zero"),
(1e-50, "tiny"),
(1e50, "huge"),
(f64::INFINITY, "pos_inf"),
],
[],
)?;
assert_eq!(db.zcard("z_floats")?, 6);
let float_all = db.zget_all("z_floats")?;
assert_eq!(float_all[0].0, b"neg_inf");
assert_eq!(float_all[float_all.len() - 1].0, b"pos_inf");
let popped_min = db.zpopmin("z_floats", 2)?;
assert_eq!(popped_min.len(), 2);
assert_eq!(popped_min[0].0, b"neg_inf");
assert_eq!(db.zcard("z_floats")?, 4);
let popped_max = db.zpopmax("z_floats", 2)?;
assert_eq!(popped_max.len(), 2);
assert_eq!(popped_max[0].0, b"pos_inf");
assert_eq!(db.zcard("z_floats")?, 2);
assert_eq!(db.zincrby("z_new_incr", 42.5, "item1")?, 42.5);
assert_eq!(db.zscore("z_new_incr", "item1")?, Some(42.5));
assert_eq!(db.zincrby("z_new_incr", -10.5, "item1")?, 32.0);
assert_eq!(db.zscore("z_new_incr", "item1")?, Some(32.0));
db.zadd("z_set1", &[(10.0, "a"), (20.0, "b")], [])?;
db.zadd("z_set2", &[(30.0, "a"), (15.0, "b")], [])?;
let inter_min = db.zinter(&[("z_set1", 1.0), ("z_set2", 1.0)], Aggregate::Min)?;
assert_eq!(
inter_min,
vec![(b"a".to_vec(), 10.0), (b"b".to_vec(), 15.0)]
);
let inter_max = db.zinter(&[("z_set1", 1.0), ("z_set2", 1.0)], Aggregate::Max)?;
assert_eq!(
inter_max,
vec![(b"b".to_vec(), 20.0), (b"a".to_vec(), 30.0)]
);
let union_min = db.zunion(&[("z_set1", 1.0), ("z_set2", 1.0)], Aggregate::Min)?;
assert_eq!(
union_min,
vec![(b"a".to_vec(), 10.0), (b"b".to_vec(), 15.0)]
);
assert_eq!(db.zintercard(&["z_set1", "z_set2"], 0)?, 2);
assert_eq!(db.zintercard(&["z_set1", "z_set2"], 1)?, 1);
assert_eq!(db.zintercard(&["z_set1", "z_set2"], 10)?, 2);
assert_eq!(db.zintercard(&["z_set1", "nonexistent"], 5)?, 0);
let zmscores = db.zmscore("z_set1", &["a", "b", "c"])?;
assert_eq!(zmscores, vec![Some(10.0), Some(20.0), None]);
let zmscores_empty = db.zmscore("empty_zset", &["a", "b"])?;
assert_eq!(zmscores_empty, vec![None, None]);
let zmget_empty = db.zmget("empty_zset", &["a", "b"])?;
assert!(zmget_empty.is_empty());
Ok(())
}