use std::collections::HashSet;
use serde_json::Value;
use super::Backend;
#[derive(Debug)]
pub struct RawStmt {
pub sql: String,
pub params: Vec<Value>,
pub is_write: bool,
}
pub fn compile_raw_stmt(
backend: Backend,
text: &str,
params: Value,
is_write: Option<bool>,
) -> Result<RawStmt, String> {
let trimmed = text.trim();
if trimmed.is_empty() {
return Err("原生 SQL 文本为空".to_string());
}
let is_write = is_write.unwrap_or_else(|| infer_is_write(trimmed));
match params {
Value::Array(items) => Ok(RawStmt {
sql: text.to_string(),
params: items,
is_write,
}),
Value::Null => Ok(RawStmt {
sql: text.to_string(),
params: Vec::new(),
is_write,
}),
Value::Object(names) => {
let (sql, ordered) = compile_named(backend, text, &names)?;
Ok(RawStmt {
sql,
params: ordered,
is_write,
})
}
other => Err(format!(
"原生 SQL params 仅支持数组(位置档)或对象(命名档),收到 {}",
json_type_name(&other)
)),
}
}
fn compile_named(
backend: Backend,
text: &str,
params: &serde_json::Map<String, Value>,
) -> Result<(String, Vec<Value>), String> {
let chars: Vec<char> = text.chars().collect();
let n = chars.len();
let mut out = String::with_capacity(text.len() + 8);
let mut ordered: Vec<Value> = Vec::new();
let mut used: HashSet<String> = HashSet::new();
let mut i = 0;
while i < n {
let c = chars[i];
if c == '\'' || c == '"' {
let quote = c;
out.push(c);
i += 1;
while i < n {
if chars[i] == quote {
if i + 1 < n && chars[i + 1] == quote {
out.push(quote);
out.push(quote);
i += 2;
continue;
}
break;
}
out.push(chars[i]);
i += 1;
}
if i < n {
out.push(quote); i += 1;
}
continue;
}
if c == '-' && i + 1 < n && chars[i + 1] == '-' {
while i < n && chars[i] != '\n' {
out.push(chars[i]);
i += 1;
}
continue;
}
if c == '/' && i + 1 < n && chars[i + 1] == '*' {
out.push(c);
out.push('*');
i += 2;
while i < n {
if chars[i] == '*' && i + 1 < n && chars[i + 1] == '/' {
out.push('*');
out.push('/');
i += 2;
break;
}
out.push(chars[i]);
i += 1;
}
continue;
}
if c == ':' && i + 1 < n && chars[i + 1] == ':' {
out.push(':');
out.push(':');
i += 2;
continue;
}
if c == ':' && i + 1 < n && (chars[i + 1].is_ascii_alphabetic() || chars[i + 1] == '_') {
let start = i + 1;
let mut j = start;
while j < n && (chars[j].is_ascii_alphanumeric() || chars[j] == '_') {
j += 1;
}
let name: String = chars[start..j].iter().collect();
match params.get(&name) {
Some(v) => {
out.push_str(&backend.placeholder(ordered.len()));
ordered.push(v.clone());
used.insert(name);
}
None => return Err(format!("原生 SQL 命名参数 :{} 未在 params 中提供", name)),
}
i = j;
continue;
}
out.push(c);
i += 1;
}
let mut unused: Vec<String> = params
.keys()
.filter(|k| !used.contains(*k))
.map(|k| format!(":{}", k))
.collect();
if !unused.is_empty() {
unused.sort();
return Err(format!(
"原生 SQL params 中存在未使用的命名参数: {}",
unused.join(", ")
));
}
Ok((out, ordered))
}
fn infer_is_write(trimmed: &str) -> bool {
let mut s = trimmed.trim_start();
if let Some(rest) = s.strip_prefix('(') {
s = rest.trim_start();
}
let word: String = s
.chars()
.take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
.collect();
!matches!(
word.to_ascii_uppercase().as_str(),
"SELECT" | "WITH" | "EXPLAIN" | "SHOW" | "PRAGMA" | "TABLE"
)
}
fn json_type_name(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}