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