Skip to main content

ironwork_compile/
sql.rs

1//! The host type of a declared item, which EXEC SQL binds by: what `rt::sql` needs from the layout.
2//! The SQLCA's fields by name, and what DESCRIBE puts in each SQLNAME, as `rt::lir` has them.
3
4use crate::layout::Layout;
5use rt::lir::{Dimension, SqlNames, SqlcaField};
6use rt::sql::HostType;
7use rt::storage::Kind;
8use syntax::Pos;
9use syntax::ast::{Expr, Literal, Operand, Ref};
10use syntax::sql::Names;
11use zarch::hfp::Precision;
12
13/// The host type of layout item `item`, or why it cannot be a host variable.
14pub fn host_type(layout: &Layout, item: usize) -> Result<HostType, String> {
15    let it = &layout.items[item];
16    let name = it.name.as_deref().unwrap_or("FILLER");
17    Ok(match it.kind {
18        Kind::Binary { scale: 0, signed, .. } => match it.size {
19            2 => HostType::SmallInt { signed },
20            4 => HostType::Integer { signed },
21            8 => HostType::BigInt { signed },
22            n => return Err(format!("{name}: a binary item of {n} bytes has no SQL type")),
23        },
24        Kind::Binary { .. } => return Err(format!("{name}: a binary item with decimal places has no SQL type")),
25        Kind::Packed { digits, scale, signed } if digits <= 31 => HostType::Decimal { digits, scale, signed },
26        Kind::Zoned { digits, scale, signed, sign } if digits <= 31 => HostType::Zoned { digits, scale, signed, sign },
27        Kind::Packed { .. } | Kind::Zoned { .. } => return Err(format!("{name}: more than 31 digits has no SQL type")),
28        Kind::Float(Precision::Short) => HostType::Real,
29        Kind::Float(Precision::Long) => HostType::Double,
30        Kind::Alnum { .. } => HostType::Char(it.size),
31        Kind::Dbcs { edit: None, .. } => HostType::Graphic(it.size / 2),
32        Kind::Group => structure(layout, item, name)?,
33        _ => return Err(format!("{name}: this USAGE or PICTURE has no SQL type")),
34    })
35}
36
37/// How a multiple-row statement takes layout item `var`, named with or without subscripts, and its
38/// indicator: a host-variable array, an item of one OCCURS named without subscripts, with its
39/// dimension; None for one host variable; or why it is neither (Db2 13 for z/OS, Host-variable
40/// arrays in COBOL).
41pub fn host_array(layout: &Layout, var: usize, subscripted: bool, indicator: Option<(usize, bool)>) -> Result<Option<Dimension>, String> {
42    let it = &layout.items[var];
43    let name = it.name.as_deref().unwrap_or("FILLER");
44    match it.dims.len() {
45        _ if subscripted => return Ok(None),
46        0 => return Ok(None),
47        1 => {}
48        _ => return Err(format!("{name} is a table of more than one dimension, which no host-variable array is")),
49    }
50    if matches!(host_type(layout, var), Ok(HostType::Structure(_))) {
51        return Err(format!("{name} is a host-structure array, which Db2 for z/OS does not take in COBOL"));
52    }
53    let (stride, count) = it.dims[0];
54    match indicator {
55        None => Ok(Some(Dimension { stride, count, indicator_stride: 0 })),
56        Some((i, false)) if layout.items[i].dims.len() == 1 => {
57            let (indicator_stride, indicators) = layout.items[i].dims[0];
58            Ok(Some(Dimension { stride, count: count.min(indicators), indicator_stride }))
59        }
60        Some(_) => Err(format!("{name}'s indicator is not an indicator array, as a host-variable array's must be")),
61    }
62}
63
64/// A group is VARCHAR when it is a 49-level length halfword and a 49-level text, and otherwise a
65/// host structure of its members.
66fn structure(layout: &Layout, item: usize, name: &str) -> Result<HostType, String> {
67    let members: Vec<usize> = layout.items[item].children.iter().copied().filter(|&c| layout.items[c].redefines.is_none()).collect();
68    if let [length, text] = members[..] {
69        let (l, t) = (&layout.items[length], &layout.items[text]);
70        if l.level == 49 && t.level == 49 && l.size == 2 && matches!(l.kind, Kind::Binary { scale: 0, .. }) {
71            match t.kind {
72                Kind::Alnum { .. } => return Ok(HostType::VarChar(t.size)),
73                Kind::Dbcs { edit: None, .. } => return Ok(HostType::VarGraphic(t.size / 2)),
74                _ => {}
75            }
76        }
77    }
78    let mut out = Vec::new();
79    for m in members {
80        if layout.items[m].table {
81            return Err(format!("{name}: a host structure holding a table"));
82        }
83        match host_type(layout, m)? {
84            HostType::Structure(_) => return Err(format!("{name}: a host structure nested in another")),
85            t => out.push((m, t)),
86        }
87    }
88    if out.is_empty() {
89        return Err(format!("{name}: a group with no members"));
90    }
91    Ok(HostType::Structure(out))
92}
93
94pub fn sql_names(names: Names) -> SqlNames {
95    match names {
96        Names::Names => SqlNames::Names,
97        Names::Labels => SqlNames::Labels,
98        Names::Any => SqlNames::Any,
99    }
100}
101
102/// The SQLCA's fields by name, in the order they are filled.
103pub fn sqlca_fields(pos: Pos) -> Vec<(SqlcaField, Ref)> {
104    let named = |name: &str, subscript: Option<u8>| {
105        let subscripts = subscript.map(|n| vec![Expr::Operand(Operand::Literal(Literal::Number(n.to_string())))]).unwrap_or_default();
106        Ref { name: name.into(), qualifiers: Vec::new(), subscripts, refmod: None, pos }
107    };
108    let mut fields = vec![
109        (SqlcaField::CaId, named("SQLCAID", None)),
110        (SqlcaField::CaBc, named("SQLCABC", None)),
111        (SqlcaField::Code, named("SQLCODE", None)),
112        (SqlcaField::ErrMl, named("SQLERRML", None)),
113        (SqlcaField::ErrMc, named("SQLERRMC", None)),
114        (SqlcaField::ErrP, named("SQLERRP", None)),
115        (SqlcaField::State, named("SQLSTATE", None)),
116    ];
117    fields.extend((1..=6).map(|n| (SqlcaField::ErrD(n), named("SQLERRD", Some(n)))));
118    let warnings = ["SQLWARN0", "SQLWARN1", "SQLWARN2", "SQLWARN3", "SQLWARN4", "SQLWARN5", "SQLWARN6", "SQLWARN7", "SQLWARN8", "SQLWARN9", "SQLWARNA"];
119    fields.extend((0..).zip(warnings).map(|(n, name)| (SqlcaField::Warn(n), named(name, None))));
120    fields
121}