diff --git a/src/backend.rs b/src/backend.rs index 2820977..3547bc5 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -11,9 +11,8 @@ use crate::kvspace_common::{ use crate::r#const::*; use crate::store::KVStore; use crate::xvalue::{ - from_wire, from_wire_head, is_ptr, ptr_target, xvalue_langtype, XValue, REF_EXT, REF_PTR, + from_wire, from_wire_head, is_ptr, ptr_target, XValue, REF_EXT, REF_PTR, }; -use crate::xvalue_byte::char_byte_to_wire; pub struct Backend { store: S, @@ -24,35 +23,6 @@ impl Backend { Backend { store } } - /// langtype attr key 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.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); - } - } - // ── 目录与路径工具 ────────────────────────────────────────────── fn is_dir(path: &str) -> bool { @@ -119,8 +89,7 @@ impl Backend { let vals = self.store.get_many(&refs); for (i, data) in vals.iter().enumerate() { if let Some(data) = data { - let lt = self.langtype_of(&prefixes[i]); - let v = from_wire(data, <); + let v = from_wire(data); if is_ptr(&v) { let target = ptr_target(&v); if i + 1 < parts.len() { @@ -185,15 +154,14 @@ impl Backend { match self.store.get(dir) { None => XValue::None, Some(data) => { - let langtype = self.langtype_of(dir); - from_wire(&data, &langtype) + from_wire(&data) } } } fn prefix_ext(&self, prefix: &str) -> String { if let Some(data) = self.store.get(prefix) { - let head = from_wire_head(&data, &self.langtype_of(prefix)); + let head = from_wire_head(&data); if head.r#ref == REF_EXT { return String::from_utf8_lossy(head.body(&data)).into_owned(); } @@ -229,10 +197,9 @@ impl KVSpace for Backend { // ext-index 仅是主存未命中键的回退目标:延迟到确有缺失键才查 prefix_ext—— // 否则每次读都为它多付一次 GET(frame 槽恒命中,曾是最热的一条无谓往返)。 let mut missing: Vec = Vec::new(); - for (idx, (i, full)) in full_keys.iter().enumerate() { + for (idx, (i, _full)) in full_keys.iter().enumerate() { if let Some(data) = &full_vals[idx] { - let langtype = self.langtype_of(full); - results[*i] = Some(from_wire(data, &langtype)); + results[*i] = Some(from_wire(data)); } else { missing.push(*i); } @@ -253,10 +220,9 @@ 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, t)) in ext_keys.iter().enumerate() { + for (idx, (i, _t)) in ext_keys.iter().enumerate() { results[*i] = Some(if let Some(data) = &ext_vals[idx] { - let langtype = self.langtype_of(t); - from_wire(data, &langtype) + from_wire(data) } else { XValue::None }); @@ -360,16 +326,11 @@ impl KVSpace for Backend { 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.len as usize + crate::headlenpow::PREFIX != raw.len() { + let attr = crate::headlenpow::attr_len(&raw, head.len as usize) + .ok_or_else(|| format!("invalid XValue at {key}"))?; + if head.len as usize + crate::headlenpow::PREFIX + attr as usize != raw.len() { return Err(format!("invalid XValue at {key}")); } - // 固定头无类型: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)?; @@ -393,8 +354,7 @@ impl KVSpace for Backend { return Err(format!("{}: {}", ERR_EXT_WRITE, key)); } } - // 写序固定:attr 先落,x 后落(x 写 = 提交点,x 存在则类型必完整)。 - self.set_langtype(&key, &langtype); + // 单 key 整体写(类型在值 attr 区):天然原子,无 attr key、无写序。 self.store.set(&key, &raw); } Ok(()) @@ -482,14 +442,9 @@ impl KVSpace for Backend { if Self::is_dir(&resolved) { let link_key = strip_dir_suf(&resolved); - self.store.del(&[ - &Self::langtype_key(link_key), - &Self::langtype_key(&resolved), - link_key, - &resolved, - ]); + self.store.del(&[link_key, &resolved]); } else { - self.store.del(&[&Self::langtype_key(&resolved), &resolved]); + self.store.del(&[&resolved]); } } Ok(()) @@ -504,7 +459,7 @@ impl KVSpace for Backend { link_key = strip_dir_suf(prefix); } if let Some(data) = self.store.get(link_key) { - let head = from_wire_head(&data, &self.langtype_of(link_key)); + let head = from_wire_head(&data); if head.is_ptr() { return self.del(&[prefix.to_string()]); } @@ -525,10 +480,10 @@ impl KVSpace for Backend { .filter(|key| !crate::r#const::is_reserved(key)) .collect(); - self.store.del(&[&Self::langtype_key(&resolved), &resolved]); - for k in &keys { - self.store.del(&[&Self::langtype_key(k), k]); - } + let mut del_keys: Vec<&str> = Vec::with_capacity(keys.len() + 1); + del_keys.push(&resolved); + del_keys.extend(keys.iter().map(String::as_str)); + self.store.del(&del_keys); Ok(()) } @@ -543,7 +498,7 @@ impl KVSpace for Backend { return Err(format!("Cp: source not found: {}", src)); } // attr key 与值同址:读解析后的物理 src 路径的类型。 - let v = from_wire(&raw, &self.langtype_of(&self.resolve_parent(src))); + let v = from_wire(&raw); self.set(&[KVPair { key: dst.to_string(), val: v, @@ -657,11 +612,10 @@ impl KVSpace for Backend { } let _ = capacity; let resolved = self.resolve_path(path); - // 固定头空目录 marker:storetype 0、ptr 0、空 body;类型 "lib" 走 attr key。 - let value = crate::headlenpow::to_wire(0, 0, &[]) + // 固定头空目录 marker:storetype 0、ptr 0、空 body;类型 "lib" 在值 attr 区。 + let value = crate::headlenpow::to_wire(0, 0, &[], "lib") .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(()) @@ -683,10 +637,9 @@ impl KVSpace for Backend { 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()) + // 扩展定位:storetype 3(ST_EXT)+ ptr 2(PTR_EXT)+ 定位串 body + 类型 attr 区。 + let value = crate::headlenpow::to_wire(3, 2, ext_path.as_bytes(), &langtype) .ok_or_else(|| format!("ExtIndex locator invalid: {ext_path}"))?; - self.set_langtype(&resolved, &langtype); self.store.set(&resolved, &value); Ok(()) } @@ -699,14 +652,14 @@ impl KVSpace for Backend { link_key = strip_dir_suf(&resolved); } if let Some(data) = self.store.get(link_key) { - let head = from_wire_head(&data, &self.langtype_of(link_key)); + let head = from_wire_head(&data); if head.is_ptr() { - self.store.del(&[&Self::langtype_key(link_key), link_key]); + self.store.del(&[link_key]); return Ok(()); } } - self.store.del(&[&Self::langtype_key(&resolved), &resolved]); + self.store.del(&[&resolved]); Ok(()) } diff --git a/src/ffi.rs b/src/ffi.rs index 42ee591..7dedc6c 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -63,8 +63,7 @@ impl Handle { fn flush(&mut self) -> Result<(), String> { if let Some(key) = self.pending_key.take() { let xv = std::mem::take(&mut self.write_buf); - let langtype = self.kv.langtype_of(&key); - let val = from_wire(&xv, &langtype); + let val = from_wire(&xv); self.kv.set(&[KVPair { key: key.clone(), val, @@ -177,13 +176,13 @@ 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。 +/// 固定头预留:head + attr 区(langtype 尾部)一次写,body 偏移恒 = PREFIX。 fn build_xvalue_head( r#ref: u8, storetype: u8, body_len: usize, body_cap: usize, + langtype: &str, ) -> Option> { if body_len > body_cap { return None; @@ -194,7 +193,7 @@ fn build_xvalue_head( (2, 3) => (3, 2), _ => return None, }; - crate::headlenpow::reserve(st, ptr, body_len) + crate::headlenpow::reserve(st, ptr, body_len, langtype) } // ── 内部助手 ───────────────────────────────────────────────────────── @@ -423,8 +422,7 @@ pub extern "C" fn kvspaceSetValue( } let raw = raw.to_vec(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let langtype = hd.kv.langtype_of(&key); - let val = from_wire(&raw, &langtype); + let val = from_wire(&raw); hd.kv.set(&[KVPair { key: key.clone(), val, @@ -566,14 +564,8 @@ pub extern "C" fn kvspaceGetHead( if value.is_empty() { return 1; } - /* 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); + /* langtype 在值 attr 区(单 key 整体);无类型 = 空 attr。 */ + let head = crate::xvalue::from_wire_head(&value); if head.headlen == 0 { return 1; } @@ -607,14 +599,14 @@ pub extern "C" fn kvspaceWriteInPlace( write_err(err, err_cap, "kvspace: write-in-place on missing key"); return 1; } - let langtype = hd.kv.langtype_of(&key); - let head = from_wire_head(&existing, &langtype); + let head = from_wire_head(&existing); let head_len = head.head_len() as usize; + let blen = head.body_len.max(0) as usize; + let attr = crate::headlenpow::attr_len(&existing, blen).unwrap_or(0) as usize; if head.headlen == 0 || head.body_len < 0 || head.body_len as u32 != body_len - || head.body_cap > usize::MAX as u64 - || head_len.checked_add(head.body_cap as usize) != Some(existing.len()) + || head_len.checked_add(blen).and_then(|x| x.checked_add(attr)) != Some(existing.len()) { write_err(err, err_cap, "kvspace: write-in-place body_len mismatch"); return 1; @@ -627,7 +619,7 @@ pub extern "C" fn kvspaceWriteInPlace( } /// 新位置写:按 (ref, storetype, body_len, body_cap) 攒好 head 到 write_buf、置 pending,返回 body 偏移指针。 -/// kvspace 层不写 attr key——`x‥langtype` 归属上层:需要类型时调用方自写 attr key(attr 先落、x 后落)。 +/// langtype(可为空)随 head + attr 区整体写入——单 key 一次写。 #[no_mangle] pub extern "C" fn kvspaceWriteNewPlace( h: *mut Handle, @@ -636,6 +628,7 @@ pub extern "C" fn kvspaceWriteNewPlace( storetype: u8, body_len: u32, body_cap: u64, + langtype: *const c_char, body: *mut *mut u8, err: *mut c_char, err_cap: u32, @@ -649,11 +642,12 @@ 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, body_len as usize, cap) else { + let Some(xv) = build_xvalue_head(r#ref, storetype, body_len as usize, cap, lt) else { write_err(err, err_cap, "invalid XValue geometry"); return 1; }; @@ -951,12 +945,10 @@ pub extern "C" fn kvspaceWatch( return 1; } let key = unsafe { cstr(key) }.to_string(); - /* 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, - ); + /* watch 目标与 key 值同型(相等判据):langtype 在值 attr 区。 */ + let target_v = from_wire(unsafe { + std::slice::from_raw_parts(target, target_len as usize) + }); 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 是冷路径) */ diff --git a/src/fs/kvspace.rs b/src/fs/kvspace.rs index 4ff635b..b348bd0 100644 --- a/src/fs/kvspace.rs +++ b/src/fs/kvspace.rs @@ -15,7 +15,6 @@ 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__"; @@ -95,34 +94,6 @@ 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 @@ -292,7 +263,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 = from_wire(&data, &self.langtype_of(&cur)); + let v = from_wire(&data); if is_ptr(&v) { let target = ptr_target(&v); if i + 1 < parts.len() { @@ -307,7 +278,7 @@ impl FsKVSpace { fn prefix_ext(&self, prefix: &str) -> String { if let Some(data) = self.read_leaf(prefix) { - let head = from_wire_head(&data, &self.langtype_of(prefix)); + let head = from_wire_head(&data); if head.r#ref == crate::xvalue::REF_EXT { return String::from_utf8_lossy(head.body(&data)).into_owned(); } @@ -391,16 +362,16 @@ impl KVSpace for FsKVSpace { if Self::is_dir_key(&full) { return self .read_leaf(&full) - .map(|raw| from_wire(&raw, &self.langtype_of(&full))) + .map(|raw| from_wire(&raw)) .unwrap_or(XValue::None); } if let Some(data) = self.read_leaf(&full) { - return from_wire(&data, &self.langtype_of(&full)); + return from_wire(&data); } if !ext_t.is_empty() { let target = join_path(&ext_t, k); if let Some(data) = self.read_leaf(&target) { - return from_wire(&data, &self.langtype_of(&target)); + return from_wire(&data); } } XValue::None @@ -496,16 +467,11 @@ impl KVSpace for FsKVSpace { 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.len as usize + crate::headlenpow::PREFIX != raw.len() { + let attr = crate::headlenpow::attr_len(&raw, head.len as usize) + .ok_or_else(|| format!("invalid XValue at {key}"))?; + if head.len as usize + crate::headlenpow::PREFIX + attr as usize != raw.len() { return Err(format!("invalid XValue at {key}")); } - // 固定头无类型: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)?; @@ -529,8 +495,7 @@ impl KVSpace for FsKVSpace { return Err(format!("{}: {}", ERR_EXT_WRITE, key)); } } - // 写序固定:attr 先落,x 后落(x 写 = 提交点,x 存在则类型必完整)。 - self.set_langtype(&key, &langtype); + // 单 key 整体写(类型在值 attr 区):天然原子,无 attr key、无写序。 self.ensure_dir(&parent); if Self::is_dir_key(&key) { self.ensure_dir(&key); @@ -582,7 +547,6 @@ impl KVSpace for FsKVSpace { 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); } Ok(()) @@ -603,7 +567,7 @@ impl KVSpace for FsKVSpace { &resolved }; if let Some(data) = self.read_leaf(link_key) { - if from_wire_head(&data, &self.langtype_of(link_key)).is_ptr() { + if from_wire_head(&data).is_ptr() { return self.del(&[resolved]); } } @@ -620,8 +584,6 @@ impl KVSpace for FsKVSpace { fs::remove_file(&node).map_err(|e| format!("DelTree {key}: {e}"))?; } } - // base 值(非目录形态)的 langtype attr key 是 base 的兄弟文件,单独清除。 - self.remove_leaf(&Self::langtype_key(base)); Ok(()) } @@ -634,7 +596,7 @@ impl KVSpace for FsKVSpace { return Err(format!("Cp: source not found: {}", src)); } // attr key 与值同址:读解析后的物理 src 路径的类型。 - let v = from_wire(&raw, &self.langtype_of(&self.resolve_parent(src))); + let v = from_wire(&raw); self.set(&[KVPair { key: dst.to_string(), val: v, @@ -682,10 +644,6 @@ impl KVSpace for FsKVSpace { Self::copy_recursive(&smem, &dmem) .map_err(|e| format!("CpTree {}→{}: {}", src, dst, e))?; } - // 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); @@ -722,10 +680,6 @@ 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)); @@ -764,10 +718,9 @@ impl KVSpace for FsKVSpace { let resolved = self.resolve_path(path); self.ensure_dir(&resolved); if self.read_leaf(&resolved).is_none() { - // 固定头空目录 marker:storetype 0、ptr 0、空 body;类型 "lib" 走 attr key。 - let value = crate::headlenpow::to_wire(0, 0, &[]) + // 固定头空目录 marker:storetype 0、ptr 0、空 body;类型 "lib" 在值 attr 区。 + let value = crate::headlenpow::to_wire(0, 0, &[], "lib") .ok_or_else(|| format!("Mkindex: invalid directory {resolved}"))?; - self.set_langtype(&resolved, "lib"); self.write_leaf(&resolved, &value); } Ok(()) @@ -788,10 +741,9 @@ impl KVSpace for FsKVSpace { 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()) + // 扩展定位:storetype 3(ST_EXT)+ ptr 2(PTR_EXT)+ 定位串 body + 类型 attr 区。 + let value = crate::headlenpow::to_wire(3, 2, ext_path.as_bytes(), &langtype) .ok_or_else(|| format!("ExtIndex locator invalid: {ext_path}"))?; - self.set_langtype(&resolved, &langtype); self.ensure_dir(&resolved); self.write_leaf(&resolved, &value); Ok(()) @@ -799,7 +751,6 @@ 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); Ok(()) } diff --git a/src/headlenpow.rs b/src/headlenpow.rs index 7657ce7..a7b9c27 100644 --- a/src/headlenpow.rs +++ b/src/headlenpow.rs @@ -49,35 +49,90 @@ pub fn from_wire(data: &[u8]) -> Option> { }) } -pub fn to_wire(storetype: u8, ptr: u8, body: &[u8]) -> Option> { +/// attr 区(#571):body 后恒跟 [attr_len:u16 LE][records],record = [tag:u8][len:u16][data], +/// tag 1 = langtype,2+ 预留。attr_len 含自身 2 字节;空 attr = [2]。total = 8 + len + attr_len。 +pub const ATTR_TAG_LANGTYPE: u8 = 1; + +/// 构造 attr 区字节(含 attr_len 前缀);langtype 空 → 空 attr `[2,0]`。 +pub fn attr_bytes(langtype: &str) -> Option> { + if langtype.is_empty() { + return Some(vec![2, 0]); + } + let l = langtype.len(); + let alen = 2usize.checked_add(3)?.checked_add(l)?; + let alen16 = u16::try_from(alen).ok()?; + let mut v = Vec::with_capacity(5 + l); + v.push((alen16 & 0xff) as u8); + v.push((alen16 >> 8) as u8); + v.push(ATTR_TAG_LANGTYPE); + v.push((l & 0xff) as u8); + v.push((l >> 8) as u8); + v.extend_from_slice(langtype.as_bytes()); + Some(v) +} + +/// 读 attr 总长(含自身 2 字节;空 attr = 2)。坏 wire → None。 +#[inline] +pub fn attr_len(data: &[u8], body_len: usize) -> Option { + let p = data.get(PREFIX.checked_add(body_len)?..PREFIX + body_len + 2)?; + Some(u16::from_le_bytes([p[0], p[1]])) +} + +/// 从 wire attr 区取 tag=1(langtype)记录 data;无/坏 attr → None。 +pub fn langtype_from_wire(data: &[u8], body_len: usize) -> Option<&[u8]> { + let alen = usize::from(attr_len(data, body_len)?); + if alen < 2 { + return None; + } + let mut p = PREFIX + body_len + 2; + let end = PREFIX + body_len + alen; + while p + 3 <= end { + let tag = data[p]; + let rlen = usize::from(u16::from_le_bytes([data[p + 1], data[p + 2]])); + if p + 3 + rlen > end { + return None; + } + if tag == ATTR_TAG_LANGTYPE { + return Some(&data[p + 3..p + 3 + rlen]); + } + p += 3 + rlen; + } + None +} + +pub fn to_wire(storetype: u8, ptr: u8, body: &[u8], langtype: &str) -> Option> { if storetype > 3 || ptr > 2 { return None; } + let attr = attr_bytes(langtype)?; let len = u64::try_from(body.len()).ok()?; - let total = PREFIX.checked_add(body.len())?; + let total = PREFIX.checked_add(body.len())?.checked_add(attr.len())?; let mut data = Vec::new(); data.try_reserve_exact(total).ok()?; data.resize(total, 0); write_len(&mut data, len); data[6] = storetype; data[7] = ptr; - data[PREFIX..].copy_from_slice(body); + data[PREFIX..PREFIX + body.len()].copy_from_slice(body); + data[PREFIX + body.len()..].copy_from_slice(&attr); Some(data) } -/// 预留:产 len 字节零 body 的固定头(后由调用方填充)。 -pub fn reserve(storetype: u8, ptr: u8, content: usize) -> Option> { +/// 预留:产 len 字节零 body 的固定头 + attr 区(后由调用方填充 body)。 +pub fn reserve(storetype: u8, ptr: u8, content: usize, langtype: &str) -> Option> { if storetype > 3 || ptr > 2 { return None; } + let attr = attr_bytes(langtype)?; let len = u64::try_from(content).ok()?; - let total = PREFIX.checked_add(content)?; + let total = PREFIX.checked_add(content)?.checked_add(attr.len())?; let mut data = Vec::new(); data.try_reserve_exact(total).ok()?; data.resize(total, 0); write_len(&mut data, len); data[6] = storetype; data[7] = ptr; + data[PREFIX + content..].copy_from_slice(&attr); Some(data) } @@ -87,53 +142,58 @@ mod tests { #[test] fn fixtures() { - let scalar = to_wire(0, 0, &42i64.to_le_bytes()).unwrap(); - assert_eq!(scalar.len(), 16); + let scalar = to_wire(0, 0, &42i64.to_le_bytes(), "int64").unwrap(); + assert_eq!(scalar.len(), 26); /* 8 + 8 body + 10 attr */ 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()); + assert_eq!(langtype_from_wire(&scalar, 8).unwrap(), b"int64"); - let ptr = to_wire(1, 1, b"/varx").unwrap(); + let ptr = to_wire(1, 1, b"/varx", "int64").unwrap(); assert_eq!(from_wire(&ptr).unwrap().ptr, 1); assert_eq!(from_wire(&ptr).unwrap().body, b"/varx"); + assert_eq!(langtype_from_wire(&ptr, 5).unwrap(), b"int64"); - let ext = to_wire(3, 2, b"s3://").unwrap(); + 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); + assert_eq!(attr_len(&ext, 5).unwrap(), 2); /* 空 attr */ - let text = to_wire(1, 0, "你😀".as_bytes()).unwrap(); + let text = to_wire(1, 0, "你😀".as_bytes(), "char/utf8").unwrap(); assert_eq!(from_wire(&text).unwrap().len, 7); /* 你(3B)+😀(4B) */ + assert_eq!(langtype_from_wire(&text, 7).unwrap(), b"char/utf8"); } #[test] fn malformed() { - 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(); + 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(); + 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(); + 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, 0, 3).unwrap(); + let mut v = reserve(1, 0, 3, "char/utf8").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"); + assert_eq!(langtype_from_wire(&v, 3).unwrap(), b"char/utf8"); } #[test] fn u48_roundtrip() { for len in [0u64, 1, 0xffffff, 0xffffffff, 0xffffffffffff] { - let mut v = reserve(1, 0, 0).unwrap(); + 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 2be22a9..6b67a3b 100644 --- a/src/kvspace.rs +++ b/src/kvspace.rs @@ -42,14 +42,15 @@ pub trait KVSpace { fn get(&mut self, prefix: &str, keys: &[String], resolve: bool) -> Vec; /// 单点读原始字节(不经 wire 转换)。无值返回空。 fn get_raw(&mut self, key: &str) -> Vec; - /// 读 x‥langtype attr key 的明文类型串:attr key 值是字符串 XValue(char/utf8), - /// 解出明文;无/空/解析失败 → 空串。 + /// 读 key 值的 attr 区明文类型串(#571 单 key 整体);无/空/解析失败 → 空串。 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(), - } + let raw = self.get_raw(key); + let Some(h) = crate::headlenpow::from_wire(&raw) else { + return String::new(); + }; + crate::headlenpow::langtype_from_wire(&raw, h.len as usize) + .map(|b| String::from_utf8_lossy(b).into_owned()) + .unwrap_or_default() } /// 单点写:Set 写完整 XValue,并维护目录索引;只解析父路径 link,整键不穿透。 fn set(&mut self, pairs: &[KVPair]) -> Result<(), String>; diff --git a/src/kvspace_common.rs b/src/kvspace_common.rs index 20f7e0e..d3116b2 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_langtype, XValue}; +use crate::xvalue::{body_bytes, is_none, plain, XValue}; /// JoinPath 拼接父子路径。 pub fn join_path(parent: &str, child: &str) -> String { @@ -137,7 +137,7 @@ pub fn validate_ptr( // 是同一个 kind 的不同形状,语义兼容。整串相等曾把「定长实参传给变长形参」 // 这类合法场景误判为类型不符而拒绝写入。 let actual = norm_langtype( - &crate::xvalue::from_wire_head(&v.to_wire(), &xvalue_langtype(&v)).langtype, + &crate::xvalue::from_wire_head(&v.to_wire()).langtype, ); let expect = norm_langtype(target_kindexpr); if actual != expect { diff --git a/src/xvalue.rs b/src/xvalue.rs index 2014170..ba48e33 100644 --- a/src/xvalue.rs +++ b/src/xvalue.rs @@ -394,10 +394,23 @@ 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) } -/// 指针 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() +/// 指针 wire 转换:固定头恒 ST_SLACK + PTR_PTR,body = 目标 key 路径。 +/// 目标 kindexpr 拼入 attr 区(单 key 整体)。 +pub fn xvalue_to_wire_ptr(target_kindexpr: &str, raw: &[u8]) -> Vec { + crate::headlenpow::to_wire(ST_SLACK, REF_PTR, raw, target_kindexpr).unwrap_or_default() +} + +/// kind + dims → 完整 langtype 串(`[d0,d1]kind`;dims 空 = 裸 kind)。 +fn kindexpr_of(kind: &str, dims: &[i32]) -> String { + if dims.is_empty() { + kind.to_string() + } else { + format!( + "[{}]{}", + dims.iter().map(i32::to_string).collect::>().join(","), + kind + ) + } } /// array_len → dims:char/* 恒一维(含空串/单字符);其余标量(≤1)=0 维、多元素=1 维。 @@ -412,7 +425,7 @@ fn array_to_header(kind: &str, array_len: i32) -> Vec { } /// 固定 8 字节 head 落盘:storetype 按 kind/dims 判定,ptr 按 ref 判定,body = raw 原样。 -/// langtype 不进 head(调用方写 x‥langtype attr key)。 +/// langtype 拼入 attr 区(单 key 整体;None 无类型 = 空 attr)。 /// /// storetype 判定(对齐 kvspace 三正交轴与 C codec): /// - ref==1(指针)→ ST_SLACK;ref==2(@ext)→ ST_EXT; @@ -441,11 +454,16 @@ pub fn to_wire_head(kind: &str, r#ref: i32, dims: &[i32], raw: &[u8]) -> Vec 2 => REF_EXT, _ => REF_INLINE, }; - crate::headlenpow::to_wire(storetype, ptr, raw).unwrap_or_default() + let langtype = if kind == "None" { + String::new() + } else { + kindexpr_of(kind, dims) + }; + crate::headlenpow::to_wire(storetype, ptr, raw, &langtype).unwrap_or_default() } -/// 从固定 8 字节 head + 侧写 langtype 解析 XValueHead(langtype 从 x‥langtype attr key 读)。 -pub fn from_wire_head(data: &[u8], langtype: &str) -> XValueHead { +/// 从完整 XValue 字节(固定 head + body + attr 区)解析 XValueHead(langtype 从 attr 取)。 +pub fn from_wire_head(data: &[u8]) -> XValueHead { let Some(h) = crate::headlenpow::from_wire(data) else { return XValueHead::default(); }; @@ -456,8 +474,11 @@ pub fn from_wire_head(data: &[u8], langtype: &str) -> XValueHead { if body_len < 0 { return XValueHead::default(); } + let langtype = crate::headlenpow::langtype_from_wire(data, h.len as usize) + .map(|b| String::from_utf8_lossy(b).into_owned()) + .unwrap_or_default(); let dims = if h.storetype == 2 || (h.storetype == 1 && langtype.starts_with('[')) { - parse_kindexpr(langtype).0 + parse_kindexpr(&langtype).0 } else { Vec::new() }; @@ -471,17 +492,17 @@ pub fn from_wire_head(data: &[u8], langtype: &str) -> XValueHead { REF_INLINE }, storetype: h.storetype, - langtype: langtype.to_string(), + langtype, phys_dims: dims, body_len, body_cap: h.len as u64, } } -/// 从完整 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 缺失 = 无类型)。 +/// 从完整 XValue 字节(固定 head + body + attr)组装 XValue。 +pub fn from_wire(data: &[u8]) -> XValue { + let h = from_wire_head(data); + // 空 langtype 表示 None(无类型 = 空 attr)。 if h.langtype.is_empty() && !h.is_ptr() && h.r#ref() != REF_EXT as i32 { return XValue::None; } @@ -578,12 +599,7 @@ mod tests { fn roundtrip(v: &XValue) { let bytes = v.to_wire(); - assert_eq!( - *v, - from_wire(&bytes, &xvalue_langtype(v)), - "roundtrip {:?}", - v - ); + assert_eq!(*v, from_wire(&bytes), "roundtrip {:?}", v); } #[test] @@ -632,9 +648,9 @@ mod tests { #[test] fn directory_and_extension_wire() { - // 目录值:SHORT、inline、空 body,固定 8 字节 head。 + // 目录值:SHORT、inline、空 body,固定 8 字节 head + attr(langtype "lib")。 let dir = to_wire_head("lib", 0, &[], &[]); - assert_eq!(dir.len(), 8); + assert_eq!(dir.len(), 8 + 2 + 3 + 3); let head = crate::headlenpow::from_wire(&dir).unwrap(); assert_eq!( (head.storetype, head.ptr, head.len, head.body.len()), @@ -646,14 +662,14 @@ mod tests { locator: "/lib/f/".into(), }) .to_wire(); - assert_eq!(ext.len(), 8 + "/lib/f/".len()); + assert_eq!(ext.len(), 8 + "/lib/f/".len() + 11); /* + attr(rwfunc 记录) */ let head = crate::headlenpow::from_wire(&ext).unwrap(); assert_eq!( (head.storetype, head.ptr, head.body), (ST_EXT, REF_EXT, b"/lib/f/".as_slice()) ); assert_eq!( - from_wire(&ext, "rwfunc"), + from_wire(&ext), XValue::Ext(ExtHandle { langtype: "rwfunc".into(), locator: "/lib/f/".into() @@ -664,13 +680,13 @@ mod tests { #[test] fn head_fields() { 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!(bytes.len(), 8 + 24 + 15); // 固定头 8 + 3×f64 body + attr("[3]float64") + let h = from_wire_head(&bytes); assert_eq!(h.kind(), "float64"); assert_eq!(h.dims(), vec![3]); assert_eq!(h.array_len(), 3); assert!(!h.is_ptr()); assert_eq!(h.r#ref(), 0); - assert_eq!(h.head_len() as usize + h.body_len as usize, bytes.len()); + assert_eq!(h.head_len() as usize + h.body_len as usize + 15, bytes.len()); } } diff --git a/tests/metadata_fs.rs b/tests/metadata_fs.rs index 1e03597..c0f5de4 100644 --- a/tests/metadata_fs.rs +++ b/tests/metadata_fs.rs @@ -6,10 +6,10 @@ 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::to_wire(0, 0, &[]).unwrap(); + let raw = kvspace_durable::headlenpow::to_wire(0, 0, &[], "").unwrap(); kv.set(&[KVPair { key: "/lib/f/".into(), - val: kvspace_durable::xvalue::from_wire(&raw, ""), + val: kvspace_durable::xvalue::from_wire(&raw), raw: Some(raw.clone()), }]) .unwrap(); diff --git a/tests/prefix_list.rs b/tests/prefix_list.rs index ee3f268..3749ae8 100644 --- a/tests/prefix_list.rs +++ b/tests/prefix_list.rs @@ -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::to_wire(0, 0, &[]).unwrap(); + let dir = kvspace_durable::headlenpow::to_wire(0, 0, &[], "").unwrap(); let code = - kvspace_durable::headlenpow::to_wire(1, 0, &[0, 0, 0, 0, 0, b'x']).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::to_wire(1, 1, b"/x").unwrap(); + let ptr = kvspace_durable::headlenpow::to_wire(1, 1, b"/x", "").unwrap(); assert!(kv .set(&[KVPair { key: "/vthread/1/[1]/[1,0]".into(), @@ -107,12 +107,12 @@ fn new_wire_directories_and_extensions_use_physical_keys() { let inspect = store.clone(); let mut backend = Backend::new(store); backend.mkindex("/lib/", 32).unwrap(); - let dir = kvspace_durable::headlenpow::to_wire(0, 0, &[]).unwrap(); + let dir = kvspace_durable::headlenpow::to_wire(0, 0, &[], "").unwrap(); let slot = - kvspace_durable::headlenpow::to_wire(1, 0, &[0, 0, 0, 0, 0]).unwrap(); - let map = kvspace_durable::headlenpow::to_wire(0, 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::to_wire(0, 0, &7i64.to_le_bytes()).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()), diff --git a/tests/read_cache.rs b/tests/read_cache.rs index 5169c02..ec4b4d9 100644 --- a/tests/read_cache.rs +++ b/tests/read_cache.rs @@ -10,13 +10,28 @@ use kvspace_durable::ffi::*; -/// 64KB 级 char/utf8 值:storetype 1 变长、body 全 0(仅用于内存观测)。 -/// wire = [len:u48 @0-5][storetype:u8 @6][ptr:u8 @7][body],body 偏移恒 8。 +/// attr 区:langtype 记录(#571 单 key 整体);空 langtype = 空 attr [2,0]。 +fn attr_of(lt: &str) -> Vec { + if lt.is_empty() { + return vec![2, 0]; + } + let mut v = Vec::new(); + let alen = (2u16 + 3 + lt.len() as u16); + v.extend_from_slice(&alen.to_le_bytes()); + v.push(1); // tag = langtype + v.extend_from_slice(&(lt.len() as u16).to_le_bytes()); + v.extend_from_slice(lt.as_bytes()); + v +} + +/// 64KB 级 char/utf8 值:storetype 1 变长、body 全 0(仅用于内存观测)+ 空 attr。 +/// wire = [len:u48 @0-5][storetype:u8 @6][ptr:u8 @7][body][attr],body 偏移恒 8。 fn xvalue_pad(n: usize) -> Vec { 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.extend_from_slice(&attr_of("")); v } @@ -26,23 +41,14 @@ fn xvalue_i64(n: i64) -> Vec { v[6] = 0; // storetype: short v[7] = 0; // ptr: inline v[8..16].copy_from_slice(&n.to_le_bytes()); + v.extend_from_slice(&attr_of("int64")); 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}" - ); + /* 值含 attr 区(类型随值整体写,无 attr key)。 */ let v = xvalue_i64(n); let mut err = [0i8; 128]; let rc = kvspaceSetValue( @@ -280,12 +286,19 @@ fn xvalue_utf8(s: &str) -> Vec { v[6] = 1; // storetype: slack v[7] = 0; // ptr: inline v[8..].copy_from_slice(s.as_bytes()); + v.extend_from_slice(&attr_of("")); v } unsafe fn read_str(p: *const u8, l: u32) -> String { assert!(l >= 8); - String::from_utf8_lossy(std::slice::from_raw_parts(p.add(8), (l - 8) as usize)).into_owned() + /* body = head len 字节(head 后接 attr 区,不读入)。 */ + let b = std::slice::from_raw_parts(p, 6); + let mut n = 0u64; + for i in (0..6).rev() { + n = (n << 8) | u64::from(b[i]); + } + String::from_utf8_lossy(std::slice::from_raw_parts(p.add(8), n as usize)).into_owned() } unsafe fn set_str(h: *mut Handle, key: &str, s: &str) {