use super::run::SqlHost;
use super::{HostType, NULL_WITHOUT_INDICATOR, Outcome, ReadError, SqlError, Value, write};
use crate::abend::Abend;
use crate::lir::{Dimension, HostArray, HostPlace, Sqlca, SqlcaField};
use crate::store::ProgramFacts;
use crate::vocab::Pos;
use zarch::ebcdic::CodePage;
type R<T> = Result<T, Abend>;
pub(super) type Warnings = [bool; 11];
pub(super) const TRUNCATED: usize = 1;
pub(super) const COLUMN_COUNT: usize = 3;
pub(super) const RESULT_SETS: usize = 9;
pub(super) struct Target<'a> {
pub offset: usize,
pub len: usize,
pub ty: &'a HostType,
pub indicator: Option<usize>,
}
impl Target<'_> {
fn element(&self, k: usize, array: Option<Dimension>) -> (usize, Option<usize>) {
match array {
Some(d) => (self.offset + k * d.stride as usize, self.indicator.map(|at| at + k * d.indicator_stride as usize)),
None => (self.offset, self.indicator),
}
}
fn value(&self, mem: &[u8], (offset, indicator): (usize, Option<usize>), page: &CodePage, numproc: numeric::Numproc) -> Result<Value, ReadError> {
if let Some(at) = indicator
&& i16::from_be_bytes([mem[at], mem[at + 1]]) < 0
{
return Ok(Value::Null);
}
super::read(&mem[offset..offset + self.len], self.ty, page, numproc)
}
fn write(&self, mem: &mut [u8], (offset, indicator): (usize, Option<usize>), value: &Value, page: &CodePage, warnings: &mut Warnings) -> Result<(), SqlError> {
let set = match value {
Value::Null => match indicator {
Some(_) => -1,
None => return Err(NULL_WITHOUT_INDICATOR),
},
value => {
let written = write(value, &mut mem[offset..offset + self.len], self.ty, page)?;
warnings[TRUNCATED] |= written.truncated_from.is_some();
written.truncated_from.map_or(0, |n| n.min(i16::MAX as usize) as i16)
}
};
if let Some(at) = indicator {
mem[at..at + 2].copy_from_slice(&set.to_be_bytes());
}
Ok(())
}
}
fn targets<'a, 'w, P: Copy + 'a, S>(x: &mut impl SqlHost<'w, P, S>, places: impl IntoIterator<Item = &'a HostPlace<P>>, first: bool) -> R<Vec<Target<'a>>> {
let mut out = Vec::new();
let mut located = None;
for place in places {
let ((offset, len), indicator) = match (place.member, located) {
(Some((at, _)), Some(structure)) if at != 0 => structure,
_ => {
let loc = if first { x.locate_first(place.var)? } else { x.locate(place.var, false)? };
let indicator = match place.indicator {
Some((p, _)) => Some(x.locate_first(p)?.offset),
None => None,
};
located = Some(((loc.offset, loc.len), indicator));
((loc.offset, loc.len), indicator)
}
};
let ty = match &place.ty {
Ok(ty) => ty,
Err(abend) => return Err(x.untyped(*abend)),
};
let (offset, len) = match place.member {
Some((at, size)) => (offset + at as usize, size as usize),
None => (offset, len),
};
let indicator = indicator.zip(place.indicator).map(|(at, (_, element))| at + element as usize);
out.push(Target { offset, len, ty, indicator });
}
Ok(out)
}
pub(super) fn inputs<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, places: &[HostPlace<P>]) -> R<Result<Vec<Value>, SqlError>> {
let targets = targets(x, places, false)?;
let at = places.first().map(|p| x.place_pos(p.var)).unwrap_or_default();
read_targets(x, &targets, at)
}
pub(super) fn read_targets<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, targets: &[Target], at: Pos) -> R<Result<Vec<Value>, SqlError>> {
let facts = x.facts();
let (page, numproc) = (facts.page(), facts.options().numproc);
let mut values = Vec::with_capacity(targets.len());
for t in targets {
let mem = x.mem();
if let Some(at) = t.indicator
&& i16::from_be_bytes([mem[at], mem[at + 1]]) < 0
{
values.push(Value::Null);
continue;
}
match super::read(&mem[t.offset..t.offset + t.len], t.ty, page, numproc) {
Ok(v) => values.push(v),
Err(ReadError::Check(c)) => return Err(Abend::check(c, at)),
Err(ReadError::Sql(e)) => return Ok(Err(e)),
}
}
Ok(Ok(values))
}
pub(super) fn traced<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, places: &[HostPlace<P>]) -> R<Result<Vec<Value>, SqlError>> {
for t in targets(x, places, false)? {
if let Some(taint) = x.taint() {
taint.read(t.offset, t.len);
}
}
inputs(x, places)
}
pub(super) fn assign<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, into: &[HostPlace<P>], row: &[Value], warnings: &mut Warnings) -> R<Result<(), SqlError>> {
let targets = targets(x, into, false)?;
assign_targets(x, &targets, row, warnings)
}
fn array_targets<'a, 'w, P: Copy + 'a, S>(x: &mut impl SqlHost<'w, P, S>, arrays: &'a [HostArray<P>]) -> R<Vec<(Target<'a>, Option<Dimension>)>> {
let targets = targets(x, arrays.iter().map(|a| &a.place), true)?;
Ok(targets.into_iter().zip(arrays.iter().map(|a| a.array)).collect())
}
pub(super) fn input_rows<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, arrays: &[HostArray<P>], rows: usize) -> R<Result<Vec<Vec<Value>>, SqlError>> {
let targets = array_targets(x, arrays)?;
let facts = x.facts();
let (page, numproc) = (facts.page(), facts.options().numproc);
let mut out = Vec::with_capacity(rows);
for k in 0..rows {
let mut row = Vec::with_capacity(targets.len());
for (t, array) in &targets {
match t.value(x.mem(), t.element(k, *array), page, numproc) {
Ok(v) => row.push(v),
Err(ReadError::Check(c)) => return Err(Abend::check(c, x.place_pos(arrays[0].place.var))),
Err(ReadError::Sql(e)) => return Ok(Err(e)),
}
}
out.push(row);
}
Ok(Ok(out))
}
pub(super) fn assign_rows<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, into: &[HostArray<P>], rows: &[Vec<Value>], warnings: &mut Warnings) -> R<(usize, Option<SqlError>)> {
let targets = array_targets(x, into)?;
if rows.first().is_some_and(|row| targets.len() < row.len()) {
warnings[COLUMN_COUNT] = true;
}
let page = x.facts().page();
for (k, row) in rows.iter().enumerate() {
for ((t, array), value) in targets.iter().zip(row) {
let at = t.element(k, *array);
if let Err(e) = t.write(x.mem(), at, value, page, warnings) {
return Ok((k, Some(e)));
}
taint_written(x, t, at);
}
}
Ok((rows.len(), None))
}
pub(super) fn assign_returned<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, args: &[HostPlace<P>], returned: &[Option<Value>], warnings: &mut Warnings) -> R<Result<(), SqlError>> {
let targets = targets(x, args, false)?;
let page = x.facts().page();
for (t, value) in targets.iter().zip(returned) {
let Some(value) = value else { continue };
let at = (t.offset, t.indicator);
if let Err(e) = t.write(x.mem(), at, value, page, warnings) {
return Ok(Err(e));
}
taint_written(x, t, at);
}
Ok(Ok(()))
}
fn taint_written<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, t: &Target, (offset, indicator): (usize, Option<usize>)) {
if let Some(taint) = x.taint() {
taint.set(offset, t.len, true);
if let Some(at) = indicator {
taint.set(at, 2, true);
}
}
}
pub(super) fn assign_targets<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, targets: &[Target], row: &[Value], warnings: &mut Warnings) -> R<Result<(), SqlError>> {
if targets.len() != row.len() {
warnings[COLUMN_COUNT] = true;
}
let page = x.facts().page();
let mem = x.mem();
for (t, value) in targets.iter().zip(row) {
let indicator = match value {
Value::Null => match t.indicator {
Some(_) => -1,
None => return Ok(Err(NULL_WITHOUT_INDICATOR)),
},
value => match write(value, &mut mem[t.offset..t.offset + t.len], t.ty, page) {
Err(e) => return Ok(Err(e)),
Ok(written) => {
warnings[TRUNCATED] |= written.truncated_from.is_some();
written.truncated_from.map_or(0, |n| n.min(i16::MAX as usize) as i16)
}
},
};
if let Some(at) = t.indicator {
mem[at..at + 2].copy_from_slice(&indicator.to_be_bytes());
}
}
if let Some(taint) = x.taint() {
for t in targets {
taint.set(t.offset, t.len, true);
if let Some(at) = t.indicator {
taint.set(at, 2, true);
}
}
}
Ok(Ok(()))
}
pub(super) fn sqlca<'w, P: Copy, S>(x: &mut impl SqlHost<'w, P, S>, sqlca: &Sqlca<P>, o: &Outcome, warnings: &Warnings) {
let page = x.facts().page();
for (field, place, ty) in &sqlca.fields {
let value = match *field {
SqlcaField::CaId => Value::Char("SQLCA".into()),
SqlcaField::CaBc => Value::Int(136),
SqlcaField::Code => Value::Int(o.sqlcode.into()),
SqlcaField::ErrMl => Value::Int(o.tokens.len().min(70) as i64),
SqlcaField::ErrMc => Value::Char(o.tokens.clone()),
SqlcaField::ErrP => Value::Char(String::new()),
SqlcaField::State => Value::Char(o.sqlstate.clone()),
SqlcaField::ErrD(n) => Value::Int(if n == 3 { o.affected } else { 0 }),
SqlcaField::Warn(n) => Value::Char(match warnings[n as usize] {
false => " ",
true if n as usize == RESULT_SETS => "Z",
true => "W",
}.into()),
};
let Ok(loc) = x.locate(*place, false) else { continue };
let _ = write(&value, &mut x.mem()[loc.offset..loc.offset + loc.len], ty, page);
if let Some(t) = x.taint() {
t.set(loc.offset, loc.len, true);
}
}
}