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, VecDeque};
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; CALL; or a
15    /// dynamic statement string's own command word.
16    pub verb: &'a str,
17    /// The cursor, the statement name PREPARE gives, or the procedure CALL names.
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    /// A PREPARE's description of its statement's result columns, which DESCRIBE gives the program.
35    pub columns: Vec<Column>,
36    /// A CALL's arguments as the procedure returns them, None for one it does not return.
37    pub parameters: Vec<Option<Value>>,
38}
39
40/// A result column as DESCRIBE describes it.
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct Column {
43    pub name: String,
44    pub ty: ColumnType,
45    pub nullable: bool,
46}
47
48/// A result column's Db2 data type, as an SQLDA's SQLTYPE and SQLLEN give it.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum ColumnType {
51    Char(u16),
52    VarChar(u16),
53    /// GRAPHIC and VARGRAPHIC, their lengths in DBCS characters.
54    Graphic(u16),
55    VarGraphic(u16),
56    SmallInt,
57    Integer,
58    BigInt,
59    Decimal { precision: u8, scale: u8 },
60    /// A decimal the backend gives no precision or scale, as an expression's result may be.
61    Numeric,
62    Real,
63    Double,
64    Date,
65    Time,
66    /// TIMESTAMP(p).
67    Timestamp(u8),
68    Binary(u16),
69    VarBinary(u16),
70    /// A type the backend names that has no Db2 counterpart; DESCRIBE refuses it by this name.
71    Other(String),
72}
73
74impl Outcome {
75    pub fn ok() -> Self {
76        Self { sqlcode: 0, sqlstate: "00000".into(), affected: 0, rows: Vec::new(), tokens: String::new(), columns: Vec::new(), parameters: Vec::new() }
77    }
78
79    pub fn rows(rows: Vec<Vec<Value>>) -> Self {
80        Self { rows, ..Self::ok() }
81    }
82
83    pub fn error(sqlcode: i32, sqlstate: &str) -> Self {
84        Self { sqlcode, sqlstate: sqlstate.into(), ..Self::ok() }
85    }
86}
87
88/// Why a backend could not answer at all, which ends the run: a replay that does not hold the
89/// call (`SQLR`), or a connection that failed (`SQL`).
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct Abandoned {
92    pub code: &'static str,
93    pub message: String,
94}
95
96pub type Answer = Result<Outcome, Abandoned>;
97
98pub trait Database {
99    /// SELECT INTO, SET, VALUES INTO, INSERT, UPDATE and DELETE, and a dynamic statement that is
100    /// not a query.
101    fn execute(&mut self, call: &Call) -> Answer;
102    /// Checks a statement string PREPARE names, which then runs through `execute` or `open`.
103    fn prepare(&mut self, call: &Call) -> Answer;
104    fn open(&mut self, call: &Call) -> Answer;
105    /// One row, or no row at the end.
106    fn fetch(&mut self, call: &Call) -> Answer;
107    /// Up to `rows` rows for a rowset FETCH, fewer at the end.
108    fn fetch_rows(&mut self, call: &Call, rows: u32) -> Answer;
109    /// A multiple-row INSERT: the INSERT of one row `call` names, once for each of `rows`, whose
110    /// values `call.inputs` holds one after another. SQLERRD(3) is the rows inserted. ATOMIC undoes
111    /// them all when one fails; NOT ATOMIC keeps the rest, -253, or -254 when none went in.
112    fn insert_rows(&mut self, call: &Call, rows: &[Vec<Value>], atomic: bool) -> Answer;
113    /// CALL of a stored procedure, which a backend that runs none refuses; the answer's
114    /// `parameters` give the arguments as the procedure returns them
115    /// ([`numeric::assumptions::CALL_FROM_A_RECORDING`]).
116    fn call(&mut self, call: &Call) -> Answer {
117        Err(Abandoned { code: "EXEC", message: format!("EXEC SQL CALL {} was reached: this database runs no stored procedures; a recording of the CALL answers it (--sql-replay)", call.cursor.unwrap_or_default()) })
118    }
119    fn close(&mut self, call: &Call) -> Answer;
120    fn commit(&mut self, call: &Call) -> Answer;
121    fn rollback(&mut self, call: &Call) -> Answer;
122    /// Closes every cursor, held ones too, as the end of a CICS task does, so the next task on the
123    /// same connection finds none open. A backend without cursors of its own has nothing to close.
124    fn close_all(&mut self) -> Result<(), Abandoned> {
125        Ok(())
126    }
127}
128
129#[derive(Clone, Debug, PartialEq)]
130pub struct OpenCursor {
131    pub with_hold: bool,
132    /// On a row, so a positioned UPDATE or DELETE has one to change.
133    pub positioned: bool,
134    /// The prepared statement a cursor for one runs.
135    pub statement: Option<String>,
136    /// The rows from the current position's first: the current rowset's, then those a rowset
137    /// FETCH read ahead of a row FETCH, which moves from the rowset's first row (Db2 13 SQL, FETCH,
138    /// Table 6).
139    pub held: VecDeque<Vec<Value>>,
140    /// How many of `held` the current position takes, 0 before the first.
141    pub current: usize,
142    /// The rows the last FETCH asked for, when it was a rowset FETCH, which the next asks for again
143    /// without FOR n ROWS.
144    pub rowset_size: Option<u32>,
145}
146
147impl OpenCursor {
148    /// Where the backend's own cursor is: on the one row the position takes.
149    pub fn on_backend_row(&self) -> bool {
150        self.current == 1 && self.held.len() == 1
151    }
152}
153
154/// A statement PREPARE made, as EXECUTE and OPEN run it and DESCRIBE describes it.
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct Prepared {
157    pub text: String,
158    pub query: bool,
159    pub markers: usize,
160    pub columns: Vec<Column>,
161}
162
163/// A run unit's connection to its database, and the state the runtime keeps rather than asking
164/// the backend, so every backend answers alike.
165pub struct Session<'w> {
166    pub database: &'w mut dyn Database,
167    /// Open cursors by program and cursor name, as two programs may declare the same name.
168    cursors: HashMap<(String, String), OpenCursor>,
169    /// Prepared statements by program and statement name, a statement name's scope being a
170    /// cursor name's.
171    prepared: HashMap<(String, String), Prepared>,
172    /// Whether any statement has reached the database since the last COMMIT or ROLLBACK.
173    pub pending: bool,
174}
175
176impl<'w> Session<'w> {
177    pub fn new(database: &'w mut (dyn Database + '_)) -> Self {
178        Self { database, cursors: HashMap::new(), prepared: HashMap::new(), pending: false }
179    }
180
181    pub fn cursor(&mut self, program: &str, name: &str) -> Option<&mut OpenCursor> {
182        self.cursors.get_mut(&(program.to_owned(), name.to_owned()))
183    }
184
185    pub fn opened(&mut self, program: &str, name: &str, with_hold: bool, statement: Option<&str>) {
186        let cursor = OpenCursor { with_hold, positioned: false, statement: statement.map(str::to_owned), held: VecDeque::new(), current: 0, rowset_size: None };
187        self.cursors.insert((program.to_owned(), name.to_owned()), cursor);
188    }
189
190    pub fn prepared(&self, program: &str, name: &str) -> Option<&Prepared> {
191        self.prepared.get(&(program.to_owned(), name.to_owned()))
192    }
193
194    pub fn prepare(&mut self, program: &str, name: &str, statement: Option<Prepared>) {
195        let key = (program.to_owned(), name.to_owned());
196        match statement {
197            Some(p) => self.prepared.insert(key, p),
198            None => self.prepared.remove(&key),
199        };
200    }
201
202    /// Whether an open cursor of `program` runs the statement `name`, which PREPARE may not replace.
203    pub fn running(&self, program: &str, name: &str) -> bool {
204        self.cursors.iter().any(|((p, _), c)| p == program && c.statement.as_deref() == Some(name))
205    }
206
207    pub fn closed(&mut self, program: &str, name: &str) {
208        self.cursors.remove(&(program.to_owned(), name.to_owned()));
209    }
210
211    /// COMMIT closes every cursor not declared WITH HOLD, and leaves a held one before its next row.
212    /// It destroys the unit of work's prepared statements but those held cursors run, as
213    /// KEEPDYNAMIC(NO) does.
214    pub fn committed(&mut self) {
215        self.cursors.retain(|_, c| c.with_hold);
216        for c in self.cursors.values_mut() {
217            c.positioned = false;
218            c.held.drain(..c.current);
219            c.current = 0;
220        }
221        let cursors = &self.cursors;
222        self.prepared.retain(|(program, name), _| cursors.iter().any(|((p, _), c)| p == program && c.statement.as_deref() == Some(name)));
223        self.pending = false;
224    }
225
226    pub fn rolled_back(&mut self) {
227        self.cursors.clear();
228        self.prepared.clear();
229        self.pending = false;
230    }
231
232    /// Ends a unit of work that no EXEC SQL statement ends: at SYNCPOINT, or at the end of a run
233    /// unit or task. The database is asked only while it holds work or an open cursor, and the
234    /// call names `program` with ordinal 0.
235    pub fn settle(&mut self, program: &str, commit: bool) -> Answer {
236        let answer = if !self.pending && self.cursors.is_empty() {
237            Outcome::ok()
238        } else {
239            let verb = if commit { "COMMIT" } else { "ROLLBACK" };
240            let call = Call { program, ordinal: 0, verb, cursor: None, text: verb, inputs: &[] };
241            if commit { self.database.commit(&call)? } else { self.database.rollback(&call)? }
242        };
243        if commit && answer.sqlcode >= 0 {
244            self.committed();
245        } else {
246            self.rolled_back();
247        }
248        Ok(answer)
249    }
250
251    /// Ends a CICS task: settles its unit of work, then closes the held cursors a commit leaves
252    /// open, as the end of a task closes every cursor.
253    pub fn end_task(&mut self, program: &str, commit: bool) -> Answer {
254        let answer = self.settle(program, commit)?;
255        if !self.cursors.is_empty() {
256            self.database.close_all()?;
257            self.cursors.clear();
258        }
259        self.prepared.clear();
260        Ok(answer)
261    }
262}