From 42d81efc1759f8b67fc79ecbb2f500743be900b2 Mon Sep 17 00:00:00 2001 From: miaobyte Date: Fri, 30 Oct 2026 09:44:16 +0800 Subject: [PATCH] =?UTF-8?q?kvspace-durable:=20XValue=20head=20=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=8C=96=20+=20WriteNewPlace=20=E5=8E=BB=20langtype?= =?UTF-8?q?=20+=20=E5=91=BD=E5=90=8D=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fs/redis 后端适配固定 8 字节 head(storetype/ptr/reserved/len) - WriteNewPlace 去 langtype;sidecar→attr 命名清理 - ffi / kvspace_common / metadata / xvalue_* 同步 Co-Authored-By: Claude Code --- src/backend.rs | 224 ++++++++++++------------ src/const.rs | 7 +- src/ffi.rs | 179 +++++-------------- src/fs/kvspace.rs | 257 +++++++++++----------------- src/headlenpow.rs | 390 ++++++++++-------------------------------- src/kvspace.rs | 15 +- src/kvspace_common.rs | 6 +- src/lib.rs | 1 - src/metadata.rs | 77 --------- src/store.rs | 6 +- src/xvalue.rs | 317 ++++++++++++++++++---------------- src/xvalue_bool.rs | 8 +- src/xvalue_byte.rs | 20 +-- src/xvalue_float.rs | 14 +- src/xvalue_int.rs | 26 +-- src/xvalue_uint.rs | 26 +-- tests/metadata_fs.rs | 111 +----------- tests/prefix_list.rs | 136 ++------------- tests/read_cache.rs | 67 ++++---- 19 files changed, 637 insertions(+), 1250 deletions(-) delete mode 100644 src/metadata.rs diff --git a/src/backend.rs b/src/backend.rs index 7d7b713..2820977 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -10,7 +10,10 @@ use crate::kvspace_common::{ }; use crate::r#const::*; use crate::store::KVStore; -use crate::xvalue::{decode_xvalue, decode_xvalue_head, is_ptr, ptr_target, XValue, REF_EXT}; +use crate::xvalue::{ + from_wire, from_wire_head, is_ptr, ptr_target, xvalue_langtype, XValue, REF_EXT, REF_PTR, +}; +use crate::xvalue_byte::char_byte_to_wire; pub struct Backend { store: S, @@ -21,22 +24,32 @@ impl Backend { Backend { store } } - fn sync_metadata(&mut self, key: &str, raw: &[u8]) -> Result<(), String> { - let head = decode_xvalue_head(raw); - if !raw.is_empty() && head.headlen == 0 { - return Err(format!("invalid XValue at {key}")); - } - self.set_metadata(key, head.ro, head.vid) + /// langtype attr key key:`x‥langtype`。 + fn langtype_key(key: &str) -> String { + format!("{key}{RUNTIME_MEMBER_SEP}langtype") } - fn metadata_at(&self, key: &str) -> Result<(bool, u32), String> { - let meta = - crate::metadata::key_for(key).ok_or_else(|| format!("reserved metadata key: {key}"))?; - match self.store.get(&meta) { - None => Ok((false, 0)), - Some(data) => { - crate::metadata::decode(&data).ok_or_else(|| format!("invalid metadata at {key}")) - } + /// 读 key 的 langtype attr key:attr key 值是字符串 XValue(char/utf8), + /// 解出明文类型串;无/空/解析失败 → 空串。 + fn langtype_of(&self, key: &str) -> String { + self.store + .get(&Self::langtype_key(key)) + .map(|data| match from_wire(&data, "[]char/utf8") { + XValue::CharByte(b) => String::from_utf8_lossy(&b.data).into_owned(), + _ => String::new(), + }) + .unwrap_or_default() + } + + /// 写/删 key 的 langtype attr key:attr key 值存字符串 XValue(char/utf8)的 wire 字节; + /// 空串 → 删除(无类型)。 + fn set_langtype(&self, key: &str, langtype: &str) { + let ltkey = Self::langtype_key(key); + if langtype.is_empty() { + self.store.del(&[<key]); + } else { + let wire = char_byte_to_wire(langtype.as_bytes(), &[]); + self.store.set(<key, &wire); } } @@ -106,7 +119,8 @@ impl Backend { let vals = self.store.get_many(&refs); for (i, data) in vals.iter().enumerate() { if let Some(data) = data { - let v = decode_xvalue(data); + let lt = self.langtype_of(&prefixes[i]); + let v = from_wire(data, <); if is_ptr(&v) { let target = ptr_target(&v); if i + 1 < parts.len() { @@ -137,7 +151,9 @@ impl Backend { let end = rest.find(['/', '·']).unwrap_or(rest.len()); if end > 0 { let name = &rest[..end]; - if dir != PATH_SEP || name != META_ROOT_NAME { + // RUNTIME_MEMBER_SEP(‥)前缀键是系统变量/langtype attr key,List 时隐藏。 + if (dir != PATH_SEP || name != META_ROOT_NAME) && !name.contains(RUNTIME_MEMBER_SEP) + { let direct_dir = rest.as_bytes().get(end) == Some(&b'/') && rest.len() == end + 1; names @@ -168,13 +184,16 @@ impl Backend { fn get_dir(&self, dir: &str) -> XValue { match self.store.get(dir) { None => XValue::None, - Some(data) => decode_xvalue(&data), + Some(data) => { + let langtype = self.langtype_of(dir); + from_wire(&data, &langtype) + } } } fn prefix_ext(&self, prefix: &str) -> String { if let Some(data) = self.store.get(prefix) { - let head = decode_xvalue_head(&data); + let head = from_wire_head(&data, &self.langtype_of(prefix)); if head.r#ref == REF_EXT { return String::from_utf8_lossy(head.body(&data)).into_owned(); } @@ -184,18 +203,6 @@ impl Backend { } impl KVSpace for Backend { - fn set_metadata(&mut self, key: &str, ro: bool, vid: u32) -> Result<(), String> { - let meta = - crate::metadata::key_for(key).ok_or_else(|| format!("reserved metadata key: {key}"))?; - if ro || vid != 0 { - let value = crate::metadata::encode(ro, vid) - .ok_or_else(|| format!("cannot encode metadata at {key}"))?; - self.store.set(&meta, &value); - } else { - self.store.del(&[&meta]); - } - Ok(()) - } fn get(&mut self, prefix: &str, keys: &[String], resolve: bool) -> Vec { Self::assert_dir(prefix); let prefix = if resolve { @@ -207,7 +214,7 @@ impl KVSpace for Backend { let mut full_keys: Vec<(usize, String)> = Vec::new(); for (i, k) in keys.iter().enumerate() { let full = join_path(&prefix, k); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { results[i] = Some(XValue::None); continue; } @@ -222,9 +229,10 @@ impl KVSpace for Backend { // ext-index 仅是主存未命中键的回退目标:延迟到确有缺失键才查 prefix_ext—— // 否则每次读都为它多付一次 GET(frame 槽恒命中,曾是最热的一条无谓往返)。 let mut missing: Vec = Vec::new(); - for (idx, (i, _)) in full_keys.iter().enumerate() { + for (idx, (i, full)) in full_keys.iter().enumerate() { if let Some(data) = &full_vals[idx] { - results[*i] = Some(decode_xvalue(data)); + let langtype = self.langtype_of(full); + results[*i] = Some(from_wire(data, &langtype)); } else { missing.push(*i); } @@ -245,9 +253,10 @@ impl KVSpace for Backend { if !ext_keys.is_empty() { let ext_refs: Vec<&str> = ext_keys.iter().map(|(_, t)| t.as_str()).collect(); let ext_vals = self.store.get_many(&ext_refs); - for (idx, (i, _)) in ext_keys.iter().enumerate() { + for (idx, (i, t)) in ext_keys.iter().enumerate() { results[*i] = Some(if let Some(data) = &ext_vals[idx] { - decode_xvalue(data) + let langtype = self.langtype_of(t); + from_wire(data, &langtype) } else { XValue::None }); @@ -260,7 +269,7 @@ impl KVSpace for Backend { } fn get_raw(&mut self, key: &str) -> Vec { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Vec::new(); } let (mut p, l) = sep_path(key); @@ -269,7 +278,7 @@ impl KVSpace for Backend { } let p = self.resolve_path(&p); let full = join_path(&p, &l); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { return Vec::new(); } if let Some(data) = self.store.get(&full) { @@ -285,13 +294,8 @@ impl KVSpace for Backend { Vec::new() } - fn get_metadata(&mut self, key: &str) -> Result<(bool, u32), String> { - let resolved = self.resolve_path(key); - self.metadata_at(&resolved) - } - fn get_part(&mut self, key: &str, off: u32, len: u32) -> Vec { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Vec::new(); } let (mut p, l) = sep_path(key); @@ -300,7 +304,7 @@ impl KVSpace for Backend { } let p = self.resolve_path(&p); let full = join_path(&p, &l); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { return Vec::new(); } if self.store.exists(&full) { @@ -317,7 +321,7 @@ impl KVSpace for Backend { } fn set_part(&mut self, key: &str, off: u32, buf: &[u8]) -> Result<(), String> { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Err(format!("reserved metadata key: {key}")); } let (mut p, l) = sep_path(key); @@ -326,7 +330,7 @@ impl KVSpace for Backend { } let p = self.resolve_path(&p); let full = join_path(&p, &l); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { return Err(format!("reserved metadata key: {full}")); } if self.store.exists(&full) { @@ -346,20 +350,28 @@ impl KVSpace for Backend { fn set(&mut self, pairs: &[KVPair]) -> Result<(), String> { for pair in pairs { - if crate::metadata::is_reserved(&pair.key) { + if crate::r#const::is_reserved(&pair.key) { return Err(format!("reserved metadata key: {}", pair.key)); } let key = self.resolve_parent(&pair.key); - if key.contains("//") || crate::metadata::is_reserved(&key) { + if key.contains("//") || crate::r#const::is_reserved(&key) { return Err(format!("invalid key: {key}")); } - let raw = pair.raw.clone().unwrap_or_else(|| pair.val.encode()); - let head = crate::headlenpow::decode(&raw) + let raw = pair.raw.clone().unwrap_or_else(|| pair.val.to_wire()); + let head = crate::headlenpow::from_wire(&raw) .ok_or_else(|| format!("invalid XValue at {key}"))?; - if head.total != raw.len() { + if head.len as usize + crate::headlenpow::PREFIX != raw.len() { return Err(format!("invalid XValue at {key}")); } - if let XValue::Ptr(ptr) = decode_xvalue(&raw) { + // 固定头无类型:langtype 走 x‥langtype attr key(值侧写自 val)。 + // ‥ 后缀 key(系统变量/attr key 本体)是叶子,无类型、不写嵌套 attr key。 + let langtype = if key.contains(RUNTIME_MEMBER_SEP) { + String::new() + } else { + xvalue_langtype(&pair.val) + }; + // 指针校验用声明的 target_kindexpr(val 为 Ptr 时可得;raw 直写无类型则跳过)。 + if let XValue::Ptr(ptr) = &pair.val { validate_ptr(self, &ptr.target, &ptr.target_kindexpr)?; } let (parent, _, _) = split_index(&key); @@ -376,19 +388,20 @@ impl KVSpace for Backend { if !ext_target.is_empty() && self.store.get(&key).is_none() { let suffix = key.strip_prefix(&parent).unwrap_or(""); if self.store.get(&format!("{ext_target}{suffix}")).is_some() - && !(head.flags & 4 != 0 && is_frame_operand_key(&key)) + && !(head.ptr == REF_PTR && is_frame_operand_key(&key)) { return Err(format!("{}: {}", ERR_EXT_WRITE, key)); } } - self.sync_metadata(&key, &raw)?; + // 写序固定:attr 先落,x 后落(x 写 = 提交点,x 存在则类型必完整)。 + self.set_langtype(&key, &langtype); self.store.set(&key, &raw); } Ok(()) } fn list(&mut self, prefix: &str, expand_ext: bool, resolve: bool) -> Vec { - if crate::metadata::is_reserved(prefix) { + if crate::r#const::is_reserved(prefix) { return Vec::new(); } Self::assert_dir(prefix); @@ -400,7 +413,7 @@ impl KVSpace for Backend { if !Self::is_dir(&resolved) { return Vec::new(); } - if crate::metadata::is_reserved(&resolved) { + if crate::r#const::is_reserved(&resolved) { return Vec::new(); } @@ -451,11 +464,11 @@ impl KVSpace for Backend { fn del(&mut self, keys: &[String]) -> Result<(), String> { for key in keys { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Err(format!("reserved metadata key: {key}")); } let resolved = self.resolve_parent(key); - if crate::metadata::is_reserved(&resolved) { + if crate::r#const::is_reserved(&resolved) { return Err(format!("reserved metadata key: {resolved}")); } let (parent, _, _) = split_index(&resolved); @@ -469,19 +482,21 @@ impl KVSpace for Backend { if Self::is_dir(&resolved) { let link_key = strip_dir_suf(&resolved); - self.store.del(&[link_key, &resolved]); - self.sync_metadata(link_key, &[])?; - self.sync_metadata(&resolved, &[])?; + self.store.del(&[ + &Self::langtype_key(link_key), + &Self::langtype_key(&resolved), + link_key, + &resolved, + ]); } else { - self.store.del(&[&resolved]); - self.sync_metadata(&resolved, &[])?; + self.store.del(&[&Self::langtype_key(&resolved), &resolved]); } } Ok(()) } fn del_tree(&mut self, prefix: &str) -> Result<(), String> { - if crate::metadata::is_reserved(prefix) { + if crate::r#const::is_reserved(prefix) { return Err(format!("reserved metadata key: {prefix}")); } let mut link_key = prefix; @@ -489,14 +504,14 @@ impl KVSpace for Backend { link_key = strip_dir_suf(prefix); } if let Some(data) = self.store.get(link_key) { - let head = decode_xvalue_head(&data); + let head = from_wire_head(&data, &self.langtype_of(link_key)); if head.is_ptr() { return self.del(&[prefix.to_string()]); } } let resolved = self.resolve_path(prefix); - if crate::metadata::is_reserved(&resolved) { + if crate::r#const::is_reserved(&resolved) { return Err(format!("reserved metadata key: {resolved}")); } let mut scan = resolved.clone(); @@ -507,14 +522,12 @@ impl KVSpace for Backend { .store .scan_keys(&scan) .into_iter() - .filter(|key| !crate::metadata::is_reserved(key)) + .filter(|key| !crate::r#const::is_reserved(key)) .collect(); - self.store.del(&[&resolved]); - self.sync_metadata(&resolved, &[])?; + self.store.del(&[&Self::langtype_key(&resolved), &resolved]); for k in &keys { - self.store.del(&[k]); - self.sync_metadata(k, &[])?; + self.store.del(&[&Self::langtype_key(k), k]); } Ok(()) @@ -522,32 +535,31 @@ impl KVSpace for Backend { /// 单 key 拷贝:src 处 XValue(head+body 原样)写到 dst,并注册进 dst 父 index;不触碰 src·/成员。 fn cp(&mut self, src: &str, dst: &str) -> Result<(), String> { - if crate::metadata::is_reserved(src) || crate::metadata::is_reserved(dst) { + if crate::r#const::is_reserved(src) || crate::r#const::is_reserved(dst) { return Err("reserved metadata key".into()); } let raw = self.get_raw(src); if raw.is_empty() { return Err(format!("Cp: source not found: {}", src)); } - let (ro, vid) = self.metadata_at(src)?; - let v = decode_xvalue(&raw); + // attr key 与值同址:读解析后的物理 src 路径的类型。 + let v = from_wire(&raw, &self.langtype_of(&self.resolve_parent(src))); self.set(&[KVPair { key: dst.to_string(), val: v, raw: Some(raw), - }])?; - self.set_metadata(dst, ro, vid) + }]) } /// 递归子树拷贝:以 src 为根,把整棵物理子树(base + 所有 ·/ 后代 key)字节级重映射到 dst。 /// extindex 成员的 marker(含 ext_path)原样复制 → 在 dst 侧生成指向同一只读扩展的新 extindex。 fn cp_tree(&mut self, src: &str, dst: &str) -> Result<(), String> { - if crate::metadata::is_reserved(src) || crate::metadata::is_reserved(dst) { + if crate::r#const::is_reserved(src) || crate::r#const::is_reserved(dst) { return Err("reserved metadata key".into()); } let src_res = self.resolve_path(src); let dst_res = self.resolve_path(dst); - if crate::metadata::is_reserved(&src_res) || crate::metadata::is_reserved(&dst_res) { + if crate::r#const::is_reserved(&src_res) || crate::r#const::is_reserved(&dst_res) { return Err("reserved metadata key".into()); } let mut src_scan = src_res.clone(); @@ -568,7 +580,7 @@ impl KVSpace for Backend { .store .scan_keys(&src_scan) .into_iter() - .filter(|key| !crate::metadata::is_reserved(key)) + .filter(|key| !crate::r#const::is_reserved(key)) .collect(); if keys.is_empty() { return Err(format!("CpTree: source not found: {}", src)); @@ -579,9 +591,7 @@ impl KVSpace for Backend { let suffix = &k[src_scan.len()..]; let new_key = format!("{}{}", dst_base, suffix); if let Some(data) = self.store.get(k) { - let (ro, vid) = self.metadata_at(k)?; self.store.set(&new_key, &data); - self.set_metadata(&new_key, ro, vid)?; } } Ok(()) @@ -589,7 +599,7 @@ impl KVSpace for Backend { /// 浅拷贝:base 值 + 一层 · 成员(不递归成员子树、不遍历 / 子节点)。用于单 struct/扁平容器。 fn cp_list(&mut self, src: &str, dst: &str) -> Result<(), String> { - if crate::metadata::is_reserved(src) || crate::metadata::is_reserved(dst) { + if crate::r#const::is_reserved(src) || crate::r#const::is_reserved(dst) { return Err("reserved metadata key".into()); } let mut src_base = self.resolve_path(src); @@ -597,7 +607,7 @@ impl KVSpace for Backend { src_base.pop(); } let mut dst_base = self.resolve_path(dst); - if crate::metadata::is_reserved(&src_base) || crate::metadata::is_reserved(&dst_base) { + if crate::r#const::is_reserved(&src_base) || crate::r#const::is_reserved(&dst_base) { return Err("reserved metadata key".into()); } if Self::is_dir(&dst_base) && dst_base != PATH_SEP { @@ -610,7 +620,7 @@ impl KVSpace for Backend { .store .scan_keys(&src_base) .into_iter() - .filter(|key| !crate::metadata::is_reserved(key)) + .filter(|key| !crate::r#const::is_reserved(key)) .collect(); if keys.is_empty() { return Err(format!("CpList: source not found: {}", src)); @@ -618,8 +628,9 @@ impl KVSpace for Backend { let _ = self.del_tree(&dst_base); for k in &keys { let suffix = &k[src_base.len()..]; - // Copy the base and direct members, including ext directories. + // Copy the base(含其 langtype attr key)、直接 · 成员(含成员 attr key)与 ext 目录。 let one_level = suffix.is_empty() + || suffix == format!("{RUNTIME_MEMBER_SEP}langtype") || (suffix.starts_with(OBJ_SEP) && { let rest = &suffix[OBJ_SEP.len()..]; let member = rest.strip_suffix(PATH_SEP).unwrap_or(rest); @@ -630,9 +641,7 @@ impl KVSpace for Backend { } if let Some(data) = self.store.get(k) { let new_key = format!("{}{}", dst_base, suffix); - let (ro, vid) = self.metadata_at(k)?; self.store.set(&new_key, &data); - self.set_metadata(&new_key, ro, vid)?; } } Ok(()) @@ -648,9 +657,11 @@ impl KVSpace for Backend { } let _ = capacity; let resolved = self.resolve_path(path); - let value = crate::headlenpow::encode(5, 0, 0, 0, "lib", &[], 0) + // 固定头空目录 marker:storetype 0、ptr 0、空 body;类型 "lib" 走 attr key。 + let value = crate::headlenpow::to_wire(0, 0, &[]) .ok_or_else(|| format!("Mkindex: invalid directory {resolved}"))?; if self.store.get(&resolved).is_none() { + self.set_langtype(&resolved, "lib"); self.store.set(&resolved, &value); } Ok(()) @@ -668,25 +679,14 @@ impl KVSpace for Backend { } let resolved = self.resolve_parent(path); - let source = self - .store - .get(ext_path) - .ok_or_else(|| format!("ExtIndex target missing: {ext_path}"))?; - let langtype = decode_xvalue_head(&source).langtype; - let pow = (5..=31) - .find(|&p| 18 + langtype.len() <= 1usize << p) - .ok_or_else(|| format!("ExtIndex type too long: {langtype}"))?; - let len = ext_path.len() as u64; - let value = crate::headlenpow::encode( - pow, - 3, - len, - len, - &langtype, - ext_path.as_bytes(), - ext_path.len(), - ) - .ok_or_else(|| format!("ExtIndex locator invalid: {ext_path}"))?; + if self.store.get(ext_path).is_none() { + return Err(format!("ExtIndex target missing: {ext_path}")); + } + let langtype = self.langtype_of(ext_path); + // 扩展定位:storetype 3(ST_EXT)+ ptr 2(PTR_EXT)+ 定位串 body。 + let value = crate::headlenpow::to_wire(3, 2, ext_path.as_bytes()) + .ok_or_else(|| format!("ExtIndex locator invalid: {ext_path}"))?; + self.set_langtype(&resolved, &langtype); self.store.set(&resolved, &value); Ok(()) } @@ -699,16 +699,14 @@ impl KVSpace for Backend { link_key = strip_dir_suf(&resolved); } if let Some(data) = self.store.get(link_key) { - let head = decode_xvalue_head(&data); + let head = from_wire_head(&data, &self.langtype_of(link_key)); if head.is_ptr() { - self.store.del(&[link_key]); - self.set_metadata(link_key, false, 0)?; + self.store.del(&[&Self::langtype_key(link_key), link_key]); return Ok(()); } } - self.store.del(&[&resolved]); - self.set_metadata(&resolved, false, 0)?; + self.store.del(&[&Self::langtype_key(&resolved), &resolved]); Ok(()) } diff --git a/src/const.rs b/src/const.rs index 2abc294..2d0ff5e 100644 --- a/src/const.rs +++ b/src/const.rs @@ -4,11 +4,16 @@ // ── 路径结构 ────────────────────────────────────────────────────────────── pub const PATH_SEP: &str = "/"; // 路径分隔符 -pub const META_ROOT_NAME: &str = ".kvspace-meta"; +pub const META_ROOT_NAME: &str = ".kvspace-meta"; // 保留区(不再有 meta sidecar,仅守卫防业务写) pub const DIR_INDEX_SUF: &str = "/"; // 目录索引键后缀(尾斜杠 = 目录,必须以 / 开头的 key 保证不冲突) pub const RUNTIME_MEMBER_SEP: &str = "‥"; // 运行时保留字段前缀(U+2025)——‥ 的唯一定义处,List 时隐藏 pub const EXT_INDEX_HEAD: &str = "…"; // Directory display marker. +/// `/.kvspace-meta` 保留区判定(原 metadata::is_reserved,随 meta 删除迁至常量模块)。 +pub fn is_reserved(key: &str) -> bool { + key == "/.kvspace-meta" || key.starts_with("/.kvspace-meta/") +} + // ── 错误消息 ──────────────────────────────────────────────────────────── pub const ERR_DIR_MUST_END_WITH_SLASH: &str = "kvspace: index must end with /"; diff --git a/src/ffi.rs b/src/ffi.rs index db7900d..42ee591 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -7,7 +7,7 @@ // - 句柄:kvspaceConnect 返回 *mut Handle(Box),kvspaceClose 释放。 // - 输入字符串:*const c_char(NUL 终止);输入字节:*const u8 + u32 len。 // - 读出字节:kvspaceGet/ListAt 返回句柄内常驻/回收缓冲的借用偏移指针,调用方不得 free; -// codec(XvalueEncode/New*)产出为 frontend malloc 缓冲,调用方以 libc free 释放。 +// codec(New*)产出为 frontend malloc 缓冲,调用方以 libc free 释放。 // - 错误:返回 c_int(0=成功,1=失败),失败信息写入 err 缓冲(err_cap 上限)。 // // 注意:本层函数不得 panic 跨边界(panic 会 abort 进程);调用方保证入参合法。 @@ -20,7 +20,7 @@ use std::time::Duration; use crate::conn::conn; use crate::kvspace::{KVPair, KVSpace}; -use crate::xvalue::{decode_xvalue, decode_xvalue_head, encode_head, new_ptr}; +use crate::xvalue::{from_wire, from_wire_head, new_ptr}; use crate::xvalue_bool::new_bool; use crate::xvalue_byte::new_char_byte; use crate::xvalue_float::new_float64; @@ -56,24 +56,20 @@ pub struct Handle { retired: Vec>, write_buf: Vec, pending_key: Option, - pending_meta: Option<(bool, u32)>, } impl Handle { /// 落盘上一笔惰性写(调用方已把 body 填进 write_buf),并失效该 key 的读缓存。 fn flush(&mut self) -> Result<(), String> { if let Some(key) = self.pending_key.take() { - let meta = self.pending_meta.take(); let xv = std::mem::take(&mut self.write_buf); - let val = decode_xvalue(&xv); + let langtype = self.kv.langtype_of(&key); + let val = from_wire(&xv, &langtype); self.kv.set(&[KVPair { key: key.clone(), val, raw: Some(xv), }])?; - if let Some((ro, vid)) = meta { - self.kv.set_metadata(&key, ro, vid)?; - } self.read_cache.remove(&key); } Ok(()) @@ -181,20 +177,24 @@ unsafe fn kv_flush<'a>(h: *mut Handle) -> Result<&'a mut dyn KVSpace, String> { Ok(&mut *hd.kv) } +/// 固定头预留:langtype 不入 head(由调用方写 x‥langtype attr key), +/// wire = [storetype][ptr][reserved:u16=0][len:u32],body 偏移恒 = PREFIX。 fn build_xvalue_head( r#ref: u8, storetype: u8, - langtype: &str, body_len: usize, body_cap: usize, ) -> Option> { - let flags = match (r#ref, storetype) { - (0, 0..=2) => storetype, - (1, 1) => 5, - (2, 3) => 3, + if body_len > body_cap { + return None; + } + let (st, ptr) = match (r#ref, storetype) { + (0, 0..=3) => (storetype, 0), + (1, 1) => (1, 1), + (2, 3) => (3, 2), _ => return None, }; - crate::headlenpow::reserve(flags, langtype, body_len, body_cap) + crate::headlenpow::reserve(st, ptr, body_len) } // ── 内部助手 ───────────────────────────────────────────────────────── @@ -269,8 +269,6 @@ pub struct kvspaceHead_t { pub headlen: u16, // head 总字节数 pub r#ref: u8, // 存储位置:0=inline 1=ptr 2=@ext pub storetype: u8, // Wire storage class. - pub ro: u8, // 1=只读,0=可写 - pub vid: u32, // vthread id pub body_len: i32, // body 字节数 pub ndim: i32, // 物理维数(标量=0) pub dims: [i32; 8], // 物理字段 dims(X_MAX_NDIM=8) @@ -298,8 +296,6 @@ fn fill_head(head: &crate::xvalue::XValueHead, out: *mut kvspaceHead_t) { for (i, d) in dims.iter().take(8).enumerate() { o.dims[i] = *d; } - o.ro = head.ro as u8; - o.vid = head.vid; o.body_len = head.body_len; o.body_offset = head.head_len(); o.body_cap = head.body_cap; @@ -319,7 +315,6 @@ pub extern "C" fn kvspaceConnect(dsn: *const c_char) -> *mut Handle { retired: Vec::new(), write_buf: Vec::new(), pending_key: None, - pending_meta: None, })), Err(_) => std::ptr::null_mut(), } @@ -406,8 +401,6 @@ pub extern "C" fn kvspaceSetValue( key: *const c_char, value: *const u8, value_len: u32, - ro: u8, - vid: u32, err: *mut c_char, err_cap: u32, ) -> c_int { @@ -424,19 +417,19 @@ pub extern "C" fn kvspaceSetValue( } let key = unsafe { cstr(key) }.to_string(); let raw = unsafe { std::slice::from_raw_parts(value, value_len as usize) }; - if crate::headlenpow::decode(raw).map(|head| head.total) != Some(raw.len()) { + if crate::headlenpow::from_wire(raw).is_none() { write_err(err, err_cap, "invalid XValue"); return 1; } let raw = raw.to_vec(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let val = decode_xvalue(&raw); + let langtype = hd.kv.langtype_of(&key); + let val = from_wire(&raw, &langtype); hd.kv.set(&[KVPair { key: key.clone(), val, raw: Some(raw), - }])?; - hd.kv.set_metadata(&key, ro != 0, vid) + }]) })); match result { Ok(result) => { @@ -573,20 +566,18 @@ pub extern "C" fn kvspaceGetHead( if value.is_empty() { return 1; } - let head = crate::xvalue::decode_xvalue_head(&value); + /* langtype 在 x‥langtype attr key(明文事实源);attr key 缺失 = None/无类型。 */ + let langtype = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + hd.kv.langtype_of(&key) + })) { + Ok(lt) => lt, + Err(_) => return 1, + }; + let head = crate::xvalue::from_wire_head(&value, &langtype); if head.headlen == 0 { return 1; } - let (ro, vid) = - match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| hd.kv.get_metadata(&key))) { - Ok(Ok(meta)) => meta, - _ => return 1, - }; fill_head(&head, out); - unsafe { - (*out).ro = ro as u8; - (*out).vid = vid; - } 0 } @@ -616,7 +607,8 @@ pub extern "C" fn kvspaceWriteInPlace( write_err(err, err_cap, "kvspace: write-in-place on missing key"); return 1; } - let head = decode_xvalue_head(&existing); + let langtype = hd.kv.langtype_of(&key); + let head = from_wire_head(&existing, &langtype); let head_len = head.head_len() as usize; if head.headlen == 0 || head.body_len < 0 @@ -627,31 +619,21 @@ pub extern "C" fn kvspaceWriteInPlace( write_err(err, err_cap, "kvspace: write-in-place body_len mismatch"); return 1; } - let meta = match hd.kv.get_metadata(&key) { - Ok(value) => value, - Err(e) => { - write_err(err, err_cap, &e); - return 1; - } - }; hd.read_cache.remove(&key); hd.write_buf = existing; hd.pending_key = Some(key); - hd.pending_meta = Some(meta); unsafe { *body = hd.write_buf.as_mut_ptr().add(head_len) }; 0 } -/// 新位置写:按 (ref, storetype, langtype, body_len) 攒好 head 到 write_buf、置 pending,返回 body 偏移指针。 +/// 新位置写:按 (ref, storetype, body_len, body_cap) 攒好 head 到 write_buf、置 pending,返回 body 偏移指针。 +/// kvspace 层不写 attr key——`x‥langtype` 归属上层:需要类型时调用方自写 attr key(attr 先落、x 后落)。 #[no_mangle] pub extern "C" fn kvspaceWriteNewPlace( h: *mut Handle, key: *const c_char, r#ref: u8, storetype: u8, - ro: u8, - vid: u32, - langtype: *const c_char, body_len: u32, body_cap: u64, body: *mut *mut u8, @@ -667,20 +649,19 @@ pub extern "C" fn kvspaceWriteNewPlace( return 1; } let key = unsafe { cstr(key) }.to_string(); - let lt = unsafe { cstr(langtype) }; let Ok(cap) = usize::try_from(body_cap) else { write_err(err, err_cap, "capacity exceeds platform size"); return 1; }; - let Some(xv) = build_xvalue_head(r#ref, storetype, lt, body_len as usize, cap) else { + let Some(xv) = build_xvalue_head(r#ref, storetype, body_len as usize, cap) else { write_err(err, err_cap, "invalid XValue geometry"); return 1; }; - let head_len = xv.len() - cap; + /* 固定头:body 偏移恒 = PREFIX(=8)。 */ + let head_len = crate::headlenpow::PREFIX; hd.read_cache.remove(&key); hd.write_buf = xv; hd.pending_key = Some(key); - hd.pending_meta = Some((ro != 0, vid)); unsafe { *body = hd.write_buf.as_mut_ptr().add(head_len) }; 0 } @@ -970,13 +951,17 @@ pub extern "C" fn kvspaceWatch( return 1; } let key = unsafe { cstr(key) }.to_string(); - let target_v = - decode_xvalue(unsafe { std::slice::from_raw_parts(target, target_len as usize) }); + /* watch 目标与 key 值同型(相等判据),langtype 取 key 的 attr key。 */ + let langtype = hd.kv.langtype_of(&key); + let target_v = from_wire( + unsafe { std::slice::from_raw_parts(target, target_len as usize) }, + &langtype, + ); let v = hd.kv.watch(&key, &target_v, Duration::from_nanos(tick_ns)); if let Some(old) = hd.read_cache.remove(&key) { hd.retired.push(old); /* 旧视图保活到 Close(watch 是冷路径) */ } - hd.read_cache.insert(key.clone(), v.encode()); + hd.read_cache.insert(key.clone(), v.to_wire()); let b = hd.read_cache.get_mut(&key).unwrap(); unsafe { *out = b.as_mut_ptr(); @@ -1005,80 +990,6 @@ pub extern "C" fn kvspaceClear(h: *mut Handle, err: *mut c_char, err_cap: u32) - // ── XValue 编解码(head + 标准标量构造器) ───────────────────────────── -/// 通用 XValue 编码(内联,ref=0)。dims/ndim 直接落盘:ndim=0 标量,dims 可为 NULL。 -/// kvlang 的自有 kind(rwir/rwfunc/scope)经此构造:body 由 kvlang 自己编码。 -#[no_mangle] -pub extern "C" fn kvspaceXvalueEncode( - kind: *const c_char, - raw: *const u8, - raw_len: u32, - dims: *const i32, - ndim: i32, - out: *mut *mut u8, - out_len: *mut u32, -) -> c_int { - let raw = unsafe { std::slice::from_raw_parts(raw, raw_len as usize) }; - let dims = ffi_dims(dims, ndim); - alloc( - encode_head(unsafe { cstr(kind) }, 0, dims, raw), - out, - out_len, - ) -} - -/// 带权限编码:显式指定 ref(0/1/2)、ro(1=只读)、vid。用于权限位落盘。 -#[no_mangle] -pub extern "C" fn kvspaceXvalueEncodeMode( - kind: *const c_char, - raw: *const u8, - raw_len: u32, - dims: *const i32, - ndim: i32, - r#ref: c_int, - ro: u8, - vid: u32, - out: *mut *mut u8, - out_len: *mut u32, -) -> c_int { - let raw = unsafe { std::slice::from_raw_parts(raw, raw_len as usize) }; - let dims = ffi_dims(dims, ndim); - if ro != 0 || vid != 0 { - return 1; - } - alloc( - encode_head(unsafe { cstr(kind) }, r#ref, dims, raw), - out, - out_len, - ) -} - -#[inline] -fn ffi_dims<'a>(dims: *const i32, ndim: i32) -> &'a [i32] { - if dims.is_null() || ndim <= 0 { - &[] - } else { - unsafe { std::slice::from_raw_parts(dims, ndim as usize) } - } -} - -/// 解码 XValueHead(不解析 body)。返回 kindexpr/ro/vid/body_len/body_offset。 -#[no_mangle] -pub extern "C" fn kvspaceDecodeHead( - data: *const u8, - data_len: u32, - out: *mut kvspaceHead_t, -) -> c_int { - if data.is_null() || out.is_null() { - return 1; - } - let head = decode_xvalue_head(unsafe { std::slice::from_raw_parts(data, data_len as usize) }); - if head.headlen == 0 { - return 1; - } - fill_head(&head, out); - 0 -} - // ── 标准标量构造器(返回完整 XValue 字节) ───────────────────────────── #[no_mangle] @@ -1094,7 +1005,7 @@ pub extern "C" fn kvspaceNewPtr( return 1; } let v = new_ptr(kindexpr, unsafe { cstr(target) }); - alloc(v.encode(), out, out_len) + alloc(v.to_wire(), out, out_len) } #[no_mangle] @@ -1105,25 +1016,25 @@ pub extern "C" fn kvspaceNewChar( out_len: *mut u32, ) -> c_int { let v = new_char_byte(unsafe { std::slice::from_raw_parts(bytes, len as usize) }); - alloc(v.encode(), out, out_len) + alloc(v.to_wire(), out, out_len) } #[no_mangle] pub extern "C" fn kvspaceNewBool(v: u8, out: *mut *mut u8, out_len: *mut u32) -> c_int { let x = new_bool(&[v != 0]); - alloc(x.encode(), out, out_len) + alloc(x.to_wire(), out, out_len) } #[no_mangle] pub extern "C" fn kvspaceNewInt64(v: i64, out: *mut *mut u8, out_len: *mut u32) -> c_int { let x = new_int64(&[v]); - alloc(x.encode(), out, out_len) + alloc(x.to_wire(), out, out_len) } #[no_mangle] pub extern "C" fn kvspaceNewFloat64(v: f64, out: *mut *mut u8, out_len: *mut u32) -> c_int { let x = new_float64(&[v]); - alloc(x.encode(), out, out_len) + alloc(x.to_wire(), out, out_len) } // ── slot view(shm-only)───────────────────────────────────────────── diff --git a/src/fs/kvspace.rs b/src/fs/kvspace.rs index 238b23e..4ff635b 100644 --- a/src/fs/kvspace.rs +++ b/src/fs/kvspace.rs @@ -15,6 +15,7 @@ use crate::kvspace_common::{ }; use crate::r#const::*; use crate::xvalue::*; +use crate::xvalue_byte::char_byte_to_wire; const SELF_MARKER: &str = "__self__"; const DIR_MARKER: &str = "__dir__"; @@ -94,6 +95,34 @@ impl FsKVSpace { key.ends_with(DIR_INDEX_SUF) || key.ends_with(OBJ_SEP) } + /// langtype attr key:`x‥langtype`。 + fn langtype_key(key: &str) -> String { + format!("{key}{RUNTIME_MEMBER_SEP}langtype") + } + + /// 读 key 的 langtype attr key:attr key 值是字符串 XValue(char/utf8), + /// 解出明文类型串;无/空/解析失败 → 空串。 + fn langtype_of(&self, key: &str) -> String { + self.read_leaf(&Self::langtype_key(key)) + .map(|data| match from_wire(&data, "[]char/utf8") { + XValue::CharByte(b) => String::from_utf8_lossy(&b.data).into_owned(), + _ => String::new(), + }) + .unwrap_or_default() + } + + /// 写/删 key 的 langtype attr key:attr key 值存字符串 XValue(char/utf8)的 wire 字节; + /// 空串 → 删除(无类型)。 + fn set_langtype(&self, key: &str, langtype: &str) { + let ltkey = Self::langtype_key(key); + if langtype.is_empty() { + self.remove_leaf(<key); + } else { + let wire = char_byte_to_wire(langtype.as_bytes(), &[]); + self.write_leaf(<key, &wire); + } + } + fn leaf_marker(key: &str) -> &'static str { if key != PATH_SEP && key.ends_with(DIR_INDEX_SUF) { DIR_MARKER @@ -131,7 +160,7 @@ impl FsKVSpace { } /// 解析逻辑 key 到叶文件(先 meta,再 ext 扩展存储)。 fn physical_file(&mut self, key: &str) -> Option { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return None; } let (mut p, l) = sep_path(key); @@ -140,7 +169,7 @@ impl FsKVSpace { } let p = self.resolve_path(&p); let full = join_path(&p, &l); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { return None; } if let Some(f) = self.leaf_file(&full) { @@ -180,86 +209,6 @@ impl FsKVSpace { } } - fn sync_metadata(&mut self, key: &str, raw: &[u8]) -> Result<(), String> { - let head = decode_xvalue_head(raw); - if !raw.is_empty() && head.headlen == 0 { - return Err(format!("invalid XValue at {key}")); - } - self.set_metadata(key, head.ro, head.vid) - } - - fn metadata_at(&self, key: &str) -> Result<(bool, u32), String> { - let meta = - crate::metadata::key_for(key).ok_or_else(|| format!("reserved metadata key: {key}"))?; - match self.read_leaf(&meta) { - None => Ok((false, 0)), - Some(data) => { - crate::metadata::decode(&data).ok_or_else(|| format!("invalid metadata at {key}")) - } - } - } - - fn metadata_entries(&self) -> Vec<(String, PathBuf)> { - let mut entries = Vec::new(); - if let Ok(dir) = fs::read_dir(self.fs_path("/.kvspace-meta/")) { - for item in dir.flatten() { - let name = item.file_name().to_string_lossy().into_owned(); - if let Some(key) = crate::metadata::original_key(&name) { - entries.push((key, item.path())); - } - } - } - entries - } - - fn in_tree(key: &str, base: &str) -> bool { - base == PATH_SEP - || key == base - || key - .strip_prefix(base) - .is_some_and(|rest| rest.starts_with(PATH_SEP) || rest.starts_with(OBJ_SEP)) - } - - fn remove_metadata_tree(&self, base: &str) -> Result<(), String> { - for (key, path) in self.metadata_entries() { - if Self::in_tree(&key, base) { - fs::remove_file(path).map_err(|e| format!("remove metadata for {key}: {e}"))?; - } - } - Ok(()) - } - - fn copy_metadata_tree(&self, src: &str, dst: &str, one_level: bool) -> Result<(), String> { - for (key, path) in self.metadata_entries() { - if !Self::in_tree(&key, src) { - continue; - } - let suffix = &key[src.len()..]; - if one_level && !suffix.is_empty() { - let Some(member) = suffix.strip_prefix(OBJ_SEP) else { - continue; - }; - if member.contains(OBJ_SEP) || member.contains(PATH_SEP) { - continue; - } - } - let target = if src == PATH_SEP { - format!( - "{}/{}", - dst.trim_end_matches('/'), - key.trim_start_matches('/') - ) - } else { - format!("{dst}{suffix}") - }; - let meta = crate::metadata::key_for(&target) - .ok_or_else(|| format!("reserved metadata key: {target}"))?; - let data = fs::read(path).map_err(|e| format!("copy metadata for {key}: {e}"))?; - self.write_leaf(&meta, &data); - } - Ok(()) - } - fn parent_name(path: &str) -> (String, String) { let mut path = path.to_string(); if Self::is_dir_key(&path) && path != PATH_SEP { @@ -343,7 +292,7 @@ impl FsKVSpace { for (i, p) in parts.iter().enumerate() { cur = join_path(&cur, p); if let Some(data) = self.read_leaf(&cur) { - let v = decode_xvalue(&data); + let v = from_wire(&data, &self.langtype_of(&cur)); if is_ptr(&v) { let target = ptr_target(&v); if i + 1 < parts.len() { @@ -358,7 +307,7 @@ impl FsKVSpace { fn prefix_ext(&self, prefix: &str) -> String { if let Some(data) = self.read_leaf(prefix) { - let head = decode_xvalue_head(&data); + let head = from_wire_head(&data, &self.langtype_of(prefix)); if head.r#ref == crate::xvalue::REF_EXT { return String::from_utf8_lossy(head.body(&data)).into_owned(); } @@ -384,6 +333,10 @@ impl FsKVSpace { continue; } let name = Self::unescape_name(&fname); + // RUNTIME_MEMBER_SEP(‥)前缀键是系统变量/langtype attr key,List 时隐藏。 + if name.contains(RUNTIME_MEMBER_SEP) { + continue; + } if name.ends_with(OBJ_SEP) { children.push(name.trim_end_matches(OBJ_SEP).to_string()); } else if e.path().is_dir() { @@ -418,18 +371,6 @@ impl FsKVSpace { } impl KVSpace for FsKVSpace { - fn set_metadata(&mut self, key: &str, ro: bool, vid: u32) -> Result<(), String> { - let meta = - crate::metadata::key_for(key).ok_or_else(|| format!("reserved metadata key: {key}"))?; - if ro || vid != 0 { - let value = crate::metadata::encode(ro, vid) - .ok_or_else(|| format!("cannot encode metadata at {key}"))?; - self.write_leaf(&meta, &value); - } else { - self.remove_leaf(&meta); - } - Ok(()) - } fn get(&mut self, prefix: &str, keys: &[String], resolve: bool) -> Vec { if prefix != PATH_SEP && !Self::is_dir_key(prefix) { panic!("{}: {}", ERR_DIR_MUST_END_WITH_SLASH, prefix); @@ -444,22 +385,22 @@ impl KVSpace for FsKVSpace { keys.iter() .map(|k| { let full = join_path(&prefix, k); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { return XValue::None; } if Self::is_dir_key(&full) { return self .read_leaf(&full) - .map(|raw| decode_xvalue(&raw)) + .map(|raw| from_wire(&raw, &self.langtype_of(&full))) .unwrap_or(XValue::None); } if let Some(data) = self.read_leaf(&full) { - return decode_xvalue(&data); + return from_wire(&data, &self.langtype_of(&full)); } if !ext_t.is_empty() { let target = join_path(&ext_t, k); if let Some(data) = self.read_leaf(&target) { - return decode_xvalue(&data); + return from_wire(&data, &self.langtype_of(&target)); } } XValue::None @@ -468,7 +409,7 @@ impl KVSpace for FsKVSpace { } fn get_raw(&mut self, key: &str) -> Vec { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Vec::new(); } if Self::is_dir_key(key) { @@ -483,7 +424,7 @@ impl KVSpace for FsKVSpace { } let p = self.resolve_path(&p); let full = join_path(&p, &l); - if crate::metadata::is_reserved(&full) { + if crate::r#const::is_reserved(&full) { return Vec::new(); } if let Some(data) = self.read_leaf(&full) { @@ -499,13 +440,8 @@ impl KVSpace for FsKVSpace { Vec::new() } - fn get_metadata(&mut self, key: &str) -> Result<(bool, u32), String> { - let resolved = self.resolve_path(key); - self.metadata_at(&resolved) - } - fn get_part(&mut self, key: &str, off: u32, len: u32) -> Vec { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Vec::new(); } if Self::is_dir_key(key) { @@ -533,7 +469,7 @@ impl KVSpace for FsKVSpace { } fn set_part(&mut self, key: &str, off: u32, buf: &[u8]) -> Result<(), String> { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Err(format!("reserved metadata key: {key}")); } let pf = self @@ -550,20 +486,28 @@ impl KVSpace for FsKVSpace { fn set(&mut self, pairs: &[KVPair]) -> Result<(), String> { for pair in pairs { - if crate::metadata::is_reserved(&pair.key) { + if crate::r#const::is_reserved(&pair.key) { return Err(format!("reserved metadata key: {}", pair.key)); } let key = self.resolve_parent(&pair.key); - if key.contains("//") || crate::metadata::is_reserved(&key) { + if key.contains("//") || crate::r#const::is_reserved(&key) { return Err(format!("invalid key: {key}")); } - let raw = pair.raw.clone().unwrap_or_else(|| pair.val.encode()); - let head = crate::headlenpow::decode(&raw) + let raw = pair.raw.clone().unwrap_or_else(|| pair.val.to_wire()); + let head = crate::headlenpow::from_wire(&raw) .ok_or_else(|| format!("invalid XValue at {key}"))?; - if head.total != raw.len() { + if head.len as usize + crate::headlenpow::PREFIX != raw.len() { return Err(format!("invalid XValue at {key}")); } - if let XValue::Ptr(ptr) = decode_xvalue(&raw) { + // 固定头无类型:langtype 走 x‥langtype attr key(值侧写自 val)。 + // ‥ 后缀 key(系统变量/attr key 本体)是叶子,无类型、不写嵌套 attr key。 + let langtype = if key.contains(RUNTIME_MEMBER_SEP) { + String::new() + } else { + crate::xvalue::xvalue_langtype(&pair.val) + }; + // 指针校验用声明的 target_kindexpr(val 为 Ptr 时可得;raw 直写无类型则跳过)。 + if let XValue::Ptr(ptr) = &pair.val { validate_ptr(self, &ptr.target, &ptr.target_kindexpr)?; } let (parent, _, _) = split_index(&key); @@ -580,23 +524,24 @@ impl KVSpace for FsKVSpace { if !ext_target.is_empty() && self.read_leaf(&key).is_none() { let suffix = key.strip_prefix(&parent).unwrap_or(""); if self.read_leaf(&format!("{ext_target}{suffix}")).is_some() - && !(head.flags & 4 != 0 && is_frame_operand_key(&key)) + && !(head.ptr == crate::xvalue::REF_PTR && is_frame_operand_key(&key)) { return Err(format!("{}: {}", ERR_EXT_WRITE, key)); } } + // 写序固定:attr 先落,x 后落(x 写 = 提交点,x 存在则类型必完整)。 + self.set_langtype(&key, &langtype); self.ensure_dir(&parent); if Self::is_dir_key(&key) { self.ensure_dir(&key); } - self.sync_metadata(&key, &raw)?; self.write_leaf(&key, &raw); } Ok(()) } fn list(&mut self, prefix: &str, expand_ext: bool, resolve: bool) -> Vec { - if crate::metadata::is_reserved(prefix) { + if crate::r#const::is_reserved(prefix) { return Vec::new(); } if prefix != PATH_SEP && !Self::is_dir_key(prefix) { @@ -610,7 +555,7 @@ impl KVSpace for FsKVSpace { if !Self::is_dir_key(&resolved) { return Vec::new(); } - if crate::metadata::is_reserved(&resolved) { + if crate::r#const::is_reserved(&resolved) { return Vec::new(); } let mut members = self.dir_children(&resolved); @@ -630,25 +575,25 @@ impl KVSpace for FsKVSpace { fn del(&mut self, keys: &[String]) -> Result<(), String> { for key in keys { - if crate::metadata::is_reserved(key) { + if crate::r#const::is_reserved(key) { return Err(format!("reserved metadata key: {key}")); } let resolved = self.resolve_parent(key); - if crate::metadata::is_reserved(&resolved) { + if crate::r#const::is_reserved(&resolved) { return Err(format!("reserved metadata key: {resolved}")); } + self.remove_leaf(&Self::langtype_key(&resolved)); self.remove_leaf(&resolved); - self.set_metadata(&resolved, false, 0)?; } Ok(()) } fn del_tree(&mut self, prefix: &str) -> Result<(), String> { - if crate::metadata::is_reserved(prefix) { + if crate::r#const::is_reserved(prefix) { return Err(format!("reserved metadata key: {prefix}")); } let resolved = self.resolve_path(prefix); - if crate::metadata::is_reserved(&resolved) { + if crate::r#const::is_reserved(&resolved) { return Err(format!("reserved metadata key: {resolved}")); } // 若 prefix 本身是链接(叶 Ptr),只删链接。 @@ -658,7 +603,7 @@ impl KVSpace for FsKVSpace { &resolved }; if let Some(data) = self.read_leaf(link_key) { - if decode_xvalue_head(&data).is_ptr() { + if from_wire_head(&data, &self.langtype_of(link_key)).is_ptr() { return self.del(&[resolved]); } } @@ -675,35 +620,35 @@ impl KVSpace for FsKVSpace { fs::remove_file(&node).map_err(|e| format!("DelTree {key}: {e}"))?; } } - self.remove_metadata_tree(base)?; + // base 值(非目录形态)的 langtype attr key 是 base 的兄弟文件,单独清除。 + self.remove_leaf(&Self::langtype_key(base)); Ok(()) } fn cp(&mut self, src: &str, dst: &str) -> Result<(), String> { - if crate::metadata::is_reserved(src) || crate::metadata::is_reserved(dst) { + if crate::r#const::is_reserved(src) || crate::r#const::is_reserved(dst) { return Err("reserved metadata key".into()); } let raw = self.get_raw(src); if raw.is_empty() { return Err(format!("Cp: source not found: {}", src)); } - let (ro, vid) = self.metadata_at(src)?; - let v = decode_xvalue(&raw); + // attr key 与值同址:读解析后的物理 src 路径的类型。 + let v = from_wire(&raw, &self.langtype_of(&self.resolve_parent(src))); self.set(&[KVPair { key: dst.to_string(), val: v, raw: Some(raw), - }])?; - self.set_metadata(dst, ro, vid) + }]) } fn cp_tree(&mut self, src: &str, dst: &str) -> Result<(), String> { - if crate::metadata::is_reserved(src) || crate::metadata::is_reserved(dst) { + if crate::r#const::is_reserved(src) || crate::r#const::is_reserved(dst) { return Err("reserved metadata key".into()); } let src_res = self.resolve_path(src); let dst_res = self.resolve_path(dst); - if crate::metadata::is_reserved(&src_res) || crate::metadata::is_reserved(&dst_res) { + if crate::r#const::is_reserved(&src_res) || crate::r#const::is_reserved(&dst_res) { return Err("reserved metadata key".into()); } let de_suffix = |k: &str| { @@ -737,7 +682,10 @@ impl KVSpace for FsKVSpace { Self::copy_recursive(&smem, &dmem) .map_err(|e| format!("CpTree {}→{}: {}", src, dst, e))?; } - self.copy_metadata_tree(&src_base, &dst_base, false)?; + // base 值的 langtype attr key 是 base 的兄弟文件,单独复制。 + if let Some(lt) = self.read_leaf(&Self::langtype_key(&src_base)) { + self.write_leaf(&Self::langtype_key(&dst_base), <); + } // 确保 dst 父目录存在(成员名单结构派生,无需登记)。 let (parent, _name) = Self::parent_name(&dst_base); self.ensure_dir(&parent); @@ -745,12 +693,12 @@ impl KVSpace for FsKVSpace { } fn cp_list(&mut self, src: &str, dst: &str) -> Result<(), String> { - if crate::metadata::is_reserved(src) || crate::metadata::is_reserved(dst) { + if crate::r#const::is_reserved(src) || crate::r#const::is_reserved(dst) { return Err("reserved metadata key".into()); } let src_res = self.resolve_path(src); let dst_res = self.resolve_path(dst); - if crate::metadata::is_reserved(&src_res) || crate::metadata::is_reserved(&dst_res) { + if crate::r#const::is_reserved(&src_res) || crate::r#const::is_reserved(&dst_res) { return Err("reserved metadata key".into()); } let de_suffix = |k: &str| { @@ -774,6 +722,10 @@ impl KVSpace for FsKVSpace { if let Some(data) = self.read_leaf(&src_base) { self.write_leaf(&dst_base, &data); } + // base 值的 langtype attr key 是 base 的兄弟文件,单独复制。 + if let Some(lt) = self.read_leaf(&Self::langtype_key(&src_base)) { + self.write_leaf(&Self::langtype_key(&dst_base), <); + } // 一层成员目录:只拷直接条目(marker + 直接成员值),子目录只取其 base 值 __self__,不递归。 if smem.is_dir() { let dmem = self.node_path(&format!("{}{}", dst_base, OBJ_SEP)); @@ -795,7 +747,6 @@ impl KVSpace for FsKVSpace { } } } - self.copy_metadata_tree(&src_base, &dst_base, true)?; let (parent, _name) = Self::parent_name(&dst_base); self.ensure_dir(&parent); Ok(()) @@ -813,8 +764,10 @@ impl KVSpace for FsKVSpace { let resolved = self.resolve_path(path); self.ensure_dir(&resolved); if self.read_leaf(&resolved).is_none() { - let value = crate::headlenpow::encode(5, 0, 0, 0, "lib", &[], 0) + // 固定头空目录 marker:storetype 0、ptr 0、空 body;类型 "lib" 走 attr key。 + let value = crate::headlenpow::to_wire(0, 0, &[]) .ok_or_else(|| format!("Mkindex: invalid directory {resolved}"))?; + self.set_langtype(&resolved, "lib"); self.write_leaf(&resolved, &value); } Ok(()) @@ -831,24 +784,14 @@ impl KVSpace for FsKVSpace { if !self.prefix_ext(ext_path).is_empty() { return Err(format!("{}: {}", ERR_EXT_CASCADE, ext_path)); } - let source = self - .read_leaf(ext_path) - .ok_or_else(|| format!("ExtIndex target missing: {ext_path}"))?; - let langtype = decode_xvalue_head(&source).langtype; - let pow = (5..=31) - .find(|&p| 18 + langtype.len() <= 1usize << p) - .ok_or_else(|| format!("ExtIndex type too long: {langtype}"))?; - let len = ext_path.len() as u64; - let value = crate::headlenpow::encode( - pow, - 3, - len, - len, - &langtype, - ext_path.as_bytes(), - ext_path.len(), - ) - .ok_or_else(|| format!("ExtIndex locator invalid: {ext_path}"))?; + if self.read_leaf(ext_path).is_none() { + return Err(format!("ExtIndex target missing: {ext_path}")); + } + let langtype = self.langtype_of(ext_path); + // 扩展定位:storetype 3(ST_EXT)+ ptr 2(PTR_EXT)+ 定位串 body。 + let value = crate::headlenpow::to_wire(3, 2, ext_path.as_bytes()) + .ok_or_else(|| format!("ExtIndex locator invalid: {ext_path}"))?; + self.set_langtype(&resolved, &langtype); self.ensure_dir(&resolved); self.write_leaf(&resolved, &value); Ok(()) @@ -856,8 +799,8 @@ impl KVSpace for FsKVSpace { fn del_ext_index(&mut self, path: &str) -> Result<(), String> { let resolved = self.resolve_parent(path); + self.remove_leaf(&Self::langtype_key(&resolved)); self.remove_leaf(&resolved); - self.set_metadata(&resolved, false, 0)?; Ok(()) } diff --git a/src/headlenpow.rs b/src/headlenpow.rs index 3932b9d..7657ce7 100644 --- a/src/headlenpow.rs +++ b/src/headlenpow.rs @@ -1,274 +1,83 @@ -pub const PREFIX: usize = 18; - -#[derive(Debug, PartialEq)] -pub struct Head<'a> { - pub pow: u8, - pub flags: u8, - pub a: u64, - pub b: u64, - pub langtype: &'a str, - pub body: &'a [u8], - pub content_len: usize, - pub total: usize, +pub const PREFIX: usize = 8; + +/// u48 len 的唯一定义点:wire 是 6 字节小端(offset 0-5),Rust 侧一律经 +/// read_len/write_len 读写,禁止各处手写拼接。 +#[inline] +pub fn read_len(data: &[u8]) -> Option { + let b = data.get(0..6)?; + let mut v = 0u64; + for i in (0..6).rev() { + v = (v << 8) | u64::from(b[i]); + } + Some(v) } -fn scalar_width(s: &str) -> Option { - match s { - "bool" | "int8" | "uint8" | "float8/e4m3" | "float8/e5m2" => Some(1), - "int16" | "uint16" | "float16" | "bfloat16" => Some(2), - "int32" | "uint32" | "float32" => Some(4), - "int64" | "uint64" | "float64" => Some(8), - _ => None, +#[inline] +pub fn write_len(data: &mut [u8], v: u64) { + for i in 0..6 { + data[i] = (v >> (8 * i)) as u8; } } -fn short_width(s: &str) -> Option { - scalar_width(s).or_else(|| match s { - "" | "def struct" | "lib" | "rwfunc" => Some(0), - "def rwir" => Some(5), - "time" | "duration" => Some(8), - _ if s.starts_with('/') || s.contains('·') => Some(0), - _ => None, - }) -} - -fn min_pow(n: usize) -> Option { - (5..=31).find(|&p| (n as u64 + PREFIX as u64) <= (1u64 << p)) -} - -fn decimal(s: &str) -> Option { - if s.is_empty() || (s.len() > 1 && s.starts_with('0')) || !s.bytes().all(|b| b.is_ascii_digit()) - { - return None; - } - s.parse().ok() -} - -fn slack_count(s: &str, body: &[u8]) -> Option { - match s { - "char/utf8" => Some(std::str::from_utf8(body).ok()?.chars().count() as u64), - "uint8" => Some(body.len() as u64), - "char/ascii" if body.is_ascii() => Some(body.len() as u64), - "char/utf32" if body.len() % 4 == 0 => { - for chunk in body.chunks_exact(4) { - let cp = u32::from_le_bytes(chunk.try_into().ok()?); - char::from_u32(cp)?; - } - Some((body.len() / 4) as u64) - } - _ => None, - } -} - -fn valid_slack_type(langtype: &str, body: &[u8]) -> bool { - let Some(end) = langtype.find(']') else { - return false; - }; - if !langtype.starts_with('[') { - return false; - } - let Some(expected) = decimal(&langtype[1..end]) else { - return false; - }; - slack_count(&langtype[end + 1..], body) == Some(expected) -} - -fn tensor_width(langtype: &str) -> Option<(u64, u64)> { - let end = langtype.find(']')?; - if !langtype.starts_with('[') { - return None; - } - let dims = &langtype[1..end]; - if dims.is_empty() { - return None; - } - let mut numel = 1u64; - for dim in dims.split(',') { - numel = numel.checked_mul(decimal(dim)?)?; - } - let width = scalar_width(&langtype[end + 1..])? as u64; - Some((numel, width)) +#[derive(Debug, PartialEq)] +pub struct Head<'a> { + pub storetype: u8, + pub ptr: u8, + pub len: u64, + pub body: &'a [u8], } -pub fn decode(data: &[u8]) -> Option> { - let pow = *data.first()?; - if !(5..=31).contains(&pow) { - return None; - } - let headlen = 1usize.checked_shl(pow as u32)?; - if data.len() < headlen { - return None; - } - let flags = data[1]; - if flags & !7 != 0 { +pub fn from_wire(data: &[u8]) -> Option> { + if data.len() < PREFIX { return None; } - let a = u64::from_le_bytes(data[2..10].try_into().ok()?); - let b = u64::from_le_bytes(data[10..18].try_into().ok()?); - let type_region = &data[PREFIX..headlen]; - let type_len = type_region - .iter() - .position(|&v| v == 0) - .unwrap_or(type_region.len()); - let langtype = std::str::from_utf8(&type_region[..type_len]).ok()?; - let (content, cap) = match flags { - 0 => { - if a != 0 || b != 0 || min_pow(type_len)? != pow { - return None; - } - let width = short_width(langtype)? as u64; - (width, width) - } - 1 => { - if a > b { - return None; - } - let code = langtype == "rwir" || langtype == "def langtype" || langtype == "rwfunc"; - if pow != if code { 5 } else { 6 } { - return None; - } - if langtype == "rwir" && a < 5 { - return None; - } - if langtype == "rwfunc" && a < 5 { - return None; - } - (a, b) - } - 2 => { - if pow != 7 { - return None; - } - let (numel, width) = tensor_width(langtype)?; - if a != numel || b != width { - return None; - } - let size = a.checked_mul(b)?; - (size, size) - } - 3 | 5 => { - if a == 0 || a > b || min_pow(type_len)? != pow { - return None; - } - (a, b) - } - _ => return None, - }; - let content_len = usize::try_from(content).ok()?; - let cap = usize::try_from(cap).ok()?; - let total = headlen.checked_add(cap)?; - if total > data.len() { + let storetype = data[6]; + let ptr = data[7]; + if storetype > 3 || ptr > 2 { return None; } - let body = &data[headlen..total]; - if flags == 0 && langtype == "bool" && body[0] > 1 { - return None; - } - if flags == 1 - && langtype != "rwir" - && langtype != "def langtype" - && langtype != "rwfunc" - && !valid_slack_type(langtype, &body[..content_len]) - { - return None; - } - if (flags == 3 || flags == 5) - && (body[..content_len].contains(&0) || std::str::from_utf8(&body[..content_len]).is_err()) - { + let len = read_len(data)?; + let blen = usize::try_from(len).ok()?; + if PREFIX.checked_add(blen)? > data.len() { return None; } Some(Head { - pow, - flags, - a, - b, - langtype, - body, - content_len, - total, + storetype, + ptr, + len, + body: &data[PREFIX..PREFIX + blen], }) } -pub fn encode( - pow: u8, - flags: u8, - a: u64, - b: u64, - langtype: &str, - body: &[u8], - cap: usize, -) -> Option> { - if langtype.contains('\0') || body.len() > cap || !(5..=31).contains(&pow) { +pub fn to_wire(storetype: u8, ptr: u8, body: &[u8]) -> Option> { + if storetype > 3 || ptr > 2 { return None; } - let headlen = 1usize.checked_shl(pow as u32)?; - if langtype.len() > headlen.checked_sub(PREFIX)? { - return None; - } - let total = headlen.checked_add(cap)?; + let len = u64::try_from(body.len()).ok()?; + let total = PREFIX.checked_add(body.len())?; let mut data = Vec::new(); data.try_reserve_exact(total).ok()?; data.resize(total, 0); - data[0] = pow; - data[1] = flags; - data[2..10].copy_from_slice(&a.to_le_bytes()); - data[10..18].copy_from_slice(&b.to_le_bytes()); - data[PREFIX..PREFIX + langtype.len()].copy_from_slice(langtype.as_bytes()); - data[headlen..headlen + body.len()].copy_from_slice(body); - if decode(&data)?.content_len != body.len() { - return None; - } + write_len(&mut data, len); + data[6] = storetype; + data[7] = ptr; + data[PREFIX..].copy_from_slice(body); Some(data) } -pub fn reserve(flags: u8, langtype: &str, content: usize, cap: usize) -> Option> { - if langtype.contains('\0') || content > cap { +/// 预留:产 len 字节零 body 的固定头(后由调用方填充)。 +pub fn reserve(storetype: u8, ptr: u8, content: usize) -> Option> { + if storetype > 3 || ptr > 2 { return None; } - let (pow, a, b) = match flags { - 0 if short_width(langtype)? == content && cap == content => { - (min_pow(langtype.len())?, 0, 0) - } - 1 => { - let code = matches!(langtype, "rwir" | "def langtype" | "rwfunc"); - if code { - if (langtype == "rwir" && content < 5) || (langtype == "rwfunc" && content < 5) { - return None; - } - } else { - let end = langtype.find(']')?; - if !langtype.starts_with('[') - || decimal(&langtype[1..end]).is_none() - || !matches!( - &langtype[end + 1..], - "char/utf8" | "uint8" | "char/utf32" | "char/ascii" - ) - { - return None; - } - } - (if code { 5 } else { 6 }, content as u64, cap as u64) - } - 2 => { - let (numel, width) = tensor_width(langtype)?; - if numel.checked_mul(width)? != content as u64 || cap != content { - return None; - } - (7, numel, width) - } - 3 | 5 if content > 0 => (min_pow(langtype.len())?, content as u64, cap as u64), - _ => return None, - }; - let headlen = 1usize.checked_shl(pow as u32)?; - let total = headlen.checked_add(cap)?; + let len = u64::try_from(content).ok()?; + let total = PREFIX.checked_add(content)?; let mut data = Vec::new(); data.try_reserve_exact(total).ok()?; data.resize(total, 0); - data[0] = pow; - data[1] = flags; - data[2..10].copy_from_slice(&a.to_le_bytes()); - data[10..18].copy_from_slice(&b.to_le_bytes()); - data[PREFIX..PREFIX + langtype.len()].copy_from_slice(langtype.as_bytes()); + write_len(&mut data, len); + data[6] = storetype; + data[7] = ptr; Some(data) } @@ -278,76 +87,55 @@ mod tests { #[test] fn fixtures() { - let scalar = encode(5, 0, 0, 0, "int64", &42i64.to_le_bytes(), 8).unwrap(); - assert_eq!(scalar.len(), 40); - assert_eq!(&scalar[..2], &[5, 0]); - assert_eq!(&scalar[18..23], b"int64"); - assert_eq!(decode(&scalar).unwrap().content_len, 8); - - let text = "你😀".as_bytes(); - let utf8 = encode(6, 1, text.len() as u64, 16, "[2]char/utf8", text, 16).unwrap(); - assert_eq!(utf8.len(), 80); - assert_eq!(decode(&utf8).unwrap().content_len, text.len()); - - let map_type = "[int64]·[]char/utf32"; - let map = encode(6, 0, 0, 0, map_type, &[], 0).unwrap(); - assert_eq!(decode(&map).unwrap().langtype, map_type); - - let def = encode(5, 0, 0, 0, "def rwir", &[0; 5], 5).unwrap(); - assert_eq!(decode(&def).unwrap().content_len, 5); - - let time = encode(5, 0, 0, 0, "time", &[1; 8], 8).unwrap(); - assert_eq!(decode(&time).unwrap().content_len, 8); + let scalar = to_wire(0, 0, &42i64.to_le_bytes()).unwrap(); + assert_eq!(scalar.len(), 16); + assert_eq!(&scalar[0..6], &[8, 0, 0, 0, 0, 0]); /* len=8 @0-5 */ + assert_eq!(scalar[6], 0); /* storetype @6 */ + assert_eq!(scalar[7], 0); /* ptr @7 */ + assert_eq!(from_wire(&scalar).unwrap().len, 8); + assert_eq!(from_wire(&scalar).unwrap().body, &42i64.to_le_bytes()); - let ptr = encode(5, 5, 5, 8, "int64", b"/varx", 8).unwrap(); - assert_eq!(decode(&ptr).unwrap().body.len(), 8); + let ptr = to_wire(1, 1, b"/varx").unwrap(); + assert_eq!(from_wire(&ptr).unwrap().ptr, 1); + assert_eq!(from_wire(&ptr).unwrap().body, b"/varx"); - let ext = encode(5, 3, 5, 8, "int64", b"s3://", 8).unwrap(); - assert_eq!(decode(&ext).unwrap().content_len, 5); + let ext = to_wire(3, 2, b"s3://").unwrap(); + assert_eq!(from_wire(&ext).unwrap().ptr, 2); + assert_eq!(from_wire(&ext).unwrap().len, 5); - let rwir = encode(5, 1, 8, 8, "rwir", &[0; 8], 8).unwrap(); - assert_eq!(decode(&rwir).unwrap().content_len, 8); - - let def_type = encode(5, 1, 3, 3, "def langtype", b"int", 3).unwrap(); - assert_eq!(decode(&def_type).unwrap().content_len, 3); - - let func = encode(5, 0, 0, 0, "rwfunc", &[], 0).unwrap(); - let anchor = encode(5, 1, 5, 8, "rwfunc", &[0; 5], 8).unwrap(); - assert_eq!(decode(&func).unwrap().content_len, 0); - assert_eq!(decode(&anchor).unwrap().content_len, 5); - let call = encode(5, 1, 6, 6, "rwfunc", &[0, 0, 0, 0, 0, b'f'], 6).unwrap(); - assert_eq!(decode(&call).unwrap().body, &[0, 0, 0, 0, 0, b'f']); - - let tensor = encode(7, 2, 6, 4, "[2,3]float32", &[0; 24], 24).unwrap(); - assert_eq!(decode(&tensor).unwrap().total, 152); + let text = to_wire(1, 0, "你😀".as_bytes()).unwrap(); + assert_eq!(from_wire(&text).unwrap().len, 7); /* 你(3B)+😀(4B) */ } #[test] fn malformed() { - assert!(encode(5, 0, 0, 0, "int64", &[0; 7], 7).is_none()); - assert!(encode(5, 0, 0, 0, "bool", &[2], 1).is_none()); - assert!(encode(6, 1, 2, 2, "[2]char/utf8", b"\xc0\xaf", 2).is_none()); - assert!(encode(5, 5, 5, 8, "int64", b"/a\0bc", 8).is_none()); - assert!(encode(5, 3, 5, 8, "int64", b"s3\0//", 8).is_none()); - assert!(encode(5, 3, 0, 8, "int64", b"", 8).is_none()); - assert!(encode(5, 3, 1, 8, "int64", b"\xff", 8).is_none()); - assert!(encode(5, 1, 4, 8, "rwir", &[0; 4], 8).is_none()); - assert!(encode(5, 1, 4, 8, "rwfunc", &[0; 4], 8).is_none()); - assert!(encode(6, 1, 1, 1, "[1]char/ascii", &[0x80], 1).is_none()); - assert!(encode(6, 1, 4, 4, "[1]char/utf32", &0xd800u32.to_le_bytes(), 4).is_none()); - assert!(encode(6, 1, 4, 4, "[1]char/utf32", &0x110000u32.to_le_bytes(), 4).is_none()); - let mut ptr = encode(5, 5, 5, 8, "int64", b"/varx", 8).unwrap(); - ptr[1] |= 0x80; - assert!(decode(&ptr).is_none()); + assert!(to_wire(4, 0, b"").is_none()); /* storetype > 3 */ + assert!(to_wire(0, 3, b"").is_none()); /* ptr > 2 */ + let mut v = to_wire(1, 0, b"x").unwrap(); + v[6] = 4; /* storetype @6 越界 */ + assert!(from_wire(&v).is_none()); + let mut v2 = to_wire(1, 0, b"x").unwrap(); + v2[7] = 3; /* ptr @7 越界 */ + assert!(from_wire(&v2).is_none()); + let mut v3 = to_wire(1, 0, b"xyz").unwrap(); + v3[4] = 0xff; /* len 越界 */ + assert!(from_wire(&v3).is_none()); } #[test] fn reserved_body() { - let mut v = reserve(1, "[2]char/utf8", 3, 9).unwrap(); - v[64..67].copy_from_slice("éa".as_bytes()); - let h = decode(&v).unwrap(); - assert_eq!((h.a, h.b, h.total), (3, 9, 73)); - assert!(reserve(2, "[2,3]float32", 23, 23).is_none()); - assert!(reserve(1, "rwfunc", 4, 5).is_none()); + let mut v = reserve(1, 0, 3).unwrap(); + v[8..11].copy_from_slice("éa".as_bytes()); + assert_eq!(from_wire(&v).unwrap().len, 3); + assert_eq!(from_wire(&v).unwrap().body, b"\xc3\xa9a"); + } + + #[test] + fn u48_roundtrip() { + for len in [0u64, 1, 0xffffff, 0xffffffff, 0xffffffffffff] { + let mut v = reserve(1, 0, 0).unwrap(); + write_len(&mut v, len); + assert_eq!(read_len(&v), Some(len), "len={len:#x}"); + } } } diff --git a/src/kvspace.rs b/src/kvspace.rs index 9650ee3..2be22a9 100644 --- a/src/kvspace.rs +++ b/src/kvspace.rs @@ -8,7 +8,7 @@ use crate::xvalue::XValue; pub struct KVPair { pub key: String, pub val: XValue, - pub raw: Option>, // 原始 XValue 字节:Set 透传,保留 head 权限位(ro/vid) + pub raw: Option>, // 原始 XValue 字节:Set 透传,逐字节保留 } /// KVSpace KV 存储接口。 @@ -40,10 +40,17 @@ pub trait KVSpace { /// 单点读:Get 返回完整 XValue,整存整取。 fn get(&mut self, prefix: &str, keys: &[String], resolve: bool) -> Vec; - /// 单点读原始字节(不 decode/re-encode,保 head 权限位 ro/vid)。无值返回空。 + /// 单点读原始字节(不经 wire 转换)。无值返回空。 fn get_raw(&mut self, key: &str) -> Vec; - fn get_metadata(&mut self, key: &str) -> Result<(bool, u32), String>; - fn set_metadata(&mut self, key: &str, ro: bool, vid: u32) -> Result<(), String>; + /// 读 x‥langtype attr key 的明文类型串:attr key 值是字符串 XValue(char/utf8), + /// 解出明文;无/空/解析失败 → 空串。 + fn langtype_of(&mut self, key: &str) -> String { + let ltkey = format!("{key}{}langtype", crate::r#const::RUNTIME_MEMBER_SEP); + match crate::xvalue::from_wire(&self.get_raw(<key), "[]char/utf8") { + XValue::CharByte(b) => String::from_utf8_lossy(&b.data).into_owned(), + _ => String::new(), + } + } /// 单点写:Set 写完整 XValue,并维护目录索引;只解析父路径 link,整键不穿透。 fn set(&mut self, pairs: &[KVPair]) -> Result<(), String>; diff --git a/src/kvspace_common.rs b/src/kvspace_common.rs index 44981c5..20f7e0e 100644 --- a/src/kvspace_common.rs +++ b/src/kvspace_common.rs @@ -4,7 +4,7 @@ use std::time::Duration; use crate::kvspace::KVSpace; use crate::r#const::*; -use crate::xvalue::{body_bytes, is_none, plain, XValue}; +use crate::xvalue::{body_bytes, is_none, plain, xvalue_langtype, XValue}; /// JoinPath 拼接父子路径。 pub fn join_path(parent: &str, child: &str) -> String { @@ -136,7 +136,9 @@ pub fn validate_ptr( // 比 **kind 层**(剥 `[dims]`),不比整串:`[5]char/utf32` 与声明 `[]char/utf32` // 是同一个 kind 的不同形状,语义兼容。整串相等曾把「定长实参传给变长形参」 // 这类合法场景误判为类型不符而拒绝写入。 - let actual = norm_langtype(&crate::xvalue::decode_xvalue_head(&v.encode()).langtype); + let actual = norm_langtype( + &crate::xvalue::from_wire_head(&v.to_wire(), &xvalue_langtype(&v)).langtype, + ); let expect = norm_langtype(target_kindexpr); if actual != expect { return Err(format!( diff --git a/src/lib.rs b/src/lib.rs index 3758598..a1512aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,6 @@ pub mod fs; pub mod headlenpow; pub mod kvspace; pub mod kvspace_common; -pub mod metadata; pub mod redis; pub mod s3; pub mod store; diff --git a/src/metadata.rs b/src/metadata.rs deleted file mode 100644 index e2f6104..0000000 --- a/src/metadata.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::headlenpow; -use crate::r#const::META_ROOT_NAME; - -pub fn key_for(key: &str) -> Option { - if !key.starts_with('/') || is_reserved(key) { - return None; - } - let mut out = format!("/{META_ROOT_NAME}/"); - const HEX: &[u8; 16] = b"0123456789abcdef"; - for b in key.bytes() { - out.push(HEX[(b >> 4) as usize] as char); - out.push(HEX[(b & 15) as usize] as char); - } - Some(out) -} - -pub fn is_reserved(key: &str) -> bool { - key == "/.kvspace-meta" || key.starts_with("/.kvspace-meta/") -} - -pub fn original_key(hex: &str) -> Option { - if hex.len() % 2 != 0 { - return None; - } - let mut bytes = Vec::with_capacity(hex.len() / 2); - for pair in hex.as_bytes().chunks_exact(2) { - let hi = (pair[0] as char).to_digit(16)?; - let lo = (pair[1] as char).to_digit(16)?; - bytes.push(((hi << 4) | lo) as u8); - } - let key = String::from_utf8(bytes).ok()?; - if key.starts_with('/') && !is_reserved(&key) { - Some(key) - } else { - None - } -} - -pub fn encode(ro: bool, vid: u32) -> Option> { - let mut body = [0u8; 5]; - body[0] = u8::from(ro); - body[1..].copy_from_slice(&vid.to_le_bytes()); - headlenpow::encode(6, 1, 5, 5, "[5]uint8", &body, 5) -} - -pub fn decode(data: &[u8]) -> Option<(bool, u32)> { - let h = headlenpow::decode(data)?; - if h.total != data.len() - || h.flags != 1 - || h.a != 5 - || h.b != 5 - || h.langtype != "[5]uint8" - || h.body[0] > 1 - { - return None; - } - Some(( - h.body[0] == 1, - u32::from_le_bytes(h.body[1..5].try_into().ok()?), - )) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn metadata_roundtrip() { - assert_eq!(key_for("/a").as_deref(), Some("/.kvspace-meta/2f61")); - assert_eq!(key_for("/.kvspace-meta/2f61"), None); - assert_eq!(original_key("2f61").as_deref(), Some("/a")); - let mut data = encode(true, 0x12345678).unwrap(); - assert_eq!(decode(&data), Some((true, 0x12345678))); - data[64] = 2; - assert_eq!(decode(&data), None); - } -} diff --git a/src/store.rs b/src/store.rs index 3d14242..335197c 100644 --- a/src/store.rs +++ b/src/store.rs @@ -2,8 +2,10 @@ pub(crate) fn is_subtree_key(prefix: &str, key: &str) -> bool { if prefix.is_empty() || prefix == "/" { return key.starts_with('/'); } - key.strip_prefix(prefix) - .is_some_and(|rest| rest.is_empty() || rest.starts_with('/') || rest.starts_with('·')) + // '/'(层级)、'·'(成员)、'‥'(系统变量/langtype attr key)都是子树边界。 + key.strip_prefix(prefix).is_some_and(|rest| { + rest.is_empty() || rest.starts_with('/') || rest.starts_with('·') || rest.starts_with('‥') + }) } pub trait KVStore { diff --git a/src/xvalue.rs b/src/xvalue.rs index 8e72cc9..2014170 100644 --- a/src/xvalue.rs +++ b/src/xvalue.rs @@ -1,6 +1,6 @@ // xvalue.rs — 对齐 xvalue.go // XValue 是所有值的统一枚举(Go 的 interface + 具体类型 → 枚举变体)。 -// XValueHead + XValue 编解码(head + body)。 +// XValueHead + XValue wire 转换(head + body)。 use crate::r#const::*; @@ -8,6 +8,12 @@ pub const REF_INLINE: u8 = 0; pub const REF_PTR: u8 = 1; pub const REF_EXT: u8 = 2; +// 固定 8 字节 head 的 storetype(存储形态,对齐 kvspace 三正交轴 ST_*)。 +pub const ST_SHORT: u8 = 0; // 短定长:None/标量/目录/map 头/空 rwfunc 目录值 +pub const ST_SLACK: u8 = 1; // 变长:字符/字节数组/代码槽/指针路径 +pub const ST_LARGE: u8 = 2; // 大定长:张量 +pub const ST_EXT: u8 = 3; // 扩展定位 @ext + pub fn is_map_langtype(kind: &str) -> bool { kind.contains(OBJ_SEP) } @@ -54,8 +60,6 @@ pub struct XValueHead { pub storetype: u8, // Storage class. pub langtype: String, pub phys_dims: Vec, - pub ro: bool, - pub vid: u32, pub body_len: i32, pub body_cap: u64, } @@ -98,8 +102,8 @@ impl XValueHead { &data[off..off + self.body_len as usize] } - /// 用 body 字节解码为 XValue。 - pub fn decode(&self, body: &[u8]) -> XValue { + /// 用 body 字节组装为 XValue。 + pub fn from_wire(&self, body: &[u8]) -> XValue { if self.is_ptr() { return XValue::Ptr(Ptr { target_kindexpr: self.langtype.clone(), @@ -119,22 +123,22 @@ impl XValueHead { return XValue::Map(MapValue { langtype: kind }); } match kind.as_str() { - KIND_BOOL => XValue::Bool(crate::xvalue_bool::decode_bool(body, &dims)), - KIND_INT8 => XValue::Int8(crate::xvalue_int::decode_int8(body, &dims)), - KIND_INT16 => XValue::Int16(crate::xvalue_int::decode_int16(body, &dims)), - KIND_INT32 => XValue::Int32(crate::xvalue_int::decode_int32(body, &dims)), - KIND_INT64 => XValue::Int64(crate::xvalue_int::decode_int64(body, &dims)), - KIND_UINT8 => XValue::Uint8(crate::xvalue_uint::decode_uint8(body, &dims)), - KIND_UINT16 => XValue::Uint16(crate::xvalue_uint::decode_uint16(body, &dims)), - KIND_UINT32 => XValue::Uint32(crate::xvalue_uint::decode_uint32(body, &dims)), - KIND_UINT64 => XValue::Uint64(crate::xvalue_uint::decode_uint64(body, &dims)), - KIND_FLOAT32 => XValue::Float32(crate::xvalue_float::decode_float32(body, &dims)), - KIND_FLOAT64 => XValue::Float64(crate::xvalue_float::decode_float64(body, &dims)), - KIND_CHAR_UTF8 => XValue::CharByte(crate::xvalue_byte::decode_char_byte(body, &dims)), + KIND_BOOL => XValue::Bool(crate::xvalue_bool::bool_from_wire(body, &dims)), + KIND_INT8 => XValue::Int8(crate::xvalue_int::int8_from_wire(body, &dims)), + KIND_INT16 => XValue::Int16(crate::xvalue_int::int16_from_wire(body, &dims)), + KIND_INT32 => XValue::Int32(crate::xvalue_int::int32_from_wire(body, &dims)), + KIND_INT64 => XValue::Int64(crate::xvalue_int::int64_from_wire(body, &dims)), + KIND_UINT8 => XValue::Uint8(crate::xvalue_uint::uint8_from_wire(body, &dims)), + KIND_UINT16 => XValue::Uint16(crate::xvalue_uint::uint16_from_wire(body, &dims)), + KIND_UINT32 => XValue::Uint32(crate::xvalue_uint::uint32_from_wire(body, &dims)), + KIND_UINT64 => XValue::Uint64(crate::xvalue_uint::uint64_from_wire(body, &dims)), + KIND_FLOAT32 => XValue::Float32(crate::xvalue_float::float32_from_wire(body, &dims)), + KIND_FLOAT64 => XValue::Float64(crate::xvalue_float::float64_from_wire(body, &dims)), + KIND_CHAR_UTF8 => XValue::CharByte(crate::xvalue_byte::char_byte_from_wire(body, &dims)), KIND_CHAR_ASCII => { - XValue::CharAscii(crate::xvalue_byte::decode_char_ascii(body, &dims)) + XValue::CharAscii(crate::xvalue_byte::char_ascii_from_wire(body, &dims)) } - KIND_CHAR => XValue::Char32(crate::xvalue_byte::decode_char32(body, &dims)), + KIND_CHAR => XValue::Char32(crate::xvalue_byte::char32_from_wire(body, &dims)), _ => XValue::Opaque(Opaque { kind: kind.clone(), body: body.to_vec(), @@ -146,7 +150,7 @@ impl XValueHead { // ── Arr:定长/多维数组的 shape 载体 ───────────────────────────────────── /// 定长/多维数组 = 连续元素 + 形状。dims 空 = 标量(ndim 0),[n] = 一维, -/// [d0,d1] = 二维。decode 时从 head 透传,encode 时原样落盘,保证往返不丢 shape。 +/// [d0,d1] = 二维。from_wire 时从 head 透传,to_wire 时原样落盘,保证往返不丢 shape。 #[derive(Clone, Debug, PartialEq)] pub struct Arr { pub data: Vec, @@ -239,27 +243,27 @@ impl XValue { } } - pub fn encode(&self) -> Vec { + pub fn to_wire(&self) -> Vec { match self { - XValue::None => encode_head("None", 0, &[], &[]), - XValue::Ptr(p) => xvalue_encode_ptr(&p.target_kindexpr, p.target.as_bytes()), - XValue::Bool(d) => crate::xvalue_bool::encode_bool(&d.data, &d.dims), - XValue::Int8(d) => crate::xvalue_int::encode_int8(&d.data, &d.dims), - XValue::Int16(d) => crate::xvalue_int::encode_int16(&d.data, &d.dims), - XValue::Int32(d) => crate::xvalue_int::encode_int32(&d.data, &d.dims), - XValue::Int64(d) => crate::xvalue_int::encode_int64(&d.data, &d.dims), - XValue::Uint8(d) => crate::xvalue_uint::encode_uint8(&d.data, &d.dims), - XValue::Uint16(d) => crate::xvalue_uint::encode_uint16(&d.data, &d.dims), - XValue::Uint32(d) => crate::xvalue_uint::encode_uint32(&d.data, &d.dims), - XValue::Uint64(d) => crate::xvalue_uint::encode_uint64(&d.data, &d.dims), - XValue::Float32(d) => crate::xvalue_float::encode_float32(&d.data, &d.dims), - XValue::Float64(d) => crate::xvalue_float::encode_float64(&d.data, &d.dims), - XValue::CharByte(d) => crate::xvalue_byte::encode_char_byte(&d.data, &d.dims), - XValue::CharAscii(d) => crate::xvalue_byte::encode_char_ascii(&d.data, &d.dims), - XValue::Char32(d) => crate::xvalue_byte::encode_char32(&d.data, &d.dims), - XValue::Map(m) => encode_head(&m.langtype, 0, &[], &[]), - XValue::Ext(e) => encode_head(&e.langtype, 2, &[], e.locator.as_bytes()), - XValue::Opaque(o) => xvalue_encode(&o.kind, &o.body, o.array_len), + XValue::None => to_wire_head("None", 0, &[], &[]), + XValue::Ptr(p) => xvalue_to_wire_ptr(&p.target_kindexpr, p.target.as_bytes()), + XValue::Bool(d) => crate::xvalue_bool::bool_to_wire(&d.data, &d.dims), + XValue::Int8(d) => crate::xvalue_int::int8_to_wire(&d.data, &d.dims), + XValue::Int16(d) => crate::xvalue_int::int16_to_wire(&d.data, &d.dims), + XValue::Int32(d) => crate::xvalue_int::int32_to_wire(&d.data, &d.dims), + XValue::Int64(d) => crate::xvalue_int::int64_to_wire(&d.data, &d.dims), + XValue::Uint8(d) => crate::xvalue_uint::uint8_to_wire(&d.data, &d.dims), + XValue::Uint16(d) => crate::xvalue_uint::uint16_to_wire(&d.data, &d.dims), + XValue::Uint32(d) => crate::xvalue_uint::uint32_to_wire(&d.data, &d.dims), + XValue::Uint64(d) => crate::xvalue_uint::uint64_to_wire(&d.data, &d.dims), + XValue::Float32(d) => crate::xvalue_float::float32_to_wire(&d.data, &d.dims), + XValue::Float64(d) => crate::xvalue_float::float64_to_wire(&d.data, &d.dims), + XValue::CharByte(d) => crate::xvalue_byte::char_byte_to_wire(&d.data, &d.dims), + XValue::CharAscii(d) => crate::xvalue_byte::char_ascii_to_wire(&d.data, &d.dims), + XValue::Char32(d) => crate::xvalue_byte::char32_to_wire(&d.data, &d.dims), + XValue::Map(m) => to_wire_head(&m.langtype, 0, &[], &[]), + XValue::Ext(e) => to_wire_head(&e.langtype, 2, &[], e.locator.as_bytes()), + XValue::Opaque(o) => xvalue_to_wire(&o.kind, &o.body, o.array_len), } } @@ -384,16 +388,16 @@ fn bool_string(b: bool) -> String { } } -// Headlenpow XValue encoding. +// Headlenpow XValue wire 转换。 -pub fn xvalue_encode(kind: &str, raw: &[u8], array_len: i32) -> Vec { - encode_head(kind, 0, &array_to_header(kind, array_len), raw) +pub fn xvalue_to_wire(kind: &str, raw: &[u8], array_len: i32) -> Vec { + to_wire_head(kind, 0, &array_to_header(kind, array_len), raw) } -/// 指针编码:langtype = 目标完整 kindexpr(含其 [dims]/引用性),storetype = 目标语义 storetype, -/// 物理字段恒空,body = 目标 key 路径。 -pub fn xvalue_encode_ptr(target_kindexpr: &str, raw: &[u8]) -> Vec { - encode_head(target_kindexpr, 1, &[], raw) +/// 指针 wire 转换:固定头恒 ST_SLACK + PTR_PTR,body = 目标 key 路径。目标 kindexpr 不进 head +/// (由调用方写 x‥langtype attr key)。 +pub fn xvalue_to_wire_ptr(_target_kindexpr: &str, raw: &[u8]) -> Vec { + crate::headlenpow::to_wire(ST_SLACK, REF_PTR, raw).unwrap_or_default() } /// array_len → dims:char/* 恒一维(含空串/单字符);其余标量(≤1)=0 维、多元素=1 维。 @@ -407,132 +411,132 @@ fn array_to_header(kind: &str, array_len: i32) -> Vec { } } -pub fn encode_head(kind: &str, r#ref: i32, dims: &[i32], raw: &[u8]) -> Vec { - let (pow, flags, a, b, langtype) = if r#ref == 1 || r#ref == 2 { - let pow = (5..=31).find(|&p| kind.len() + crate::headlenpow::PREFIX <= 1usize << p); - let Some(pow) = pow else { return Vec::new() }; - ( - pow, - if r#ref == 1 { 5 } else { 3 }, - raw.len() as u64, - raw.len() as u64, - kind.to_string(), - ) +/// 固定 8 字节 head 落盘:storetype 按 kind/dims 判定,ptr 按 ref 判定,body = raw 原样。 +/// langtype 不进 head(调用方写 x‥langtype attr key)。 +/// +/// storetype 判定(对齐 kvspace 三正交轴与 C codec): +/// - ref==1(指针)→ ST_SLACK;ref==2(@ext)→ ST_EXT; +/// - rwir/def langtype 或带 body 的 rwfunc(代码槽)→ ST_SLACK; +/// - char/*(字符/字节数组)→ ST_SLACK; +/// - dims 非空且非 map/struct → ST_LARGE(张量); +/// - 否则 ST_SHORT(None/标量/目录/map 头/空 rwfunc 目录值)。 +pub fn to_wire_head(kind: &str, r#ref: i32, dims: &[i32], raw: &[u8]) -> Vec { + let storetype = if r#ref == 2 { + ST_EXT + } else if r#ref == 1 { + ST_SLACK } else if r#ref != 0 { return Vec::new(); - } else if kind.is_empty() || kind == "None" { - (5, 0, 0, 0, String::new()) - } else if matches!(kind, "char/utf8" | "char/ascii" | "char/utf32") { - let count = match kind { - "char/utf8" => std::str::from_utf8(raw).ok().map(|s| s.chars().count()), - "char/ascii" if raw.is_ascii() => Some(raw.len()), - "char/utf32" - if raw.len() % 4 == 0 - && raw.chunks_exact(4).all(|c| { - char::from_u32(u32::from_le_bytes(c.try_into().unwrap())).is_some() - }) => - { - Some(raw.len() / 4) - } - _ => None, - }; - let Some(count) = count else { - return Vec::new(); - }; - ( - 6, - 1, - raw.len() as u64, - raw.len() as u64, - format!("[{count}]{kind}"), - ) - } else if matches!(kind, "rwir" | "rwfunc") && raw.len() >= 5 || kind == "def langtype" { - (5, 1, raw.len() as u64, raw.len() as u64, kind.to_string()) + } else if matches!(kind, "rwir" | "def langtype") || (kind == "rwfunc" && !raw.is_empty()) { + ST_SLACK + } else if kind.starts_with("char/") { + ST_SLACK } else if !dims.is_empty() && !kind.contains('·') && !kind.starts_with('/') { - let Some(numel) = dims.iter().try_fold(1u64, |n, &d| { - u64::try_from(d).ok().and_then(|d| n.checked_mul(d)) - }) else { - return Vec::new(); - }; - let width = elem_size(kind); - if width <= 0 { - return Vec::new(); - } - ( - 7, - 2, - numel, - width as u64, - format!( - "[{}]{kind}", - dims.iter() - .map(i32::to_string) - .collect::>() - .join(",") - ), - ) + ST_LARGE } else { - let pow = (5..=31).find(|&p| kind.len() + crate::headlenpow::PREFIX <= 1usize << p); - let Some(pow) = pow else { return Vec::new() }; - (pow, 0, 0, 0, kind.to_string()) + ST_SHORT + }; + let ptr = match r#ref { + 1 => REF_PTR, + 2 => REF_EXT, + _ => REF_INLINE, }; - crate::headlenpow::encode(pow, flags, a, b, &langtype, raw, raw.len()).unwrap_or_default() + crate::headlenpow::to_wire(storetype, ptr, raw).unwrap_or_default() } -pub fn decode_xvalue_head(data: &[u8]) -> XValueHead { - let Some(h) = crate::headlenpow::decode(data) else { +/// 从固定 8 字节 head + 侧写 langtype 解析 XValueHead(langtype 从 x‥langtype attr key 读)。 +pub fn from_wire_head(data: &[u8], langtype: &str) -> XValueHead { + let Some(h) = crate::headlenpow::from_wire(data) else { return XValueHead::default(); }; - if h.total != data.len() { - return XValueHead::default(); - } - let (Ok(headlen), Ok(body_len)) = - (u16::try_from(1usize << h.pow), i32::try_from(h.content_len)) - else { + let Ok(headlen) = u16::try_from(crate::headlenpow::PREFIX) else { return XValueHead::default(); }; - let dims = if h.flags == 2 || (h.flags == 1 && h.langtype.starts_with('[')) { - parse_kindexpr(h.langtype).0 + let body_len = i32::try_from(h.len).unwrap_or(-1); + if body_len < 0 { + return XValueHead::default(); + } + let dims = if h.storetype == 2 || (h.storetype == 1 && langtype.starts_with('[')) { + parse_kindexpr(langtype).0 } else { Vec::new() }; XValueHead { headlen, - r#ref: if h.flags == 5 { + r#ref: if h.ptr == REF_PTR { REF_PTR - } else if h.flags == 3 { + } else if h.ptr == REF_EXT { REF_EXT } else { REF_INLINE }, - storetype: h.flags & 3, - langtype: h.langtype.to_string(), + storetype: h.storetype, + langtype: langtype.to_string(), phys_dims: dims, - ro: false, - vid: 0, body_len, - body_cap: (h.total - headlen as usize) as u64, + body_cap: h.len as u64, } } -/// 解析完整 XValue(head + body)为 XValue。 -pub fn decode_xvalue(data: &[u8]) -> XValue { - let h = decode_xvalue_head(data); - // An empty inline langtype denotes None;Ptr/Ext 的空 langtype 表示「目标不约束」。 +/// 从完整 XValue 字节(固定 head + 侧写 langtype + body)组装 XValue。 +pub fn from_wire(data: &[u8], langtype: &str) -> XValue { + let h = from_wire_head(data, langtype); + // 空 langtype 表示 None(head 固定后 attr key 缺失 = 无类型)。 if h.langtype.is_empty() && !h.is_ptr() && h.r#ref() != REF_EXT as i32 { return XValue::None; } - h.decode(h.body(data)) + h.from_wire(h.body(data)) } -/// 返回 XValue 的 body 字节。 +/// 返回 XValue 的 body 字节(固定头:body 偏移恒 8)。 pub fn body_bytes(v: &XValue) -> Vec { if is_none(v) { return Vec::new(); } - let data = v.encode(); - let h = decode_xvalue_head(&data); - h.body(&data).to_vec() + let data = v.to_wire(); + match crate::headlenpow::from_wire(&data) { + Some(h) => h.body.to_vec(), + None => Vec::new(), + } +} + +/// 返回 XValue 的完整 langtype 串(含 [dims] 形状段),用于写 x‥langtype attr key。 +/// None → 空串;Ptr/Ext → 目标 kindexpr;Map → 完整 map 类型;Opaque → 自定义 kind; +/// 数组 → `[d0,d1]kind`(无 dims 则裸 kind)。 +pub fn xvalue_langtype(v: &XValue) -> String { + let dims: &[i32] = match v { + XValue::None => return String::new(), + XValue::Ptr(p) => return p.target_kindexpr.clone(), + XValue::Ext(e) => return e.langtype.clone(), + XValue::Map(m) => return m.langtype.clone(), + XValue::Opaque(o) => return o.kind.clone(), + XValue::Bool(d) => &d.dims, + XValue::Int8(d) => &d.dims, + XValue::Int16(d) => &d.dims, + XValue::Int32(d) => &d.dims, + XValue::Int64(d) => &d.dims, + XValue::Uint8(d) => &d.dims, + XValue::Uint16(d) => &d.dims, + XValue::Uint32(d) => &d.dims, + XValue::Uint64(d) => &d.dims, + XValue::Float32(d) => &d.dims, + XValue::Float64(d) => &d.dims, + XValue::CharByte(d) => &d.dims, + XValue::CharAscii(d) => &d.dims, + XValue::Char32(d) => &d.dims, + }; + if dims.is_empty() { + v.kind().to_string() + } else { + format!( + "[{}]{}", + dims.iter() + .map(i32::to_string) + .collect::>() + .join(","), + v.kind() + ) + } } /// ElemSize 返回 kind 的单元素字节数;≤0 表示非定长类型(非 byte 派生)。 @@ -573,8 +577,13 @@ mod tests { use crate::xvalue_int::new_int64; fn roundtrip(v: &XValue) { - let bytes = v.encode(); - assert_eq!(*v, decode_xvalue(&bytes), "roundtrip {:?}", v); + let bytes = v.to_wire(); + assert_eq!( + *v, + from_wire(&bytes, &xvalue_langtype(v)), + "roundtrip {:?}", + v + ); } #[test] @@ -623,21 +632,28 @@ mod tests { #[test] fn directory_and_extension_wire() { - let dir = encode_head("lib", 0, &[], &[]); - let head = crate::headlenpow::decode(&dir).unwrap(); - assert_eq!((head.flags, head.langtype, head.content_len), (0, "lib", 0)); + // 目录值:SHORT、inline、空 body,固定 8 字节 head。 + let dir = to_wire_head("lib", 0, &[], &[]); + assert_eq!(dir.len(), 8); + let head = crate::headlenpow::from_wire(&dir).unwrap(); + assert_eq!( + (head.storetype, head.ptr, head.len, head.body.len()), + (ST_SHORT, REF_INLINE, 0, 0) + ); + // @ext:ST_EXT + PTR_EXT,body = 定位串(8 + body)。 let ext = XValue::Ext(ExtHandle { langtype: "rwfunc".into(), locator: "/lib/f/".into(), }) - .encode(); - let head = crate::headlenpow::decode(&ext).unwrap(); + .to_wire(); + assert_eq!(ext.len(), 8 + "/lib/f/".len()); + let head = crate::headlenpow::from_wire(&ext).unwrap(); assert_eq!( - (head.flags, head.langtype, head.body), - (3, "rwfunc", b"/lib/f/".as_slice()) + (head.storetype, head.ptr, head.body), + (ST_EXT, REF_EXT, b"/lib/f/".as_slice()) ); assert_eq!( - decode_xvalue(&ext), + from_wire(&ext, "rwfunc"), XValue::Ext(ExtHandle { langtype: "rwfunc".into(), locator: "/lib/f/".into() @@ -647,8 +663,9 @@ mod tests { #[test] fn head_fields() { - let bytes = new_float64(&[1.0, 2.0, 3.0]).encode(); - let h = decode_xvalue_head(&bytes); + let bytes = new_float64(&[1.0, 2.0, 3.0]).to_wire(); + assert_eq!(bytes.len(), 8 + 24); // 固定头 8 + 3×f64 body + let h = from_wire_head(&bytes, "[3]float64"); assert_eq!(h.kind(), "float64"); assert_eq!(h.dims(), vec![3]); assert_eq!(h.array_len(), 3); diff --git a/src/xvalue_bool.rs b/src/xvalue_bool.rs index 8cfac6b..3da0c55 100644 --- a/src/xvalue_bool.rs +++ b/src/xvalue_bool.rs @@ -1,7 +1,7 @@ // xvalue_bool.rs — 对齐 xvalue_bool.go use crate::r#const::*; -use crate::xvalue::{dims_from_len, encode_head, Arr, XValue}; +use crate::xvalue::{dims_from_len, to_wire_head, Arr, XValue}; pub fn new_bool(v: &[bool]) -> XValue { XValue::Bool(Arr { @@ -10,14 +10,14 @@ pub fn new_bool(v: &[bool]) -> XValue { }) } -pub fn decode_bool(body: &[u8], dims: &[i32]) -> Arr { +pub fn bool_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body.iter().map(|&b| b != 0).collect(), dims: dims.to_vec(), } } -pub fn encode_bool(data: &[bool], dims: &[i32]) -> Vec { +pub fn bool_to_wire(data: &[bool], dims: &[i32]) -> Vec { let raw: Vec = data.iter().map(|&b| if b { 1 } else { 0 }).collect(); - encode_head(KIND_BOOL, 0, dims, &raw) + to_wire_head(KIND_BOOL, 0, dims, &raw) } diff --git a/src/xvalue_byte.rs b/src/xvalue_byte.rs index f8b6ec1..204dad4 100644 --- a/src/xvalue_byte.rs +++ b/src/xvalue_byte.rs @@ -1,7 +1,7 @@ // xvalue_byte.rs — 对齐 xvalue_byte.go(char/utf8、char/ascii、char/utf32) use crate::r#const::*; -use crate::xvalue::{encode_head, Arr, XValue}; +use crate::xvalue::{to_wire_head, Arr, XValue}; /// char/* 恒一维序列(含空串/单字符),dims = [len]。 fn char_dims(n: usize) -> Vec { @@ -42,19 +42,19 @@ pub fn new_char(kind: &str, s: &str) -> XValue { } } -pub fn decode_char_byte(body: &[u8], dims: &[i32]) -> Arr { +pub fn char_byte_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body.to_vec(), dims: dims.to_vec(), } } -pub fn decode_char_ascii(body: &[u8], dims: &[i32]) -> Arr { +pub fn char_ascii_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body.to_vec(), dims: dims.to_vec(), } } -pub fn decode_char32(body: &[u8], dims: &[i32]) -> Arr { +pub fn char32_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(4) @@ -64,15 +64,15 @@ pub fn decode_char32(body: &[u8], dims: &[i32]) -> Arr { } } -pub fn encode_char_byte(data: &[u8], dims: &[i32]) -> Vec { - encode_head(KIND_CHAR_UTF8, 0, dims, data) +pub fn char_byte_to_wire(data: &[u8], dims: &[i32]) -> Vec { + to_wire_head(KIND_CHAR_UTF8, 0, dims, data) } -pub fn encode_char_ascii(data: &[u8], dims: &[i32]) -> Vec { - encode_head(KIND_CHAR_ASCII, 0, dims, data) +pub fn char_ascii_to_wire(data: &[u8], dims: &[i32]) -> Vec { + to_wire_head(KIND_CHAR_ASCII, 0, dims, data) } -pub fn encode_char32(data: &[u32], dims: &[i32]) -> Vec { +pub fn char32_to_wire(data: &[u32], dims: &[i32]) -> Vec { let raw: Vec = data.iter().flat_map(|&v| v.to_le_bytes()).collect(); - encode_head(KIND_CHAR, 0, dims, &raw) + to_wire_head(KIND_CHAR, 0, dims, &raw) } /// IsCharKind 判断 kind 是否为字符家族(前缀 char/)。 diff --git a/src/xvalue_float.rs b/src/xvalue_float.rs index 6f60ca1..e0fc50b 100644 --- a/src/xvalue_float.rs +++ b/src/xvalue_float.rs @@ -1,7 +1,7 @@ // xvalue_float.rs — 对齐 xvalue_float.go use crate::r#const::*; -use crate::xvalue::{dims_from_len, encode_head, Arr, XValue}; +use crate::xvalue::{dims_from_len, to_wire_head, Arr, XValue}; pub fn new_float32(v: &[f32]) -> XValue { XValue::Float32(Arr { @@ -16,7 +16,7 @@ pub fn new_float64(v: &[f64]) -> XValue { }) } -pub fn decode_float32(body: &[u8], dims: &[i32]) -> Arr { +pub fn float32_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(4) @@ -25,7 +25,7 @@ pub fn decode_float32(body: &[u8], dims: &[i32]) -> Arr { dims: dims.to_vec(), } } -pub fn decode_float64(body: &[u8], dims: &[i32]) -> Arr { +pub fn float64_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(8) @@ -35,17 +35,17 @@ pub fn decode_float64(body: &[u8], dims: &[i32]) -> Arr { } } -pub fn encode_float32(data: &[f32], dims: &[i32]) -> Vec { +pub fn float32_to_wire(data: &[f32], dims: &[i32]) -> Vec { let raw: Vec = data .iter() .flat_map(|&v| v.to_bits().to_le_bytes()) .collect(); - encode_head(KIND_FLOAT32, 0, dims, &raw) + to_wire_head(KIND_FLOAT32, 0, dims, &raw) } -pub fn encode_float64(data: &[f64], dims: &[i32]) -> Vec { +pub fn float64_to_wire(data: &[f64], dims: &[i32]) -> Vec { let raw: Vec = data .iter() .flat_map(|&v| v.to_bits().to_le_bytes()) .collect(); - encode_head(KIND_FLOAT64, 0, dims, &raw) + to_wire_head(KIND_FLOAT64, 0, dims, &raw) } diff --git a/src/xvalue_int.rs b/src/xvalue_int.rs index e7f8100..f5b76a5 100644 --- a/src/xvalue_int.rs +++ b/src/xvalue_int.rs @@ -1,7 +1,7 @@ // xvalue_int.rs — 对齐 xvalue_int.go use crate::r#const::*; -use crate::xvalue::{dims_from_len, encode_head, Arr, XValue}; +use crate::xvalue::{dims_from_len, to_wire_head, Arr, XValue}; pub fn new_int64(v: &[i64]) -> XValue { XValue::Int64(Arr { @@ -10,13 +10,13 @@ pub fn new_int64(v: &[i64]) -> XValue { }) } -pub fn decode_int8(body: &[u8], dims: &[i32]) -> Arr { +pub fn int8_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body.iter().map(|&b| b as i8).collect(), dims: dims.to_vec(), } } -pub fn decode_int16(body: &[u8], dims: &[i32]) -> Arr { +pub fn int16_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(2) @@ -25,7 +25,7 @@ pub fn decode_int16(body: &[u8], dims: &[i32]) -> Arr { dims: dims.to_vec(), } } -pub fn decode_int32(body: &[u8], dims: &[i32]) -> Arr { +pub fn int32_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(4) @@ -34,7 +34,7 @@ pub fn decode_int32(body: &[u8], dims: &[i32]) -> Arr { dims: dims.to_vec(), } } -pub fn decode_int64(body: &[u8], dims: &[i32]) -> Arr { +pub fn int64_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(8) @@ -44,28 +44,28 @@ pub fn decode_int64(body: &[u8], dims: &[i32]) -> Arr { } } -pub fn encode_int8(data: &[i8], dims: &[i32]) -> Vec { +pub fn int8_to_wire(data: &[i8], dims: &[i32]) -> Vec { let raw: Vec = data.iter().map(|&v| v as u8).collect(); - encode_head(KIND_INT8, 0, dims, &raw) + to_wire_head(KIND_INT8, 0, dims, &raw) } -pub fn encode_int16(data: &[i16], dims: &[i32]) -> Vec { +pub fn int16_to_wire(data: &[i16], dims: &[i32]) -> Vec { let raw: Vec = data .iter() .flat_map(|&v| (v as u16).to_le_bytes()) .collect(); - encode_head(KIND_INT16, 0, dims, &raw) + to_wire_head(KIND_INT16, 0, dims, &raw) } -pub fn encode_int32(data: &[i32], dims: &[i32]) -> Vec { +pub fn int32_to_wire(data: &[i32], dims: &[i32]) -> Vec { let raw: Vec = data .iter() .flat_map(|&v| (v as u32).to_le_bytes()) .collect(); - encode_head(KIND_INT32, 0, dims, &raw) + to_wire_head(KIND_INT32, 0, dims, &raw) } -pub fn encode_int64(data: &[i64], dims: &[i32]) -> Vec { +pub fn int64_to_wire(data: &[i64], dims: &[i32]) -> Vec { let raw: Vec = data .iter() .flat_map(|&v| (v as u64).to_le_bytes()) .collect(); - encode_head(KIND_INT64, 0, dims, &raw) + to_wire_head(KIND_INT64, 0, dims, &raw) } diff --git a/src/xvalue_uint.rs b/src/xvalue_uint.rs index d5fceae..0189d9f 100644 --- a/src/xvalue_uint.rs +++ b/src/xvalue_uint.rs @@ -1,15 +1,15 @@ // xvalue_uint.rs — 对齐 xvalue_uint.go use crate::r#const::*; -use crate::xvalue::{encode_head, Arr}; +use crate::xvalue::{to_wire_head, Arr}; -pub fn decode_uint8(body: &[u8], dims: &[i32]) -> Arr { +pub fn uint8_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body.to_vec(), dims: dims.to_vec(), } } -pub fn decode_uint16(body: &[u8], dims: &[i32]) -> Arr { +pub fn uint16_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(2) @@ -18,7 +18,7 @@ pub fn decode_uint16(body: &[u8], dims: &[i32]) -> Arr { dims: dims.to_vec(), } } -pub fn decode_uint32(body: &[u8], dims: &[i32]) -> Arr { +pub fn uint32_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(4) @@ -27,7 +27,7 @@ pub fn decode_uint32(body: &[u8], dims: &[i32]) -> Arr { dims: dims.to_vec(), } } -pub fn decode_uint64(body: &[u8], dims: &[i32]) -> Arr { +pub fn uint64_from_wire(body: &[u8], dims: &[i32]) -> Arr { Arr { data: body .chunks_exact(8) @@ -37,18 +37,18 @@ pub fn decode_uint64(body: &[u8], dims: &[i32]) -> Arr { } } -pub fn encode_uint8(data: &[u8], dims: &[i32]) -> Vec { - encode_head(KIND_UINT8, 0, dims, data) +pub fn uint8_to_wire(data: &[u8], dims: &[i32]) -> Vec { + to_wire_head(KIND_UINT8, 0, dims, data) } -pub fn encode_uint16(data: &[u16], dims: &[i32]) -> Vec { +pub fn uint16_to_wire(data: &[u16], dims: &[i32]) -> Vec { let raw: Vec = data.iter().flat_map(|&v| v.to_le_bytes()).collect(); - encode_head(KIND_UINT16, 0, dims, &raw) + to_wire_head(KIND_UINT16, 0, dims, &raw) } -pub fn encode_uint32(data: &[u32], dims: &[i32]) -> Vec { +pub fn uint32_to_wire(data: &[u32], dims: &[i32]) -> Vec { let raw: Vec = data.iter().flat_map(|&v| v.to_le_bytes()).collect(); - encode_head(KIND_UINT32, 0, dims, &raw) + to_wire_head(KIND_UINT32, 0, dims, &raw) } -pub fn encode_uint64(data: &[u64], dims: &[i32]) -> Vec { +pub fn uint64_to_wire(data: &[u64], dims: &[i32]) -> Vec { let raw: Vec = data.iter().flat_map(|&v| v.to_le_bytes()).collect(); - encode_head(KIND_UINT64, 0, dims, &raw) + to_wire_head(KIND_UINT64, 0, dims, &raw) } diff --git a/tests/metadata_fs.rs b/tests/metadata_fs.rs index 409be84..1e03597 100644 --- a/tests/metadata_fs.rs +++ b/tests/metadata_fs.rs @@ -1,80 +1,15 @@ -use kvspace_durable::{conn, metadata, new_map_langtype, KVPair, KVSpace, XValue}; +use kvspace_durable::{conn, KVPair, XValue}; use std::fs; -fn set(kv: &mut dyn KVSpace, key: &str, ro: bool, vid: u32) { - let raw = kvspace_durable::xvalue::encode_head("int32", 0, &[], &7i32.to_le_bytes()); - kv.set(&[KVPair { - key: key.into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - kv.set_metadata(key, ro, vid).unwrap(); -} - -#[test] -fn metadata_tracks_fs_subtrees() { - let root = std::env::temp_dir().join(format!("kvspace-meta-fs-{}", std::process::id())); - let _ = fs::remove_dir_all(&root); - let mut kv = conn(&format!("fs://{}", root.display())); - let kv = kv.as_mut(); - set(kv, "/src/a", true, 7); - set(kv, "/src/sub/b", true, 8); - let meta = |key: &str| root.join(metadata::key_for(key).unwrap().trim_start_matches('/')); - assert_eq!( - metadata::decode(&fs::read(meta("/src/a")).unwrap()), - Some((true, 7)) - ); - assert_eq!(kv.get_metadata("/src/a").unwrap(), (true, 7)); - assert!(kv - .list("/", false, false) - .iter() - .all(|name| !name.contains(".kvspace-meta"))); - - kv.cp_tree("/src", "/dst").unwrap(); - assert_eq!( - metadata::decode(&fs::read(meta("/dst/a")).unwrap()), - Some((true, 7)) - ); - assert_eq!( - metadata::decode(&fs::read(meta("/dst/sub/b")).unwrap()), - Some((true, 8)) - ); - kv.del_tree("/src").unwrap(); - assert!(!meta("/src/a").exists()); - assert!(!meta("/src/sub/b").exists()); - assert!(meta("/dst/a").exists()); - kv.del_tree("/dst").unwrap(); - assert!(!meta("/dst/a").exists()); - assert!(!meta("/dst/sub/b").exists()); - - kv.set(&[KVPair { - key: "/m".into(), - val: new_map_langtype("[int64]·[]char/utf32"), - raw: None, - }]) - .unwrap(); - set(kv, "/m·x", true, 9); - kv.cp_list("/m", "/n").unwrap(); - assert_eq!( - metadata::decode(&fs::read(meta("/n·x")).unwrap()), - Some((true, 9)) - ); - kv.del_tree("/m").unwrap(); - assert!(!meta("/m·x").exists()); - assert!(meta("/n·x").exists()); - let _ = fs::remove_dir_all(&root); -} - #[test] fn reads_new_wire_directory_value() { let root = std::env::temp_dir().join(format!("kvspace-new-dir-fs-{}", std::process::id())); let _ = fs::remove_dir_all(&root); let mut kv = conn(&format!("fs://{}", root.display())); - let raw = kvspace_durable::headlenpow::encode(5, 0, 0, 0, "rwfunc", &[], 0).unwrap(); + let raw = kvspace_durable::headlenpow::to_wire(0, 0, &[]).unwrap(); kv.set(&[KVPair { key: "/lib/f/".into(), - val: kvspace_durable::xvalue::decode_xvalue(&raw), + val: kvspace_durable::xvalue::from_wire(&raw, ""), raw: Some(raw.clone()), }]) .unwrap(); @@ -83,41 +18,12 @@ fn reads_new_wire_directory_value() { let _ = fs::remove_dir_all(&root); } -#[test] -fn copies_pointer_slot_metadata() { - let root = std::env::temp_dir().join(format!("kvspace-pointer-meta-fs-{}", std::process::id())); - let _ = fs::remove_dir_all(&root); - let mut kv = conn(&format!("fs://{}", root.display())); - let kv = kv.as_mut(); - let target = - kvspace_durable::headlenpow::encode(5, 0, 0, 0, "int32", &7i32.to_le_bytes(), 4).unwrap(); - let pointer = kvspace_durable::headlenpow::encode(5, 5, 7, 7, "int32", b"/target", 7).unwrap(); - for (key, raw) in [("/target", target), ("/link", pointer)] { - kv.set(&[KVPair { - key: key.into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - } - kv.set_metadata("/target", true, 42).unwrap(); - kv.set_metadata("/link", true, 17).unwrap(); - kv.cp("/link", "/copy").unwrap(); - assert_eq!(kv.get_metadata("/copy").unwrap(), (true, 42)); - let key = metadata::key_for("/copy").unwrap(); - assert_eq!( - metadata::decode(&fs::read(root.join(key.trim_start_matches('/'))).unwrap()), - Some((true, 17)) - ); - let _ = fs::remove_dir_all(&root); -} - #[test] fn value_and_directory_keys_coexist() { let root = std::env::temp_dir().join(format!("kvspace-dir-value-fs-{}", std::process::id())); let _ = fs::remove_dir_all(&root); let mut kv = conn(&format!("fs://{}", root.display())); - let value = kvspace_durable::xvalue::encode_head("int64", 0, &[], &7i64.to_le_bytes()); + let value = kvspace_durable::xvalue::to_wire_head("int64", 0, &[], &7i64.to_le_bytes()); kv.set(&[KVPair { key: "/node".into(), val: XValue::None, @@ -126,12 +32,9 @@ fn value_and_directory_keys_coexist() { .unwrap(); kv.mkindex("/node/", 0).unwrap(); let directory = kv.get_raw("/node/"); - assert_eq!( - kvspace_durable::headlenpow::decode(&directory) - .unwrap() - .langtype, - "lib" - ); + let dh = kvspace_durable::headlenpow::from_wire(&directory).unwrap(); + assert_eq!(dh.storetype, 0); /* 目录 = storetype 0 空 body;langtype 在 attr key */ + assert_eq!(dh.ptr, 0); assert_eq!(kv.get_raw("/node"), value); for name in ["__dir__", "__self__", "~abc"] { kv.set(&[KVPair { diff --git a/tests/prefix_list.rs b/tests/prefix_list.rs index 597a74c..ee3f268 100644 --- a/tests/prefix_list.rs +++ b/tests/prefix_list.rs @@ -1,4 +1,4 @@ -use kvspace_durable::{metadata, Backend, KVPair, KVSpace, KVStore, XValue}; +use kvspace_durable::{Backend, KVPair, KVSpace, KVStore, XValue}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -46,9 +46,9 @@ impl KVStore for MemoryStore { fn frame_pointer_shadows_code_operand() { let store = MemoryStore::default(); let mut kv = Backend::new(store); - let dir = kvspace_durable::headlenpow::encode(5, 0, 0, 0, "lib", &[], 0).unwrap(); + let dir = kvspace_durable::headlenpow::to_wire(0, 0, &[]).unwrap(); let code = - kvspace_durable::headlenpow::encode(5, 1, 6, 6, "rwir", &[0, 0, 0, 0, 0, b'x'], 6).unwrap(); + kvspace_durable::headlenpow::to_wire(1, 0, &[0, 0, 0, 0, 0, b'x']).unwrap(); for (key, raw) in [ ("/lib/f/", dir), ("/lib/f/[1,-1]", code.clone()), @@ -62,7 +62,7 @@ fn frame_pointer_shadows_code_operand() { .unwrap(); } kv.ext_index("/vthread/1/[1]/", "/lib/f/").unwrap(); - let ptr = kvspace_durable::headlenpow::encode(5, 5, 2, 2, "int64", b"/x", 2).unwrap(); + let ptr = kvspace_durable::headlenpow::to_wire(1, 1, b"/x").unwrap(); assert!(kv .set(&[KVPair { key: "/vthread/1/[1]/[1,0]".into(), @@ -79,71 +79,12 @@ fn frame_pointer_shadows_code_operand() { assert_eq!(kv.get_raw("/vthread/1/[1]/[1,-1]"), ptr); } -#[test] -fn stores_and_removes_metadata_sidecars() { - let store = MemoryStore::default(); - let inspect = store.clone(); - let mut backend = Backend::new(store); - let key = "/private"; - let meta = metadata::key_for(key).unwrap(); - let raw = kvspace_durable::xvalue::encode_head("int32", 0, &[], &7i32.to_le_bytes()); - backend - .set(&[KVPair { - key: key.into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - backend.set_metadata(key, true, 42).unwrap(); - assert_eq!( - metadata::decode(&inspect.get(&meta).unwrap()), - Some((true, 42)) - ); - assert_eq!(backend.get_metadata(key).unwrap(), (true, 42)); - backend.cp(key, "/copy").unwrap(); - let copy_meta = metadata::key_for("/copy").unwrap(); - assert_eq!( - metadata::decode(&inspect.get(©_meta).unwrap()), - Some((true, 42)) - ); - backend.del(&["/copy".into()]).unwrap(); - assert!(inspect.get(©_meta).is_none()); - let raw = kvspace_durable::xvalue::encode_head("int32", 0, &[], &8i32.to_le_bytes()); - backend - .set(&[KVPair { - key: key.into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - assert!(inspect.get(&meta).is_none()); - assert_eq!(backend.get_metadata(key).unwrap(), (false, 0)); - - let raw = kvspace_durable::xvalue::encode_head("int32", 0, &[], &9i32.to_le_bytes()); - backend - .set(&[KVPair { - key: "/tree/a".into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - backend.set_metadata("/tree/a", true, 19).unwrap(); - backend.cp_tree("/tree", "/tree_copy").unwrap(); - let copied = metadata::key_for("/tree_copy/a").unwrap(); - assert_eq!( - metadata::decode(&inspect.get(&copied).unwrap()), - Some((true, 19)) - ); - backend.del_tree("/tree_copy").unwrap(); - assert!(inspect.get(&copied).is_none()); -} - #[test] fn lists_physical_children() { let store = MemoryStore::default(); store.set( "/m", - &kvspace_durable::xvalue::encode_head("[int64]·int64", 0, &[], &[]), + &kvspace_durable::xvalue::to_wire_head("[int64]·int64", 0, &[], &[]), ); store.set("/m·[10]", b"value"); store.set("/m·[2]", b"value"); @@ -160,72 +101,18 @@ fn lists_physical_children() { assert_eq!(backend.list("/", false, false), vec!["d", "m"]); } -#[test] -fn copies_new_wire_metadata() { - let store = MemoryStore::default(); - let inspect = store.clone(); - let mut backend = Backend::new(store); - let raw = - kvspace_durable::headlenpow::encode(5, 0, 0, 0, "int32", &7i32.to_le_bytes(), 4).unwrap(); - backend - .set(&[KVPair { - key: "/src/a".into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - backend.set_metadata("/src/a", true, 29).unwrap(); - backend.cp("/src/a", "/copy").unwrap(); - backend.cp_tree("/src", "/tree").unwrap(); - for key in ["/copy", "/tree/a"] { - let meta = metadata::key_for(key).unwrap(); - assert_eq!( - metadata::decode(&inspect.get(&meta).unwrap()), - Some((true, 29)) - ); - } -} - -#[test] -fn copies_pointer_slot_metadata() { - let store = MemoryStore::default(); - let inspect = store.clone(); - let mut backend = Backend::new(store); - let target = - kvspace_durable::headlenpow::encode(5, 0, 0, 0, "int32", &7i32.to_le_bytes(), 4).unwrap(); - let pointer = kvspace_durable::headlenpow::encode(5, 5, 7, 7, "int32", b"/target", 7).unwrap(); - for (key, raw) in [("/target", target), ("/link", pointer)] { - backend - .set(&[KVPair { - key: key.into(), - val: XValue::None, - raw: Some(raw), - }]) - .unwrap(); - } - backend.set_metadata("/target", true, 42).unwrap(); - backend.set_metadata("/link", true, 17).unwrap(); - backend.cp("/link", "/copy").unwrap(); - assert_eq!(backend.get_metadata("/copy").unwrap(), (true, 42)); - let key = metadata::key_for("/copy").unwrap(); - assert_eq!( - metadata::decode(&inspect.get(&key).unwrap()), - Some((true, 17)) - ); -} - #[test] fn new_wire_directories_and_extensions_use_physical_keys() { let store = MemoryStore::default(); let inspect = store.clone(); let mut backend = Backend::new(store); backend.mkindex("/lib/", 32).unwrap(); - let dir = kvspace_durable::headlenpow::encode(5, 0, 0, 0, "rwfunc", &[], 0).unwrap(); + let dir = kvspace_durable::headlenpow::to_wire(0, 0, &[]).unwrap(); let slot = - kvspace_durable::headlenpow::encode(5, 1, 5, 5, "rwir", &[0, 0, 0, 0, 0], 5).unwrap(); - let map = kvspace_durable::headlenpow::encode(5, 0, 0, 0, "[int64]·int64", &[], 0).unwrap(); + kvspace_durable::headlenpow::to_wire(1, 0, &[0, 0, 0, 0, 0]).unwrap(); + let map = kvspace_durable::headlenpow::to_wire(0, 0, &[]).unwrap(); let scalar = - kvspace_durable::headlenpow::encode(5, 0, 0, 0, "int64", &7i64.to_le_bytes(), 8).unwrap(); + kvspace_durable::headlenpow::to_wire(0, 0, &7i64.to_le_bytes()).unwrap(); for (key, raw) in [ ("/lib/f/", dir), ("/lib/f/[1,0]", slot.clone()), @@ -247,7 +134,8 @@ fn new_wire_directories_and_extensions_use_physical_keys() { assert!(inspect .0 .borrow() - .values() - .all(|raw| kvspace_durable::headlenpow::decode(raw).is_some())); + .iter() + .filter(|(key, _)| !key.ends_with("‥langtype")) + .all(|(_, raw)| kvspace_durable::headlenpow::from_wire(raw).is_some())); assert!(inspect.get("/m·").is_none()); } diff --git a/tests/read_cache.rs b/tests/read_cache.rs index c46a207..5169c02 100644 --- a/tests/read_cache.rs +++ b/tests/read_cache.rs @@ -10,29 +10,39 @@ use kvspace_durable::ffi::*; -/// 64KB 级 char/utf8 值:[n]char/utf8,body 全 0(仅用于内存观测)。 +/// 64KB 级 char/utf8 值:storetype 1 变长、body 全 0(仅用于内存观测)。 +/// wire = [len:u48 @0-5][storetype:u8 @6][ptr:u8 @7][body],body 偏移恒 8。 fn xvalue_pad(n: usize) -> Vec { - let mut v = vec![0u8; 64 + n]; - v[0] = 6; // headlen = 64 - v[1] = 1; // flags: array/storetype 1 - v[2..10].copy_from_slice(&(n as u64).to_le_bytes()); - v[10..18].copy_from_slice(&(n as u64).to_le_bytes()); - let lt = format!("[{n}]char/utf8"); - v[18..18 + lt.len()].copy_from_slice(lt.as_bytes()); + let mut v = vec![0u8; 8 + n]; + v[0..6].copy_from_slice(&(n as u64).to_le_bytes()[..6]); + v[6] = 1; // storetype: slack + v[7] = 0; // ptr: inline v } fn xvalue_i64(n: i64) -> Vec { - let mut v = vec![0u8; 40]; - v[0] = 5; // headlen = 32 - v[18..23].copy_from_slice(b"int64"); - v[32..40].copy_from_slice(&n.to_le_bytes()); + let mut v = vec![0u8; 16]; + v[0..6].copy_from_slice(&8u64.to_le_bytes()[..6]); + v[6] = 0; // storetype: short + v[7] = 0; // ptr: inline + v[8..16].copy_from_slice(&n.to_le_bytes()); v } unsafe fn set(h: *mut Handle, key: &str, n: i64) { let mut k = key.as_bytes().to_vec(); k.push(0); + /* 值用裸 wire 写(测试自构 head),但 watch/read 走 langtype attr key 判定类型, + 故写值前先落明文 "int64" attr key——对齐生产侧 set 的「attr 先落、x 后落」。 */ + let mut lt = key.as_bytes().to_vec(); + lt.extend_from_slice("\u{2025}langtype".as_bytes()); + lt.push(0); + let lt_v = xvalue_utf8("int64"); + assert_eq!( + kvspaceSetValue(h, lt.as_ptr() as *const _, lt_v.as_ptr(), lt_v.len() as u32, std::ptr::null_mut(), 0), + 0, + "set langtype {key}" + ); let v = xvalue_i64(n); let mut err = [0i8; 128]; let rc = kvspaceSetValue( @@ -40,8 +50,6 @@ unsafe fn set(h: *mut Handle, key: &str, n: i64) { k.as_ptr() as *const _, v.as_ptr(), v.len() as u32, - 0, - 0, err.as_mut_ptr(), err.len() as u32, ); @@ -58,9 +66,9 @@ unsafe fn get_ptr(h: *mut Handle, key: &str) -> (*mut u8, u32) { } unsafe fn read_i64(p: *const u8, l: u32) -> i64 { - assert!(l >= 40, "len={l}"); + assert!(l >= 16, "len={l}"); let mut b = [0u8; 8]; - b.copy_from_slice(std::slice::from_raw_parts(p.add(32), 8)); + b.copy_from_slice(std::slice::from_raw_parts(p.add(8), 8)); i64::from_le_bytes(b) } @@ -159,7 +167,7 @@ fn read_cache_lifetime_and_invalidation() { let mut pp: *mut u8 = std::ptr::null_mut(); let mut pl: u32 = 0; assert_eq!( - kvspaceGetPart(h, ckey("/a").as_ptr() as *const _, 32, 8, &mut pp, &mut pl), + kvspaceGetPart(h, ckey("/a").as_ptr() as *const _, 8, 8, &mut pp, &mut pl), 0 ); let mut b = [0u8; 8]; @@ -200,8 +208,6 @@ fn memory_growth() { k.as_ptr() as *const _, v.as_ptr(), v.len() as u32, - 0, - 0, std::ptr::null_mut(), 0 ), @@ -212,7 +218,7 @@ fn memory_growth() { for i in 0..N { let key = format!("/k{i}"); let (p, l) = get_ptr(h, &key); - assert!(!p.is_null() && l == 64 + 64 * 1024); + assert!(!p.is_null() && l == 8 + 64 * 1024); if i + 1 == 500 || i + 1 == 1000 || i + 1 == N { eprintln!( "rss after {} reads: {} KB (+{})", @@ -258,7 +264,7 @@ fn same_key_two_reads_and_watch_retire() { &mut wl, ); assert_eq!(rc, 0); - assert!(!wp.is_null() && wl >= 40); + assert!(!wp.is_null() && wl >= 16); assert_eq!(read_i64(p1, l1), 7, "old view must stay valid after watch"); assert_eq!(read_i64(wp, wl), 7); @@ -269,20 +275,17 @@ fn same_key_two_reads_and_watch_retire() { fn xvalue_utf8(s: &str) -> Vec { let n = s.len(); - let mut v = vec![0u8; 64 + n]; - v[0] = 6; - v[1] = 1; - v[2..10].copy_from_slice(&(n as u64).to_le_bytes()); - v[10..18].copy_from_slice(&(n as u64).to_le_bytes()); - let lt = format!("[{n}]char/utf8"); - v[18..18 + lt.len()].copy_from_slice(lt.as_bytes()); - v[64..].copy_from_slice(s.as_bytes()); + let mut v = vec![0u8; 8 + n]; + v[0..6].copy_from_slice(&(n as u64).to_le_bytes()[..6]); + v[6] = 1; // storetype: slack + v[7] = 0; // ptr: inline + v[8..].copy_from_slice(s.as_bytes()); v } unsafe fn read_str(p: *const u8, l: u32) -> String { - assert!(l >= 64); - String::from_utf8_lossy(std::slice::from_raw_parts(p.add(64), (l - 64) as usize)).into_owned() + assert!(l >= 8); + String::from_utf8_lossy(std::slice::from_raw_parts(p.add(8), (l - 8) as usize)).into_owned() } unsafe fn set_str(h: *mut Handle, key: &str, s: &str) { @@ -295,8 +298,6 @@ unsafe fn set_str(h: *mut Handle, key: &str, s: &str) { k.as_ptr() as *const _, v.as_ptr(), v.len() as u32, - 0, - 0, std::ptr::null_mut(), 0 ),