use rupg_common::SqlState;
use crate::binary::Recv;
use crate::error::TypeError;
pub const BIT_MAX_LEN: usize = 2_147_483_640;
fn bit_type(varying: bool, typmod: i32) -> String {
if varying { format!("bit varying({typmod})") } else { format!("bit({typmod})") }
}
fn length_error(len: usize, typmod: i32, varying: bool) -> TypeError {
if varying {
TypeError::new(
SqlState::STRING_DATA_RIGHT_TRUNCATION,
format!("bit string too long for type {}", bit_type(true, typmod)),
)
} else {
TypeError::new(
SqlState::STRING_DATA_LENGTH_MISMATCH,
format!("bit string length {len} does not match type {}", bit_type(false, typmod)),
)
}
}
fn check_length(len: usize, typmod: i32, varying: bool) -> Result<(), TypeError> {
let Ok(limit) = usize::try_from(typmod) else { return Ok(()) };
if limit == 0 || len == limit || varying && len < limit {
return Ok(());
}
Err(length_error(len, typmod, varying))
}
fn char_at(s: &str, at: usize) -> &str {
let len = s[at..].chars().next().map_or(0, char::len_utf8);
&s[at..at + len]
}
pub fn bit_in(s: &str, typmod: i32, varying: bool) -> Result<String, TypeError> {
let (hex, digits) = match s.as_bytes().first() {
Some(b'b' | b'B') => (false, &s[1..]),
Some(b'x' | b'X') => (true, &s[1..]),
_ => (false, s),
};
let start = s.len() - digits.len();
let len = if hex {
if digits.len() > BIT_MAX_LEN / 4 {
return Err(TypeError::new(
SqlState::PROGRAM_LIMIT_EXCEEDED,
format!("bit string length exceeds the maximum allowed ({BIT_MAX_LEN})"),
));
}
digits.len() * 4
} else {
digits.len()
};
check_length(len, typmod, varying)?;
let mut out = String::with_capacity(len);
for (i, c) in digits.bytes().enumerate() {
if hex {
let Some(v) = char::from(c).to_digit(16).filter(|_| c.is_ascii()) else {
return Err(TypeError::new(
SqlState::INVALID_TEXT_REPRESENTATION,
format!("\"{}\" is not a valid hexadecimal digit", char_at(s, start + i)),
));
};
out.push_str(&format!("{v:04b}"));
} else if c == b'0' || c == b'1' {
out.push(char::from(c));
} else {
return Err(TypeError::new(
SqlState::INVALID_TEXT_REPRESENTATION,
format!("\"{}\" is not a valid binary digit", char_at(s, start + i)),
));
}
}
Ok(out)
}
pub fn bit_coerce(
bits: &str,
typmod: i32,
varying: bool,
explicit: bool,
) -> Result<String, TypeError> {
if check_length(bits.len(), typmod, varying).is_ok() {
return Ok(bits.to_string());
}
if !explicit {
return Err(length_error(bits.len(), typmod, varying));
}
let len = usize::try_from(typmod).unwrap_or(0);
let mut out: String = bits.chars().take(len).collect();
while out.len() < len {
out.push('0');
}
Ok(out)
}
pub fn bit_recv(recv: &mut Recv<'_>, typmod: i32, varying: bool) -> Result<String, TypeError> {
let len = recv.i32()?;
let Some(len) = usize::try_from(len).ok().filter(|&len| len <= BIT_MAX_LEN) else {
return Err(TypeError::new(
SqlState::INVALID_BINARY_REPRESENTATION,
"invalid length in external bit string".to_string(),
));
};
check_length(len, typmod, varying)?;
let bytes = recv.bytes(len.div_ceil(8))?;
Ok((0..len).map(|i| if (bytes[i / 8] >> (7 - i % 8)) & 1 == 1 { '1' } else { '0' }).collect())
}
#[must_use]
pub fn bit_bytes(bits: &str) -> Vec<u8> {
let mut out = vec![0u8; bits.len().div_ceil(8)];
for (i, c) in bits.bytes().enumerate() {
if c == b'1' {
out[i / 8] |= 0x80 >> (i % 8);
}
}
out
}
pub fn bit_send(bits: &str, out: &mut Vec<u8>) {
let len = i32::try_from(bits.len()).unwrap_or(i32::MAX);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&bit_bytes(bits));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_forms() {
assert_eq!(bit_in("1011", -1, false).unwrap(), "1011");
assert_eq!(bit_in("", -1, false).unwrap(), "");
assert_eq!(bit_in("x1f", -1, true).unwrap(), "00011111");
assert_eq!(bit_in("X1F", 8, false).unwrap(), "00011111");
assert_eq!(bit_in("b101", -1, true).unwrap(), "101");
assert_eq!(bit_in("XABC", -1, false).unwrap(), "101010111100");
assert_eq!(
bit_in("102", -1, true).unwrap_err().message,
"\"2\" is not a valid binary digit"
);
assert_eq!(
bit_in("x1g", -1, true).unwrap_err().message,
"\"g\" is not a valid hexadecimal digit"
);
assert_eq!(bit_in("é", -1, true).unwrap_err().message, "\"é\" is not a valid binary digit");
let error = bit_in("101", 4, false).unwrap_err();
assert_eq!(
(error.sqlstate.as_str(), error.message.as_str()),
("22026", "bit string length 3 does not match type bit(4)")
);
let error = bit_in("1010", 3, true).unwrap_err();
assert_eq!(
(error.sqlstate.as_str(), error.message.as_str()),
("22001", "bit string too long for type bit varying(3)")
);
assert_eq!(bit_in("1010", 5, true).unwrap(), "1010");
}
#[test]
fn length_casts() {
assert_eq!(bit_coerce("101", 5, false, true).unwrap(), "10100");
assert_eq!(bit_coerce("10101", 3, false, true).unwrap(), "101");
assert_eq!(bit_coerce("101", 2, true, true).unwrap(), "10");
assert_eq!(bit_coerce("101", 5, true, true).unwrap(), "101");
assert!(bit_coerce("101", 5, false, false).is_err());
assert_eq!(bit_coerce("101", -1, false, false).unwrap(), "101");
}
#[test]
fn binary_forms() {
let mut out = Vec::new();
bit_send("1010101010", &mut out);
assert_eq!(out, [0, 0, 0, 10, 0xaa, 0x80]);
assert_eq!(bit_recv(&mut Recv::new(&out), -1, true).unwrap(), "1010101010");
let mut out = Vec::new();
bit_send("", &mut out);
assert_eq!(out, [0, 0, 0, 0]);
assert!(bit_recv(&mut Recv::new(&[0xff, 0xff, 0xff, 0xff]), -1, true).is_err());
}
}