rust_store_core/dialect/
raw.rs1use std::collections::HashSet;
13
14use serde_json::Value;
15
16use super::Backend;
17
18#[derive(Debug)]
20pub struct RawStmt {
21 pub sql: String,
23 pub params: Vec<Value>,
25 pub is_write: bool,
27}
28
29pub fn compile_raw_stmt(
35 backend: Backend,
36 text: &str,
37 params: Value,
38 is_write: Option<bool>,
39) -> Result<RawStmt, String> {
40 let trimmed = text.trim();
41 if trimmed.is_empty() {
42 return Err("原生 SQL 文本为空".to_string());
43 }
44 let is_write = is_write.unwrap_or_else(|| infer_is_write(trimmed));
45 match params {
46 Value::Array(items) => Ok(RawStmt { sql: text.to_string(), params: items, is_write }),
48 Value::Null => Ok(RawStmt { sql: text.to_string(), params: Vec::new(), is_write }),
49 Value::Object(names) => {
51 let (sql, ordered) = compile_named(backend, text, &names)?;
52 Ok(RawStmt { sql, params: ordered, is_write })
53 }
54 other => Err(format!(
55 "原生 SQL params 仅支持数组(位置档)或对象(命名档),收到 {}",
56 json_type_name(&other)
57 )),
58 }
59}
60
61fn compile_named(
67 backend: Backend,
68 text: &str,
69 params: &serde_json::Map<String, Value>,
70) -> Result<(String, Vec<Value>), String> {
71 let chars: Vec<char> = text.chars().collect();
72 let n = chars.len();
73 let mut out = String::with_capacity(text.len() + 8);
74 let mut ordered: Vec<Value> = Vec::new();
75 let mut used: HashSet<String> = HashSet::new();
76 let mut i = 0;
77 while i < n {
78 let c = chars[i];
79 if c == '\'' || c == '"' {
81 let quote = c;
82 out.push(c);
83 i += 1;
84 while i < n {
85 if chars[i] == quote {
86 if i + 1 < n && chars[i + 1] == quote {
87 out.push(quote);
89 out.push(quote);
90 i += 2;
91 continue;
92 }
93 break;
94 }
95 out.push(chars[i]);
96 i += 1;
97 }
98 if i < n {
99 out.push(quote); i += 1;
101 }
102 continue;
103 }
104 if c == '-' && i + 1 < n && chars[i + 1] == '-' {
106 while i < n && chars[i] != '\n' {
107 out.push(chars[i]);
108 i += 1;
109 }
110 continue;
111 }
112 if c == '/' && i + 1 < n && chars[i + 1] == '*' {
114 out.push(c);
115 out.push('*');
116 i += 2;
117 while i < n {
118 if chars[i] == '*' && i + 1 < n && chars[i + 1] == '/' {
119 out.push('*');
120 out.push('/');
121 i += 2;
122 break;
123 }
124 out.push(chars[i]);
125 i += 1;
126 }
127 continue;
128 }
129 if c == ':' && i + 1 < n && chars[i + 1] == ':' {
131 out.push(':');
132 out.push(':');
133 i += 2;
134 continue;
135 }
136 if c == ':' && i + 1 < n && (chars[i + 1].is_ascii_alphabetic() || chars[i + 1] == '_') {
138 let start = i + 1;
139 let mut j = start;
140 while j < n && (chars[j].is_ascii_alphanumeric() || chars[j] == '_') {
141 j += 1;
142 }
143 let name: String = chars[start..j].iter().collect();
144 match params.get(&name) {
145 Some(v) => {
146 out.push_str(&backend.placeholder(ordered.len()));
147 ordered.push(v.clone());
148 used.insert(name);
149 }
150 None => return Err(format!("原生 SQL 命名参数 :{} 未在 params 中提供", name)),
151 }
152 i = j;
153 continue;
154 }
155 out.push(c);
156 i += 1;
157 }
158 let mut unused: Vec<String> = params
160 .keys()
161 .filter(|k| !used.contains(*k))
162 .map(|k| format!(":{}", k))
163 .collect();
164 if !unused.is_empty() {
165 unused.sort();
166 return Err(format!(
167 "原生 SQL params 中存在未使用的命名参数: {}",
168 unused.join(", ")
169 ));
170 }
171 Ok((out, ordered))
172}
173
174fn infer_is_write(trimmed: &str) -> bool {
177 let mut s = trimmed.trim_start();
178 if let Some(rest) = s.strip_prefix('(') {
179 s = rest.trim_start();
180 }
181 let word: String = s
182 .chars()
183 .take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
184 .collect();
185 !matches!(
186 word.to_ascii_uppercase().as_str(),
187 "SELECT" | "WITH" | "EXPLAIN" | "SHOW" | "PRAGMA" | "TABLE"
188 )
189}
190
191fn json_type_name(v: &Value) -> &'static str {
192 match v {
193 Value::Null => "null",
194 Value::Bool(_) => "bool",
195 Value::Number(_) => "number",
196 Value::String(_) => "string",
197 Value::Array(_) => "array",
198 Value::Object(_) => "object",
199 }
200}