Skip to main content

ironwork_rt/sql/
database.rs

1//! What answers SQL statements. A backend sees each statement's identity, canonical text and input
2//! values, and answers with rows or a code; the runtime decides what those rows mean for the
3//! program, so every backend answers alike.
4
5use super::Value;
6use std::collections::HashMap;
7
8/// One statement as a backend receives it.
9#[derive(Clone, Copy, Debug)]
10pub struct Call<'a> {
11    pub program: &'a str,
12    /// The statement's place among its program's EXEC SQL blocks, from 1.
13    pub ordinal: u32,
14    /// SELECT, INSERT, UPDATE, DELETE, OPEN, FETCH, CLOSE, COMMIT or ROLLBACK; PREPARE; or a
15    /// dynamic statement string's own command word.
16    pub verb: &'a str,
17    /// The cursor, or the statement name PREPARE gives.
18    pub cursor: Option<&'a str>,
19    /// The canonical text, with `?` for each input; a dynamic statement's string as it runs.
20    pub text: &'a str,
21    pub inputs: &'a [Value],
22}
23
24/// A backend's answer.
25#[derive(Clone, Debug, PartialEq)]
26pub struct Outcome {
27    pub sqlcode: i32,
28    pub sqlstate: String,
29    /// Rows an INSERT, UPDATE or DELETE affected: SQLERRD(3).
30    pub affected: i64,
31    pub rows: Vec<Vec<Value>>,
32    /// SQLERRMC's message tokens.
33    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/// Why a backend could not answer at all, which ends the run: a replay that does not hold the
51/// call (`SQLR`), or a connection that failed (`SQL`).
52#[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    /// SELECT INTO, SET, VALUES INTO, INSERT, UPDATE and DELETE, and a dynamic statement that is
62    /// not a query.
63    fn execute(&mut self, call: &Call) -> Answer;
64    /// Checks a statement string PREPARE names, which then runs through `execute` or `open`.
65    fn prepare(&mut self, call: &Call) -> Answer;
66    fn open(&mut self, call: &Call) -> Answer;
67    /// One row, or no row at the end.
68    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    /// Closes every cursor, held ones too, as the end of a CICS task does, so the next task on the
73    /// same connection finds none open. A backend without cursors of its own has nothing to close.
74    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    /// On a row, so a positioned UPDATE or DELETE has one to change.
83    pub positioned: bool,
84    /// The prepared statement a cursor for one runs.
85    pub statement: Option<String>,
86}
87
88/// A statement PREPARE made, as EXECUTE and OPEN run it.
89#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Prepared {
91    pub text: String,
92    pub query: bool,
93    pub markers: usize,
94}
95
96/// A run unit's connection to its database, and the state the runtime keeps rather than asking
97/// the backend, so every backend answers alike.
98pub struct Session<'w> {
99    pub database: &'w mut dyn Database,
100    /// Open cursors by program and cursor name, as two programs may declare the same name.
101    cursors: HashMap<(String, String), OpenCursor>,
102    /// Prepared statements by program and statement name, a statement name's scope being a
103    /// cursor name's.
104    prepared: HashMap<(String, String), Prepared>,
105    /// Whether any statement has reached the database since the last COMMIT or ROLLBACK.
106    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    /// Whether an open cursor of `program` runs the statement `name`, which PREPARE may not replace.
135    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    /// COMMIT closes every cursor not declared WITH HOLD, and leaves a held one before its next row.
144    /// It destroys the unit of work's prepared statements but those held cursors run, as
145    /// KEEPDYNAMIC(NO) does.
146    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    /// Ends a unit of work that no EXEC SQL statement ends: at SYNCPOINT, or at the end of a run
161    /// unit or task. The database is asked only while it holds work or an open cursor, and the
162    /// call names `program` with ordinal 0.
163    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    /// Ends a CICS task: settles its unit of work, then closes the held cursors a commit leaves
180    /// open, as the end of a task closes every cursor.
181    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}