From 5973767d670700a44fbeab574fb488c6be8b5435 Mon Sep 17 00:00:00 2001 From: miaobyte Date: Fri, 30 Oct 2026 14:56:02 +0800 Subject: [PATCH 1/3] =?UTF-8?q?kvspace:=20#571=20=E5=8D=95=20key=20attr=20?= =?UTF-8?q?=E5=B0=BE=EF=BC=88langtype=20=E7=A7=BB=E5=85=A5=E5=90=8C=20key?= =?UTF-8?q?=20=E5=B0=BE=E9=83=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire:[len:u48][storetype:u8][ptr:u8][body][attr],head 固定 8 字节, body 偏移 8;attr 区从 8+len 开始,[attr_len:u16][record...], record=[tag:u8][len:u16][data],tag 1=langtype(2+ 预留 ro/vid)。 - HeadNew* 增加 langtype 参数(单 key 一次写,无 x‥langtype attr key) - 新增 HeadAttrLen/HeadLangtype 读取 - codec_abi / test_codec_abi / test_xvalue_head 同步 Co-Authored-By: Claude Code --- cli/kvspace.rs | 133 ++++++++++++++++++---------------- include/kvspace/kvspace.h | 30 ++++---- include/kvspace/xvalue_head.h | 62 +++++++++++++--- src/codec_abi.c | 26 ++++--- src/frontend.c | 6 +- src/xvalue_head.c | 76 ++++++++++++++----- tests/test_codec_abi.c | 52 +++++++++---- tests/test_xvalue_head.c | 78 ++++++++++++++------ 8 files changed, 304 insertions(+), 159 deletions(-) diff --git a/cli/kvspace.rs b/cli/kvspace.rs index 3f1acae..47a33cc 100644 --- a/cli/kvspace.rs +++ b/cli/kvspace.rs @@ -127,17 +127,40 @@ extern "C" { fn kvspaceNewPtr( target_kindexpr: *const c_char, target: *const c_char, + langtype: *const c_char, + out: *mut *mut u8, + out_len: *mut u32, + ) -> c_int; + fn kvspaceNewChar( + bytes: *const u8, + len: u32, + langtype: *const c_char, + out: *mut *mut u8, + out_len: *mut u32, + ) -> c_int; + fn kvspaceNewBool( + v: u8, + langtype: *const c_char, + out: *mut *mut u8, + out_len: *mut u32, + ) -> c_int; + fn kvspaceNewInt64( + v: i64, + langtype: *const c_char, + out: *mut *mut u8, + out_len: *mut u32, + ) -> c_int; + fn kvspaceNewFloat64( + v: f64, + langtype: *const c_char, out: *mut *mut u8, out_len: *mut u32, ) -> c_int; - fn kvspaceNewChar(bytes: *const u8, len: u32, out: *mut *mut u8, out_len: *mut u32) -> c_int; - fn kvspaceNewBool(v: u8, out: *mut *mut u8, out_len: *mut u32) -> c_int; - fn kvspaceNewInt64(v: i64, out: *mut *mut u8, out_len: *mut u32) -> c_int; - fn kvspaceNewFloat64(v: f64, out: *mut *mut u8, out_len: *mut u32) -> c_int; fn kvspaceXvalueHeadNewScalar( storetype: u8, raw: *const u8, raw_len: u32, + langtype: *const c_char, out: *mut *mut u8, out_len: *mut u64, ) -> c_int; @@ -175,8 +198,8 @@ struct Value { body: Vec, } -/* head 固定 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8;wire 无 - langtype——类型/形状由 kvspaceGetHead 从 x‥langtype attr key 读。 */ +/* head 固定 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8; + langtype 作为 attr 记录 tag=1 置于 body 之尾(值内,非独立 key,见 #571)。 */ fn from_wire(kv: *mut c_void, key: &str, data: &[u8]) -> Value { let mut h = zero_head(); let ok = unsafe { kvspaceGetHead(kv, cs(key), &mut h) == 0 }; @@ -188,8 +211,12 @@ fn from_wire(kv: *mut c_void, key: &str, data: &[u8]) -> Value { let (_ltdims, kind) = parse_langtype(<); let ndim = if ok { (h.ndim.max(0) as usize).min(8) } else { 0 }; let dims: Vec = if ok { h.dims[..ndim].to_vec() } else { vec![] }; - let body = if data.len() > KVSPACE_XVALUE_HEAD_LEN { - data[KVSPACE_XVALUE_HEAD_LEN..].to_vec() + /* body 只到 head.len 为止;其后是 attr 区,不属 body。 */ + let body_len = if ok { h.body_len.max(0) as usize } else { 0 }; + let start = KVSPACE_XVALUE_HEAD_LEN; + let end = (start + body_len).min(data.len()); + let body = if end > start { + data[start..end].to_vec() } else { vec![] }; @@ -313,7 +340,7 @@ fn parse_value(raw: &str) -> (Vec, String) { Some(i) => (&rest[..i], &rest[i + 1..]), None => ("", rest), }; - if unsafe { kvspaceNewPtr(cs(k), cs(t), &mut out, &mut len) != 0 } { + if unsafe { kvspaceNewPtr(cs(k), cs(t), cs(k), &mut out, &mut len) != 0 } { fatalf(&format!("parse value failed: {:?}", raw)); } let v = take_wire(out, len); @@ -323,13 +350,14 @@ fn parse_value(raw: &str) -> (Vec, String) { if !langtype.contains('·') { fatalf("map requires key·value langtype"); } - // 空 body 的短定长头(类型在 x‥langtype attr key,由 layout/明文层写)。 + // 空 body 的短定长头(langtype 写进值内 attr 区)。 let mut len64: u64 = 0; if unsafe { kvspaceXvalueHeadNewScalar( KVSPACE_XVALUE_ST_SHORT, std::ptr::null(), 0, + cs(langtype), &mut out, &mut len64, ) != 0 @@ -342,10 +370,18 @@ fn parse_value(raw: &str) -> (Vec, String) { let split = raw.find(':').unwrap_or(raw.len()); let kind = &raw[..split]; let repr = raw[split..].trim_start_matches(':'); + let langtype = match kind { + "int" => "int64", + "float" => "float64", + "float32" => "float32", + "bool" => "bool", + _ => "[]char/utf8", // string 与默认字符串 + }; match kind { "int" => unsafe { if kvspaceNewInt64( repr.parse().unwrap_or_else(|_| fatalf(&format!("parse value failed: {:?}", raw))), + cs(langtype), &mut out, &mut len, ) != 0 @@ -356,6 +392,7 @@ fn parse_value(raw: &str) -> (Vec, String) { "float" => unsafe { if kvspaceNewFloat64( repr.parse().unwrap_or_else(|_| fatalf(&format!("parse value failed: {:?}", raw))), + cs(langtype), &mut out, &mut len, ) != 0 @@ -374,6 +411,7 @@ fn parse_value(raw: &str) -> (Vec, String) { KVSPACE_XVALUE_ST_SHORT, &f as *const f32 as *const u8, 4, + cs(langtype), &mut out, &mut len64, ) != 0 @@ -389,33 +427,39 @@ fn parse_value(raw: &str) -> (Vec, String) { _ => fatalf(&format!("bool 只接受 true/false: {:?}", raw)), }; unsafe { - if kvspaceNewBool(b as u8, &mut out, &mut len) != 0 { + if kvspaceNewBool(b as u8, cs(langtype), &mut out, &mut len) != 0 { fatalf(&format!("parse value failed: {:?}", raw)); } } } "string" => unsafe { - if kvspaceNewChar(repr.as_ptr(), repr.len() as u32, &mut out, &mut len) != 0 { + if kvspaceNewChar( + repr.as_ptr(), + repr.len() as u32, + cs(langtype), + &mut out, + &mut len, + ) != 0 + { fatalf(&format!("parse value failed: {:?}", raw)); } }, - "nil" => return (Vec::new(), String::new()), // 删除(无 attr) + "nil" => return (Vec::new(), String::new()), // 删除(无值) "index" => fatalf("use mkindex for directories"), // 无 kind 前缀或未识别前缀 → 默认按字符串处理(见 help 的 value 语法)。 _ => unsafe { - if kvspaceNewChar(raw.as_ptr(), raw.len() as u32, &mut out, &mut len) != 0 { + if kvspaceNewChar( + raw.as_ptr(), + raw.len() as u32, + cs(langtype), + &mut out, + &mut len, + ) != 0 + { fatalf(&format!("parse value failed: {:?}", raw)); } }, } - let langtype = match kind { - "int" => "int64", - "float" => "float64", - "float32" => "float32", - "bool" => "bool", - "string" => "[]char/utf8", - _ => "[]char/utf8", // 默认字符串同 string - }; (take_wire(out, len), langtype.to_string()) } @@ -428,51 +472,14 @@ fn take_wire(out: *mut u8, len: u32) -> Vec { } } -// 写主值 + x‥langtype attr key(#566:创建 key 时 attr 与主 key 同时写; -// 写序 attr 先落、x 后落——x 写=提交点)。attr 值是字符串 XValue(char/utf8, -// body=明文类型串)。langtype 空(如不约束指针)→ 只写主值。 -fn set_one(kv: *mut c_void, key: &str, val: &[u8], langtype: &str) { +// 单 key 写整值(head + body + attr 尾,langtype 已在 attr 区内,见 #571)。 +fn set_one(kv: *mut c_void, key: &str, val: &[u8]) { if val.is_empty() { let key_ptr = cs(key); let mut err = [0u8; 256]; unsafe { kvspaceDel(kv, &key_ptr, 1, err.as_mut_ptr() as *mut c_char, 256) }; return; } - if !langtype.is_empty() { - let attr = format!("{key}\u{2025}langtype"); - let mut out: *mut u8 = std::ptr::null_mut(); - let mut len: u32 = 0; - if unsafe { - kvspaceNewChar( - langtype.as_ptr(), - langtype.len() as u32, - &mut out, - &mut len, - ) != 0 - } { - fatalf("parse value failed: langtype attr key"); - } - let aw = take_wire(out, len); - let mut err = [0u8; 256]; - let rc = unsafe { - kvspaceSetValue( - kv, - cs(&attr), - aw.as_ptr(), - aw.len() as u32, - err.as_mut_ptr() as *mut c_char, - 256, - ) - }; - if rc != 0 { - let n = err.iter().position(|&b| b == 0).unwrap_or(err.len()); - fatalf(&format!( - "set {} attr key: {}", - attr, - String::from_utf8_lossy(&err[..n]) - )); - } - } let ck = cs(key); let mut err = [0u8; 256]; let rc = unsafe { @@ -625,8 +632,8 @@ fn main() { if tail.len() != 2 { fatalf("usage: kvspace set "); } - let (val, langtype) = parse_value(&tail[1]); - set_one(kv, &tail[0], &val, &langtype); + let (val, _langtype) = parse_value(&tail[1]); + set_one(kv, &tail[0], &val); } "head" => { for k in tail { diff --git a/include/kvspace/kvspace.h b/include/kvspace/kvspace.h index 79f3670..430d9c7 100644 --- a/include/kvspace/kvspace.h +++ b/include/kvspace/kvspace.h @@ -6,9 +6,9 @@ * shm://... → libkvspace-c.so.1 * 其余(redis/fs/s3)→ libkvspace_durable.so.1 * - * Wire: [len:u48 LE@0-5][storetype:u8@6][ptr:u8@7][body@8]. Head is fixed - * 8 bytes; body offset is always 8. Type/shape lives in the x‥langtype - * attr key as a plaintext langtype string. + * Wire: [len:u48 LE@0-5][storetype:u8@6][ptr:u8@7][body@8][langtype@8+len]. + * Head is fixed 8 bytes; body offset is always 8; langtype (plaintext UTF-8, + * incl. dims) is the value tail at 8+len. Single-key write — no attr key. */ #ifndef KVSPACE_H @@ -33,7 +33,7 @@ extern "C" { #define KVSPACE_STORETYPE_FIXED_LARGE 2 #define KVSPACE_STORETYPE_EXT 3 -/* XValue 元数据。ro/vid 来自 GetHead 的 attr key。 */ +/* XValue 元数据。langtype 取自 value 尾部 @8+len。 */ typedef struct { uint16_t headlen; /* head 总字节数;body 起于偏移 headlen */ uint8_t ref; /* 存储位置:见 KVSPACE_REF_* */ @@ -146,7 +146,7 @@ int kvspaceGetPart(void *h, const char *key, uint32_t offset, uint32_t len, int kvspaceSetPart(void *h, const char *key, uint32_t offset, const uint8_t *buf, uint32_t buf_len, char *err, uint32_t err_cap); -/* Read the wire head and ro/vid attr key. Missing keys return nonzero. */ +/* Read the wire head + langtype tail. Missing keys return nonzero. */ int kvspaceGetHead(void *h, const char *key, kvspaceHead_t *out); /* 就地写:key 必须已存在、kind 不变、body_len 必须等于原 body_len——返回原 box 的 body @@ -157,9 +157,9 @@ int kvspaceWriteInPlace(void *h, const char *key, int resolve, uint32_t body_len uint8_t **body, char *err, uint32_t err_cap); /* Reserve a new XValue and return its body pointer; fill it before another KVSpace call. - * kvspace 层不写 attr key——langtype 由上层调用方写 `x‥langtype` attr key(attr 先落、x 后落)。 */ + * langtype(可为空 = 无类型)随头/尾部整体写入——单 key 一次写。 */ int kvspaceWriteNewPlace(void *h, const char *key, uint8_t ref, uint8_t storetype, - uint32_t body_len, uint64_t body_cap, + uint32_t body_len, uint64_t body_cap, const char *langtype, uint8_t **body, char *err, uint32_t err_cap); /* 只返回前缀下子项计数,无缓冲、无需释放。resolve=1 穿透 link。 */ @@ -186,15 +186,19 @@ int kvspaceWatch(void *h, const char *key, const uint8_t *target, uint32_t targe uint64_t tick_ns, uint8_t **out, uint32_t *out_len); /* ── XValue 构造(无 handle,由前端静态实现,byte-identical)─────────── - * head 固定 8 字节,直接 memcpy struct + body(见 kvspace/xvalue_head.h), - * 类型/形状走 x‥langtype attr key(kvspaceGetHead 读)。 */ + * head 固定 8 字节,直接 memcpy struct + body + langtype 尾部 + * (见 kvspace/xvalue_head.h)。langtype 为空串 = 无类型(不追加尾部)。 */ int kvspaceNewPtr(const char *target_kindexpr, const char *target, uint8_t **out, uint32_t *out_len); -int kvspaceNewChar(const uint8_t *bytes, uint32_t len, uint8_t **out, uint32_t *out_len); -int kvspaceNewBool(uint8_t v, uint8_t **out, uint32_t *out_len); -int kvspaceNewInt64(int64_t v, uint8_t **out, uint32_t *out_len); -int kvspaceNewFloat64(double v, uint8_t **out, uint32_t *out_len); +int kvspaceNewChar(const uint8_t *bytes, uint32_t len, const char *langtype, + uint8_t **out, uint32_t *out_len); +int kvspaceNewBool(uint8_t v, const char *langtype, + uint8_t **out, uint32_t *out_len); +int kvspaceNewInt64(int64_t v, const char *langtype, + uint8_t **out, uint32_t *out_len); +int kvspaceNewFloat64(double v, const char *langtype, + uint8_t **out, uint32_t *out_len); #ifdef __cplusplus } diff --git a/include/kvspace/xvalue_head.h b/include/kvspace/xvalue_head.h index 080d759..c620d6f 100644 --- a/include/kvspace/xvalue_head.h +++ b/include/kvspace/xvalue_head.h @@ -1,9 +1,12 @@ -/* Wire: [len:u48][storetype:u8][ptr:u8][body]. Head is fixed 8 bytes, - body offset is always 8. Type/shape (including dims) is NOT in the head — it - lives in the x‥langtype attr key as a plaintext langtype string. */ +/* Wire: [len:u48][storetype:u8][ptr:u8][body][attr]. Head is fixed 8 bytes, + body offset is always 8; attr region starts at 8+len and is length-prefixed + (total = 8 + len + attr_len). Type rides in the same single-key value — no + attr key. attr = [attr_len:u16][records]; record = [tag:u8][len:u16][data], + tag 1 = langtype, tags 2+ reserved (ro/vid...). */ #ifndef XVALUE_HEAD_H #define XVALUE_HEAD_H +#include #include #ifdef __cplusplus @@ -45,24 +48,65 @@ static inline void kvspaceXvalueHeadSetLen(kvspaceXvalueHead *h, uint64_t v) { } } -/* 构造(固定头 + body,不含类型;langtype 由调用方写 x‥langtype attr key)。 - 0 on success. -1 and *out cleared on failure. */ +/* attr 区常量:record tag 1 = langtype;2+ 预留扩展(ro/vid 等,未实现)。 */ +#define KVSPACE_XVALUE_ATTR_TAG_LANGTYPE 1u + +/* 读 attr 总长(含自身 2 字节;空 attr = 2)。 */ +static inline uint16_t kvspaceXvalueHeadAttrLen(const uint8_t *wire, + uint64_t body_len) { + const uint8_t *p = wire + KVSPACE_XVALUE_HEAD_LEN + body_len; + return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); +} + +/* 扫 attr records 取 tag=1(langtype)记录 data;未找到/坏 attr → 返回 0。 */ +static inline uint16_t kvspaceXvalueHeadLangtype(const uint8_t *wire, + uint64_t body_len, + const uint8_t **out) { + *out = NULL; + uint16_t alen = kvspaceXvalueHeadAttrLen(wire, body_len); + if (alen < 2) + return 0; + const uint8_t *p = wire + KVSPACE_XVALUE_HEAD_LEN + body_len + 2; + const uint8_t *end = wire + KVSPACE_XVALUE_HEAD_LEN + body_len + alen; + while (p + 3 <= end) { + uint8_t tag = p[0]; + uint16_t rlen = (uint16_t)((uint16_t)p[1] | ((uint16_t)p[2] << 8)); + if (p + 3 + rlen > end) + return 0; + if (tag == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE) { + *out = p + 3; + return rlen; + } + p += 3 + rlen; + } + return 0; +} + +/* 构造(固定头 + body + attr 区,单 key 整体)。langtype 空/NULL = 无 langtype + 记录(空 attr = [attr_len:2])。0 on success. -1 and *out cleared on failure. */ int kvspaceXvalueHeadNewNone(uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewScalar(uint8_t storetype, const uint8_t *raw, - uint32_t raw_len, uint8_t **out, uint64_t *out_len); + uint32_t raw_len, const char *langtype, + uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewSlack(const uint8_t *data, uint64_t data_len, - uint64_t cap, uint8_t **out, uint64_t *out_len); + uint64_t cap, const char *langtype, + uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewTensor(const uint64_t *dims, uint32_t ndim, const uint8_t *raw, uint64_t raw_len, + const char *langtype, uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewPtr(const uint8_t *path, uint32_t path_len, + const char *langtype, uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewExt(const uint8_t *locator, uint32_t locator_len, + const char *langtype, uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadNewCode(const uint8_t *body, uint64_t body_len, - uint64_t cap, uint8_t **out, uint64_t *out_len); + uint64_t cap, const char *langtype, + uint8_t **out, uint64_t *out_len); int kvspaceXvalueHeadReserve(uint8_t storetype, uint64_t body_len, - uint64_t cap, uint8_t **out, uint64_t *out_len); + uint64_t cap, const char *langtype, + uint8_t **out, uint64_t *out_len); #ifdef __cplusplus } diff --git a/src/codec_abi.c b/src/codec_abi.c index 5a7aae4..fc0c214 100644 --- a/src/codec_abi.c +++ b/src/codec_abi.c @@ -22,39 +22,42 @@ int kvspaceNewPtr(const char *target_kindexpr, const char *target, /* `any` 不是类型名(空 kindexpr = 不约束,见 spec [[文法与合法性]])。 */ if (strcmp(target_kindexpr, "any") == 0) return 1; - /* target_kindexpr 写入 x‥langtype attr key(由调用方/GetHead 层处理); - head 本体固定,只存指针路径。 */ + /* target_kindexpr 拼入 value 尾部(单 key 整体,langtype 尾部)。 */ uint8_t *value = NULL; uint64_t total = 0; if (kvspaceXvalueHeadNewPtr((const uint8_t *)target, - (uint32_t)strlen(target), &value, &total) != 0) + (uint32_t)strlen(target), target_kindexpr, + &value, &total) != 0) return 1; return finish(value, total, out, out_len); } -int kvspaceNewChar(const uint8_t *bytes, uint32_t len, uint8_t **out, uint32_t *out_len) { +int kvspaceNewChar(const uint8_t *bytes, uint32_t len, const char *langtype, + uint8_t **out, uint32_t *out_len) { if (!out || !out_len || (len && !bytes)) return 1; uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewSlack(bytes, len, len, &value, &total) != 0) + if (kvspaceXvalueHeadNewSlack(bytes, len, len, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); } -int kvspaceNewBool(uint8_t v, uint8_t **out, uint32_t *out_len) { +int kvspaceNewBool(uint8_t v, const char *langtype, + uint8_t **out, uint32_t *out_len) { if (!out || !out_len) return 1; uint8_t b = v ? 1 : 0; uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, &b, 1, + if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, &b, 1, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); } -int kvspaceNewInt64(int64_t v, uint8_t **out, uint32_t *out_len) { +int kvspaceNewInt64(int64_t v, const char *langtype, + uint8_t **out, uint32_t *out_len) { if (!out || !out_len) return 1; uint8_t raw[8]; @@ -62,13 +65,14 @@ int kvspaceNewInt64(int64_t v, uint8_t **out, uint32_t *out_len) { raw[i] = (uint8_t)((uint64_t)v >> (8 * i)); uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, + if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); } -int kvspaceNewFloat64(double v, uint8_t **out, uint32_t *out_len) { +int kvspaceNewFloat64(double v, const char *langtype, + uint8_t **out, uint32_t *out_len) { if (!out || !out_len) return 1; uint64_t bits; @@ -78,7 +82,7 @@ int kvspaceNewFloat64(double v, uint8_t **out, uint32_t *out_len) { raw[i] = (uint8_t)(bits >> (8 * i)); uint8_t *value = NULL; uint64_t total = 0; - if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, + if (kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, langtype, &value, &total) != 0) return 1; return finish(value, total, out, out_len); diff --git a/src/frontend.c b/src/frontend.c index 4cb24db..a658a04 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -39,7 +39,7 @@ typedef struct { int (*writeinplace)(void *h, const char *key, int resolve, uint32_t body_len, uint8_t **body, char *err, uint32_t err_cap); int (*writenewplace)(void *h, const char *key, uint8_t ref, uint8_t storetype, - uint32_t body_len, uint64_t body_cap, + uint32_t body_len, uint64_t body_cap, const char *langtype, uint8_t **body, char *err, uint32_t err_cap); int (*listlen)(void *h, const char *prefix, int expand_ext, int resolve, int32_t *out_count); int (*listat)(void *h, const char *prefix, int expand_ext, int resolve, int32_t idx, @@ -347,11 +347,11 @@ int kvspaceWriteInPlace(void *h, const char *key, int resolve, uint32_t body_len } int kvspaceWriteNewPlace(void *h, const char *key, uint8_t ref, uint8_t storetype, - uint32_t body_len, uint64_t body_cap, + uint32_t body_len, uint64_t body_cap, const char *langtype, uint8_t **body, char *err, uint32_t err_cap) { kvspace_handle *x = H(h); return x->vt->writenewplace(x->backend, key, ref, storetype, - body_len, body_cap, body, err, err_cap); + body_len, body_cap, langtype, body, err, err_cap); } int kvspaceListLen(void *h, const char *prefix, int expand_ext, int resolve, int32_t *out_count) { diff --git a/src/xvalue_head.c b/src/xvalue_head.c index 232bac2..f11f6ba 100644 --- a/src/xvalue_head.c +++ b/src/xvalue_head.c @@ -3,14 +3,15 @@ #include #include -/* V2 最终态(#566):head 固定 8 字节 [len:u48 LE@0-5][storetype:u8@6][ptr:u8@7], - body 偏移恒 = 8。类型/形状(含 dims)不在 head——明文 langtype 串存 x‥langtype - attr key。固定布局:直接 memcpy struct + body。 */ +/* V2 最终态(#571):head 固定 8 字节 [len:u48 LE@0-5][storetype:u8@6][ptr:u8@7], + body 偏移恒 = 8;attr 区 @8+len(长度自描述,含 langtype 记录)。整体单 key + 一次写,天然原子——无 attr key。langtype 空 = 无记录(空 attr)。 */ #define UINT48_MAX UINT64_C(0xFFFFFFFFFFFF) static int build(uint8_t storetype, uint8_t ptr, const uint8_t *body, - uint64_t len, uint8_t **out, uint64_t *out_len) { + uint64_t len, const char *langtype, uint8_t **out, + uint64_t *out_len) { if (!out || !out_len || (len && !body)) return -1; *out = NULL; @@ -18,7 +19,11 @@ static int build(uint8_t storetype, uint8_t ptr, const uint8_t *body, return -1; if (len > UINT48_MAX) return -1; - uint64_t n = (uint64_t)KVSPACE_XVALUE_HEAD_LEN + len; + uint64_t ltl = langtype ? strlen(langtype) : 0; + uint64_t alen = 2 + (ltl ? 3 + ltl : 0); /* attr 总长(含自身 2 字节) */ + if (alen > UINT16_MAX) + return -1; + uint64_t n = (uint64_t)KVSPACE_XVALUE_HEAD_LEN + len + alen; if (n > SIZE_MAX) return -1; uint8_t *p = (uint8_t *)calloc(1, (size_t)n); @@ -29,6 +34,15 @@ static int build(uint8_t storetype, uint8_t ptr, const uint8_t *body, memcpy(p, &h, sizeof h); if (len) memcpy(p + KVSPACE_XVALUE_HEAD_LEN, body, len); + uint8_t *a = p + KVSPACE_XVALUE_HEAD_LEN + len; + a[0] = (uint8_t)alen; + a[1] = (uint8_t)(alen >> 8); + if (ltl) { + a[2] = KVSPACE_XVALUE_ATTR_TAG_LANGTYPE; + a[3] = (uint8_t)ltl; + a[4] = (uint8_t)(ltl >> 8); + memcpy(a + 5, langtype, ltl); + } *out = p; *out_len = n; return 0; @@ -36,72 +50,85 @@ static int build(uint8_t storetype, uint8_t ptr, const uint8_t *body, int kvspaceXvalueHeadNewNone(uint8_t **out, uint64_t *out_len) { return build(KVSPACE_XVALUE_ST_SHORT, KVSPACE_XVALUE_PTR_INLINE, NULL, 0, - out, out_len); + NULL, out, out_len); } int kvspaceXvalueHeadNewScalar(uint8_t storetype, const uint8_t *raw, - uint32_t raw_len, uint8_t **out, uint64_t *out_len) { + uint32_t raw_len, const char *langtype, + uint8_t **out, uint64_t *out_len) { if (raw_len && !raw) return -1; - return build(storetype, KVSPACE_XVALUE_PTR_INLINE, raw, raw_len, out, out_len); + return build(storetype, KVSPACE_XVALUE_PTR_INLINE, raw, raw_len, langtype, + out, out_len); } int kvspaceXvalueHeadNewSlack(const uint8_t *data, uint64_t data_len, - uint64_t cap, uint8_t **out, uint64_t *out_len) { + uint64_t cap, const char *langtype, + uint8_t **out, uint64_t *out_len) { if (cap < data_len || data_len > UINT48_MAX || (data_len && !data)) return -1; (void)cap; return build(KVSPACE_XVALUE_ST_SLACK, KVSPACE_XVALUE_PTR_INLINE, data, - data_len, out, out_len); + data_len, langtype, out, out_len); } int kvspaceXvalueHeadNewTensor(const uint64_t *dims, uint32_t ndim, const uint8_t *raw, uint64_t raw_len, + const char *langtype, uint8_t **out, uint64_t *out_len) { if (!dims || ndim == 0 || ndim > 64 || raw_len > UINT48_MAX || (raw_len && !raw)) return -1; (void)dims; (void)ndim; - /* 形状在 attr key langtype;body 字节数 = raw_len(明文层校验 numel×width) */ + /* 形状在 langtype 尾部;body 字节数 = raw_len(明文层校验 numel×width) */ return build(KVSPACE_XVALUE_ST_LARGE, KVSPACE_XVALUE_PTR_INLINE, raw, - raw_len, out, out_len); + raw_len, langtype, out, out_len); } int kvspaceXvalueHeadNewPtr(const uint8_t *path, uint32_t path_len, + const char *langtype, uint8_t **out, uint64_t *out_len) { if (!path_len || !path) return -1; return build(KVSPACE_XVALUE_ST_SLACK, KVSPACE_XVALUE_PTR_PTR, path, path_len, - out, out_len); + langtype, out, out_len); } int kvspaceXvalueHeadNewExt(const uint8_t *locator, uint32_t locator_len, + const char *langtype, uint8_t **out, uint64_t *out_len) { if (!locator_len || !locator) return -1; return build(KVSPACE_XVALUE_ST_EXT, KVSPACE_XVALUE_PTR_EXT, locator, - locator_len, out, out_len); + locator_len, langtype, out, out_len); } int kvspaceXvalueHeadNewCode(const uint8_t *body, uint64_t body_len, - uint64_t cap, uint8_t **out, uint64_t *out_len) { + uint64_t cap, const char *langtype, + uint8_t **out, uint64_t *out_len) { if (body_len > cap || body_len > UINT48_MAX || (body_len && !body)) return -1; (void)cap; return build(KVSPACE_XVALUE_ST_SLACK, KVSPACE_XVALUE_PTR_INLINE, body, - body_len, out, out_len); + body_len, langtype, out, out_len); } int kvspaceXvalueHeadReserve(uint8_t storetype, uint64_t body_len, - uint64_t cap, uint8_t **out, uint64_t *out_len) { + uint64_t cap, const char *langtype, + uint8_t **out, uint64_t *out_len) { if (body_len > UINT48_MAX || cap < body_len) return -1; - /* 固定头无容量字段:预留 = 产 body_len 字节零 body 的占位(后由调用方填充)。 */ + /* 固定头无容量字段:预留 = 产 body_len 字节零 body 的占位(后由调用方填充), + attr 区(含 langtype 记录)一并写入。 */ (void)cap; - if (body_len > SIZE_MAX - KVSPACE_XVALUE_HEAD_LEN) + uint64_t ltl = langtype ? strlen(langtype) : 0; + uint64_t alen = 2 + (ltl ? 3 + ltl : 0); + if (alen > UINT16_MAX) + return -1; + if (body_len > SIZE_MAX - KVSPACE_XVALUE_HEAD_LEN - alen) return -1; - uint64_t n = (uint64_t)KVSPACE_XVALUE_HEAD_LEN + body_len; + uint64_t n = (uint64_t)KVSPACE_XVALUE_HEAD_LEN + body_len + alen; if (n > SIZE_MAX) return -1; uint8_t *p = (uint8_t *)calloc(1, (size_t)n); @@ -110,6 +137,15 @@ int kvspaceXvalueHeadReserve(uint8_t storetype, uint64_t body_len, kvspaceXvalueHead h = {.storetype = storetype, .ptr = KVSPACE_XVALUE_PTR_INLINE}; kvspaceXvalueHeadSetLen(&h, body_len); memcpy(p, &h, sizeof h); + uint8_t *a = p + KVSPACE_XVALUE_HEAD_LEN + body_len; + a[0] = (uint8_t)alen; + a[1] = (uint8_t)(alen >> 8); + if (ltl) { + a[2] = KVSPACE_XVALUE_ATTR_TAG_LANGTYPE; + a[3] = (uint8_t)ltl; + a[4] = (uint8_t)(ltl >> 8); + memcpy(a + 5, langtype, ltl); + } *out = p; *out_len = n; return 0; diff --git a/tests/test_codec_abi.c b/tests/test_codec_abi.c index c68e186..f36b411 100644 --- a/tests/test_codec_abi.c +++ b/tests/test_codec_abi.c @@ -8,41 +8,59 @@ #define CHECK(x) do { if (!(x)) { fprintf(stderr, "failed: %s\n", #x); return 1; } } while (0) -/* 固定 head 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8; - langtype 不在 head(x‥langtype attr key)。构造直接 memcpy struct + body—— - New* 产物用 kvspaceXvalueHead 字段断言。 */ +/* 固定 head 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8, + attr 区 @8+len(attr_len:u16 自描述,含 langtype 记录)。单 key 整体。 */ + +/* 校验 attr 区:total = 8+body_len+attr_len;langtype 记录存在且与 lt 一致。 */ +static int attr_ok(const uint8_t *value, uint32_t len, uint64_t body_len, + const char *langtype) { + size_t lt = strlen(langtype); + const uint8_t *a = value + KVSPACE_XVALUE_HEAD_LEN + body_len; + uint64_t alen = (uint64_t)a[0] | ((uint64_t)a[1] << 8); + if ((uint64_t)len != KVSPACE_XVALUE_HEAD_LEN + body_len + alen) + return 0; + if (lt == 0) + return alen == 2; + if (alen != 2 + 3 + lt) + return 0; + return a[2] == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE && a[3] == lt && a[4] == 0 && + memcmp(a + 5, langtype, lt) == 0; +} int main(void) { uint8_t *value = NULL; uint32_t len = 0; kvspaceXvalueHead h; - CHECK(kvspaceNewInt64(42, &value, &len) == 0); - CHECK(len == 16); + CHECK(kvspaceNewInt64(42, "int64", &value, &len) == 0); + CHECK(len == 26); memcpy(&h, value, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && h.ptr == KVSPACE_XVALUE_PTR_INLINE && kvspaceXvalueHeadLen(&h) == 8); + CHECK(attr_ok(value, len, 8, "int64")); uint64_t v = 0; for (int i = 7; i >= 0; i--) v = (v << 8) | value[KVSPACE_XVALUE_HEAD_LEN + i]; CHECK(v == 42); free(value); - CHECK(kvspaceNewBool(1, &value, &len) == 0); - CHECK(len == 9); + CHECK(kvspaceNewBool(1, "bool", &value, &len) == 0); + CHECK(len == 18); memcpy(&h, value, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && kvspaceXvalueHeadLen(&h) == 1 && value[KVSPACE_XVALUE_HEAD_LEN] == 1); + CHECK(attr_ok(value, len, 1, "bool")); free(value); - CHECK(kvspaceNewBool(0, &value, &len) == 0); + CHECK(kvspaceNewBool(0, "bool", &value, &len) == 0); CHECK(value[KVSPACE_XVALUE_HEAD_LEN] == 0); free(value); - CHECK(kvspaceNewFloat64(2.5, &value, &len) == 0); - CHECK(len == 16); + CHECK(kvspaceNewFloat64(2.5, "float64", &value, &len) == 0); + CHECK(len == 28); memcpy(&h, value, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && kvspaceXvalueHeadLen(&h) == 8); + CHECK(attr_ok(value, len, 8, "float64")); uint64_t bits = 0; for (int i = 7; i >= 0; i--) bits = (bits << 8) | value[KVSPACE_XVALUE_HEAD_LEN + i]; @@ -52,28 +70,30 @@ int main(void) { free(value); const uint8_t utf8[] = {0xe4, 0xbd, 0xa0, 0xf0, 0x9f, 0x98, 0x80}; - CHECK(kvspaceNewChar(utf8, sizeof utf8, &value, &len) == 0); - CHECK(len == 8 + sizeof utf8); + CHECK(kvspaceNewChar(utf8, sizeof utf8, "char/utf8", &value, &len) == 0); + CHECK(len == 8 + sizeof utf8 + 14); memcpy(&h, value, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SLACK && h.ptr == KVSPACE_XVALUE_PTR_INLINE && kvspaceXvalueHeadLen(&h) == sizeof utf8); CHECK(memcmp(value + KVSPACE_XVALUE_HEAD_LEN, utf8, sizeof utf8) == 0); + CHECK(attr_ok(value, len, sizeof utf8, "char/utf8")); free(value); - CHECK(kvspaceNewChar(NULL, 0, &value, &len) == 0); - CHECK(len == 8); + CHECK(kvspaceNewChar(NULL, 0, "", &value, &len) == 0); + CHECK(len == 10); free(value); CHECK(kvspaceNewPtr("int64", "/x", &value, &len) == 0); - CHECK(len == 10); + CHECK(len == 20); memcpy(&h, value, sizeof h); CHECK(h.ptr == KVSPACE_XVALUE_PTR_PTR && h.storetype == KVSPACE_XVALUE_ST_SLACK && kvspaceXvalueHeadLen(&h) == 2); CHECK(memcmp(value + KVSPACE_XVALUE_HEAD_LEN, "/x", 2) == 0); + CHECK(attr_ok(value, len, 2, "int64")); free(value); /* 空 kindexpr = 不约束:Ptr 目标允许为空;`any` 不是类型名,创建即拒。 */ CHECK(kvspaceNewPtr("", "/x", &value, &len) == 0); - CHECK(len == 10); + CHECK(len == 12); memcpy(&h, value, sizeof h); CHECK(h.ptr == KVSPACE_XVALUE_PTR_PTR && kvspaceXvalueHeadLen(&h) == 2); free(value); diff --git a/tests/test_xvalue_head.c b/tests/test_xvalue_head.c index 5609876..26f0b6b 100644 --- a/tests/test_xvalue_head.c +++ b/tests/test_xvalue_head.c @@ -4,8 +4,8 @@ #include #include -/* 固定 head 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8。 - 类型不在 head(x‥langtype attr key)——存储层只验固定头/body。 */ +/* 固定 head 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8, + attr 区 @8+len(attr_len:u16 自描述,含 langtype 记录)。单 key 整体——无 attr key。 */ static int failures = 0; #define CHECK(cond, ...) \ @@ -18,19 +18,36 @@ static int failures = 0; } \ } while (0) +/* 校验 attr 区:total = 8+body_len+attr_len;langtype 记录存在且与 lt 一致。 */ +static int attr_ok(const uint8_t *v, uint64_t total, uint64_t body_len, + const char *lt) { + size_t l = strlen(lt); + const uint8_t *a = v + KVSPACE_XVALUE_HEAD_LEN + body_len; + uint64_t alen = (uint64_t)a[0] | ((uint64_t)a[1] << 8); + if (total != KVSPACE_XVALUE_HEAD_LEN + body_len + alen) + return 0; + if (l == 0) + return alen == 2; + if (alen != 2 + 3 + l) + return 0; + return a[2] == KVSPACE_XVALUE_ATTR_TAG_LANGTYPE && a[3] == l && a[4] == 0 && + memcmp(a + 5, lt, l) == 0; +} + static void test_none(void) { uint8_t *buf = NULL; uint64_t n = 0; CHECK(kvspaceXvalueHeadNewNone(&buf, &n) == 0, "none new"); - CHECK(n == 8, "none len %llu", (unsigned long long)n); + CHECK(n == 10, "none len %llu", (unsigned long long)n); kvspaceXvalueHead h; memcpy(&h, buf, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SHORT && h.ptr == KVSPACE_XVALUE_PTR_INLINE && kvspaceXvalueHeadLen(&h) == 0, "none head"); - /* wire 布局:len:u48@0-5、storetype@6、ptr@7 */ + /* wire 布局:len:u48@0-5、storetype@6、ptr@7、attr_len@8 */ CHECK(buf[0] == 0 && buf[5] == 0 && buf[6] == KVSPACE_XVALUE_ST_SHORT && buf[7] == KVSPACE_XVALUE_PTR_INLINE, "none wire layout"); + CHECK(attr_ok(buf, n, 0, ""), "none empty attr"); free(buf); } @@ -38,13 +55,20 @@ static void test_scalar(void) { uint8_t raw[8] = {0x0a, 0, 0, 0, 0, 0, 0, 0}; uint8_t *buf = NULL; uint64_t n = 0; - CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, + CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, "int64", &buf, &n) == 0, "i64 new"); - CHECK(n == 16, "i64 len %llu", (unsigned long long)n); + CHECK(n == 26, "i64 len %llu", (unsigned long long)n); kvspaceXvalueHead h; memcpy(&h, buf, sizeof h); CHECK(kvspaceXvalueHeadLen(&h) == 8 && h.storetype == KVSPACE_XVALUE_ST_SHORT, "i64 head"); CHECK(memcmp(buf + KVSPACE_XVALUE_HEAD_LEN, raw, 8) == 0, "i64 body@8"); + CHECK(attr_ok(buf, n, 8, "int64"), "i64 langtype attr"); + free(buf); + /* 无类型(空 langtype):空 attr [attr_len:2]。 */ + CHECK(kvspaceXvalueHeadNewScalar(KVSPACE_XVALUE_ST_SHORT, raw, 8, "", + &buf, &n) == 0, "scalar no-langtype"); + CHECK(n == 18, "no-langtype len %llu", (unsigned long long)n); + CHECK(attr_ok(buf, n, 8, ""), "no-langtype empty attr"); free(buf); } @@ -52,8 +76,8 @@ static void test_slack(void) { const uint8_t hi[] = {'h', 'i'}; uint8_t *buf = NULL; uint64_t n = 0; - CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 2, &buf, &n) == 0, "slack new"); - CHECK(n == 10, "slack len %llu", (unsigned long long)n); + CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 2, "[]char/utf8", &buf, &n) == 0, "slack new"); + CHECK(n == 26, "slack len %llu", (unsigned long long)n); kvspaceXvalueHead h; memcpy(&h, buf, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SLACK && @@ -63,8 +87,9 @@ static void test_slack(void) { buf[6] == KVSPACE_XVALUE_ST_SLACK && buf[7] == KVSPACE_XVALUE_PTR_INLINE, "slack wire layout"); CHECK(memcmp(buf + KVSPACE_XVALUE_HEAD_LEN, hi, 2) == 0, "slack body"); + CHECK(attr_ok(buf, n, 2, "[]char/utf8"), "slack langtype attr"); free(buf); - CHECK(kvspaceXvalueHeadNewSlack(hi, 2, 1, &buf, &n) != 0, "slack capcap"); const uint8_t call_body[] = {0, 0, 0, 0, 0, 'f'}; CHECK(kvspaceXvalueHeadNewCode(call_body, sizeof call_body, sizeof call_body, - &value, &len) == 0, "code new"); + "rwir", &value, &len) == 0, "code new"); memcpy(&h, value, sizeof h); CHECK(h.storetype == KVSPACE_XVALUE_ST_SLACK && kvspaceXvalueHeadLen(&h) == sizeof call_body, "code head"); CHECK(memcmp(value + KVSPACE_XVALUE_HEAD_LEN, call_body, sizeof call_body) == 0, "code body"); + CHECK(attr_ok(value, len, sizeof call_body, "rwir"), "code langtype attr"); free(value); - CHECK(kvspaceXvalueHeadNewCode(call_body, 5, 3, &value, &len) != 0, + CHECK(kvspaceXvalueHeadNewCode(call_body, 5, 3, "", &value, &len) != 0, "code cap Date: Fri, 30 Oct 2026 14:56:02 +0800 Subject: [PATCH 2/3] =?UTF-8?q?kvspace:=20=E5=89=8D=E7=AB=AF=20backend=5Fp?= =?UTF-8?q?ath=20dladdr=20=E4=BC=98=E5=85=88=EF=BC=88=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=E5=8F=AF=E6=95=B4=E4=BD=93=E9=9A=8F=E7=9B=AE=E5=BD=95=E5=88=86?= =?UTF-8?q?=E5=8F=91=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端 dlopen 路径优先取本前端 .so 所在目录(dladdr),未取到自身路径时 回退 /usr/lib/kvspace(macOS /usr/local/lib/kvspace)。产物统一放 kvlang/bin 时后端随同目录,无需系统安装。 Co-Authored-By: Claude Code --- src/frontend.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/frontend.c b/src/frontend.c index a658a04..865ace2 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -9,7 +9,8 @@ * shm://... → libkvspace-c.so.1 / macOS: libkvspace-c.dylib * 其余(redis/fs/s3) → libkvspace_durable.so.1 / macOS: libkvspace_durable.dylib * 先按 SONAME 用 RTLD_NOLOAD 复用宿主已加载的后端(单二进制 memfd 场景), - * 未命中再按固定目录 dlopen:Linux /usr/lib/kvspace、macOS /usr/local/lib/kvspace。 + * 未命中再 dlopen:优先本前端 .so 所在目录(dladdr,产物可整体随目录分发), + * 回退固定目录 Linux /usr/lib/kvspace、macOS /usr/local/lib/kvspace。 */ #ifndef _GNU_SOURCE @@ -142,9 +143,21 @@ static const char *backend_soname(const char *dsn) { #endif } -/* 后端目录固定(运行期没有目录类环境变量):Linux /usr/lib/kvspace; - * macOS /usr/local/lib/kvspace(/usr 受 SIP 保护)。 */ +/* 后端目录:优先本前端 .so 所在目录(dladdr,产物可整体放在任意目录如 + * kvlang/bin,后端随同目录),未取到自身路径时回退固定目录 + * Linux /usr/lib/kvspace、macOS /usr/local/lib/kvspace(旧安装布局)。 */ static char *backend_path(const char *soname, char *buf, size_t cap) { + Dl_info info; + if (dladdr((const void *)backend_path, &info) && info.dli_fname) { + const char *slash = strrchr(info.dli_fname, '/'); + if (slash) { + size_t dlen = (size_t)(slash - info.dli_fname); + if (dlen < cap) { + snprintf(buf, cap, "%.*s/%s", (int)dlen, info.dli_fname, soname); + return buf; + } + } + } const char *dir; #if defined(__APPLE__) dir = "/usr/local/lib/kvspace"; From 8ca70a3fa10693a5a2ed3b1681dc1e4b39e0eac8 Mon Sep 17 00:00:00 2001 From: miaobyte Date: Fri, 30 Oct 2026 14:56:02 +0800 Subject: [PATCH 3/3] =?UTF-8?q?kvspace=20CLI:=20=E9=80=82=E9=85=8D=20#571?= =?UTF-8?q?=EF=BC=88langtype=20=E5=80=BC=E5=86=85=20attr=20=E5=B0=BE?= =?UTF-8?q?=EF=BC=8C=E5=8D=95=20key=20=E5=86=99=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - parse_value 补 langtype 参数 - set_one 删除独立 attr key 写,改单 key 整体写 - from_wire/head 纯 wire 自解(attr 尾) Co-Authored-By: Claude Code --- cli/kvspace.rs | 105 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/cli/kvspace.rs b/cli/kvspace.rs index 47a33cc..57faa0d 100644 --- a/cli/kvspace.rs +++ b/cli/kvspace.rs @@ -127,7 +127,6 @@ extern "C" { fn kvspaceNewPtr( target_kindexpr: *const c_char, target: *const c_char, - langtype: *const c_char, out: *mut *mut u8, out_len: *mut u32, ) -> c_int; @@ -199,20 +198,26 @@ struct Value { } /* head 固定 8 字节 [len:u48 LE@0-5][storetype@6][ptr@7],body 偏移恒 8; - langtype 作为 attr 记录 tag=1 置于 body 之尾(值内,非独立 key,见 #571)。 */ -fn from_wire(kv: *mut c_void, key: &str, data: &[u8]) -> Value { - let mut h = zero_head(); - let ok = unsafe { kvspaceGetHead(kv, cs(key), &mut h) == 0 }; - let lt = if ok { - String::from_utf8_lossy(langtype_bytes(&h)).into_owned() - } else { - String::new() + langtype 作为 attr 记录 tag=1 置于 body 之尾(值内,非独立 key,见 #571)。 + 值自描述:直接解 wire,不经 kvspaceGetHead(它对 Ptr 穿透,读不到本体)。 */ +fn from_wire(data: &[u8]) -> Value { + if data.len() < KVSPACE_XVALUE_HEAD_LEN { + return Value { kind: String::new(), r#ref: 0, dims: vec![], body: vec![] }; + } + let body_len = wire_len(data) as usize; + let storetype = data[6]; + let ptr = data[7]; + let r#ref = match ptr { + 1 => 1, + 2 => 2, + _ => { + let _ = storetype; + 0 + } }; + let lt = wire_langtype(data, body_len); let (_ltdims, kind) = parse_langtype(<); - let ndim = if ok { (h.ndim.max(0) as usize).min(8) } else { 0 }; - let dims: Vec = if ok { h.dims[..ndim].to_vec() } else { vec![] }; /* body 只到 head.len 为止;其后是 attr 区,不属 body。 */ - let body_len = if ok { h.body_len.max(0) as usize } else { 0 }; let start = KVSPACE_XVALUE_HEAD_LEN; let end = (start + body_len).min(data.len()); let body = if end > start { @@ -222,12 +227,47 @@ fn from_wire(kv: *mut c_void, key: &str, data: &[u8]) -> Value { }; Value { kind, - r#ref: if ok { h.r#ref as i32 } else { 0 }, - dims, + r#ref, + dims: vec![], body, } } +/// u48 小端 @0-5(唯一读点,与 C 侧 kvspaceXvalueHeadLen 一致)。 +fn wire_len(data: &[u8]) -> u64 { + let mut v = 0u64; + for i in (0..6).rev() { + v = (v << 8) | data[i] as u64; + } + v +} + +/// 扫 attr 区取 tag=1(langtype);无记录/坏 attr → 空串。 +fn wire_langtype(data: &[u8], body_len: usize) -> String { + let base = KVSPACE_XVALUE_HEAD_LEN as usize + body_len; + if base + 2 > data.len() { + return String::new(); + } + let alen = (data[base] as usize) | ((data[base + 1] as usize) << 8); + if alen < 2 || base + alen > data.len() { + return String::new(); + } + let end = base + alen; + let mut p = base + 2; + while p + 3 <= end { + let tag = data[p]; + let rlen = (data[p + 1] as usize) | ((data[p + 2] as usize) << 8); + if p + 3 + rlen > end { + return String::new(); + } + if tag == 1 { + return String::from_utf8_lossy(&data[p + 3..p + 3 + rlen]).into_owned(); + } + p += 3 + rlen; + } + String::new() +} + /// langtype 无 ref 前缀(ref 是独立字段):只剥前导 [dims],其余为基 kind。 fn parse_langtype(lt: &str) -> (Vec, String) { if lt.contains('·') { @@ -329,7 +369,7 @@ fn get(kv: *mut c_void, key: &str) -> Vec { } // 解析 value 语法为 (wire, langtype):langtype 是无前缀裸 kindexpr(int64/[]char/utf8/键·值), -// 由 set_one 写 x‥langtype attr key(字符串 XValue);空 = 无类型(不约束指针)。 +// 由 codec 写进值内 attr 区(tag=1 记录,随 head+body 单 key 整体落);空 = 无类型。 fn parse_value(raw: &str) -> (Vec, String) { let mut out: *mut u8 = std::ptr::null_mut(); let mut len: u32 = 0; @@ -340,7 +380,7 @@ fn parse_value(raw: &str) -> (Vec, String) { Some(i) => (&rest[..i], &rest[i + 1..]), None => ("", rest), }; - if unsafe { kvspaceNewPtr(cs(k), cs(t), cs(k), &mut out, &mut len) != 0 } { + if unsafe { kvspaceNewPtr(cs(k), cs(t), &mut out, &mut len) != 0 } { fatalf(&format!("parse value failed: {:?}", raw)); } let v = take_wire(out, len); @@ -557,7 +597,7 @@ fn read_ext(kv: *mut c_void, prefix: &str) -> String { if v.is_empty() { return String::new(); } - let d = from_wire(kv, prefix, &v); + let d = from_wire(&v); if d.kind != "extindex" || d.body.is_empty() { return String::new(); } @@ -624,7 +664,7 @@ fn main() { if v.is_empty() { println!("{}\t(nil)", k); } else { - println!("{}\t{}", k, format_value(&from_wire(kv, k, &v))); + println!("{}\t{}", k, format_value(&from_wire(&v))); } } } @@ -637,22 +677,23 @@ fn main() { } "head" => { for k in tail { - let mut h = zero_head(); - if unsafe { kvspaceGetHead(kv, cs(k), &mut h) } != 0 { + let v = get(kv, k); + if v.is_empty() { println!("{}\t(nil)", k); } else { - let lt = String::from_utf8_lossy(langtype_bytes(&h)).into_owned(); - let (_, kind) = parse_langtype(<); - let dims = &h.dims[..(h.ndim.max(0) as usize).min(8)]; + let d = from_wire(&v); + let storetype = v[6]; + let ptr = v[7]; + let dims = &d.dims[..]; println!( "{}\t{}\tref={}\tstoretype={}\tndim={}\tdims=[{}]", k, - kind, - h.r#ref, - h.storetype, - h.ndim, + d.kind, + ptr, + storetype, + dims.len(), dims.iter() - .map(|d| d.to_string()) + .map(|x| x.to_string()) .collect::>() .join(",") ); @@ -858,10 +899,10 @@ fn fprint_list(kv: *mut c_void, prefix: &str, show_ext: bool, show_kind: bool) { if v.is_empty() { println!("{}", c); } else if show_kind { - let d = from_wire(kv, &full, &v); + let d = from_wire(&v); println!("{}\t{}\t{}", c, d.kind, plain(&d)); } else { - let d = from_wire(kv, &full, &v); + let d = from_wire(&v); println!("{}\t{}", c, plain(&d)); } } @@ -904,10 +945,10 @@ fn fprint_tree(kv: *mut c_void, prefix: &str, indent: &str, show_ext: bool, show if v.is_empty() { println!("{}{}{}", indent, branch, c); } else if show_kind { - let d = from_wire(kv, &full, &v); + let d = from_wire(&v); println!("{}{}{}\t{}\t{}", indent, branch, c, d.kind, plain(&d)); } else { - let d = from_wire(kv, &full, &v); + let d = from_wire(&v); println!("{}{}{}\t{}", indent, branch, c, plain(&d)); } }