use aok::Void;
use tempfile::tempdir;
use wedb_embed::WeDb;
use wedb_embed::string::{
DelEx, GetEx, Set, StringLCSArgs, StringLCSMatchedRange, StringLCSResult, StringLCSType,
StringSetArgs, StringSetType, compute_lcs, normalize_range, string_digest,
};
#[ctor::ctor(unsafe)]
fn _log_init() {
log_init::init();
}
#[test]
fn test_string_get_and_set() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
let pairs = [
("test-string-key1", "test-strings-value1"),
("test-string-key2", "test-strings-value2"),
("test-string-key3", "test-strings-value3"),
("test-string-key4", "test-strings-value4"),
("test-string-key5", "test-strings-value5"),
("test-string-key6", "test-strings-value6"),
];
for (k, v) in pairs {
db.set(k, v, &[])?;
}
for (k, v) in pairs {
let val = db.get(k)?;
assert_eq!(val, Some(v.as_bytes().to_vec()));
}
for (k, _) in pairs {
assert_eq!(db.del(&[k])?, 1);
assert_eq!(db.get(k)?, None);
}
Ok(())
}
#[test]
fn test_string_set_options() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
let res = db.set("k1", "v1", &[Set::Nx])?;
assert_eq!(res, Some(Vec::new()));
assert_eq!(db.get("k1")?, Some(b"v1".to_vec()));
let res_nx_fail = db.set("k1", "v2", &[Set::Nx])?;
assert_eq!(res_nx_fail, None);
assert_eq!(db.get("k1")?, Some(b"v1".to_vec()));
let res_xx_fail = db.set("k_nonexist", "v", &[Set::Xx])?;
assert_eq!(res_xx_fail, None);
assert_eq!(db.get("k_nonexist")?, None);
let res_xx_ok = db.set("k1", "v_updated", &[Set::Xx])?;
assert_eq!(res_xx_ok, Some(Vec::new()));
assert_eq!(db.get("k1")?, Some(b"v_updated".to_vec()));
let old = db.set("k1", "v_new", &[Set::Get])?;
assert_eq!(old, Some(b"v_updated".to_vec()));
assert_eq!(db.get("k1")?, Some(b"v_new".to_vec()));
let if_eq_fail = db.set("k1", "v_fail", &[Set::IfEq(b"wrong")])?;
assert_eq!(if_eq_fail, None);
assert_eq!(db.get("k1")?, Some(b"v_new".to_vec()));
let if_eq_ok = db.set("k1", "v_pass", &[Set::IfEq(b"v_new")])?;
assert_eq!(if_eq_ok, Some(Vec::new()));
assert_eq!(db.get("k1")?, Some(b"v_pass".to_vec()));
let if_ne_fail = db.set("k1", "v_ne_fail", &[Set::IfNe(b"v_pass")])?;
assert_eq!(if_ne_fail, None);
let if_ne_ok = db.set("k1", "v_ne_ok", &[Set::IfNe(b"other")])?;
assert_eq!(if_ne_ok, Some(Vec::new()));
assert_eq!(db.get("k1")?, Some(b"v_ne_ok".to_vec()));
Ok(())
}
#[test]
fn test_string_append_and_strlen() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
for i in 0..32 {
let ret = db.append("test_append_k", "a")?;
assert_eq!(ret, i + 1);
}
assert_eq!(db.strlen("test_append_k")?, 32);
assert_eq!(db.del(&["test_append_k"])?, 1);
assert_eq!(db.strlen("test_append_k")?, 0);
Ok(())
}
#[test]
fn test_string_mget_and_mset() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
let pairs = [
("m_k1", "m_v1"),
("m_k2", "m_v2"),
("m_k3", "m_v3"),
("m_k4", "m_v4"),
];
db.mset(&pairs)?;
let keys: Vec<&str> = pairs.iter().map(|(k, _)| *k).collect();
let values = db.mget(&keys)?;
for (i, (_, v)) in pairs.iter().enumerate() {
assert_eq!(values[i], Some(v.as_bytes().to_vec()));
}
let mixed = db.mget(&["m_k1", "nonexistent_k", "m_k3"])?;
assert_eq!(
mixed,
vec![Some(b"m_v1".to_vec()), None, Some(b"m_v3".to_vec())]
);
Ok(())
}
#[test]
fn test_string_incr_and_decr() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
assert_eq!(db.incr("counter")?, 1);
assert_eq!(db.incr("counter")?, 2);
assert_eq!(db.incrby("counter", 10)?, 12);
assert_eq!(db.decr("counter")?, 11);
assert_eq!(db.decrby("counter", 5)?, 6);
assert_eq!(db.incrby("counter", -6)?, 0);
assert_eq!(db.incrby("counter", i64::MAX)?, i64::MAX);
assert!(db.incrby("counter", 1).is_err());
assert_eq!(db.incrby("counter", i64::MIN + 1)?, 0);
assert_eq!(db.incrby("counter", i64::MIN)?, i64::MIN);
assert!(db.incrby("counter", -1).is_err());
db.set("str_k", "abc", &[])?;
assert!(db.incr("str_k").is_err());
assert!(db.incrby("str_k", 5).is_err());
assert!(db.decr("str_k").is_err());
db.set("ws_k", " 123", &[])?;
assert!(db.incr("ws_k").is_err());
db.set("ws_k2", "123 ", &[])?;
assert!(db.incr("ws_k2").is_err());
Ok(())
}
#[test]
fn test_string_incrbyfloat_stored_format() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
let f1 = db.incrbyfloat("float_k", 10.5)?;
assert!((f1 - 10.5).abs() < 1e-9);
assert_eq!(db.get("float_k")?, Some(b"10.5".to_vec()));
db.del(&["float_k"])?;
let f2 = db.incrbyfloat("float_k", 3.0)?;
assert!((f2 - 3.0).abs() < 1e-9);
assert_eq!(db.get("float_k")?, Some(b"3".to_vec()));
let f3 = db.incrbyfloat("float_k", 1.5)?;
assert!((f3 - 4.5).abs() < 1e-9);
assert_eq!(db.get("float_k")?, Some(b"4.5".to_vec()));
let f4 = db.incrbyfloat("float_k", -4.5)?;
assert!((f4 - 0.0).abs() < 1e-9);
assert_eq!(db.get("float_k")?, Some(b"0".to_vec()));
assert!(db.incrbyfloat("float_k", f64::NAN).is_err());
assert!(db.incrbyfloat("float_k", f64::INFINITY).is_err());
Ok(())
}
#[test]
fn test_string_getrange_and_setrange() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.set("range_k", "Hello World", &[])?;
assert_eq!(db.getrange("range_k", 0, 4)?, b"Hello");
assert_eq!(db.getrange("range_k", -5, -1)?, b"World");
assert_eq!(db.getrange("range_k", 0, -1)?, b"Hello World");
assert_eq!(db.getrange("range_k", 100, 200)?, b"");
assert_eq!(db.getrange("range_k", -100, 2)?, b"Hel");
assert_eq!(db.getrange("range_k", -20, -15)?, b"");
assert_eq!(db.getrange("range_k", 5, 2)?, b"");
let new_len = db.setrange("range_k", 6, "Redis")?;
assert_eq!(new_len, 11);
assert_eq!(db.get("range_k")?, Some(b"Hello Redis".to_vec()));
let ext_len = db.setrange("range_k", 12, "Extension")?;
assert_eq!(ext_len, 21);
assert_eq!(db.strlen("range_k")?, 21);
let zero_len = db.setrange("nonexist_range", 0, "")?;
assert_eq!(zero_len, 0);
assert_eq!(db.get("nonexist_range")?, None);
assert!(
db.setrange("range_k", 600 * 1024 * 1024, "overflow")
.is_err()
);
Ok(())
}
#[test]
fn test_string_getdel() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.set("gd_k", "gd_v", &[])?;
assert_eq!(db.getdel("gd_k")?, Some(b"gd_v".to_vec()));
assert_eq!(db.get("gd_k")?, None);
assert_eq!(db.getdel("gd_k")?, None);
Ok(())
}
#[test]
fn test_string_lcs_comprehensive() {
let (s, e) = normalize_range(0, 5, 10);
assert_eq!((s, e), (0, 5));
let (s, e) = normalize_range(-5, -1, 10);
assert_eq!((s, e), (5, 9));
let (s, e) = normalize_range(0, -1, 0);
assert_eq!((s, e), (0, -1));
let res_str = compute_lcs(
b"abcdef",
b"acdf",
StringLCSArgs {
lcs_type: StringLCSType::None,
min_match_len: 0,
},
)
.expect("lcs str failed");
assert_eq!(res_str, StringLCSResult::Str("acdf".to_string()));
let res_len = compute_lcs(
b"abcdef",
b"acdf",
StringLCSArgs {
lcs_type: StringLCSType::Len,
min_match_len: 0,
},
)
.expect("lcs len failed");
assert_eq!(res_len, StringLCSResult::Len(4));
let res_idx = compute_lcs(
b"abcdef",
b"acdf",
StringLCSArgs {
lcs_type: StringLCSType::Idx,
min_match_len: 0,
},
)
.expect("lcs idx failed");
if let StringLCSResult::Idx(idx_res) = res_idx {
assert_eq!(idx_res.len, 4);
assert_eq!(idx_res.matches.len(), 3);
assert_eq!(
idx_res.matches[0],
StringLCSMatchedRange::new(5, 5, 3, 3, 1)
);
assert_eq!(
idx_res.matches[1],
StringLCSMatchedRange::new(2, 3, 1, 2, 2)
);
assert_eq!(
idx_res.matches[2],
StringLCSMatchedRange::new(0, 0, 0, 0, 1)
);
} else {
panic!("expected Idx result");
}
let res_idx_min = compute_lcs(
b"abcdef",
b"acdf",
StringLCSArgs {
lcs_type: StringLCSType::Idx,
min_match_len: 2,
},
)
.expect("lcs idx min failed");
if let StringLCSResult::Idx(idx_res) = res_idx_min {
assert_eq!(idx_res.len, 4);
assert_eq!(idx_res.matches.len(), 1);
assert_eq!(
idx_res.matches[0],
StringLCSMatchedRange::new(2, 3, 1, 2, 2)
);
} else {
panic!("expected Idx result with min_match_len");
}
}
#[test]
fn test_string_advanced_kvrocks_features() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path())?;
db.set("gex", "val", &[])?;
assert_eq!(db.getex("gex", Some(GetEx::Ex(10)))?, Some(b"val".to_vec()));
let (_, exp) = db.get_with_expire("gex")?;
assert!(exp > 0);
assert_eq!(
db.getex("gex", Some(GetEx::Persist))?,
Some(b"val".to_vec())
);
let (_, exp_p) = db.get_with_expire("gex")?;
assert_eq!(exp_p, 0);
db.set("cas_k", "v1", &[])?;
assert_eq!(db.cas("cas_k", "v_wrong", "v2", 0)?, 0);
assert_eq!(db.cas("cas_k", "v1", "v2", 0)?, 1);
assert_eq!(db.get("cas_k")?, Some(b"v2".to_vec()));
assert_eq!(db.cas("cas_missing", "v1", "v2", 0)?, -1);
assert_eq!(db.cad("cas_k", "v_wrong")?, 0);
assert_eq!(db.cad("cas_k", "v2")?, 1);
assert_eq!(db.get("cas_k")?, None);
assert_eq!(db.cad("cas_missing", "v1")?, -1);
db.set("del_k", "secret", &[])?;
let dig = db.digest("del_k")?.expect("digest failed");
assert!(!db.delex("del_k", DelEx::IfEq(b"wrong"))?);
assert!(db.delex("del_k", DelEx::IfDeq(dig.as_bytes()))?);
assert_eq!(db.get("del_k")?, None);
let d_hello = string_digest(b"hello");
let set_ifdeq_fail = db.set_args(
"ifdeq_k",
"new",
&StringSetArgs {
expire: 0,
set_type: StringSetType::IfDeq,
get: false,
keep_ttl: false,
cmp_value: Some(d_hello.as_bytes()),
},
)?;
assert_eq!(set_ifdeq_fail, None);
assert_eq!(db.get("ifdeq_k")?, None);
db.set("ifdeq_k", "hello", &[])?;
let set_ifdeq_ok = db.set_args(
"ifdeq_k",
"new",
&StringSetArgs {
expire: 0,
set_type: StringSetType::IfDeq,
get: false,
keep_ttl: false,
cmp_value: Some(d_hello.as_bytes()),
},
)?;
assert_eq!(set_ifdeq_ok, Some(Vec::new()));
assert_eq!(db.get("ifdeq_k")?, Some(b"new".to_vec()));
let pairs = [("k_m1", "v_m1"), ("k_m2", "v_m2")];
assert!(db.msetnx(&pairs)?);
assert_eq!(db.get("k_m1")?, Some(b"v_m1".to_vec()));
let pairs_conflict = [("k_m1", "v_m1_new"), ("k_m3", "v_m3")];
assert!(!db.msetnx(&pairs_conflict)?);
assert_eq!(db.get("k_m3")?, None);
db.set("del_opt_k", "abc", &[])?;
assert!(!db.delex("del_opt_k", DelEx::IfEq(b"def"))?);
assert!(!db.delex("del_opt_k", DelEx::IfNe(b"abc"))?);
assert!(db.delex("del_opt_k", DelEx::IfNe(b"xyz"))?);
assert_eq!(db.get("del_opt_k")?, None);
let now_ms = coarsetime::Clock::now_since_epoch().as_millis();
db.setex("keep_ttl_1", "v1", now_ms + 60_000)?;
db.setex("keep_ttl_2", "v2", now_ms + 120_000)?;
let (_, exp1_before) = db.get_with_expire("keep_ttl_1")?;
let (_, exp2_before) = db.get_with_expire("keep_ttl_2")?;
assert!(exp1_before > 0 && exp2_before > 0);
db.mset_args(
&[("keep_ttl_1", "v1_updated"), ("keep_ttl_2", "v2_updated")],
wedb_embed::string::StringMSetArgs {
expire: 0,
set_type: StringSetType::None,
keep_ttl: true,
},
)?;
let (v1_new, exp1_after) = db.get_with_expire("keep_ttl_1")?;
let (v2_new, exp2_after) = db.get_with_expire("keep_ttl_2")?;
assert_eq!(v1_new, Some(b"v1_updated".to_vec()));
assert_eq!(v2_new, Some(b"v2_updated".to_vec()));
assert_eq!(exp1_after, exp1_before);
assert_eq!(exp2_after, exp2_before);
let bin_key = b"\x00\x01\xfe\xff_bin_key";
let bin_val = b"\xde\xad\xbe\xef";
db.set(bin_key, bin_val, &[])?;
assert_eq!(db.get(bin_key)?, Some(bin_val.to_vec()));
assert_eq!(db.del(&[bin_key])?, 1);
assert_eq!(db.get(bin_key)?, None);
Ok(())
}