Skip to main content

rust_store_core/dialect/
ir.rs

1//! SQL 中间表示(IR)
2
3use serde_json::{json, Value};
4
5/// 一条参数化 SQL 语句(参数位置用 `?` / `$n` 占位,params 顺序对应)
6#[derive(Debug, Clone, PartialEq)]
7pub struct SqlStmt {
8    /// SQL 文本(标识符已按后端加引号,值全部参数化)
9    pub text: String,
10    /// 绑定参数(按占位顺序)
11    pub params: Vec<Value>,
12    /// 是否为写语句
13    pub is_write: bool,
14    /// SELECT 时的「列 → JSON 还原路径」元数据(见 `row_shape`)
15    pub row_shape: Option<RowShape>,
16    /// RETURNING 列(PG/SQLite 写语句回读用;MySQL 无)
17    pub returning: Vec<String>,
18}
19
20impl SqlStmt {
21    pub fn select(text: String, params: Vec<Value>, row_shape: RowShape) -> Self {
22        SqlStmt {
23            text,
24            params,
25            is_write: false,
26            row_shape: Some(row_shape),
27            returning: Vec::new(),
28        }
29    }
30
31    pub fn write(text: String, params: Vec<Value>) -> Self {
32        SqlStmt {
33            text,
34            params,
35            is_write: true,
36            row_shape: None,
37            returning: Vec::new(),
38        }
39    }
40
41    /// 写语句 + 行形状(SQLite claim 的 `UPDATE … RETURNING`:既写又取行;
42    /// 执行器按 rowShape 还原行为文档,行数即领取数)
43    pub fn write_returning(text: String, params: Vec<Value>, row_shape: RowShape) -> Self {
44        SqlStmt {
45            text,
46            params,
47            is_write: true,
48            row_shape: Some(row_shape),
49            returning: Vec::new(),
50        }
51    }
52
53    pub fn to_value(&self) -> Value {
54        json!({
55            "text": self.text,
56            "params": self.params,
57            "isWrite": self.is_write,
58            "rowShape": self.row_shape.as_ref().map(|r| r.to_value()).unwrap_or(Value::Null),
59            "returning": self.returning,
60        })
61    }
62}
63
64/// SELECT 输出的一列:SQL 别名(如 `t1_title` / `rel0_amount`)→ JSON 里的嵌套还原路径
65#[derive(Debug, Clone, PartialEq)]
66pub struct RowShape {
67    /// 列 → { jsonPath, isArray }。jsonPath 形如 `["title"]` 或 `["items","amount"]`,
68    /// 表示该列应嵌套还原到文档的哪个位置;isArray 表示该字段是关系数组(聚合还原)。
69    pub columns: Vec<RowCol>,
70    /// `__present` 哨兵列的别名(缺失 vs null 三态,F-07/H-01):该列存每行
71    /// 「哪些标量字段显式存在」的集合。读取时标量列值为 null,仅当字段存在于
72    /// 该集合才还原为 `key: null`,否则(缺失)不产出该键。`None` 表示本条语句
73    /// 未查该列(如 count / 关系聚合),还原时不做存在性判定。
74    pub present_alias: Option<String>,
75}
76
77#[derive(Debug, Clone, PartialEq)]
78pub struct RowCol {
79    pub alias: String,
80    pub json_path: Vec<String>,
81    /// 是否为关系聚合数组字段(`many` 关系的子文档)
82    pub is_array: bool,
83    /// 是否为 `one` 关系(LEFT JOIN 后还原为对象或 `null`,而非数组)
84    pub one: bool,
85    /// 沿 `json_path` 每一级关系的基数(`true` = `one`)。长度 = 关系级数
86    /// (`json_path.len() - 1`);根标量列为空。嵌套关系靠它逐级决定「对象 / 数组」。
87    pub ones: Vec<bool>,
88    /// 若为空:标量直接取该列;若非空:构造子文档
89    pub sub_shape: Option<RowShape>,
90    /// 值恒「存在」:为 `null` 时也强制写入(不依赖 `__present` 哨兵)。用于归一聚合
91    /// 计算列(§9.2(2))—— 空集 `$sum/$avg/$min/$max` 的语义是显式 `null`(§9.7),
92    /// 与 Mongo 输出逐行对齐。
93    pub always: bool,
94    /// §9.7「布尔归一」:该输出列对应 schema 的 `boolean` 字段 → 行还原时把 SQL 的
95    /// `0/1` 归一为 JSON `bool`(对齐 Mongo;PG 原生 `BOOLEAN` 已是 bool,归一为 no-op)。
96    /// 依据**schema 声明类型**而非驱动元数据,故对任意用户 DDL 都成立。
97    pub is_bool: bool,
98    /// JSON 列(object/array 字段落单列存 JSON 文本):行还原时把列值(JSON 文本字符串)
99    /// 解析为嵌套 JSON 值,跨后端对齐 Mongo 的嵌套文档(见执行文档 §4.5 / 不足清单 #3)。
100    pub is_json: bool,
101}
102
103impl RowShape {
104    pub fn empty() -> Self {
105        RowShape {
106            columns: Vec::new(),
107            present_alias: None,
108        }
109    }
110
111    pub fn to_value(&self) -> Value {
112        let cols: Vec<Value> = self
113            .columns
114            .iter()
115            .map(|c| {
116                json!({
117                    "alias": c.alias,
118                    "path": c.json_path,
119                    "isArray": c.is_array,
120                    "one": c.one,
121                    "ones": c.ones,
122                    "subShape": c.sub_shape.as_ref().map(|s| s.to_value()).unwrap_or(Value::Null),
123                    "always": c.always,
124                    "bool": c.is_bool,
125                    "json": c.is_json,
126                })
127            })
128            .collect();
129        json!({
130            "columns": cols,
131            "present": self.present_alias.clone().unwrap_or_default(),
132        })
133    }
134}
135
136impl RowCol {
137    pub fn scalar(alias: &str, path: &[&str]) -> Self {
138        RowCol {
139            alias: alias.to_string(),
140            json_path: path.iter().map(|s| s.to_string()).collect(),
141            is_array: false,
142            one: false,
143            ones: Vec::new(),
144            sub_shape: None,
145            always: false,
146            is_bool: false,
147            is_json: false,
148        }
149    }
150
151    /// 标量列 + 布尔标记(§9.7):来自 schema `boolean` 字段的输出列
152    pub fn scalar_bool(alias: &str, path: &[&str], is_bool: bool) -> Self {
153        let mut c = RowCol::scalar(alias, path);
154        c.is_bool = is_bool;
155        c
156    }
157
158    /// JSON 列(object/array 字段):还原时把 JSON 文本解析为嵌套值
159    pub fn json(alias: &str, path: &[&str]) -> Self {
160        let mut c = RowCol::scalar(alias, path);
161        c.is_json = true;
162        c
163    }
164
165    /// 归一聚合计算列(§9.2(2)):根标量,且 `null` 也强制写入(见 [`RowCol::always`])
166    pub fn computed(alias: &str, path: &[&str]) -> Self {
167        RowCol {
168            alias: alias.to_string(),
169            json_path: path.iter().map(|s| s.to_string()).collect(),
170            is_array: false,
171            one: false,
172            ones: Vec::new(),
173            sub_shape: None,
174            always: true,
175            is_bool: false,
176            is_json: false,
177        }
178    }
179
180    pub fn array(alias: &str, sub_shape: RowShape) -> Self {
181        RowCol {
182            alias: alias.to_string(),
183            json_path: vec![alias.to_string()],
184            is_array: true,
185            one: false,
186            ones: vec![false],
187            sub_shape: Some(sub_shape),
188            always: false,
189            is_bool: false,
190            is_json: false,
191        }
192    }
193}