use std::{thread::sleep, time::Duration};
use aok::Void;
use tempfile::tempdir;
use wedb_embed::{
WeDb,
hash::{
ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING, ERR_HASH_VALUE_NOT_FLOAT,
ERR_HASH_VALUE_NOT_INTEGER, ERR_INCREMENT_NAN_OR_INFINITY, ERR_INCREMENT_OVERFLOW,
ERR_WRONG_TYPE, HASH_EXPIRE_COND_FAILED, HASH_EXPIRE_DELETED, HASH_EXPIRE_SET_OK,
HASH_FIELD_NOT_FOUND, HASH_FIELD_PERSISTENT, HExpire, HGetEx, HSetEx, HashFetchType,
HashFieldSetCondition, HashGetExOptions, HashItemKeyComposer, HashLengthMode, HashMeta,
HashSetExOptions, HashSubkeyEncodingMode, RangeLexSpec, TTLAction, compose_hash_meta_key,
},
key_composer::KeyComposer,
prelude::*,
};
#[ctor::ctor(unsafe)]
fn _log_init() {
log_init::init();
}
#[test]
fn test_hash_basic_ops() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
let fields = ["test-hash-key-1", "test-hash-key-2", "test-hash-key-3"];
let values = [
"hash-test-value-1",
"hash-test-value-2",
"hash-test-value-3",
];
let fvs: Vec<(&str, &str)> = fields.iter().copied().zip(values.iter().copied()).collect();
assert_eq!(db.hset("test_hash->key", &fvs)?, 3);
assert_eq!(db.hlen("test_hash->key")?, 3);
assert_eq!(
db.hlen_with_mode("test_hash->key", HashLengthMode::Approximate)?,
3
);
for (f, v) in &fvs {
assert_eq!(db.hget("test_hash->key", f)?, Some(v.as_bytes().to_vec()));
assert!(db.hexists("test_hash->key", f)?);
}
assert!(!db.hexists("test_hash->key", "nonexistent_field")?);
assert_eq!(
db.hset("test_hash->key", &[("dup", "v1"), ("dup", "v2")])?,
1
);
assert_eq!(db.hget("test_hash->key", "dup")?, Some(b"v2".to_vec()));
assert!(!db.hsetnx("test_hash->key", "dup", "v_new")?);
assert_eq!(db.hget("test_hash->key", "dup")?, Some(b"v2".to_vec()));
assert!(db.hsetnx("test_hash->key", "new_nx", "v_nx")?);
assert_eq!(db.hget("test_hash->key", "new_nx")?, Some(b"v_nx".to_vec()));
assert_eq!(
db.hdel("test_hash->key", &["test-hash-key-1", "dup", "new_nx"])?,
3
);
assert_eq!(db.hlen("test_hash->key")?, 2);
assert_eq!(db.hget("test_hash->key", "test-hash-key-1")?, None);
Ok(())
}
#[test]
fn test_hash_incrby_and_incrbyfloat() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
assert_eq!(db.hincrby("h_counter", "f1", 10)?, 10);
assert_eq!(db.hincrby("h_counter", "f1", 5)?, 15);
assert_eq!(db.hincrby("h_counter", "f1", -20)?, -5);
let f1 = db.hincrbyfloat("h_float", "f2", 1.5)?;
assert!((f1 - 1.5).abs() < 1e-6);
let f2 = db.hincrbyfloat("h_float", "f2", 3.25)?;
assert!((f2 - 4.75).abs() < 1e-6);
db.hset(
"h_err",
&[
("not_int", "abc"),
("with_space", " 123"),
("trailing_space", "123 "),
("nan", "NaN"),
("inf", "Infinity"),
],
)?;
let err_int = db.hincrby("h_err", "not_int", 1).unwrap_err();
assert!(err_int.to_string().contains(ERR_HASH_VALUE_NOT_INTEGER));
let err_space = db.hincrby("h_err", "with_space", 1).unwrap_err();
assert!(err_space.to_string().contains(ERR_HASH_VALUE_NOT_INTEGER));
let err_trailing = db.hincrby("h_err", "trailing_space", 1).unwrap_err();
assert!(
err_trailing
.to_string()
.contains(ERR_HASH_VALUE_NOT_INTEGER)
);
let err_float = db.hincrbyfloat("h_err", "not_int", 1.0).unwrap_err();
assert!(err_float.to_string().contains(ERR_HASH_VALUE_NOT_FLOAT));
let err_nan = db.hincrbyfloat("h_err", "nan", 1.0).unwrap_err();
assert!(err_nan.to_string().contains(ERR_HASH_VALUE_NOT_FLOAT));
let err_inf = db.hincrbyfloat("h_err", "inf", 1.0).unwrap_err();
assert!(err_inf.to_string().contains(ERR_HASH_VALUE_NOT_FLOAT));
db.hset("h_ovf", &[("max_int", &i64::MAX.to_string())])?;
let err_ovf = db.hincrby("h_ovf", "max_int", 1).unwrap_err();
assert!(err_ovf.to_string().contains(ERR_INCREMENT_OVERFLOW));
db.hset("h_float_ovf", &[("large", &f64::MAX.to_string())])?;
let err_float_ovf = db
.hincrbyfloat("h_float_ovf", "large", f64::MAX)
.unwrap_err();
assert!(
err_float_ovf
.to_string()
.contains(ERR_INCREMENT_NAN_OR_INFINITY)
);
Ok(())
}
#[test]
fn test_hash_mget_and_keys_vals_all() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
db.hset("h_batch", &[("k1", "v1"), ("k2", "v2"), ("k3", "v3")])?;
let mget = db.hmget("h_batch", &["k1", "k_missing", "k3"])?;
assert_eq!(
mget,
vec![Some(b"v1".to_vec()), None, Some(b"v3".to_vec()),]
);
let all = db.hgetall("h_batch")?;
assert_eq!(all.len(), 3);
let keys = db.hkeys("h_batch")?;
assert_eq!(keys.len(), 3);
let vals = db.hvals("h_batch")?;
assert_eq!(vals.len(), 3);
let only_keys = db.hget_all_with_type("h_batch", HashFetchType::OnlyKey)?;
assert_eq!(only_keys.len(), 3);
assert_eq!(only_keys[0].0, b"k1");
assert!(only_keys[0].1.is_empty());
let only_vals = db.hget_all_with_type("h_batch", HashFetchType::OnlyValue)?;
assert_eq!(only_vals.len(), 3);
assert!(only_vals[0].0.is_empty());
assert_eq!(only_vals[0].1, b"v1");
assert_eq!(db.hstrlen("h_batch", "k1")?, 2);
assert_eq!(db.hstrlen("h_batch", "nonexistent")?, 0);
Ok(())
}
#[test]
fn test_hash_field_expiration_complete() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
db.hset("hexp", &[("f1", "v1"), ("f2", "v2"), ("f3", "v3")])?;
let res = db.hexpire("hexp", &["f1", "f2"], 100, HExpire::None)?;
assert_eq!(res, vec![HASH_EXPIRE_SET_OK, HASH_EXPIRE_SET_OK]);
let ttls = db.httl("hexp", &["f1", "f2", "f3", "f_none"])?;
assert!(ttls[0] > 0 && ttls[0] <= 100);
assert!(ttls[1] > 0 && ttls[1] <= 100);
assert_eq!(ttls[2], HASH_FIELD_PERSISTENT);
assert_eq!(ttls[3], HASH_FIELD_NOT_FOUND);
let pttls = db.hpttl("hexp", &["f1", "f3"])?;
assert!(pttls[0] > 0);
assert_eq!(pttls[1], HASH_FIELD_PERSISTENT);
let extimes = db.hexpiretime("hexp", &["f1", "f3"])?;
assert!(extimes[0] > 0);
assert_eq!(extimes[1], HASH_FIELD_PERSISTENT);
let pextimes = db.hpexpiretime("hexp", &["f1", "f3"])?;
assert!(pextimes[0] > 0);
assert_eq!(pextimes[1], HASH_FIELD_PERSISTENT);
let nx_res = db.hexpire("hexp", &["f1", "f3"], 200, HExpire::Nx)?;
assert_eq!(nx_res, vec![HASH_EXPIRE_COND_FAILED, HASH_EXPIRE_SET_OK]);
let xx_res = db.hexpire("hexp", &["f1", "f2"], 300, HExpire::Xx)?;
assert_eq!(xx_res, vec![HASH_EXPIRE_SET_OK, HASH_EXPIRE_SET_OK]);
let gt_fail = db.hexpire("hexp", &["f1"], 50, HExpire::Gt)?;
assert_eq!(gt_fail, vec![HASH_EXPIRE_COND_FAILED]); let gt_succ = db.hexpire("hexp", &["f1"], 500, HExpire::Gt)?;
assert_eq!(gt_succ, vec![HASH_EXPIRE_SET_OK]);
let lt_fail = db.hexpire("hexp", &["f1"], 600, HExpire::Lt)?;
assert_eq!(lt_fail, vec![HASH_EXPIRE_COND_FAILED]); let lt_succ = db.hexpire("hexp", &["f1"], 400, HExpire::Lt)?;
assert_eq!(lt_succ, vec![HASH_EXPIRE_SET_OK]);
let persist_res = db.hpersist("hexp", &["f1", "f2", "f3", "f_none"])?;
assert_eq!(
persist_res,
vec![
HASH_EXPIRE_SET_OK,
HASH_EXPIRE_SET_OK,
HASH_EXPIRE_SET_OK,
HASH_FIELD_NOT_FOUND
]
);
assert_eq!(
db.httl("hexp", &["f1", "f2", "f3"])?,
vec![
HASH_FIELD_PERSISTENT,
HASH_FIELD_PERSISTENT,
HASH_FIELD_PERSISTENT
]
);
let persist_again = db.hpersist("hexp", &["f1", "f2", "f3"])?;
assert_eq!(
persist_again,
vec![
HASH_FIELD_PERSISTENT,
HASH_FIELD_PERSISTENT,
HASH_FIELD_PERSISTENT
]
);
let imm_res = db.hexpire("hexp", &["f1"], 0, HExpire::None)?;
assert_eq!(imm_res, vec![HASH_EXPIRE_DELETED]);
assert_eq!(db.hget("hexp", "f1")?, None);
assert_eq!(db.hlen("hexp")?, 2);
Ok(())
}
#[test]
fn test_hash_legacy_encoding_compatibility() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
let legacy_meta = HashMeta::new_with_mode(HashSubkeyEncodingMode::Legacy, 0, 100, 1);
let kc = KeyComposer::new("default");
let meta_k = compose_hash_meta_key(&kc, b"legacy_key");
let mut composer = HashItemKeyComposer::new(&kc, b"legacy_key");
let item_k = composer.key_for_field(b"f1");
let mut batch = db.db.batch();
batch.insert(&db.meta, &meta_k, legacy_meta.encode());
batch.insert(&db.data, item_k, b"val1");
batch.commit()?;
assert_eq!(db.hget("legacy_key", "f1")?, Some(b"val1".to_vec()));
assert_eq!(db.hlen("legacy_key")?, 1);
assert!(db.hexists("legacy_key", "f1")?);
assert_eq!(db.hstrlen("legacy_key", "f1")?, 4);
let err_exp = db
.hexpire("legacy_key", &["f1"], 10, HExpire::None)
.unwrap_err();
assert!(
err_exp
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_ttl = db.httl("legacy_key", &["f1"]).unwrap_err();
assert!(
err_ttl
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_pttl = db.hpttl("legacy_key", &["f1"]).unwrap_err();
assert!(
err_pttl
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_ex_time = db.hexpiretime("legacy_key", &["f1"]).unwrap_err();
assert!(
err_ex_time
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_pex_time = db.hpexpiretime("legacy_key", &["f1"]).unwrap_err();
assert!(
err_pex_time
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_persist = db.hpersist("legacy_key", &["f1"]).unwrap_err();
assert!(
err_persist
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_setex = db
.set_fields_with_expire("legacy_key", &[("f1", "val2")], HashSetExOptions::default())
.unwrap_err();
assert!(
err_setex
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
let err_getex = db
.get_fields_with_expire("legacy_key", &["f1"], HashGetExOptions::default())
.unwrap_err();
assert!(
err_getex
.to_string()
.contains(ERR_HASH_FIELD_EXPIRATION_LEGACY_ENCODING)
);
Ok(())
}
#[test]
fn test_hash_hgetdel_and_hsetex_hgetex() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
let now_ms = wedb_embed::current_now_ms();
let setex_opts = HashSetExOptions {
condition: HashFieldSetCondition::Fnx,
ttl_action: TTLAction::Set,
expire_at_ms: now_ms + 10_000,
};
assert!(db.set_fields_with_expire("h_ex_key", &[("a", "1"), ("b", "2")], setex_opts)?);
assert_eq!(db.hlen("h_ex_key")?, 2);
assert!(!db.set_fields_with_expire("h_ex_key", &[("a", "1_new")], setex_opts)?);
assert!(db.hsetex("h_ex_key2", &[("c", "100")], &[HSetEx::Ex(30), HSetEx::Fnx])?);
assert_eq!(db.hlen("h_ex_key2")?, 1);
let getex_opts = HashGetExOptions {
ttl_action: TTLAction::Persist,
expire_at_ms: 0,
};
let gvals = db.get_fields_with_expire("h_ex_key", &["a", "nonexistent"], getex_opts)?;
assert_eq!(gvals, vec![Some(b"1".to_vec()), None]);
assert_eq!(db.httl("h_ex_key", &["a"])?, vec![HASH_FIELD_PERSISTENT]);
let g_val = db.hgetex("h_ex_key2", "c", Some(HGetEx::Persist))?;
assert_eq!(g_val, Some(b"100".to_vec()));
assert_eq!(db.httl("h_ex_key2", &["c"])?, vec![HASH_FIELD_PERSISTENT]);
let del_res = db.hgetdel("h_ex_key", &["a", "b", "c"])?;
assert_eq!(
del_res,
vec![Some(b"1".to_vec()), Some(b"2".to_vec()), None]
);
assert_eq!(db.hlen("h_ex_key")?, 0);
Ok(())
}
#[test]
fn test_hash_randfield_and_lex_and_scan() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
db.hset(
"h_scan_test",
&[("f1", "v1"), ("f2", "v2"), ("f3", "v3"), ("f4", "v4")],
)?;
let r1 = db.hrandfield("h_scan_test", 2, false)?;
assert_eq!(r1.len(), 2);
let r2 = db.hrandfield("h_scan_test", -3, true)?;
assert_eq!(r2.len(), 3);
assert!(r2[0].1.is_some());
let r_zero = db.hrandfield("h_scan_test", 0, false)?;
assert!(r_zero.is_empty());
let r_none = db.hrandfield("h_nonexistent", 2, false)?;
assert!(r_none.is_empty());
let spec_zero = RangeLexSpec {
min: b"f1".to_vec(),
max: b"f3".to_vec(),
minex: false,
maxex: false,
min_infinite: false,
max_infinite: false,
offset: 0,
count: Some(0),
reversed: false,
};
let lex_zero = db.hrangebylex("h_scan_test", spec_zero)?;
assert!(lex_zero.is_empty());
let spec = RangeLexSpec {
min: b"f1".to_vec(),
max: b"f3".to_vec(),
minex: false,
maxex: true, min_infinite: false,
max_infinite: false,
offset: 0,
count: None,
reversed: false,
};
let lex_items = db.hrangebylex("h_scan_test", spec)?;
assert_eq!(lex_items.len(), 2);
assert_eq!(lex_items[0].0, b"f1");
assert_eq!(lex_items[1].0, b"f2");
let spec_rev = RangeLexSpec {
min: b"f1".to_vec(),
max: b"f4".to_vec(),
minex: false,
maxex: false,
min_infinite: false,
max_infinite: false,
offset: 0,
count: Some(2),
reversed: true,
};
let rev_items = db.hrangebylex("h_scan_test", spec_rev)?;
assert_eq!(rev_items.len(), 2);
assert_eq!(rev_items[0].0, b"f4");
assert_eq!(rev_items[1].0, b"f3");
let (c1, p1) = db.hscan("h_scan_test", 0, 2, None)?;
assert_eq!(c1, 2);
assert_eq!(p1.len(), 2);
assert_eq!(p1[0].0, b"f1");
assert_eq!(p1[1].0, b"f2");
let (c2, p2) = db.hscan("h_scan_test", c1, 2, None)?;
assert_eq!(c2, 0); assert_eq!(p2.len(), 2);
assert_eq!(p2[0].0, b"f3");
assert_eq!(p2[1].0, b"f4");
let (c3, p3) = db.hscan("h_scan_test", 0, 10, Some(b"f[1-3]"))?;
assert_eq!(c3, 0);
assert_eq!(p3.len(), 3);
let r_all = db.hrandfield("h_scan_test", 4, false)?;
assert_eq!(r_all.len(), 4);
let r_neg = db.hrandfield("h_scan_test", -6, false)?;
assert_eq!(r_neg.len(), 6);
let r_over = db.hrandfield("h_scan_test", 100, true)?;
assert_eq!(r_over.len(), 4);
Ok(())
}
#[test]
fn test_hash_expired_key_cleanup_and_ttl_preservation() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
db.hset("h_inc_ttl", &[("counter", "10")])?;
db.hexpire("h_inc_ttl", &["counter"], 300, HExpire::None)?;
let ttl_before = db.httl("h_inc_ttl", &["counter"])?[0];
assert!(ttl_before > 0 && ttl_before <= 300);
let new_val = db.hincrby("h_inc_ttl", "counter", 5)?;
assert_eq!(new_val, 15);
let ttl_after = db.httl("h_inc_ttl", &["counter"])?[0];
assert!(ttl_after > 0 && ttl_after <= 300);
db.hset("h_inc_ttl", &[("float_val", "1.5")])?;
db.hexpire("h_inc_ttl", &["float_val"], 200, HExpire::None)?;
let f_val = db.hincrbyfloat("h_inc_ttl", "float_val", 2.25)?;
assert!((f_val - 3.75).abs() < 1e-6);
let f_ttl = db.httl("h_inc_ttl", &["float_val"])?[0];
assert!(f_ttl > 0 && f_ttl <= 200);
assert_eq!(db.hstrlen("h_inc_ttl", "nonexistent")?, 0);
assert_eq!(db.hstrlen("h_inc_ttl", "counter")?, 2);
db.hexpire("h_inc_ttl", &["counter"], 0, HExpire::None)?;
assert_eq!(db.hstrlen("h_inc_ttl", "counter")?, 0);
Ok(())
}
#[test]
fn test_hash_scan_and_repair_and_edge_cases() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
db.hset("h_repair", &[("f1", "v1"), ("f2", "v2"), ("f3", "v3")])?;
db.hpexpire("h_repair", &["f1", "f2"], 15, HExpire::None)?;
sleep(Duration::from_millis(25));
let len = db.hlen("h_repair")?;
assert_eq!(len, 1);
assert_eq!(db.hget("h_repair", "f1")?, None);
assert_eq!(db.hget("h_repair", "f2")?, None);
assert_eq!(db.hget("h_repair", "f3")?, Some(b"v3".to_vec()));
let imm = db.hexpire("h_repair", &["f3"], 0, HExpire::None)?;
assert_eq!(imm, vec![HASH_EXPIRE_DELETED]);
assert_eq!(db.hlen("h_repair")?, 0);
assert_eq!(db.hgetall("h_repair")?, Vec::<(Vec<u8>, Vec<u8>)>::new());
assert_eq!(db.hdel("nonexistent_hash", &["f1", "f2"])?, 0);
assert_eq!(db.hget("nonexistent_hash", "f1")?, None);
assert_eq!(
db.hmget("nonexistent_hash", &["f1", "f2"])?,
vec![None, None]
);
assert_eq!(
db.httl("nonexistent_hash", &["f1"])?,
vec![HASH_FIELD_NOT_FOUND]
);
Ok(())
}
#[test]
fn test_hash_binary_safe_keys_and_fields() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
let bin_key = b"\xff\xfe\x00\x01binary_hash";
let bin_field1 = b"\x00\x01\x02field";
let bin_field2 = b"\xaa\xbb\xcc\xdd";
let bin_val1 = b"\xde\xad\xbe\xef";
let bin_val2 = b"\x01\x02\x03\x04\x05";
assert_eq!(
db.hset(
bin_key,
&[
(bin_field1.as_slice(), bin_val1.as_slice()),
(bin_field2.as_slice(), bin_val2.as_slice())
]
)?,
2
);
assert_eq!(db.hlen(bin_key)?, 2);
assert_eq!(db.hget(bin_key, bin_field1)?, Some(bin_val1.to_vec()));
assert_eq!(db.hget(bin_key, bin_field2)?, Some(bin_val2.to_vec()));
let exp_res = db.hexpire(bin_key, &[bin_field1.as_slice()], 60, HExpire::None)?;
assert_eq!(exp_res, vec![HASH_EXPIRE_SET_OK]);
let ttl_res = db.httl(bin_key, &[bin_field1.as_slice(), bin_field2.as_slice()])?;
assert!(ttl_res[0] > 0);
assert_eq!(ttl_res[1], HASH_FIELD_PERSISTENT);
assert_eq!(db.hdel(bin_key, &[bin_field1.as_slice()])?, 1);
assert_eq!(db.hlen(bin_key)?, 1);
Ok(())
}
#[test]
fn test_hash_wrongtype_handling() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
db.set("str_conflict_key", "string_value", &[])?;
let err_hset = db.hset("str_conflict_key", &[("f1", "v1")]).unwrap_err();
assert!(err_hset.to_string().contains(ERR_WRONG_TYPE));
let err_hget = db.hget("str_conflict_key", "f1").unwrap_err();
assert!(err_hget.to_string().contains(ERR_WRONG_TYPE));
let err_hlen = db.hlen("str_conflict_key").unwrap_err();
assert!(err_hlen.to_string().contains(ERR_WRONG_TYPE));
let err_hexists = db.hexists("str_conflict_key", "f1").unwrap_err();
assert!(err_hexists.to_string().contains(ERR_WRONG_TYPE));
let err_hdel = db.hdel("str_conflict_key", &["f1"]).unwrap_err();
assert!(err_hdel.to_string().contains(ERR_WRONG_TYPE));
let err_hincrby = db.hincrby("str_conflict_key", "f1", 1).unwrap_err();
assert!(err_hincrby.to_string().contains(ERR_WRONG_TYPE));
let err_hincrbyfloat = db.hincrbyfloat("str_conflict_key", "f1", 1.0).unwrap_err();
assert!(err_hincrbyfloat.to_string().contains(ERR_WRONG_TYPE));
let err_hkeys = db.hkeys("str_conflict_key").unwrap_err();
assert!(err_hkeys.to_string().contains(ERR_WRONG_TYPE));
let err_hvals = db.hvals("str_conflict_key").unwrap_err();
assert!(err_hvals.to_string().contains(ERR_WRONG_TYPE));
let err_hgetall = db.hgetall("str_conflict_key").unwrap_err();
assert!(err_hgetall.to_string().contains(ERR_WRONG_TYPE));
let err_hstrlen = db.hstrlen("str_conflict_key", "f1").unwrap_err();
assert!(err_hstrlen.to_string().contains(ERR_WRONG_TYPE));
let err_hexpire = db
.hexpire("str_conflict_key", &["f1"], 10, HExpire::None)
.unwrap_err();
assert!(err_hexpire.to_string().contains(ERR_WRONG_TYPE));
let err_httl = db.httl("str_conflict_key", &["f1"]).unwrap_err();
assert!(err_httl.to_string().contains(ERR_WRONG_TYPE));
let err_hpersist = db.hpersist("str_conflict_key", &["f1"]).unwrap_err();
assert!(err_hpersist.to_string().contains(ERR_WRONG_TYPE));
let err_hrange = db
.hrangebylex("str_conflict_key", RangeLexSpec::default())
.unwrap_err();
assert!(err_hrange.to_string().contains(ERR_WRONG_TYPE));
let err_hscan = db.hscan("str_conflict_key", 0, 10, None).unwrap_err();
assert!(err_hscan.to_string().contains(ERR_WRONG_TYPE));
let err_hiter = db.hiter("str_conflict_key", |_, _| true).unwrap_err();
assert!(err_hiter.to_string().contains(ERR_WRONG_TYPE));
Ok(())
}
#[test]
fn test_hash_iter() -> Void {
let dir = tempdir()?;
let db = WeDb::open(dir.path(), [])?;
let mut count = 0;
db.hiter("nonexistent_hash", |_, _| {
count += 1;
true
})?;
assert_eq!(count, 0);
let pairs = [
("field1", "val1"),
("field2", "val2"),
("field3", "val3"),
("field4", "val4"),
];
db.hset("h_iter_test", &pairs)?;
let mut collected = Vec::new();
db.hiter("h_iter_test", |f, v| {
collected.push((f.to_vec(), v.to_vec()));
true
})?;
assert_eq!(collected.len(), 4);
assert_eq!(collected[0], (b"field1".to_vec(), b"val1".to_vec()));
assert_eq!(collected[1], (b"field2".to_vec(), b"val2".to_vec()));
assert_eq!(collected[2], (b"field3".to_vec(), b"val3".to_vec()));
assert_eq!(collected[3], (b"field4".to_vec(), b"val4".to_vec()));
let mut early_stopped = Vec::new();
db.hiter("h_iter_test", |f, v| {
early_stopped.push((f.to_vec(), v.to_vec()));
early_stopped.len() < 2
})?;
assert_eq!(early_stopped.len(), 2);
assert_eq!(early_stopped[0].0, b"field1");
assert_eq!(early_stopped[1].0, b"field2");
db.hset("h_iter_expire", &[("f1", "v1"), ("f2", "v2")])?;
db.hexpire("h_iter_expire", &["f1"], 1, HExpire::None)?;
sleep(Duration::from_millis(1100));
let mut valid_fields = Vec::new();
db.hiter("h_iter_expire", |f, v| {
valid_fields.push((f.to_vec(), v.to_vec()));
true
})?;
assert_eq!(valid_fields.len(), 1);
assert_eq!(valid_fields[0].0, b"f2");
assert_eq!(valid_fields[0].1, b"v2");
let bin_pairs = [
(vec![0u8, 1, 2], vec![255u8, 254]),
(vec![3u8, 4, 5], vec![100u8, 200]),
];
db.hset("h_bin_iter", &bin_pairs)?;
let mut bin_collected = Vec::new();
db.hiter("h_bin_iter", |f, v| {
bin_collected.push((f.to_vec(), v.to_vec()));
true
})?;
assert_eq!(bin_collected.len(), 2);
assert_eq!(bin_collected[0].0, vec![0u8, 1, 2]);
assert_eq!(bin_collected[0].1, vec![255u8, 254]);
assert_eq!(bin_collected[1].0, vec![3u8, 4, 5]);
assert_eq!(bin_collected[1].1, vec![100u8, 200]);
Ok(())
}