1use super::host::{self, COLUMN_COUNT, TRUNCATED, Warnings};
6use super::{Answer, Call, Database, Outcome, Session};
7use crate::abend::Abend;
8use crate::host::Host;
9use crate::lir::{AbendId, HostPlace, SqlEntry, SqlStatement, Sqlca};
10use crate::storage::Loc;
11use crate::vocab::Pos;
12
13type R<T> = Result<T, Abend>;
14
15const DEADLOCK: i32 = -911;
17
18pub trait SqlHost<'w, P: Copy, S>: Host<P> {
20 fn session(&mut self) -> Option<&mut Session<'w>>;
22 fn in_task(&self) -> bool;
24 fn program_id(&self) -> String;
25 fn text(&self, text: &S) -> String;
26 fn place_pos(&self, place: P) -> Pos;
28 fn untyped(&mut self, abend: AbendId) -> Abend;
30 fn locate_indicator(&mut self, place: P) -> R<Loc> {
33 self.locate(place, false)
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct Ran {
40 pub sqlcode: i32,
41 pub warned: bool,
43}
44
45fn session<'a, 'w: 'a, P: Copy + 'a, S: 'a>(x: &'a mut impl SqlHost<'w, P, S>) -> &'a mut Session<'w> {
46 x.session().expect("a database is attached")
47}
48
49pub fn run<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, entry: &SqlEntry<P, S>, sqlca: &Sqlca<P>, pos: Pos) -> R<Option<Ran>> {
51 let verb = x.text(&entry.verb);
52 let refused = |why: String| Abend { code: "EXEC".into(), message: format!("EXEC SQL {verb} was reached: {why}"), pos, file: None };
53 if x.session().is_none() {
54 return Err(refused("no database is attached to the run".into()));
55 }
56 let (program, text, ordinal) = (x.program_id(), x.text(&entry.text), entry.ordinal);
57 let mut warnings = Warnings::default();
58 let mut outcome = match &entry.statement {
59 SqlStatement::Query { inputs, into } => match host::inputs(x, inputs)? {
60 Err(e) => Outcome::error(e.code, e.state),
61 Ok(values) => {
62 let answer = database(x, &Call { program: &program, ordinal, verb: &verb, cursor: None, text: &text, inputs: &values }, |db, c| db.execute(c), pos)?;
63 single_row(x, answer, into, &mut warnings)?
64 }
65 },
66 SqlStatement::Change { delete, inputs, current_of } => {
67 let current_of = current_of.as_ref().map(|c| x.text(c));
68 let position = current_of.as_deref().map(|c| session(x).cursor(&program, c).map(|c| c.positioned));
69 match (position, host::inputs(x, inputs)?) {
70 (Some(None), _) => Outcome::error(-507, "24501"),
71 (Some(Some(false)), _) => Outcome::error(-508, "24504"),
72 (_, Err(e)) => Outcome::error(e.code, e.state),
73 (_, Ok(values)) => {
74 let mut answer = database(x, &Call { program: &program, ordinal, verb: &verb, cursor: current_of.as_deref(), text: &text, inputs: &values }, |db, c| db.execute(c), pos)?;
75 if current_of.is_none() && answer.sqlcode == 0 && answer.affected == 0 {
77 answer = Outcome::error(100, "02000");
78 }
79 if let (Some(c), true, true) = (¤t_of, *delete, answer.sqlcode >= 0)
80 && let Some(open) = session(x).cursor(&program, c)
81 {
82 open.positioned = false;
83 }
84 answer
85 }
86 }
87 }
88 SqlStatement::Open { cursor, inputs } => {
89 let cursor = x.text(cursor);
90 if session(x).cursor(&program, &cursor).is_some() {
91 Outcome::error(-502, "24502")
92 } else {
93 match host::inputs(x, inputs)? {
94 Err(e) => Outcome::error(e.code, e.state),
95 Ok(values) => {
96 let answer = database(x, &Call { program: &program, ordinal, verb: "OPEN", cursor: Some(&cursor), text: &text, inputs: &values }, |db, c| db.open(c), pos)?;
97 if answer.sqlcode >= 0 {
98 session(x).opened(&program, &cursor, entry.with_hold);
99 }
100 answer
101 }
102 }
103 }
104 }
105 SqlStatement::Fetch { cursor, into } => {
106 let cursor = x.text(cursor);
107 if session(x).cursor(&program, &cursor).is_none() {
108 Outcome::error(-501, "24501")
109 } else {
110 let answer = database(x, &Call { program: &program, ordinal, verb: "FETCH", cursor: Some(&cursor), text: &text, inputs: &[] }, |db, c| db.fetch(c), pos)?;
111 if answer.rows.len() > 1 {
112 return Err(Abend { code: "SQL".into(), message: format!("the database answered FETCH {cursor} with {} rows", answer.rows.len()), pos, file: None });
113 }
114 let on_row = answer.sqlcode >= 0 && answer.rows.len() == 1;
115 if let Some(open) = session(x).cursor(&program, &cursor) {
116 open.positioned = on_row;
117 }
118 let fetched = single_row(x, answer, into, &mut warnings)?;
119 Outcome { affected: i64::from(on_row), ..fetched }
120 }
121 }
122 SqlStatement::Close { cursor } => {
123 let cursor = x.text(cursor);
124 if session(x).cursor(&program, &cursor).is_none() {
125 Outcome::error(-501, "24501")
126 } else {
127 let answer = database(x, &Call { program: &program, ordinal, verb: "CLOSE", cursor: Some(&cursor), text: &text, inputs: &[] }, |db, c| db.close(c), pos)?;
128 if answer.sqlcode >= 0 {
129 session(x).closed(&program, &cursor);
130 }
131 answer
132 }
133 }
134 SqlStatement::Commit if x.in_task() => Outcome::error(-925, "2D521"),
135 SqlStatement::Rollback if x.in_task() => Outcome::error(-926, "2D521"),
136 SqlStatement::Commit => {
137 let answer = database(x, &Call { program: &program, ordinal, verb: "COMMIT", cursor: None, text: &text, inputs: &[] }, |db, c| db.commit(c), pos)?;
138 if answer.sqlcode >= 0 {
139 session(x).committed();
140 }
141 answer
142 }
143 SqlStatement::Rollback => {
144 let answer = database(x, &Call { program: &program, ordinal, verb: "ROLLBACK", cursor: None, text: &text, inputs: &[] }, |db, c| db.rollback(c), pos)?;
145 if answer.sqlcode >= 0 {
146 session(x).rolled_back();
147 }
148 answer
149 }
150 SqlStatement::Declaration => return Ok(None),
151 SqlStatement::Unsupported(what) => return Err(refused(format!("ironwork for COBOL does not run {}", x.text(what)))),
152 };
153 if outcome.sqlcode == DEADLOCK {
154 session(x).rolled_back();
155 }
156 warnings[0] = warnings[1..].iter().any(|&w| w);
157 if outcome.sqlcode == 0 && outcome.sqlstate == "00000" {
158 if warnings[TRUNCATED] {
159 outcome.sqlstate = "01004".into();
160 } else if warnings[COLUMN_COUNT] {
161 outcome.sqlstate = "01503".into();
162 }
163 }
164 host::sqlca(x, sqlca, &outcome, &warnings);
165 Ok(Some(Ran { sqlcode: outcome.sqlcode, warned: warnings[0] }))
166}
167
168fn database<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, call: &Call, run: impl FnOnce(&mut dyn Database, &Call) -> Answer, pos: Pos) -> R<Outcome> {
170 let session = session(x);
171 session.pending |= !matches!(call.verb, "COMMIT" | "ROLLBACK");
172 run(&mut *session.database, call).map_err(|a| Abend { code: a.code.into(), message: a.message, pos, file: None })
173}
174
175fn single_row<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, answer: Outcome, into: &[HostPlace<P>], warnings: &mut Warnings) -> R<Outcome> {
177 if answer.sqlcode < 0 {
178 return Ok(answer);
179 }
180 Ok(match answer.rows.len() {
181 0 => Outcome::error(100, "02000"),
182 1 => match host::assign(x, into, &answer.rows[0], warnings)? {
183 Ok(()) => answer,
184 Err(e) => Outcome::error(e.code, e.state),
185 },
186 _ => Outcome::error(-811, "21000"),
187 })
188}