1use super::dynamic::{self, Kind};
6use super::host::{self, COLUMN_COUNT, TRUNCATED, Warnings};
7use super::{Answer, Call, Database, Outcome, Prepared, Session, SqlError, Value};
8use crate::abend::Abend;
9use crate::host::Host;
10use crate::lir::{AbendId, HostPlace, SqlEntry, SqlStatement, Sqlca};
11use crate::storage::Loc;
12use crate::vocab::Pos;
13
14type R<T> = Result<T, Abend>;
15
16const DEADLOCK: i32 = -911;
18
19const STRING_RULE: &str = "its statement string is not one varying-length character or graphic string, as Db2 for z/OS requires of COBOL";
22
23#[derive(Clone, Copy)]
25struct At<'a> {
26 program: &'a str,
27 ordinal: u32,
28 pos: Pos,
29}
30
31impl<'a> At<'a> {
32 fn call(self, verb: &'a str, cursor: Option<&'a str>, text: &'a str, inputs: &'a [Value]) -> Call<'a> {
33 Call { program: self.program, ordinal: self.ordinal, verb, cursor, text, inputs }
34 }
35}
36
37pub trait SqlHost<'w, P: Copy, S>: Host<P> {
39 fn session(&mut self) -> Option<&mut Session<'w>>;
41 fn in_task(&self) -> bool;
43 fn program_id(&self) -> String;
44 fn text(&self, text: &S) -> String;
45 fn place_pos(&self, place: P) -> Pos;
47 fn untyped(&mut self, abend: AbendId) -> Abend;
49 fn sink(&mut self, kind: &'static str, pos: Pos, operand: &str);
51 fn locate_indicator(&mut self, place: P) -> R<Loc> {
54 self.locate(place, false)
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub struct Ran {
61 pub sqlcode: i32,
62 pub warned: bool,
64}
65
66fn session<'a, 'w: 'a, P: Copy + 'a, S: 'a>(x: &'a mut impl SqlHost<'w, P, S>) -> &'a mut Session<'w> {
67 x.session().expect("a database is attached")
68}
69
70pub 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>> {
72 let verb = x.text(&entry.verb);
73 let refused = |why: String| Abend { code: "EXEC".into(), message: format!("EXEC SQL {verb} was reached: {why}"), pos, file: None };
74 if let SqlStatement::Connect { what, location } = &entry.statement {
75 if !location.is_empty()
77 && let Ok(Ok(values)) = host::traced(x, location)
78 {
79 x.sink("connection-target", pos, &values.iter().map(Value::text).collect::<String>());
80 }
81 return Err(refused(format!("ironwork for COBOL does not run {}", x.text(what))));
82 }
83 let string = match &entry.statement {
86 SqlStatement::Prepare { source, .. } | SqlStatement::ExecuteImmediate { source } => match statement_string(x, source, pos)? {
87 None => return Err(refused(STRING_RULE.into())),
88 string => string,
89 },
90 _ => None,
91 };
92 if x.session().is_none() {
93 return Err(refused("no database is attached to the run".into()));
94 }
95 let (program, text, ordinal) = (x.program_id(), x.text(&entry.text), entry.ordinal);
96 let at = At { program: &program, ordinal, pos };
97 let mut warnings = Warnings::default();
98 let mut outcome = match &entry.statement {
99 SqlStatement::Query { inputs, into } => match host::inputs(x, inputs)? {
100 Err(e) => Outcome::error(e.code, e.state),
101 Ok(values) => {
102 let answer = database(x, &Call { program: &program, ordinal, verb: &verb, cursor: None, text: &text, inputs: &values }, |db, c| db.execute(c), pos)?;
103 single_row(x, answer, into, &mut warnings)?
104 }
105 },
106 SqlStatement::Change { delete, inputs, current_of } => {
107 let current_of = current_of.as_ref().map(|c| x.text(c));
108 let values = host::inputs(x, inputs)?;
109 change(x, at, (&verb, &text), values, *delete, current_of.as_deref())?
110 }
111 SqlStatement::Open { cursor, inputs } => {
112 let cursor = x.text(cursor);
113 if session(x).cursor(&program, &cursor).is_some() {
114 Outcome::error(-502, "24502")
115 } else {
116 match host::inputs(x, inputs)? {
117 Err(e) => Outcome::error(e.code, e.state),
118 Ok(values) => {
119 let answer = database(x, &Call { program: &program, ordinal, verb: "OPEN", cursor: Some(&cursor), text: &text, inputs: &values }, |db, c| db.open(c), pos)?;
120 if answer.sqlcode >= 0 {
121 session(x).opened(&program, &cursor, entry.with_hold, None);
122 }
123 answer
124 }
125 }
126 }
127 }
128 SqlStatement::Fetch { cursor, into } => {
129 let cursor = x.text(cursor);
130 if session(x).cursor(&program, &cursor).is_none() {
131 Outcome::error(-501, "24501")
132 } else {
133 let answer = database(x, &Call { program: &program, ordinal, verb: "FETCH", cursor: Some(&cursor), text: &text, inputs: &[] }, |db, c| db.fetch(c), pos)?;
134 if answer.rows.len() > 1 {
135 return Err(Abend { code: "SQL".into(), message: format!("the database answered FETCH {cursor} with {} rows", answer.rows.len()), pos, file: None });
136 }
137 let on_row = answer.sqlcode >= 0 && answer.rows.len() == 1;
138 if let Some(open) = session(x).cursor(&program, &cursor) {
139 open.positioned = on_row;
140 }
141 let fetched = single_row(x, answer, into, &mut warnings)?;
142 Outcome { affected: i64::from(on_row), ..fetched }
143 }
144 }
145 SqlStatement::Close { cursor } => {
146 let cursor = x.text(cursor);
147 if session(x).cursor(&program, &cursor).is_none() {
148 Outcome::error(-501, "24501")
149 } else {
150 let answer = database(x, &Call { program: &program, ordinal, verb: "CLOSE", cursor: Some(&cursor), text: &text, inputs: &[] }, |db, c| db.close(c), pos)?;
151 if answer.sqlcode >= 0 {
152 session(x).closed(&program, &cursor);
153 }
154 answer
155 }
156 }
157 SqlStatement::Commit => end_unit(x, at, &text, true)?,
158 SqlStatement::Rollback => end_unit(x, at, &text, false)?,
159 SqlStatement::Prepare { name, .. } => {
160 let name = x.text(name);
161 match string {
162 None => unreachable!("a PREPARE has its statement string"),
163 Some(Err(e)) => Outcome::error(e.code, e.state),
164 Some(Ok(_)) if session(x).running(&program, &name) => Outcome::error(-519, "24506"),
165 Some(Ok(text)) => {
166 session(x).prepare(&program, &name, None);
167 let kind = dynamic::kind(&text);
168 if kind == Kind::Unacceptable {
169 Outcome::error(-84, "42612")
170 } else {
171 let answer = database(x, &at.call("PREPARE", Some(&name), &text, &[]), |db, c| db.prepare(c), pos)?;
172 if answer.sqlcode >= 0 {
173 let markers = dynamic::markers(&text);
174 session(x).prepare(&program, &name, Some(Prepared { text, query: kind == Kind::Query, markers }));
175 }
176 answer
177 }
178 }
179 }
180 }
181 SqlStatement::ExecuteImmediate { .. } => match string {
182 None => unreachable!("an EXECUTE IMMEDIATE has its statement string"),
183 Some(Err(e)) => Outcome::error(e.code, e.state),
184 Some(Ok(text)) => dynamic_statement(x, at, &text, Ok(Vec::new()), &refused)?,
185 },
186 SqlStatement::Execute { name, inputs } => {
187 let name = x.text(name);
188 match session(x).prepared(&program, &name).cloned() {
189 None | Some(Prepared { query: true, .. }) => Outcome::error(-518, "07003"),
190 Some(p) if p.markers > 0 && p.markers != inputs.len() => Outcome::error(-313, "07001"),
191 Some(p) => {
192 let values = if p.markers == 0 { Ok(Vec::new()) } else { host::inputs(x, inputs)? };
193 dynamic_statement(x, at, &p.text, values, &refused)?
194 }
195 }
196 }
197 SqlStatement::OpenPrepared { cursor, statement, inputs } => {
198 let (cursor, statement) = (x.text(cursor), x.text(statement));
199 if session(x).cursor(&program, &cursor).is_some() {
200 Outcome::error(-502, "24502")
201 } else {
202 match session(x).prepared(&program, &statement).cloned() {
203 None => Outcome::error(-514, "26501"),
204 Some(Prepared { query: false, .. }) => Outcome::error(-517, "07005"),
205 Some(p) if p.markers > 0 && p.markers != inputs.len() => Outcome::error(-313, "07001"),
206 Some(p) => match if p.markers == 0 { Ok(Vec::new()) } else { host::inputs(x, inputs)? } {
207 Err(e) => Outcome::error(e.code, e.state),
208 Ok(values) => {
209 let hold = if entry.with_hold { " WITH HOLD" } else { "" };
210 let text = format!("DECLARE {cursor} CURSOR{hold} FOR {}", p.text);
211 let answer = database(x, &at.call("OPEN", Some(&cursor), &text, &values), |db, c| db.open(c), pos)?;
212 if answer.sqlcode >= 0 {
213 session(x).opened(&program, &cursor, entry.with_hold, Some(&statement));
214 }
215 answer
216 }
217 },
218 }
219 }
220 }
221 SqlStatement::Declaration => return Ok(None),
222 SqlStatement::Unsupported(what) => return Err(refused(format!("ironwork for COBOL does not run {}", x.text(what)))),
223 SqlStatement::Connect { .. } => unreachable!("CONNECT is refused before the session is asked"),
224 };
225 if outcome.sqlcode == DEADLOCK {
226 session(x).rolled_back();
227 }
228 warnings[0] = warnings[1..].iter().any(|&w| w);
229 if outcome.sqlcode == 0 && outcome.sqlstate == "00000" {
230 if warnings[TRUNCATED] {
231 outcome.sqlstate = "01004".into();
232 } else if warnings[COLUMN_COUNT] {
233 outcome.sqlstate = "01503".into();
234 }
235 }
236 host::sqlca(x, sqlca, &outcome, &warnings);
237 Ok(Some(Ran { sqlcode: outcome.sqlcode, warned: warnings[0] }))
238}
239
240fn 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> {
242 let session = session(x);
243 session.pending |= !matches!(call.verb, "COMMIT" | "ROLLBACK");
244 run(&mut *session.database, call).map_err(|a| Abend { code: a.code.into(), message: a.message, pos, file: None })
245}
246
247fn end_unit<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, text: &str, commit: bool) -> R<Outcome> {
249 if x.in_task() {
250 return Ok(if commit { Outcome::error(-925, "2D521") } else { Outcome::error(-926, "2D521") });
251 }
252 let verb = if commit { "COMMIT" } else { "ROLLBACK" };
253 let answer = database(x, &at.call(verb, None, text, &[]), |db, c| if commit { db.commit(c) } else { db.rollback(c) }, at.pos)?;
254 if answer.sqlcode >= 0 {
255 if commit { session(x).committed() } else { session(x).rolled_back() }
256 }
257 Ok(answer)
258}
259
260fn change<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, statement: (&str, &str), values: Result<Vec<Value>, SqlError>, delete: bool, current_of: Option<&str>) -> R<Outcome> {
263 let position = current_of.map(|c| session(x).cursor(at.program, c).map(|c| c.positioned));
264 let values = match (position, values) {
265 (Some(None), _) => return Ok(Outcome::error(-507, "24501")),
266 (Some(Some(false)), _) => return Ok(Outcome::error(-508, "24504")),
267 (_, Err(e)) => return Ok(Outcome::error(e.code, e.state)),
268 (_, Ok(values)) => values,
269 };
270 let mut answer = database(x, &at.call(statement.0, current_of, statement.1, &values), |db, c| db.execute(c), at.pos)?;
271 if current_of.is_none() && answer.sqlcode == 0 && answer.affected == 0 {
272 answer = Outcome::error(100, "02000");
273 }
274 if let (Some(c), true, true) = (current_of, delete, answer.sqlcode >= 0)
275 && let Some(open) = session(x).cursor(at.program, c)
276 {
277 open.positioned = false;
278 }
279 Ok(answer)
280}
281
282fn statement_string<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, source: &[crate::lir::HostPlace<P>], pos: Pos) -> R<Option<Result<String, SqlError>>> {
285 match source {
286 [place] if matches!(place.ty, Ok(super::HostType::VarChar(_) | super::HostType::VarGraphic(_)) | Err(_)) => {}
287 _ => return Ok(None),
288 }
289 Ok(Some(host::traced(x, source)?.map(|values| {
290 let string = values.first().map(Value::text).unwrap_or_default();
291 x.sink("dynamic-sql", pos, &string);
292 dynamic::normalise(&string)
293 })))
294}
295
296fn dynamic_statement<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, text: &str, values: Result<Vec<Value>, SqlError>, refused: &dyn Fn(String) -> Abend) -> R<Outcome> {
298 let verb = dynamic::verb(text);
299 Ok(match dynamic::kind(text) {
300 Kind::Query => Outcome::error(-518, "07003"),
301 Kind::Unacceptable => Outcome::error(-84, "42612"),
302 Kind::Refused(what) => return Err(refused(format!("ironwork for COBOL does not run {what}"))),
303 Kind::Commit => end_unit(x, at, text, true)?,
304 Kind::Rollback => end_unit(x, at, text, false)?,
305 Kind::Change { delete, current_of } => change(x, at, (&verb, text), values, delete, current_of.as_deref())?,
306 Kind::Other => match values {
307 Err(e) => Outcome::error(e.code, e.state),
308 Ok(values) => database(x, &at.call(&verb, None, text, &values), |db, c| db.execute(c), at.pos)?,
309 },
310 })
311}
312
313fn single_row<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, answer: Outcome, into: &[HostPlace<P>], warnings: &mut Warnings) -> R<Outcome> {
315 if answer.sqlcode < 0 {
316 return Ok(answer);
317 }
318 Ok(match answer.rows.len() {
319 0 => Outcome::error(100, "02000"),
320 1 => match host::assign(x, into, &answer.rows[0], warnings)? {
321 Ok(()) => answer,
322 Err(e) => Outcome::error(e.code, e.state),
323 },
324 _ => Outcome::error(-811, "21000"),
325 })
326}