use rupg_common::{Error, Result, SqlState};
use rupg_types::{self as types, BIT_MAX_LEN, Value};
use crate::{Call, Kernel, bad_value, type_error};
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
Some(match src {
"bit_and" => bit_and,
"bit_or" => bit_or,
"bitxor" => bit_xor,
"bitnot" => bit_not,
"bitshiftleft" => shift_left,
"bitshiftright" => shift_right,
"bitcat" => concat,
"bitsubstr" | "bitsubstr_no_len" => substring,
"bitoverlay" | "bitoverlay_no_len" => overlay,
"bitposition" => position,
"bitlength" => bit_length,
"bitoctetlength" => octet_length,
"bitgetbit" => get_bit,
"bitsetbit" => set_bit,
"bit_bit_count" => bit_count,
"bitcmp" => bit_cmp,
"bit" => bit_typmod,
"varbit" => varbit_typmod,
"bitfromint4" | "bitfromint8" => from_integer,
"bittoint4" => to_int4,
"bittoint8" => to_int8,
_ => return None,
})
}
fn bits(args: &[Value], n: usize) -> Result<&str> {
args.get(n).and_then(Value::as_str).ok_or_else(bad_value)
}
fn int4(args: &[Value], n: usize) -> Result<i32> {
match args.get(n) {
Some(Value::Int4(v)) => Ok(*v),
_ => Err(bad_value()),
}
}
fn count(n: usize) -> Value {
Value::Int4(i32::try_from(n).unwrap_or(i32::MAX))
}
fn same_length<'a>(args: &'a [Value], op: &str) -> Result<(&'a str, &'a str)> {
let (a, b) = (bits(args, 0)?, bits(args, 1)?);
if a.len() != b.len() {
return Err(Error::new(
SqlState::STRING_DATA_LENGTH_MISMATCH,
format!("cannot {op} bit strings of different sizes"),
));
}
Ok((a, b))
}
fn bitwise(a: &str, b: &str, op: fn(bool, bool) -> bool) -> Value {
let out =
a.bytes().zip(b.bytes()).map(|(x, y)| if op(x == b'1', y == b'1') { '1' } else { '0' });
Value::Text(out.collect())
}
fn bit_and(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (a, b) = same_length(args, "AND")?;
Ok(bitwise(a, b, |x, y| x && y))
}
fn bit_or(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (a, b) = same_length(args, "OR")?;
Ok(bitwise(a, b, |x, y| x || y))
}
fn bit_xor(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (a, b) = same_length(args, "XOR")?;
Ok(bitwise(a, b, |x, y| x != y))
}
fn bit_not(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let a = bits(args, 0)?;
Ok(Value::Text(a.bytes().map(|x| if x == b'1' { '0' } else { '1' }).collect()))
}
fn shift(a: &str, n: i64) -> Value {
let len = a.len();
let places = usize::try_from(n.unsigned_abs()).unwrap_or(usize::MAX).min(len);
let zeros = "0".repeat(places);
Value::Text(if n >= 0 {
format!("{}{zeros}", &a[places..])
} else {
format!("{zeros}{}", &a[..len - places])
})
}
fn shift_left(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(shift(bits(args, 0)?, i64::from(int4(args, 1)?)))
}
fn shift_right(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(shift(bits(args, 0)?, -i64::from(int4(args, 1)?)))
}
fn concat(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Text(format!("{}{}", bits(args, 0)?, bits(args, 1)?)))
}
fn negative_length() -> Error {
Error::new(SqlState::SUBSTRING_ERROR, "negative substring length not allowed")
}
fn bit_substring(a: &str, start: i32, len: Option<i32>) -> Result<String> {
let bitlen = i64::try_from(a.len()).unwrap_or(i64::MAX);
let s1 = i64::from(start.max(1));
let e1 = match len {
None => bitlen + 1,
Some(len) if len < 0 => return Err(negative_length()),
Some(len) => (i64::from(start) + i64::from(len)).min(bitlen + 1),
};
if s1 > bitlen || e1 <= s1 {
return Ok(String::new());
}
let (Ok(from), Ok(to)) = (usize::try_from(s1 - 1), usize::try_from(e1 - 1)) else {
return Err(bad_value());
};
Ok(a[from..to].to_string())
}
fn substring(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let len = if args.len() > 2 { Some(int4(args, 2)?) } else { None };
bit_substring(bits(args, 0)?, int4(args, 1)?, len).map(Value::Text)
}
fn overlay(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (a, b) = (bits(args, 0)?, bits(args, 1)?);
let start = int4(args, 2)?;
let len =
if args.len() > 3 { int4(args, 3)? } else { i32::try_from(b.len()).unwrap_or(i32::MAX) };
if start <= 0 {
return Err(negative_length());
}
let Some(end) = start.checked_add(len) else {
return Err(Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "integer out of range"));
};
let head = bit_substring(a, 1, Some(start - 1))?;
let tail = bit_substring(a, end, None)?;
Ok(Value::Text(format!("{head}{b}{tail}")))
}
fn position(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (a, b) = (bits(args, 0)?, bits(args, 1)?);
if a.is_empty() || b.len() > a.len() {
return Ok(Value::Int4(0));
}
Ok(a.find(b).map_or(Value::Int4(0), |at| count(at + 1)))
}
pub(crate) fn bit_length(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(count(bits(args, 0)?.len()))
}
fn octet_length(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(count(bits(args, 0)?.len().div_ceil(8)))
}
fn bit_index(a: &str, n: i32) -> Result<usize> {
usize::try_from(n).ok().filter(|&i| i < a.len()).ok_or_else(|| {
Error::new(
SqlState::ARRAY_SUBSCRIPT_ERROR,
format!(
"bit index {n} out of valid range (0..{})",
i64::try_from(a.len()).unwrap_or(i64::MAX) - 1
),
)
})
}
fn get_bit(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let a = bits(args, 0)?;
let i = bit_index(a, int4(args, 1)?)?;
Ok(Value::Int4(i32::from(a.as_bytes()[i] == b'1')))
}
fn set_bit(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let a = bits(args, 0)?;
let i = bit_index(a, int4(args, 1)?)?;
let new = match int4(args, 2)? {
0 => "0",
1 => "1",
_ => return Err(Error::new(SqlState::INVALID_PARAMETER_VALUE, "new bit must be 0 or 1")),
};
Ok(Value::Text(format!("{}{new}{}", &a[..i], &a[i + 1..])))
}
fn bit_count(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let ones = bits(args, 0)?.bytes().filter(|&c| c == b'1').count();
Ok(Value::Int8(i64::try_from(ones).unwrap_or(i64::MAX)))
}
fn bit_cmp(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (a, b) = (bits(args, 0)?, bits(args, 1)?);
let (x, y) = (types::bit_bytes(a), types::bit_bytes(b));
let diff = x.iter().zip(&y).find(|(p, q)| p != q).map(|(p, q)| i32::from(*p) - i32::from(*q));
Ok(Value::Int4(diff.unwrap_or(match a.len().cmp(&b.len()) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
})))
}
fn typmod_args(args: &[Value]) -> Result<(i32, bool)> {
let explicit = args.get(2).and_then(Value::as_bool).ok_or_else(bad_value)?;
Ok((int4(args, 1)?, explicit))
}
fn bit_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (typmod, explicit) = typmod_args(args)?;
types::bit_coerce(bits(args, 0)?, typmod, false, explicit).map(Value::Text).map_err(type_error)
}
fn varbit_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (typmod, explicit) = typmod_args(args)?;
types::bit_coerce(bits(args, 0)?, typmod, true, explicit).map(Value::Text).map_err(type_error)
}
fn from_integer(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let a = args.first().and_then(Value::as_i64).ok_or_else(bad_value)?;
let len =
usize::try_from(int4(args, 1)?).ok().filter(|&n| n > 0 && n <= BIT_MAX_LEN).unwrap_or(1);
let out = (0..len).rev().map(|place| {
let set = if place >= 64 { a < 0 } else { (a >> place) & 1 == 1 };
if set { '1' } else { '0' }
});
Ok(Value::Text(out.collect()))
}
fn unsigned(a: &str, max: usize) -> Option<u64> {
(a.len() <= max).then(|| a.bytes().fold(0, |v, c| (v << 1) | u64::from(c == b'1')))
}
fn to_int4(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let v = unsigned(bits(args, 0)?, 32)
.and_then(|v| u32::try_from(v).ok())
.ok_or_else(|| Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "integer out of range"))?;
Ok(Value::Int4(v.cast_signed()))
}
fn to_int8(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let v = unsigned(bits(args, 0)?, 64)
.ok_or_else(|| Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "bigint out of range"))?;
Ok(Value::Int8(v.cast_signed()))
}