use crate::data::{self, Data, Rows};
use anyhow::{Result, bail};
const WRITES: [&str; 3] = ["insert", "update", "delete"];
impl Data {
pub fn query(&self, sql: &str) -> Result<Rows> {
let mut stmts = split(sql);
let Some(stmt) = stmts.pop() else {
bail!("empty statement");
};
if !stmts.is_empty() {
bail!("one statement at a time");
}
let mut stmt = self.reader.prepare(&stmt)?;
let columns: Vec<String> = stmt.column_names().into_iter().map(str::to_owned).collect();
let count = columns.len();
let rows = stmt
.query_map([], |row| {
Ok((0..count).map(|i| data::value(row, i)).collect())
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(Rows { columns, rows })
}
pub fn write(&mut self, sql: &str) -> Result<u64> {
let stmts = split(sql);
if stmts.is_empty() {
bail!("empty statement");
}
for stmt in &stmts {
let verb = stmt
.split_whitespace()
.next()
.unwrap_or_default()
.to_ascii_lowercase();
if !WRITES.contains(&verb.as_str()) {
bail!(
"`{verb}` is not one of {} — a shape change goes through `write_column`",
WRITES.join(", ")
);
}
}
let tx = self.writer.transaction()?;
let mut touched = 0;
for stmt in &stmts {
touched += tx.execute(stmt, [])? as u64;
}
tx.commit()?;
Ok(touched)
}
}
fn split(sql: &str) -> Vec<String> {
let mut out = Vec::new();
let mut stmt = String::new();
let mut chars = sql.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'\'' | '"' | '`' => {
stmt.push(ch);
for q in chars.by_ref() {
stmt.push(q);
if q == ch {
break;
}
}
}
'-' if chars.peek() == Some(&'-') => {
for c in chars.by_ref() {
if c == '\n' {
break;
}
}
}
'/' if chars.peek() == Some(&'*') => {
let mut prev = ' ';
for c in chars.by_ref() {
if prev == '*' && c == '/' {
break;
}
prev = c;
}
}
';' => out.push(std::mem::take(&mut stmt)),
_ => stmt.push(ch),
}
}
out.push(stmt);
out.into_iter()
.map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty())
.collect()
}