ironwork_rt/sql/
database.rs1use 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,
16 pub cursor: Option<&'a str>,
17 pub text: &'a str,
19 pub inputs: &'a [Value],
20}
21
22#[derive(Clone, Debug, PartialEq)]
24pub struct Outcome {
25 pub sqlcode: i32,
26 pub sqlstate: String,
27 pub affected: i64,
29 pub rows: Vec<Vec<Value>>,
30 pub tokens: String,
32}
33
34impl Outcome {
35 pub fn ok() -> Self {
36 Self { sqlcode: 0, sqlstate: "00000".into(), affected: 0, rows: Vec::new(), tokens: String::new() }
37 }
38
39 pub fn rows(rows: Vec<Vec<Value>>) -> Self {
40 Self { rows, ..Self::ok() }
41 }
42
43 pub fn error(sqlcode: i32, sqlstate: &str) -> Self {
44 Self { sqlcode, sqlstate: sqlstate.into(), ..Self::ok() }
45 }
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct Abandoned {
52 pub code: &'static str,
53 pub message: String,
54}
55
56pub type Answer = Result<Outcome, Abandoned>;
57
58pub trait Database {
59 fn execute(&mut self, call: &Call) -> Answer;
61 fn open(&mut self, call: &Call) -> Answer;
62 fn fetch(&mut self, call: &Call) -> Answer;
64 fn close(&mut self, call: &Call) -> Answer;
65 fn commit(&mut self, call: &Call) -> Answer;
66 fn rollback(&mut self, call: &Call) -> Answer;
67 fn close_all(&mut self) -> Result<(), Abandoned> {
70 Ok(())
71 }
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub struct OpenCursor {
76 pub with_hold: bool,
77 pub positioned: bool,
79}
80
81pub struct Session<'w> {
84 pub database: &'w mut dyn Database,
85 cursors: HashMap<(String, String), OpenCursor>,
87 pub pending: bool,
89}
90
91impl<'w> Session<'w> {
92 pub fn new(database: &'w mut (dyn Database + '_)) -> Self {
93 Self { database, cursors: HashMap::new(), pending: false }
94 }
95
96 pub fn cursor(&mut self, program: &str, name: &str) -> Option<&mut OpenCursor> {
97 self.cursors.get_mut(&(program.to_owned(), name.to_owned()))
98 }
99
100 pub fn opened(&mut self, program: &str, name: &str, with_hold: bool) {
101 self.cursors.insert((program.to_owned(), name.to_owned()), OpenCursor { with_hold, positioned: false });
102 }
103
104 pub fn closed(&mut self, program: &str, name: &str) {
105 self.cursors.remove(&(program.to_owned(), name.to_owned()));
106 }
107
108 pub fn committed(&mut self) {
110 self.cursors.retain(|_, c| c.with_hold);
111 self.cursors.values_mut().for_each(|c| c.positioned = false);
112 self.pending = false;
113 }
114
115 pub fn rolled_back(&mut self) {
116 self.cursors.clear();
117 self.pending = false;
118 }
119
120 pub fn settle(&mut self, program: &str, commit: bool) -> Answer {
124 let answer = if !self.pending && self.cursors.is_empty() {
125 Outcome::ok()
126 } else {
127 let verb = if commit { "COMMIT" } else { "ROLLBACK" };
128 let call = Call { program, ordinal: 0, verb, cursor: None, text: verb, inputs: &[] };
129 if commit { self.database.commit(&call)? } else { self.database.rollback(&call)? }
130 };
131 if commit && answer.sqlcode >= 0 {
132 self.committed();
133 } else {
134 self.rolled_back();
135 }
136 Ok(answer)
137 }
138
139 pub fn end_task(&mut self, program: &str, commit: bool) -> Answer {
142 let answer = self.settle(program, commit)?;
143 if !self.cursors.is_empty() {
144 self.database.close_all()?;
145 self.cursors.clear();
146 }
147 Ok(answer)
148 }
149}