use md5::Md5;
use rupg_common::{Error, Result, SqlState};
use rupg_types::Value;
use sha2::{Digest, Sha224, Sha256, Sha384, Sha512};
use crate::text::like_bytes;
use crate::{Call, Kernel, bad_value};
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
Some(match src {
"byteacat" => byteacat,
"byteaoverlay" | "byteaoverlay_no_len" => byteaoverlay,
"bytea_substr" | "bytea_substr_no_len" => bytea_substr,
"bytea_bit_count" => bytea_bit_count,
"byteapos" => byteapos,
"byteaGetByte" => get_byte,
"byteaGetBit" => get_bit,
"byteaSetByte" => set_byte,
"byteaSetBit" => set_bit,
"bytea_reverse" => bytea_reverse,
"bytea_int2" => bytea_int2,
"bytea_int4" => bytea_int4,
"bytea_int8" => bytea_int8,
"int2_bytea" | "int4_bytea" | "int8_bytea" => int_bytea,
"bytealike" => bytealike,
"byteanlike" => byteanlike,
"like_escape_bytea" => like_escape_bytea,
"byteatrim" => byteatrim,
"bytealtrim" => bytealtrim,
"byteartrim" => byteartrim,
"md5_bytea" => md5_bytea,
"sha224_bytea" => sha224_bytea,
"sha256_bytea" => sha256_bytea,
"sha384_bytea" => sha384_bytea,
"sha512_bytea" => sha512_bytea,
"crc32_bytea" => crc32_bytea,
"crc32c_bytea" => crc32c_bytea,
_ => return None,
})
}
fn bytes(args: &[Value], at: usize) -> Result<&[u8]> {
match args.get(at) {
Some(Value::Bytea(b)) => Ok(b),
_ => Err(bad_value()),
}
}
fn int(args: &[Value], at: usize) -> Result<i64> {
args.get(at).and_then(Value::as_i64).ok_or_else(bad_value)
}
fn negative_length() -> Error {
Error::new(SqlState::SUBSTRING_ERROR, "negative substring length not allowed")
}
fn out_of_index(n: i64, len: i64) -> Error {
Error::new(
SqlState::ARRAY_SUBSCRIPT_ERROR,
format!("index {n} out of valid range, 0..{}", len - 1),
)
}
fn len_i64(b: &[u8]) -> i64 {
i64::try_from(b.len()).unwrap_or(i64::MAX)
}
fn byteacat(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (left, right) = (bytes(args, 0)?, bytes(args, 1)?);
let mut out = Vec::with_capacity(left.len() + right.len());
out.extend_from_slice(left);
out.extend_from_slice(right);
Ok(Value::Bytea(out))
}
fn substring(b: &[u8], start: i64, length: Option<i64>) -> Result<&[u8]> {
let first = start.max(1);
let end = match length {
Some(length) if length < 0 => return Err(negative_length()),
Some(length) => start.saturating_add(length),
None => i64::MAX,
};
if end <= first {
return Ok(&[]);
}
let skip = usize::try_from(first - 1).unwrap_or(usize::MAX).min(b.len());
let take = usize::try_from(end - first).unwrap_or(usize::MAX).min(b.len() - skip);
Ok(&b[skip..skip + take])
}
fn bytea_substr(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let length = if args.len() > 2 { Some(int(args, 2)?) } else { None };
Ok(Value::Bytea(substring(bytes(args, 0)?, int(args, 1)?, length)?.to_vec()))
}
fn byteaoverlay(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (b, placing, from) = (bytes(args, 0)?, bytes(args, 1)?, int(args, 2)?);
let length = if args.len() > 3 { int(args, 3)? } else { len_i64(placing) };
if from <= 0 {
return Err(negative_length());
}
let after = i32::try_from(from + length)
.map_err(|_| Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "integer out of range"))?;
let head = substring(b, 1, Some(from - 1))?;
let tail = substring(b, i64::from(after), None)?;
let mut out = Vec::with_capacity(head.len() + placing.len() + tail.len());
out.extend_from_slice(head);
out.extend_from_slice(placing);
out.extend_from_slice(tail);
Ok(Value::Bytea(out))
}
fn bytea_bit_count(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let ones: u64 = bytes(args, 0)?.iter().map(|b| u64::from(b.count_ones())).sum();
Ok(Value::Int8(i64::try_from(ones).unwrap_or(i64::MAX)))
}
fn byteapos(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (b, pattern) = (bytes(args, 0)?, bytes(args, 1)?);
if pattern.is_empty() {
return Ok(Value::Int4(1));
}
let at = b.windows(pattern.len()).position(|w| w == pattern).map_or(0, |at| at + 1);
Ok(Value::Int4(i32::try_from(at).unwrap_or(i32::MAX)))
}
fn byte_index(b: &[u8], n: i64) -> Result<usize> {
usize::try_from(n).ok().filter(|&at| at < b.len()).ok_or_else(|| out_of_index(n, len_i64(b)))
}
fn bit_index(b: &[u8], n: i64) -> Result<(usize, u32)> {
let bits = len_i64(b).saturating_mul(8);
if n < 0 || n >= bits {
return Err(out_of_index(n, bits));
}
let at = usize::try_from(n / 8).map_err(|_| bad_value())?;
let bit = u32::try_from(n % 8).map_err(|_| bad_value())?;
Ok((at, bit))
}
fn get_byte(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let b = bytes(args, 0)?;
Ok(Value::Int4(i32::from(b[byte_index(b, int(args, 1)?)?])))
}
fn get_bit(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let b = bytes(args, 0)?;
let (at, bit) = bit_index(b, int(args, 1)?)?;
Ok(Value::Int4(i32::from((b[at] >> bit) & 1)))
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn set_byte(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let b = bytes(args, 0)?;
let at = byte_index(b, int(args, 1)?)?;
let mut out = b.to_vec();
out[at] = int(args, 2)? as u8;
Ok(Value::Bytea(out))
}
fn set_bit(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let b = bytes(args, 0)?;
let (at, bit) = bit_index(b, int(args, 1)?)?;
let new_bit = int(args, 2)?;
if new_bit != 0 && new_bit != 1 {
return Err(Error::new(SqlState::INVALID_PARAMETER_VALUE, "new bit must be 0 or 1"));
}
let mut out = b.to_vec();
if new_bit == 0 {
out[at] &= !(1 << bit);
} else {
out[at] |= 1 << bit;
}
Ok(Value::Bytea(out))
}
fn bytea_reverse(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bytea(bytes(args, 0)?.iter().rev().copied().collect()))
}
fn big_endian(args: &[Value], size: usize, name: &str) -> Result<u64> {
let b = bytes(args, 0)?;
if b.len() > size {
return Err(Error::new(
SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
format!("{name} out of range"),
));
}
Ok(b.iter().fold(0, |acc, &byte| acc << 8 | u64::from(byte)))
}
#[allow(clippy::cast_possible_truncation)]
fn bytea_int2(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Int2(big_endian(args, 2, "smallint")? as u16 as i16))
}
#[allow(clippy::cast_possible_truncation)]
fn bytea_int4(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Int4(big_endian(args, 4, "integer")? as u32 as i32))
}
#[allow(clippy::cast_possible_wrap)]
fn bytea_int8(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Int8(big_endian(args, 8, "bigint")? as i64))
}
fn int_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bytea(match args.first() {
Some(Value::Int2(v)) => v.to_be_bytes().to_vec(),
Some(Value::Int4(v)) => v.to_be_bytes().to_vec(),
Some(Value::Int8(v)) => v.to_be_bytes().to_vec(),
_ => return Err(bad_value()),
}))
}
fn bytealike(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(like_bytes(bytes(args, 0)?, bytes(args, 1)?)?))
}
fn byteanlike(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(!like_bytes(bytes(args, 0)?, bytes(args, 1)?)?))
}
fn like_escape_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let (pattern, escape) = (bytes(args, 0)?, bytes(args, 1)?);
let mut out = Vec::with_capacity(pattern.len());
let Some(&e) = escape.first() else {
for &c in pattern {
if c == b'\\' {
out.push(b'\\');
}
out.push(c);
}
return Ok(Value::Bytea(out));
};
if escape.len() > 1 {
return Err(Error::new(SqlState::INVALID_ESCAPE_SEQUENCE, "invalid escape string")
.with_hint("Escape string must be empty or one character."));
}
if e == b'\\' {
return Ok(Value::Bytea(pattern.to_vec()));
}
let mut after_escape = false;
for &c in pattern {
if c == e && !after_escape {
out.push(b'\\');
after_escape = true;
} else if c == b'\\' {
out.push(b'\\');
if !after_escape {
out.push(b'\\');
}
after_escape = false;
} else {
out.push(c);
after_escape = false;
}
}
Ok(Value::Bytea(out))
}
fn trim(args: &[Value], start: bool, end: bool) -> Result<Value> {
let (mut b, set) = (bytes(args, 0)?, bytes(args, 1)?);
if start {
while let Some((_, rest)) = b.split_first().filter(|(c, _)| set.contains(c)) {
b = rest;
}
}
if end {
while let Some((_, rest)) = b.split_last().filter(|(c, _)| set.contains(c)) {
b = rest;
}
}
Ok(Value::Bytea(b.to_vec()))
}
fn byteatrim(_: &Call<'_>, args: &[Value]) -> Result<Value> {
trim(args, true, true)
}
fn bytealtrim(_: &Call<'_>, args: &[Value]) -> Result<Value> {
trim(args, true, false)
}
fn byteartrim(_: &Call<'_>, args: &[Value]) -> Result<Value> {
trim(args, false, true)
}
fn md5_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let digest = Md5::digest(bytes(args, 0)?);
Ok(Value::Text(digest.iter().map(|b| format!("{b:02x}")).collect()))
}
fn sha224_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bytea(Sha224::digest(bytes(args, 0)?).to_vec()))
}
fn sha256_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bytea(Sha256::digest(bytes(args, 0)?).to_vec()))
}
fn sha384_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bytea(Sha384::digest(bytes(args, 0)?).to_vec()))
}
fn sha512_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bytea(Sha512::digest(bytes(args, 0)?).to_vec()))
}
const fn crc_table(poly: u32) -> [u32; 256] {
let mut table = [0u32; 256];
let mut i = 0;
while i < 256 {
#[allow(clippy::cast_possible_truncation)]
let mut crc = i as u32;
let mut bit = 0;
while bit < 8 {
crc = if crc & 1 == 1 { crc >> 1 ^ poly } else { crc >> 1 };
bit += 1;
}
table[i] = crc;
i += 1;
}
table
}
const CRC32: [u32; 256] = crc_table(0xEDB8_8320);
const CRC32C: [u32; 256] = crc_table(0x82F6_3B78);
fn crc(table: &[u32; 256], b: &[u8]) -> Value {
let crc = b
.iter()
.fold(!0u32, |crc, &byte| table[((crc ^ u32::from(byte)) & 0xff) as usize] ^ crc >> 8);
Value::Int8(i64::from(!crc))
}
fn crc32_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(crc(&CRC32, bytes(args, 0)?))
}
fn crc32c_bytea(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(crc(&CRC32C, bytes(args, 0)?))
}