Skip to main content

ironwork_rt/lir/
sql.rs

1//! EXEC SQL (lir.md §9.7): the program's statement table, which the SQL runtime reads. WHENEVER is
2//! not in it: lowering emits it as `Cond::Sql` branches after the op.
3
4use super::{AbendId, PlaceId, SymId};
5use crate::sql::{HostType, fingerprint};
6use crate::{codec_enum, codec_struct};
7
8/// One EXEC SQL block, declarative ones included; `Program.sql[k − 1]` has ordinal k. `P` and `S`
9/// are the executor's handles to a data item and to text: the LIR's ids by default, the walker's
10/// own references in the interpreter.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct SqlEntry<P = PlaceId, S = SymId> {
13    pub ordinal: u32,
14    /// The command word the call record and the EXEC messages name.
15    pub verb: S,
16    pub statement: SqlStatement<P, S>,
17    /// What a `Database` call receives, `?` for each input; empty for a declaration. A dynamic
18    /// statement's call sends its statement string instead, and this names the statement.
19    pub text: S,
20    pub fingerprint: u32,
21    /// Set on the OPEN of a cursor declared WITH HOLD, and on no other entry.
22    pub with_hold: bool,
23}
24
25/// The typed statement with its host variables resolved and each OPEN given its DECLARE's inputs.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub enum SqlStatement<P = PlaceId, S = SymId> {
28    Query { inputs: Vec<HostPlace<P>>, into: Vec<HostPlace<P>> },
29    Change { delete: bool, inputs: Vec<HostPlace<P>>, current_of: Option<S> },
30    Open { cursor: S, inputs: Vec<HostPlace<P>> },
31    Fetch { cursor: S, into: Vec<HostPlace<P>> },
32    Close { cursor: S },
33    Commit,
34    Rollback,
35    /// PREPARE of the statement `name` from the statement string in `source`, which must be one
36    /// varying-length character or graphic string.
37    Prepare { name: S, source: Vec<HostPlace<P>> },
38    ExecuteImmediate { source: Vec<HostPlace<P>> },
39    /// EXECUTE of a prepared statement, `inputs` replacing its parameter markers.
40    Execute { name: S, inputs: Vec<HostPlace<P>> },
41    /// OPEN of a cursor declared for the prepared statement `statement`.
42    OpenPrepared { cursor: S, statement: S, inputs: Vec<HostPlace<P>> },
43    /// DESCRIBE [OUTPUT] of a prepared statement into the SQLDA at `descriptor`.
44    Describe { name: S, descriptor: P, names: SqlNames },
45    /// PREPARE ... INTO: PREPARE, then DESCRIBE of the statement it made.
46    PrepareInto { name: S, source: Vec<HostPlace<P>>, descriptor: P, names: SqlNames },
47    ExecuteDescriptor { name: S, descriptor: P },
48    OpenDescriptor { cursor: S, statement: S, descriptor: P },
49    FetchDescriptor { cursor: S, descriptor: P },
50    /// FETCH NEXT ROWSET into host-variable arrays; `enabled` is the cursor's WITH ROWSET
51    /// POSITIONING.
52    FetchRowset { cursor: S, rows: RowCount<P>, into: Vec<HostArray<P>>, enabled: bool },
53    /// INSERT ... FOR n ROWS, the entry's text the INSERT of one row. ATOMIC undoes every row when
54    /// one fails.
55    InsertRows { inputs: Vec<HostArray<P>>, rows: RowCount<P>, atomic: bool },
56    /// CALL of `procedure`: each host-variable argument is sent, and assigned what the procedure
57    /// returns for it.
58    Call { procedure: S, args: Vec<HostPlace<P>> },
59    /// WHENEVER, DECLARE CURSOR, INCLUDE and the other declarations: no op.
60    Declaration,
61    /// Abends EXEC, naming it, when reached.
62    Unsupported(S),
63    /// CONNECT or SET CONNECTION: the host variable naming the location, if any, goes to the
64    /// input trace, then the statement abends EXEC as an unsupported one does.
65    Connect { what: S, location: Vec<HostPlace<P>> },
66}
67
68/// What DESCRIBE puts in each SQLNAME: the column's name, its label, or its label and else its name.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum SqlNames {
71    Names,
72    Labels,
73    Any,
74}
75
76/// A host variable, or one member of a host structure at `member`'s offset and length. The
77/// indicator comes with the offset of this member's element in it.
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct HostPlace<P = PlaceId> {
80    pub var: P,
81    pub member: Option<(u32, u32)>,
82    /// Never a structure. An item with no SQL type keeps the walker's EXEC abend.
83    pub ty: Result<HostType, AbendId>,
84    pub indicator: Option<(P, u32)>,
85}
86
87/// A host variable of a multiple-row statement. A host-variable array's `place` is its first
88/// element, and `array` how far apart its elements and its indicator array's are; without `array`
89/// it is one host variable, whose value an INSERT gives every row.
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct HostArray<P = PlaceId> {
92    pub place: HostPlace<P>,
93    pub array: Option<Dimension>,
94}
95
96/// `count` is the fewest elements of the array and its indicator array.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct Dimension {
99    pub stride: u32,
100    pub count: u32,
101    pub indicator_stride: u32,
102}
103
104/// FOR n ROWS: absent, a constant, or a host variable holding n.
105#[derive(Clone, Debug, PartialEq, Eq)]
106pub enum RowCount<P = PlaceId> {
107    Implicit,
108    Constant(u32),
109    Host(HostPlace<P>),
110}
111
112/// The SQLCA fields the program declares, or its own SQLCODE and SQLSTATE, in the order filled.
113#[derive(Clone, Debug, Default, PartialEq, Eq)]
114pub struct Sqlca<P = PlaceId> {
115    pub fields: Vec<(SqlcaField, P, HostType)>,
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub enum SqlcaField {
120    CaId,
121    CaBc,
122    Code,
123    ErrMl,
124    ErrMc,
125    ErrP,
126    State,
127    /// SQLERRD(n), n from 1 to 6.
128    ErrD(u8),
129    /// SQLWARN0 to SQLWARNA as 0 to 10.
130    Warn(u8),
131}
132
133codec_struct!(SqlEntry { ordinal, verb, statement, text, fingerprint, with_hold });
134codec_enum!(SqlStatement {
135    Query { inputs, into } = 0,
136    Change { delete, inputs, current_of } = 1,
137    Open { cursor, inputs } = 2,
138    Fetch { cursor, into } = 3,
139    Close { cursor } = 4,
140    Commit = 5,
141    Rollback = 6,
142    Declaration = 7,
143    Unsupported(what) = 8,
144    Connect { what, location } = 9,
145    Prepare { name, source } = 10,
146    ExecuteImmediate { source } = 11,
147    Execute { name, inputs } = 12,
148    OpenPrepared { cursor, statement, inputs } = 13,
149    Describe { name, descriptor, names } = 14,
150    PrepareInto { name, source, descriptor, names } = 15,
151    ExecuteDescriptor { name, descriptor } = 16,
152    OpenDescriptor { cursor, statement, descriptor } = 17,
153    FetchDescriptor { cursor, descriptor } = 18,
154    FetchRowset { cursor, rows, into, enabled } = 19,
155    InsertRows { inputs, rows, atomic } = 20,
156    Call { procedure, args } = 21,
157});
158codec_enum!(SqlNames {
159    Names = 0,
160    Labels = 1,
161    Any = 2,
162});
163codec_struct!(HostPlace { var, member, ty, indicator } check host_place_valid);
164codec_struct!(HostArray { place, array });
165codec_struct!(Dimension { stride, count, indicator_stride });
166codec_enum!(RowCount { Implicit = 0, Constant(n) = 1, Host(place) = 2 });
167codec_struct!(Sqlca { fields } check sqlca_valid);
168codec_enum!(SqlcaField {
169    CaId = 0,
170    CaBc = 1,
171    Code = 2,
172    ErrMl = 3,
173    ErrMc = 4,
174    ErrP = 5,
175    State = 6,
176    ErrD(n) = 7,
177    Warn(n) = 8,
178});
179
180/// `crate::sql::read` and `write` take a structure's members one by one, never the structure.
181fn not_a_structure(ty: &HostType) -> Result<(), String> {
182    match ty {
183        HostType::Structure(_) => Err("a host structure where lowering gives its members".into()),
184        _ => Ok(()),
185    }
186}
187
188fn host_place_valid(place: &HostPlace) -> Result<(), String> {
189    place.ty.as_ref().map_or(Ok(()), not_a_structure)
190}
191
192fn sqlca_valid(sqlca: &Sqlca) -> Result<(), String> {
193    for (field, _, ty) in &sqlca.fields {
194        match *field {
195            SqlcaField::ErrD(n) if !(1..=6).contains(&n) => return Err(format!("SQLERRD({n}) is not an SQLCA field")),
196            SqlcaField::Warn(n) if n > 10 => return Err(format!("SQLWARN{n} is not an SQLCA field")),
197            _ => not_a_structure(ty)?,
198        }
199    }
200    Ok(())
201}
202
203/// load-module.md §7: ordinals run from 1, each fingerprint is its text's, and only the OPEN of a
204/// cursor declared WITH HOLD says so.
205pub(super) fn table_valid(table: &[SqlEntry], symbols: &[String]) -> Result<(), String> {
206    let symbol = |id: SymId| symbols.get(id as usize).ok_or_else(|| format!("symbol {id} of a table of {}", symbols.len()));
207    for (k, entry) in (1u32..).zip(table) {
208        if entry.ordinal != k {
209            return Err(format!("SQL entry {k} has ordinal {}", entry.ordinal));
210        }
211        let text = symbol(entry.text)?;
212        if entry.fingerprint != fingerprint(text) {
213            return Err(format!("SQL entry {k} has fingerprint {:08X}, not its text's {:08X}", entry.fingerprint, fingerprint(text)));
214        }
215        let held = match &entry.statement {
216            SqlStatement::Open { cursor, .. } | SqlStatement::OpenPrepared { cursor, .. } | SqlStatement::OpenDescriptor { cursor, .. } => text.starts_with(&format!("DECLARE {} CURSOR WITH HOLD FOR ", symbol(*cursor)?)),
217            _ => false,
218        };
219        if entry.with_hold != held {
220            return Err(format!("SQL entry {k} has WITH HOLD {} for its text", if entry.with_hold { "set" } else { "clear" }));
221        }
222    }
223    Ok(())
224}