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.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct SqlEntry<P = PlaceId> {
11    pub ordinal: u32,
12    /// The command word the call record and the EXEC messages name.
13    pub verb: SymId,
14    pub statement: SqlStatement<P>,
15    /// What a `Database` call receives, `?` for each input; empty for a declaration.
16    pub text: SymId,
17    pub fingerprint: u32,
18    /// Set on the OPEN of a cursor declared WITH HOLD, and on no other entry.
19    pub with_hold: bool,
20}
21
22/// The typed statement with its host variables resolved and each OPEN given its DECLARE's inputs.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum SqlStatement<P = PlaceId> {
25    Query { inputs: Vec<HostPlace<P>>, into: Vec<HostPlace<P>> },
26    Change { delete: bool, inputs: Vec<HostPlace<P>>, current_of: Option<SymId> },
27    Open { cursor: SymId, inputs: Vec<HostPlace<P>> },
28    Fetch { cursor: SymId, into: Vec<HostPlace<P>> },
29    Close { cursor: SymId },
30    Commit,
31    Rollback,
32    /// WHENEVER, DECLARE CURSOR, INCLUDE and the other declarations: no op.
33    Declaration,
34    /// Abends EXEC, naming it, when reached.
35    Unsupported(SymId),
36}
37
38/// A host variable, or one member of a host structure at `member`'s offset and length. The
39/// indicator comes with the offset of this member's element in it.
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct HostPlace<P = PlaceId> {
42    pub var: P,
43    pub member: Option<(u32, u32)>,
44    /// Never a structure. An item with no SQL type keeps the walker's EXEC abend.
45    pub ty: Result<HostType, AbendId>,
46    pub indicator: Option<(P, u32)>,
47}
48
49/// The SQLCA fields the program declares, or its own SQLCODE and SQLSTATE, in the order filled.
50#[derive(Clone, Debug, Default, PartialEq, Eq)]
51pub struct Sqlca<P = PlaceId> {
52    pub fields: Vec<(SqlcaField, P, HostType)>,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum SqlcaField {
57    CaId,
58    CaBc,
59    Code,
60    ErrMl,
61    ErrMc,
62    ErrP,
63    State,
64    /// SQLERRD(n), n from 1 to 6.
65    ErrD(u8),
66    /// SQLWARN0 to SQLWARNA as 0 to 10.
67    Warn(u8),
68}
69
70codec_struct!(SqlEntry { ordinal, verb, statement, text, fingerprint, with_hold });
71codec_enum!(SqlStatement {
72    Query { inputs, into } = 0,
73    Change { delete, inputs, current_of } = 1,
74    Open { cursor, inputs } = 2,
75    Fetch { cursor, into } = 3,
76    Close { cursor } = 4,
77    Commit = 5,
78    Rollback = 6,
79    Declaration = 7,
80    Unsupported(what) = 8,
81});
82codec_struct!(HostPlace { var, member, ty, indicator } check host_place_valid);
83codec_struct!(Sqlca { fields } check sqlca_valid);
84codec_enum!(SqlcaField {
85    CaId = 0,
86    CaBc = 1,
87    Code = 2,
88    ErrMl = 3,
89    ErrMc = 4,
90    ErrP = 5,
91    State = 6,
92    ErrD(n) = 7,
93    Warn(n) = 8,
94});
95
96/// `crate::sql::read` and `write` take a structure's members one by one, never the structure.
97fn not_a_structure(ty: &HostType) -> Result<(), String> {
98    match ty {
99        HostType::Structure(_) => Err("a host structure where lowering gives its members".into()),
100        _ => Ok(()),
101    }
102}
103
104fn host_place_valid(place: &HostPlace) -> Result<(), String> {
105    place.ty.as_ref().map_or(Ok(()), not_a_structure)
106}
107
108fn sqlca_valid(sqlca: &Sqlca) -> Result<(), String> {
109    for (field, _, ty) in &sqlca.fields {
110        match *field {
111            SqlcaField::ErrD(n) if !(1..=6).contains(&n) => return Err(format!("SQLERRD({n}) is not an SQLCA field")),
112            SqlcaField::Warn(n) if n > 10 => return Err(format!("SQLWARN{n} is not an SQLCA field")),
113            _ => not_a_structure(ty)?,
114        }
115    }
116    Ok(())
117}
118
119/// load-module.md §7: ordinals run from 1, each fingerprint is its text's, and only the OPEN of a
120/// cursor declared WITH HOLD says so.
121pub(super) fn table_valid(table: &[SqlEntry], symbols: &[String]) -> Result<(), String> {
122    let symbol = |id: SymId| symbols.get(id as usize).ok_or_else(|| format!("symbol {id} of a table of {}", symbols.len()));
123    for (k, entry) in (1u32..).zip(table) {
124        if entry.ordinal != k {
125            return Err(format!("SQL entry {k} has ordinal {}", entry.ordinal));
126        }
127        let text = symbol(entry.text)?;
128        if entry.fingerprint != fingerprint(text) {
129            return Err(format!("SQL entry {k} has fingerprint {:08X}, not its text's {:08X}", entry.fingerprint, fingerprint(text)));
130        }
131        let held = match &entry.statement {
132            SqlStatement::Open { cursor, .. } => text.starts_with(&format!("DECLARE {} CURSOR WITH HOLD FOR ", symbol(*cursor)?)),
133            _ => false,
134        };
135        if entry.with_hold != held {
136            return Err(format!("SQL entry {k} has WITH HOLD {} for its text", if entry.with_hold { "set" } else { "clear" }));
137        }
138    }
139    Ok(())
140}