use std::borrow::Cow;
use rupg_common::SqlState;
use crate::error::TypeError;
const VARHDRSZ: i32 = 4;
fn clip(s: &str, chars: usize) -> usize {
s.char_indices().nth(chars).map_or(s.len(), |(at, _)| at)
}
fn too_long(type_name: &str, max: usize) -> TypeError {
TypeError::new(
SqlState::STRING_DATA_RIGHT_TRUNCATION,
format!("value too long for type {type_name}({max})"),
)
}
fn cut<'a>(s: &'a str, max: usize, explicit: bool, type_name: &str) -> Result<&'a str, TypeError> {
let end = clip(s, max);
if !explicit && s.as_bytes()[end..].iter().any(|&b| b != b' ') {
return Err(too_long(type_name, max));
}
Ok(&s[..end])
}
pub fn varchar_in(s: &str, typmod: i32) -> Result<&str, TypeError> {
varchar_coerce(s, typmod, false)
}
pub fn varchar_coerce(s: &str, typmod: i32, explicit: bool) -> Result<&str, TypeError> {
if typmod < VARHDRSZ {
return Ok(s);
}
let max = (typmod - VARHDRSZ) as usize;
if s.len() <= max {
return Ok(s);
}
cut(s, max, explicit, "character varying")
}
pub fn bpchar_in(s: &str, typmod: i32) -> Result<Cow<'_, str>, TypeError> {
bpchar_coerce(s, typmod, false)
}
pub fn bpchar_coerce(s: &str, typmod: i32, explicit: bool) -> Result<Cow<'_, str>, TypeError> {
if typmod < VARHDRSZ {
return Ok(Cow::Borrowed(s));
}
let max = (typmod - VARHDRSZ) as usize;
let chars = if s.is_ascii() { s.len() } else { s.chars().count() };
if chars >= max {
return cut(s, max, explicit, "character").map(Cow::Borrowed);
}
let mut padded = String::with_capacity(s.len() + max - chars);
padded.push_str(s);
padded.extend(std::iter::repeat_n(' ', max - chars));
Ok(Cow::Owned(padded))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_length_counts_characters_and_not_bytes() {
let typmod = 3 + VARHDRSZ;
assert_eq!(varchar_in("ééé", typmod), Ok("ééé"));
assert_eq!(varchar_in("ééé ", typmod), Ok("ééé"));
let error = varchar_in("éééé", typmod).unwrap_err();
assert_eq!(
(error.sqlstate.as_str(), error.message.as_str()),
("22001", "value too long for type character varying(3)")
);
assert_eq!(varchar_coerce("éééé", typmod, true), Ok("ééé"));
assert_eq!(varchar_in("abcdef", -1), Ok("abcdef"));
assert_eq!(bpchar_in("é", typmod).as_deref(), Ok("é "));
assert_eq!(bpchar_in("abc ", typmod).as_deref(), Ok("abc"));
assert_eq!(
bpchar_in("abcd", typmod).unwrap_err().message,
"value too long for type character(3)"
);
assert_eq!(bpchar_coerce("abcd", typmod, true).as_deref(), Ok("abc"));
assert_eq!(bpchar_in("ab ", -1).as_deref(), Ok("ab "));
assert_eq!(bpchar_in("", VARHDRSZ).as_deref(), Ok(""));
}
}