use rupg_common::SqlState;
use crate::binary::Recv;
use crate::error::TypeError;
use crate::value::Value;
pub const RANGE_EMPTY: u8 = 0x01;
pub const RANGE_LB_INC: u8 = 0x02;
pub const RANGE_UB_INC: u8 = 0x04;
pub const RANGE_LB_INF: u8 = 0x08;
pub const RANGE_UB_INF: u8 = 0x10;
const EMPTY_LITERAL: &str = "empty";
#[derive(Clone, Debug, PartialEq)]
pub struct Range {
pub flags: u8,
pub lower: Value,
pub upper: Value,
}
impl Range {
pub fn empty() -> Range {
Range { flags: RANGE_EMPTY, lower: Value::Null, upper: Value::Null }
}
pub fn is_empty(&self) -> bool {
self.flags & RANGE_EMPTY != 0
}
pub fn has_lower(&self) -> bool {
self.flags & (RANGE_EMPTY | RANGE_LB_INF) == 0
}
pub fn has_upper(&self) -> bool {
self.flags & (RANGE_EMPTY | RANGE_UB_INF) == 0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RangeText {
pub flags: u8,
pub lower: Option<String>,
pub upper: Option<String>,
}
fn is_space(b: u8) -> bool {
matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C)
}
fn malformed(text: &str, detail: &str) -> TypeError {
let mut error = TypeError::new(
SqlState::INVALID_TEXT_REPRESENTATION,
format!("malformed range literal: \"{text}\""),
);
error.detail = Some(detail.to_string());
error
}
pub fn range_parse(text: &str) -> Result<RangeText, TypeError> {
let s = text.as_bytes();
let mut at = 0;
while at < s.len() && is_space(s[at]) {
at += 1;
}
if s.len() - at >= EMPTY_LITERAL.len()
&& s[at..at + EMPTY_LITERAL.len()].eq_ignore_ascii_case(EMPTY_LITERAL.as_bytes())
{
at += EMPTY_LITERAL.len();
while at < s.len() && is_space(s[at]) {
at += 1;
}
if at < s.len() {
return Err(malformed(text, "Junk after \"empty\" key word."));
}
return Ok(RangeText { flags: RANGE_EMPTY, lower: None, upper: None });
}
let mut flags = 0;
match s.get(at) {
Some(b'[') => flags |= RANGE_LB_INC,
Some(b'(') => {}
_ => return Err(malformed(text, "Missing left parenthesis or bracket.")),
}
at += 1;
let lower = parse_bound(text, &mut at)?;
if lower.is_none() {
flags |= RANGE_LB_INF;
}
if s.get(at) != Some(&b',') {
return Err(malformed(text, "Missing comma after lower bound."));
}
at += 1;
let upper = parse_bound(text, &mut at)?;
if upper.is_none() {
flags |= RANGE_UB_INF;
}
match s.get(at) {
Some(b']') => flags |= RANGE_UB_INC,
Some(b')') => {}
_ => return Err(malformed(text, "Too many commas.")),
}
at += 1;
while at < s.len() && is_space(s[at]) {
at += 1;
}
if at < s.len() {
return Err(malformed(text, "Junk after right parenthesis or bracket."));
}
Ok(RangeText { flags, lower, upper })
}
fn parse_bound(text: &str, at: &mut usize) -> Result<Option<String>, TypeError> {
let s = text.as_bytes();
let ends = |b: Option<&u8>| matches!(b, Some(b',' | b')' | b']'));
if ends(s.get(*at)) {
return Ok(None);
}
let mut out = Vec::new();
let mut quoted = false;
while quoted || !ends(s.get(*at)) {
let Some(&c) = s.get(*at) else {
return Err(malformed(text, "Unexpected end of input."));
};
*at += 1;
match c {
b'\\' => {
let Some(&next) = s.get(*at) else {
return Err(malformed(text, "Unexpected end of input."));
};
out.push(next);
*at += 1;
}
b'"' if !quoted => quoted = true,
b'"' if s.get(*at) == Some(&b'"') => {
out.push(b'"');
*at += 1;
}
b'"' => quoted = false,
_ => out.push(c),
}
}
Ok(Some(String::from_utf8_lossy(&out).into_owned()))
}
pub fn range_deparse(flags: u8, lower: Option<&[u8]>, upper: Option<&[u8]>, out: &mut Vec<u8>) {
if flags & RANGE_EMPTY != 0 {
out.extend_from_slice(EMPTY_LITERAL.as_bytes());
return;
}
out.push(if flags & RANGE_LB_INC != 0 { b'[' } else { b'(' });
if let Some(lower) = lower {
bound_escape(lower, out);
}
out.push(b',');
if let Some(upper) = upper {
bound_escape(upper, out);
}
out.push(if flags & RANGE_UB_INC != 0 { b']' } else { b')' });
}
fn bound_escape(value: &[u8], out: &mut Vec<u8>) {
let quote = value.is_empty()
|| value
.iter()
.any(|&b| matches!(b, b'"' | b'\\' | b'(' | b')' | b'[' | b']' | b',') || is_space(b));
if !quote {
out.extend_from_slice(value);
return;
}
out.push(b'"');
for &b in value {
if matches!(b, b'"' | b'\\') {
out.push(b);
}
out.push(b);
}
out.push(b'"');
}
pub type RangeBytes<'a> = (u8, Option<&'a [u8]>, Option<&'a [u8]>);
pub fn range_recv<'a>(recv: &mut Recv<'a>) -> Result<RangeBytes<'a>, TypeError> {
let flags =
recv.byte()? & (RANGE_EMPTY | RANGE_LB_INC | RANGE_LB_INF | RANGE_UB_INC | RANGE_UB_INF);
let mut bound = |inf: u8| -> Result<Option<&'a [u8]>, TypeError> {
if flags & (RANGE_EMPTY | inf) != 0 {
return Ok(None);
}
let len = recv.u32()?;
recv.bytes(usize::try_from(len).unwrap_or(usize::MAX)).map(Some)
};
let lower = bound(RANGE_LB_INF)?;
let upper = bound(RANGE_UB_INF)?;
if recv.remaining() != 0 {
return Err(TypeError::new(
SqlState::PROTOCOL_VIOLATION,
"invalid message format".to_string(),
));
}
Ok((flags, lower, upper))
}
pub fn range_send(flags: u8, lower: Option<&[u8]>, upper: Option<&[u8]>, out: &mut Vec<u8>) {
out.push(flags);
for bound in [lower, upper].into_iter().flatten() {
let len = u32::try_from(bound.len()).unwrap_or(u32::MAX);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(bound);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(text: &str) -> (u8, Option<String>, Option<String>) {
let r = range_parse(text).unwrap();
(r.flags, r.lower, r.upper)
}
fn detail(text: &str) -> String {
range_parse(text).unwrap_err().detail.unwrap()
}
#[test]
fn text_forms() {
assert_eq!(parse("[1,10)"), (RANGE_LB_INC, Some("1".into()), Some("10".into())));
assert_eq!(parse(" (,] "), (RANGE_LB_INF | RANGE_UB_INF | RANGE_UB_INC, None, None));
assert_eq!(parse(" EMPTY "), (RANGE_EMPTY, None, None));
assert_eq!(parse(r#"["a ""b"" \c",x\,y]"#).1.as_deref(), Some(r#"a "b" c"#));
assert_eq!(parse(r#"["a ""b"" \c",x\,y]"#).2.as_deref(), Some("x,y"));
assert_eq!(parse(r#"["",)"#).1.as_deref(), Some(""));
assert_eq!(detail("emptyx"), "Junk after \"empty\" key word.");
assert_eq!(detail(""), "Missing left parenthesis or bracket.");
assert_eq!(detail("[1 10)"), "Missing comma after lower bound.");
assert_eq!(detail("[1,10"), "Unexpected end of input.");
assert_eq!(detail("[1,10,11)"), "Too many commas.");
assert_eq!(detail("[1,10)x"), "Junk after right parenthesis or bracket.");
assert_eq!(detail("[1)"), "Missing comma after lower bound.");
assert_eq!(detail("[1,2\\"), "Unexpected end of input.");
let mut out = Vec::new();
range_deparse(RANGE_LB_INC, Some(b"a b"), Some(b"x\"y"), &mut out);
assert_eq!(out, br#"["a b","x""y")"#);
out.clear();
range_deparse(RANGE_LB_INF | RANGE_UB_INC, None, Some(b""), &mut out);
assert_eq!(out, br#"(,""]"#);
}
#[test]
fn binary_forms() {
let mut out = Vec::new();
range_send(RANGE_LB_INC, Some(&[0, 0, 0, 1]), Some(&[0, 0, 0, 10]), &mut out);
assert_eq!(out, [2, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 10]);
let mut recv = Recv::new(&out);
let (flags, lower, upper) = range_recv(&mut recv).unwrap();
assert_eq!(
(flags, lower, upper),
(RANGE_LB_INC, Some(&[0, 0, 0, 1][..]), Some(&[0, 0, 0, 10][..]))
);
let mut recv = Recv::new(&[0x81]);
assert_eq!(range_recv(&mut recv).unwrap(), (RANGE_EMPTY, None, None));
let mut recv = Recv::new(&[RANGE_EMPTY, 0]);
assert_eq!(range_recv(&mut recv).unwrap_err().message, "invalid message format");
}
}