1use super::Value;
6use std::collections::HashMap;
7
8#[derive(Clone, Copy, Debug)]
10pub struct Call<'a> {
11 pub program: &'a str,
12 pub ordinal: u32,
14 pub verb: &'a str,
17 pub cursor: Option<&'a str>,
19 pub text: &'a str,
21 pub inputs: &'a [Value],
22}
23
24#[derive(Clone, Debug, PartialEq)]
26pub struct Outcome {
27 pub sqlcode: i32,
28 pub sqlstate: String,
29 pub affected: i64,
31 pub rows: Vec<Vec<Value>>,
32 pub tokens: String,
34}
35
36impl Outcome {
37 pub fn ok() -> Self {
38 Self { sqlcode: 0, sqlstate: "00000".into(), affected: 0, rows: Vec::new(), tokens: String::new() }
39 }
40
41 pub fn rows(rows: Vec<Vec<Value>>) -> Self {
42 Self { rows, ..Self::ok() }
43 }
44
45 pub fn error(sqlcode: i32, sqlstate: &str) -> Self {
46 Self { sqlcode, sqlstate: sqlstate.into(), ..Self::ok() }
47 }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Abandoned {
54 pub code: &'static str,
55 pub message: String,
56}
57
58pub type Answer = Result<Outcome, Abandoned>;
59
60pub trait Database {
61 fn execute(&mut self, call: &Call) -> Answer;
64 fn prepare(&mut self, call: &Call) -> Answer;
66 fn open(&mut self, call: &Call) -> Answer;
67 fn fetch(&mut self, call: &Call) -> Answer;
69 fn close(&mut self, call: &Call) -> Answer;
70 fn commit(&mut self, call: &Call) -> Answer;
71 fn rollback(&mut self, call: &Call) -> Answer;
72 fn close_all(&mut self) -> Result<(), Abandoned> {
75 Ok(())
76 }
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
80pub struct OpenCursor {
81 pub with_hold: bool,
82 pub positioned: bool,
84 pub statement: Option<String>,
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Prepared {
91 pub text: String,
92 pub query: bool,
93 pub markers: usize,
94}
95
96pub struct Session<'w> {
99 pub database: &'w mut dyn Database,
100 cursors: HashMap<(String, String), OpenCursor>,
102 prepared: HashMap<(String, String), Prepared>,
105 pub pending: bool,
107}
108
109impl<'w> Session<'w> {
110 pub fn new(database: &'w mut (dyn Database + '_)) -> Self {
111 Self { database, cursors: HashMap::new(), prepared: HashMap::new(), pending: false }
112 }
113
114 pub fn cursor(&mut self, program: &str, name: &str) -> Option<&mut OpenCursor> {
115 self.cursors.get_mut(&(program.to_owned(), name.to_owned()))
116 }
117
118 pub fn opened(&mut self, program: &str, name: &str, with_hold: bool, statement: Option<&str>) {
119 self.cursors.insert((program.to_owned(), name.to_owned()), OpenCursor { with_hold, positioned: false, statement: statement.map(str::to_owned) });
120 }
121
122 pub fn prepared(&self, program: &str, name: &str) -> Option<&Prepared> {
123 self.prepared.get(&(program.to_owned(), name.to_owned()))
124 }
125
126 pub fn prepare(&mut self, program: &str, name: &str, statement: Option<Prepared>) {
127 let key = (program.to_owned(), name.to_owned());
128 match statement {
129 Some(p) => self.prepared.insert(key, p),
130 None => self.prepared.remove(&key),
131 };
132 }
133
134 pub fn running(&self, program: &str, name: &str) -> bool {
136 self.cursors.iter().any(|((p, _), c)| p == program && c.statement.as_deref() == Some(name))
137 }
138
139 pub fn closed(&mut self, program: &str, name: &str) {
140 self.cursors.remove(&(program.to_owned(), name.to_owned()));
141 }
142
143 pub fn committed(&mut self) {
147 self.cursors.retain(|_, c| c.with_hold);
148 self.cursors.values_mut().for_each(|c| c.positioned = false);
149 let cursors = &self.cursors;
150 self.prepared.retain(|(program, name), _| cursors.iter().any(|((p, _), c)| p == program && c.statement.as_deref() == Some(name)));
151 self.pending = false;
152 }
153
154 pub fn rolled_back(&mut self) {
155 self.cursors.clear();
156 self.prepared.clear();
157 self.pending = false;
158 }
159
160 pub fn settle(&mut self, program: &str, commit: bool) -> Answer {
164 let answer = if !self.pending && self.cursors.is_empty() {
165 Outcome::ok()
166 } else {
167 let verb = if commit { "COMMIT" } else { "ROLLBACK" };
168 let call = Call { program, ordinal: 0, verb, cursor: None, text: verb, inputs: &[] };
169 if commit { self.database.commit(&call)? } else { self.database.rollback(&call)? }
170 };
171 if commit && answer.sqlcode >= 0 {
172 self.committed();
173 } else {
174 self.rolled_back();
175 }
176 Ok(answer)
177 }
178
179 pub fn end_task(&mut self, program: &str, commit: bool) -> Answer {
182 let answer = self.settle(program, commit)?;
183 if !self.cursors.is_empty() {
184 self.database.close_all()?;
185 self.cursors.clear();
186 }
187 self.prepared.clear();
188 Ok(answer)
189 }
190}