Skip to main content

mf_search/
model.rs

1use mf_model::{
2    mark::Mark, node::Node, node_definition::NodeTree, node_pool::NodePool,
3    types::NodeId,
4};
5use serde::Serialize;
6use mf_model::rpds::{HashTrieMapSync, VectorSync};
7
8/// 扁平化后的索引文档(写入后端的基础结构)
9#[derive(Debug, Clone, Serialize)]
10pub struct IndexDoc {
11    pub node_id: String,
12    pub node_type: String,
13    pub parent_id: Option<String>,
14    /// mark 类型列表(用于简单的 "包含某 mark" 查询)
15    pub marks: Vec<String>,
16    /// 完整的 marks JSON(用于带属性的精确查询)
17    pub marks_json: String,
18    /// 扁平化的顶层属性(用于简单查询)
19    pub attrs_flat: Vec<(String, String)>,
20    /// 完整的 attrs JSON(用于嵌套属性查询)
21    pub attrs_json: String,
22    pub text: Option<String>,
23    pub path: Vec<String>,
24    // 常用 fast fields(i64)
25    pub order_i64: Option<i64>,
26    pub created_at_i64: Option<i64>,
27    pub updated_at_i64: Option<i64>,
28}
29impl IndexDoc {
30    /// 从索引文档转换回 Node
31    ///
32    /// 注意:
33    /// - content 字段会是空的,因为 IndexDoc 不保存子节点信息
34    /// - 如果需要完整的树结构,需要从 NodePool 中重建
35    pub fn to_node(&self) -> anyhow::Result<Node> {
36        // 反序列化 marks_json
37        let marks: VectorSync<Mark> =
38            serde_json::from_str(&self.marks_json).unwrap_or_default();
39
40        // 反序列化 attrs_json
41        let attrs_map: HashTrieMapSync<String, serde_json::Value> =
42            serde_json::from_str(&self.attrs_json).unwrap_or_default();
43
44        let node = Node {
45            id: self.node_id.as_str().into(),
46            r#type: self.node_type.clone(),
47            attrs: mf_model::attrs::Attrs::from(attrs_map),
48            content: VectorSync::new_sync(), // IndexDoc 不保存子节点信息
49            marks: marks.into(),
50        };
51
52        Ok(node)
53    }
54
55    /// 从节点与池快照构建索引文档
56    pub fn from_node(
57        pool: &NodePool,
58        node: &Node,
59    ) -> Self {
60        let parent_id = pool.parent_id(&node.id).cloned();
61
62        // 提取 mark 类型列表(用于简单查询)
63        let marks: Vec<String> =
64            node.marks.iter().map(|m: &Mark| m.r#type.clone()).collect();
65
66        // 序列化完整的 marks(用于带属性的查询)
67        let marks_json = serde_json::to_string(&node.marks)
68            .unwrap_or_else(|_| "[]".to_string());
69
70        // 提取扁平化的顶层属性(用于简单查询)
71        let mut attrs_flat = Vec::with_capacity(node.attrs.attrs.keys().len());
72        for (k, v) in node.attrs.attrs.iter() {
73            attrs_flat.push((k.clone(), flatten_value(v)));
74        }
75
76        // 序列化完整的 attrs(用于嵌套属性查询)
77        let attrs_json = serde_json::to_string(&node.attrs.attrs)
78            .unwrap_or_else(|_| "{}".to_string());
79
80        // 路径(根到当前)
81        let path: Vec<String> = pool
82            .get_node_path(&node.id)
83            .into_iter()
84            .map(|id| id.to_string())
85            .collect();
86
87        let text = extract_text(node);
88
89        // 提取常用 fast fields(若存在且为数值)
90        let order_i64 = extract_i64(node, "order");
91        let created_at_i64 = extract_i64(node, "created_at");
92        let updated_at_i64 = extract_i64(node, "updated_at");
93
94        IndexDoc {
95            node_id: node.id.to_string(),
96            node_type: node.r#type.clone(),
97            parent_id: parent_id.map(|id| id.to_string()),
98            marks,
99            marks_json,
100            attrs_flat,
101            attrs_json,
102            text,
103            path,
104            order_i64,
105            created_at_i64,
106            updated_at_i64,
107        }
108    }
109}
110
111/// 将属性值转为扁平字符串(便于倒排过滤)
112fn flatten_value(v: &serde_json::Value) -> String {
113    match v {
114        serde_json::Value::Null => "null".to_string(),
115        serde_json::Value::Bool(b) => b.to_string(),
116        serde_json::Value::Number(n) => n.to_string(),
117        serde_json::Value::String(s) => s.clone(),
118        _ => serde_json::to_string(v).unwrap_or_default(),
119    }
120}
121
122/// 提取用于全文字段的文本(约定: 优先 text/title/content)
123fn extract_text(node: &Node) -> Option<String> {
124    for key in ["text", "title", "content"] {
125        if let Some(serde_json::Value::String(s)) = node.attrs.get(key) {
126            if !s.is_empty() {
127                return Some(s.clone());
128            }
129        }
130    }
131    None
132}
133
134/// 从节点属性中提取 i64 数值(仅当为数值或可解析为整数的字符串时)
135fn extract_i64(
136    node: &Node,
137    key: &str,
138) -> Option<i64> {
139    node.attrs.get(key).and_then(|v| match v {
140        serde_json::Value::Number(n) => n
141            .as_i64()
142            .or_else(|| n.as_u64().and_then(|u| i64::try_from(u).ok())),
143        serde_json::Value::String(s) => s.parse::<i64>().ok(),
144        _ => None,
145    })
146}
147
148/// 收集 NodeEnum 中所有节点 id(包含子树)
149pub fn collect_node_ids_from_enum(node_enum: &NodeTree) -> Vec<NodeId> {
150    let mut ids: Vec<NodeId> = vec![node_enum.0.id.clone()];
151    for child in &node_enum.1 {
152        ids.extend(collect_node_ids_from_enum(child));
153    }
154    ids
155}