1use super::dynamic::{self, Kind};
6use super::host::{self, COLUMN_COUNT, RESULT_SETS, TRUNCATED, Target, Warnings};
7use super::sqlda::{self, Invalid, Var};
8use super::{Answer, Call, Database, Outcome, Prepared, Session, SqlError, Value};
9use crate::abend::Abend;
10use crate::host::Host;
11use crate::store::ProgramFacts;
12use crate::lir::{AbendId, HostArray, HostPlace, RowCount, SqlEntry, SqlNames, SqlStatement, Sqlca};
13use crate::storage::Loc;
14use crate::vocab::Pos;
15
16type R<T> = Result<T, Abend>;
17
18const DEADLOCK: i32 = -911;
20
21const STRING_RULE: &str = "its statement string is not one varying-length character or graphic string, as Db2 for z/OS requires of COBOL";
24
25const ROWS_RULE: &str = "FOR n ROWS's host variable is not an exact numeric item with no decimal places";
28
29const MOST_ROWS: u32 = 32767;
31
32#[derive(Clone, Copy)]
34struct At<'a> {
35 program: &'a str,
36 ordinal: u32,
37 pos: Pos,
38}
39
40impl<'a> At<'a> {
41 fn call(self, verb: &'a str, cursor: Option<&'a str>, text: &'a str, inputs: &'a [Value]) -> Call<'a> {
42 Call { program: self.program, ordinal: self.ordinal, verb, cursor, text, inputs }
43 }
44}
45
46pub trait SqlHost<'w, P: Copy, S>: Host<P> {
48 fn session(&mut self) -> Option<&mut Session<'w>>;
50 fn in_task(&self) -> bool;
52 fn program_id(&self) -> String;
53 fn text(&self, text: &S) -> String;
54 fn place_pos(&self, place: P) -> Pos;
56 fn untyped(&mut self, abend: AbendId) -> Abend;
58 fn sink(&mut self, kind: &'static str, pos: Pos, operand: &str);
60 fn locate_first(&mut self, place: P) -> R<Loc> {
64 self.locate(place, false)
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct Ran {
71 pub sqlcode: i32,
72 pub warned: bool,
74}
75
76fn session<'a, 'w: 'a, P: Copy + 'a, S: 'a>(x: &'a mut impl SqlHost<'w, P, S>) -> &'a mut Session<'w> {
77 x.session().expect("a database is attached")
78}
79
80pub 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>> {
82 let verb = x.text(&entry.verb);
83 let refused = |why: String| Abend { code: "EXEC".into(), message: format!("EXEC SQL {verb} was reached: {why}"), pos, file: None };
84 if let SqlStatement::Connect { what, location } = &entry.statement {
85 if !location.is_empty()
87 && let Ok(Ok(values)) = host::traced(x, location)
88 {
89 x.sink("connection-target", pos, &values.iter().map(Value::text).collect::<String>());
90 }
91 return Err(refused(format!("ironwork for COBOL does not run {}", x.text(what))));
92 }
93 let string = match &entry.statement {
96 SqlStatement::Prepare { source, .. } | SqlStatement::PrepareInto { source, .. } | SqlStatement::ExecuteImmediate { source } => match statement_string(x, source, pos)? {
97 None => return Err(refused(STRING_RULE.into())),
98 string => string,
99 },
100 _ => None,
101 };
102 if x.session().is_none() {
103 return Err(refused("no database is attached to the run".into()));
104 }
105 let (program, text, ordinal) = (x.program_id(), x.text(&entry.text), entry.ordinal);
106 let at = At { program: &program, ordinal, pos };
107 let mut warnings = Warnings::default();
108 let mut outcome = match &entry.statement {
109 SqlStatement::Query { inputs, into } => match host::inputs(x, inputs)? {
110 Err(e) => Outcome::error(e.code, e.state),
111 Ok(values) => {
112 let answer = database(x, &Call { program: &program, ordinal, verb: &verb, cursor: None, text: &text, inputs: &values }, |db, c| db.execute(c), pos)?;
113 single_row(x, answer, Receivers::Hosts(into), &mut warnings)?
114 }
115 },
116 SqlStatement::Change { delete, inputs, current_of } => {
117 let current_of = current_of.as_ref().map(|c| x.text(c));
118 let values = host::inputs(x, inputs)?;
119 change(x, at, (&verb, &text), values, *delete, current_of.as_deref())?
120 }
121 SqlStatement::Open { cursor, inputs } => {
122 let cursor = x.text(cursor);
123 if session(x).cursor(&program, &cursor).is_some() {
124 Outcome::error(-502, "24502")
125 } else {
126 match host::inputs(x, inputs)? {
127 Err(e) => Outcome::error(e.code, e.state),
128 Ok(values) => {
129 let answer = database(x, &Call { program: &program, ordinal, verb: "OPEN", cursor: Some(&cursor), text: &text, inputs: &values }, |db, c| db.open(c), pos)?;
130 if answer.sqlcode >= 0 {
131 session(x).opened(&program, &cursor, entry.with_hold, None);
132 }
133 answer
134 }
135 }
136 }
137 }
138 SqlStatement::Fetch { cursor, into } => {
139 let cursor = x.text(cursor);
140 fetch(x, at, (&cursor, &text), Receivers::Hosts(into), &mut warnings)?
141 }
142 SqlStatement::FetchDescriptor { cursor, descriptor } => {
143 let cursor = x.text(cursor);
144 let at_sqlda = x.locate(*descriptor, false)?.offset;
145 match sqlda::vars(x.mem(), at_sqlda, false) {
146 Err(invalid) => invalid_sqlda(invalid),
147 Ok(vars) => fetch(x, at, (&cursor, &text), Receivers::Vars(&vars), &mut warnings)?,
148 }
149 }
150 SqlStatement::FetchRowset { cursor, rows, into, enabled } => {
151 let cursor = x.text(cursor);
152 if session(x).cursor(&program, &cursor).is_none() {
153 Outcome::error(-501, "24501")
154 } else if !enabled {
155 Outcome::error(-249, "24523")
156 } else {
157 let implicit = session(x).cursor(&program, &cursor).and_then(|c| c.rowset_size).unwrap_or(1);
158 match row_count(x, rows, implicit, &refused)? {
159 Some(k) if fits(k, into.iter()) => fetch_rowset(x, at, &cursor, &text, k, into, &mut warnings)?,
160 _ => Outcome::error(-246, "42873"),
161 }
162 }
163 }
164 SqlStatement::InsertRows { inputs, rows, atomic } => match row_count(x, rows, 1, &refused)? {
165 Some(k) if fits(k, inputs.iter()) => match host::input_rows(x, inputs, k as usize)? {
166 Err(e) => Outcome::error(e.code, e.state),
167 Ok(values) => {
168 let flat = values.concat();
169 database(x, &at.call(&verb, None, &text, &flat), |db, c| db.insert_rows(c, &values, *atomic), pos)?
170 }
171 },
172 _ => Outcome::error(-246, "42873"),
173 },
174 SqlStatement::Call { procedure, args } => {
175 let procedure = x.text(procedure);
176 match host::inputs(x, args)? {
177 Err(e) => Outcome::error(e.code, e.state),
178 Ok(values) => {
179 let answer = database(x, &at.call("CALL", Some(&procedure), &text, &values), |db, c| db.call(c), pos)?;
180 returned(x, answer, &procedure, args, &mut warnings, pos)?
181 }
182 }
183 }
184 SqlStatement::Close { cursor } => {
185 let cursor = x.text(cursor);
186 if session(x).cursor(&program, &cursor).is_none() {
187 Outcome::error(-501, "24501")
188 } else {
189 let answer = database(x, &Call { program: &program, ordinal, verb: "CLOSE", cursor: Some(&cursor), text: &text, inputs: &[] }, |db, c| db.close(c), pos)?;
190 if answer.sqlcode >= 0 {
191 session(x).closed(&program, &cursor);
192 }
193 answer
194 }
195 }
196 SqlStatement::Commit => end_unit(x, at, &text, true)?,
197 SqlStatement::Rollback => end_unit(x, at, &text, false)?,
198 SqlStatement::Prepare { name, .. } => {
199 let (name, Some(string)) = (x.text(name), string) else { unreachable!("a PREPARE has its statement string") };
200 prepare(x, at, &name, string)?
201 }
202 SqlStatement::PrepareInto { name, descriptor, names, .. } => {
203 let (name, Some(string)) = (x.text(name), string) else { unreachable!("a PREPARE has its statement string") };
204 let answer = prepare(x, at, &name, string)?;
205 match session(x).prepared(&program, &name).cloned().filter(|_| answer.sqlcode >= 0) {
206 Some(p) => {
207 let described = describe(x, &p, *descriptor, *names, pos)?;
208 if described.sqlcode < 0 { described } else { answer }
209 }
210 None => answer,
211 }
212 }
213 SqlStatement::Describe { name, descriptor, names } => {
214 let name = x.text(name);
215 match session(x).prepared(&program, &name).cloned() {
216 None => Outcome::error(-516, "26501"),
217 Some(p) => describe(x, &p, *descriptor, *names, pos)?,
218 }
219 }
220 SqlStatement::ExecuteImmediate { .. } => match string {
221 None => unreachable!("an EXECUTE IMMEDIATE has its statement string"),
222 Some(Err(e)) => Outcome::error(e.code, e.state),
223 Some(Ok(text)) => dynamic_statement(x, at, &text, Ok(Vec::new()), &refused)?,
224 },
225 SqlStatement::Execute { name, inputs } => execute(x, at, &x.text(name), Sources::Hosts(inputs), &refused)?,
226 SqlStatement::ExecuteDescriptor { name, descriptor } => execute(x, at, &x.text(name), Sources::Descriptor(*descriptor), &refused)?,
227 SqlStatement::OpenPrepared { cursor, statement, inputs } => open_prepared(x, at, (&x.text(cursor), &x.text(statement)), entry.with_hold, Sources::Hosts(inputs))?,
228 SqlStatement::OpenDescriptor { cursor, statement, descriptor } => open_prepared(x, at, (&x.text(cursor), &x.text(statement)), entry.with_hold, Sources::Descriptor(*descriptor))?,
229 SqlStatement::Declaration => return Ok(None),
230 SqlStatement::Unsupported(what) => return Err(refused(format!("ironwork for COBOL does not run {}", x.text(what)))),
231 SqlStatement::Connect { .. } => unreachable!("CONNECT is refused before the session is asked"),
232 };
233 if outcome.sqlcode == DEADLOCK {
234 session(x).rolled_back();
235 }
236 warnings[0] = warnings[1..].iter().any(|&w| w);
237 if outcome.sqlcode == 0 && outcome.sqlstate == "00000" {
238 if warnings[TRUNCATED] {
239 outcome.sqlstate = "01004".into();
240 } else if warnings[COLUMN_COUNT] {
241 outcome.sqlstate = "01503".into();
242 }
243 }
244 host::sqlca(x, sqlca, &outcome, &warnings);
245 Ok(Some(Ran { sqlcode: outcome.sqlcode, warned: warnings[0] }))
246}
247
248fn 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> {
250 let session = session(x);
251 session.pending |= !matches!(call.verb, "COMMIT" | "ROLLBACK");
252 run(&mut *session.database, call).map_err(|a| Abend { code: a.code.into(), message: a.message, pos, file: None })
253}
254
255fn end_unit<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, text: &str, commit: bool) -> R<Outcome> {
257 if x.in_task() {
258 return Ok(if commit { Outcome::error(-925, "2D521") } else { Outcome::error(-926, "2D521") });
259 }
260 let verb = if commit { "COMMIT" } else { "ROLLBACK" };
261 let answer = database(x, &at.call(verb, None, text, &[]), |db, c| if commit { db.commit(c) } else { db.rollback(c) }, at.pos)?;
262 if answer.sqlcode >= 0 {
263 if commit { session(x).committed() } else { session(x).rolled_back() }
264 }
265 Ok(answer)
266}
267
268fn 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> {
271 if let Some(c) = current_of
274 && session(x).cursor(at.program, c).is_some_and(|c| c.positioned && !c.on_backend_row())
275 {
276 let message = format!("EXEC SQL {} ... WHERE CURRENT OF {c} was reached: ironwork for COBOL does not run a positioned UPDATE or DELETE of a rowset of more than one row, or after a row FETCH from one", statement.0);
277 return Err(Abend { code: "EXEC".into(), message, pos: at.pos, file: None });
278 }
279 let position = current_of.map(|c| session(x).cursor(at.program, c).map(|c| c.positioned));
280 let values = match (position, values) {
281 (Some(None), _) => return Ok(Outcome::error(-507, "24501")),
282 (Some(Some(false)), _) => return Ok(Outcome::error(-508, "24504")),
283 (_, Err(e)) => return Ok(Outcome::error(e.code, e.state)),
284 (_, Ok(values)) => values,
285 };
286 let mut answer = database(x, &at.call(statement.0, current_of, statement.1, &values), |db, c| db.execute(c), at.pos)?;
287 if current_of.is_none() && answer.sqlcode == 0 && answer.affected == 0 {
288 answer = Outcome::error(100, "02000");
289 }
290 if let (Some(c), true, true) = (current_of, delete, answer.sqlcode >= 0)
291 && let Some(open) = session(x).cursor(at.program, c)
292 {
293 open.positioned = false;
294 open.held.clear();
295 open.current = 0;
296 }
297 Ok(answer)
298}
299fn row_count<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, rows: &RowCount<P>, implicit: u32, refused: &dyn Fn(String) -> Abend) -> R<Option<u32>> {
301 let n = match rows {
302 RowCount::Implicit => return Ok(Some(implicit)),
303 RowCount::Constant(n) => i64::from(*n),
304 RowCount::Host(place) => match host::inputs(x, std::slice::from_ref(place))? {
305 Ok(values) => match values.first() {
306 Some(Value::Int(n)) => *n,
307 Some(Value::Decimal { value, scale: 0 }) => i64::try_from(*value).unwrap_or(i64::MAX),
308 _ => return Err(refused(ROWS_RULE.into())),
309 },
310 Err(_) => return Err(refused(ROWS_RULE.into())),
311 },
312 };
313 Ok(u32::try_from(n).ok().filter(|&k| (1..=MOST_ROWS).contains(&k)))
314}
315
316fn fits<'a, P: 'a>(rows: u32, arrays: impl IntoIterator<Item = &'a HostArray<P>>) -> bool {
318 arrays.into_iter().all(|a| a.array.is_none_or(|d| rows <= d.count))
319}
320
321fn fetch_rowset<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, cursor: &str, text: &str, rows: u32, into: &[HostArray<P>], warnings: &mut Warnings) -> R<Outcome> {
325 let open = session(x).cursor(at.program, cursor).expect("the cursor is open");
326 open.held.drain(..open.current);
327 let mut fetched: Vec<Vec<Value>> = open.held.iter().take(rows as usize).cloned().collect();
328 let mut answer = Outcome::ok();
329 if fetched.len() < rows as usize {
330 let more = rows - fetched.len() as u32;
331 let count = [Value::Int(more.into())];
332 answer = database(x, &at.call("FETCH", Some(cursor), text, &count), |db, c| db.fetch_rows(c, more), at.pos)?;
333 if answer.rows.len() > more as usize {
334 return Err(Abend { code: "SQL".into(), message: format!("the database answered FETCH {cursor} for {more} rows with {}", answer.rows.len()), pos: at.pos, file: None });
335 }
336 let open = session(x).cursor(at.program, cursor).expect("the cursor is open");
337 open.held.extend(answer.rows.iter().cloned());
338 fetched.append(&mut answer.rows);
339 }
340 let open = session(x).cursor(at.program, cursor).expect("the cursor is open");
341 open.current = fetched.len();
342 open.rowset_size = Some(rows);
343 open.positioned = !fetched.is_empty();
344 let (assigned, failed) = if into.is_empty() { (fetched.len(), None) } else { host::assign_rows(x, into, &fetched, warnings)? };
345 let outcome = match failed {
346 Some(e) => Outcome::error(e.code, e.state),
347 None if answer.sqlcode != 0 => answer,
348 None if fetched.len() < rows as usize => Outcome::error(100, "02000"),
349 None => Outcome::ok(),
350 };
351 Ok(Outcome { affected: assigned as i64, rows: Vec::new(), ..outcome })
352}
353
354fn returned<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, answer: Outcome, procedure: &str, args: &[HostPlace<P>], warnings: &mut Warnings, pos: Pos) -> R<Outcome> {
357 if answer.sqlcode < 0 || answer.parameters.is_empty() {
358 warnings[RESULT_SETS] = answer.sqlcode == 466;
359 return Ok(answer);
360 }
361 if answer.parameters.len() != args.len() {
362 return Err(Abend { code: "SQL".into(), message: format!("the database answered CALL {procedure} with {} arguments for its {}", answer.parameters.len(), args.len()), pos, file: None });
363 }
364 warnings[RESULT_SETS] = answer.sqlcode == 466;
365 Ok(match host::assign_returned(x, args, &answer.parameters, warnings)? {
366 Ok(()) => answer,
367 Err(e) => Outcome::error(e.code, e.state),
368 })
369}
370
371fn 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>>> {
374 match source {
375 [place] if matches!(place.ty, Ok(super::HostType::VarChar(_) | super::HostType::VarGraphic(_)) | Err(_)) => {}
376 _ => return Ok(None),
377 }
378 Ok(Some(host::traced(x, source)?.map(|values| {
379 let string = values.first().map(Value::text).unwrap_or_default();
380 x.sink("dynamic-sql", pos, &string);
381 dynamic::normalise(&string)
382 })))
383}
384
385fn 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> {
387 let verb = dynamic::verb(text);
388 Ok(match dynamic::kind(text) {
389 Kind::Query => Outcome::error(-518, "07003"),
390 Kind::Unacceptable => Outcome::error(-84, "42612"),
391 Kind::Unknown => Outcome::error(-104, "42601"),
392 Kind::Refused(what) => return Err(refused(format!("ironwork for COBOL does not run {what}"))),
393 Kind::Commit => end_unit(x, at, text, true)?,
394 Kind::Rollback => end_unit(x, at, text, false)?,
395 Kind::Change { delete, current_of } => change(x, at, (&verb, text), values, delete, current_of.as_deref())?,
396 Kind::Other => match values {
397 Err(e) => Outcome::error(e.code, e.state),
398 Ok(values) => database(x, &at.call(&verb, None, text, &values), |db, c| db.execute(c), at.pos)?,
399 },
400 })
401}
402
403enum Sources<'a, P> {
405 Hosts(&'a [HostPlace<P>]),
406 Descriptor(P),
407}
408
409enum Receivers<'a, P> {
411 Hosts(&'a [HostPlace<P>]),
412 Vars(&'a [Var]),
413}
414
415fn targets(vars: &[Var]) -> Vec<Target<'_>> {
416 vars.iter().map(|v| Target { offset: v.offset, len: v.len, ty: &v.ty, indicator: v.indicator }).collect()
417}
418
419fn invalid_sqlda(Invalid(reason): Invalid) -> Outcome {
421 Outcome { tokens: format!("{reason:02}"), ..Outcome::error(sqlda::INVALID.code, sqlda::INVALID.state) }
422}
423
424fn marker_values<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, sources: Sources<P>, markers: usize, pos: Pos) -> R<Result<Result<Vec<Value>, SqlError>, Outcome>> {
427 if markers == 0 {
428 return Ok(Ok(Ok(Vec::new())));
429 }
430 match sources {
431 Sources::Hosts(hosts) if hosts.len() != markers => Ok(Err(Outcome::error(-313, "07001"))),
432 Sources::Hosts(hosts) => Ok(Ok(host::inputs(x, hosts)?)),
433 Sources::Descriptor(descriptor) => {
434 let at = x.locate(descriptor, false)?.offset;
435 match sqlda::vars(x.mem(), at, true) {
436 Err(invalid) => Ok(Err(invalid_sqlda(invalid))),
437 Ok(vars) if vars.len() != markers => Ok(Err(Outcome::error(-313, "07001"))),
438 Ok(vars) => Ok(Ok(host::read_targets(x, &targets(&vars), pos)?)),
439 }
440 }
441 }
442}
443
444fn prepare<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, name: &str, string: Result<String, SqlError>) -> R<Outcome> {
447 let text = match string {
448 Err(e) => return Ok(Outcome::error(e.code, e.state)),
449 Ok(text) => text,
450 };
451 if session(x).running(at.program, name) {
452 return Ok(Outcome::error(-519, "24506"));
453 }
454 session(x).prepare(at.program, name, None);
455 let kind = dynamic::kind(&text);
456 match kind {
457 Kind::Unacceptable => return Ok(Outcome::error(-84, "42612")),
458 Kind::Unknown => return Ok(Outcome::error(-104, "42601")),
459 _ => {}
460 }
461 let mut answer = database(x, &at.call("PREPARE", Some(name), &text, &[]), |db, c| db.prepare(c), at.pos)?;
462 if answer.sqlcode >= 0 {
463 let markers = dynamic::markers(&text);
464 let columns = std::mem::take(&mut answer.columns);
465 session(x).prepare(at.program, name, Some(Prepared { text, query: kind == Kind::Query, markers, columns }));
466 }
467 Ok(answer)
468}
469
470fn describe<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, prepared: &Prepared, descriptor: P, names: SqlNames, pos: Pos) -> R<Outcome> {
473 let at = x.locate(descriptor, true)?.offset;
474 let page = x.facts().page();
475 let columns = prepared.query.then_some(prepared.columns.as_slice());
476 match sqlda::describe(x.mem(), at, columns, names, page) {
477 Ok(written) => {
478 if let Some(taint) = x.taint() {
479 taint.set(at, written, true);
480 }
481 Ok(Outcome::ok())
482 }
483 Err(Ok(invalid)) => Ok(invalid_sqlda(invalid)),
484 Err(Err(why)) => Err(Abend { code: "SQL".into(), message: format!("DESCRIBE was reached: {why}"), pos, file: None }),
485 }
486}
487
488fn execute<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, name: &str, sources: Sources<P>, refused: &dyn Fn(String) -> Abend) -> R<Outcome> {
489 let Some(p) = session(x).prepared(at.program, name).cloned().filter(|p| !p.query) else { return Ok(Outcome::error(-518, "07003")) };
490 match marker_values(x, sources, p.markers, at.pos)? {
491 Err(outcome) => Ok(outcome),
492 Ok(values) => dynamic_statement(x, at, &p.text, values, refused),
493 }
494}
495
496fn open_prepared<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, names: (&str, &str), with_hold: bool, sources: Sources<P>) -> R<Outcome> {
498 let (cursor, statement) = names;
499 if session(x).cursor(at.program, cursor).is_some() {
500 return Ok(Outcome::error(-502, "24502"));
501 }
502 let p = match session(x).prepared(at.program, statement).cloned() {
503 None => return Ok(Outcome::error(-514, "26501")),
504 Some(Prepared { query: false, .. }) => return Ok(Outcome::error(-517, "07005")),
505 Some(p) => p,
506 };
507 let values = match marker_values(x, sources, p.markers, at.pos)? {
508 Err(outcome) => return Ok(outcome),
509 Ok(Err(e)) => return Ok(Outcome::error(e.code, e.state)),
510 Ok(Ok(values)) => values,
511 };
512 let hold = if with_hold { " WITH HOLD" } else { "" };
513 let text = format!("DECLARE {cursor} CURSOR{hold} FOR {}", p.text);
514 let answer = database(x, &at.call("OPEN", Some(cursor), &text, &values), |db, c| db.open(c), at.pos)?;
515 if answer.sqlcode >= 0 {
516 session(x).opened(at.program, cursor, with_hold, Some(statement));
517 }
518 Ok(answer)
519}
520
521fn fetch<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, at: At, statement: (&str, &str), into: Receivers<P>, warnings: &mut Warnings) -> R<Outcome> {
523 let (cursor, text) = statement;
524 let held = session(x).cursor(at.program, cursor).map(|open| {
527 if open.current > 0 {
528 open.held.pop_front();
529 }
530 open.current = 0;
531 open.rowset_size = None;
532 open.held.front().cloned()
533 });
534 let answer = match held {
535 None => return Ok(Outcome::error(-501, "24501")),
536 Some(Some(row)) => Outcome::rows(vec![row]),
537 Some(None) => database(x, &at.call("FETCH", Some(cursor), text, &[]), |db, c| db.fetch(c), at.pos)?,
538 };
539 if answer.rows.len() > 1 {
540 return Err(Abend { code: "SQL".into(), message: format!("the database answered FETCH {cursor} with {} rows", answer.rows.len()), pos: at.pos, file: None });
541 }
542 let on_row = answer.sqlcode >= 0 && answer.rows.len() == 1;
543 if let Some(open) = session(x).cursor(at.program, cursor) {
544 open.positioned = on_row;
545 if on_row {
546 if open.held.is_empty() {
547 open.held.push_back(answer.rows[0].clone());
548 }
549 open.current = 1;
550 }
551 }
552 let fetched = single_row(x, answer, into, warnings)?;
553 Ok(Outcome { affected: i64::from(on_row), ..fetched })
554}
555
556fn single_row<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, answer: Outcome, into: Receivers<P>, warnings: &mut Warnings) -> R<Outcome> {
558 if answer.sqlcode < 0 {
559 return Ok(answer);
560 }
561 let assigned = |x: &mut _, row: &[Value], warnings: &mut Warnings| match into {
562 Receivers::Hosts(hosts) => host::assign(x, hosts, row, warnings),
563 Receivers::Vars(vars) => host::assign_targets(x, &targets(vars), row, warnings),
564 };
565 Ok(match answer.rows.len() {
566 0 => Outcome::error(100, "02000"),
567 1 => match assigned(x, &answer.rows[0], warnings)? {
568 Ok(()) => answer,
569 Err(e) => Outcome::error(e.code, e.state),
570 },
571 _ => Outcome::error(-811, "21000"),
572 })
573}