use super::Value;
use std::collections::HashMap;
#[derive(Clone, Copy, Debug)]
pub struct Call<'a> {
pub program: &'a str,
pub ordinal: u32,
pub verb: &'a str,
pub cursor: Option<&'a str>,
pub text: &'a str,
pub inputs: &'a [Value],
}
#[derive(Clone, Debug, PartialEq)]
pub struct Outcome {
pub sqlcode: i32,
pub sqlstate: String,
pub affected: i64,
pub rows: Vec<Vec<Value>>,
pub tokens: String,
}
impl Outcome {
pub fn ok() -> Self {
Self { sqlcode: 0, sqlstate: "00000".into(), affected: 0, rows: Vec::new(), tokens: String::new() }
}
pub fn rows(rows: Vec<Vec<Value>>) -> Self {
Self { rows, ..Self::ok() }
}
pub fn error(sqlcode: i32, sqlstate: &str) -> Self {
Self { sqlcode, sqlstate: sqlstate.into(), ..Self::ok() }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Abandoned {
pub code: &'static str,
pub message: String,
}
pub type Answer = Result<Outcome, Abandoned>;
pub trait Database {
fn execute(&mut self, call: &Call) -> Answer;
fn open(&mut self, call: &Call) -> Answer;
fn fetch(&mut self, call: &Call) -> Answer;
fn close(&mut self, call: &Call) -> Answer;
fn commit(&mut self, call: &Call) -> Answer;
fn rollback(&mut self, call: &Call) -> Answer;
fn close_all(&mut self) -> Result<(), Abandoned> {
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OpenCursor {
pub with_hold: bool,
pub positioned: bool,
}
pub struct Session<'w> {
pub database: &'w mut dyn Database,
cursors: HashMap<(String, String), OpenCursor>,
pub pending: bool,
}
impl<'w> Session<'w> {
pub fn new(database: &'w mut (dyn Database + '_)) -> Self {
Self { database, cursors: HashMap::new(), pending: false }
}
pub fn cursor(&mut self, program: &str, name: &str) -> Option<&mut OpenCursor> {
self.cursors.get_mut(&(program.to_owned(), name.to_owned()))
}
pub fn opened(&mut self, program: &str, name: &str, with_hold: bool) {
self.cursors.insert((program.to_owned(), name.to_owned()), OpenCursor { with_hold, positioned: false });
}
pub fn closed(&mut self, program: &str, name: &str) {
self.cursors.remove(&(program.to_owned(), name.to_owned()));
}
pub fn committed(&mut self) {
self.cursors.retain(|_, c| c.with_hold);
self.cursors.values_mut().for_each(|c| c.positioned = false);
self.pending = false;
}
pub fn rolled_back(&mut self) {
self.cursors.clear();
self.pending = false;
}
pub fn settle(&mut self, program: &str, commit: bool) -> Answer {
let answer = if !self.pending && self.cursors.is_empty() {
Outcome::ok()
} else {
let verb = if commit { "COMMIT" } else { "ROLLBACK" };
let call = Call { program, ordinal: 0, verb, cursor: None, text: verb, inputs: &[] };
if commit { self.database.commit(&call)? } else { self.database.rollback(&call)? }
};
if commit && answer.sqlcode >= 0 {
self.committed();
} else {
self.rolled_back();
}
Ok(answer)
}
pub fn end_task(&mut self, program: &str, commit: bool) -> Answer {
let answer = self.settle(program, commit)?;
if !self.cursors.is_empty() {
self.database.close_all()?;
self.cursors.clear();
}
Ok(answer)
}
}