#![warn(missing_docs)]
use crate::{
MyError,
bound::Bound,
geom::{G, GTrait},
qstring::QString,
wkb::PostGisBinary,
};
use core::fmt;
use jiff::{Timestamp, Zoned, civil::Date, tz::TimeZone};
use std::{cmp::Ordering, mem};
use tracing::error;
#[derive(Debug)]
pub enum DataType {
Str,
Num,
Bool,
Timestamp,
Date,
#[allow(dead_code)]
Interval,
Geom,
#[allow(dead_code)]
List,
}
#[derive(Clone)]
pub enum Q {
Null,
Bool(bool),
Num(f64),
Str(QString),
Geom(G),
Instant(Bound),
Interval(Bound, Bound),
List(Vec<Q>),
}
impl fmt::Debug for Q {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => write!(f, "Null"),
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
Self::Num(arg0) => f.debug_tuple("Num").field(arg0).finish(),
Self::Str(arg0) => f.debug_tuple("Str").field(arg0).finish(),
Self::Geom(x) => write!(f, "Geom({})", x.to_wkt()),
Self::Instant(arg0) => f.debug_tuple("Instant").field(arg0).finish(),
Self::Interval(arg0, arg1) => {
f.debug_tuple("Interval").field(arg0).field(arg1).finish()
}
Self::List(arg0) => f.debug_tuple("List").field(arg0).finish(),
}
}
}
impl PartialEq for Q {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Num(l0), Self::Num(r0)) => l0 == r0,
(Self::Str(l0), Self::Str(r0)) => l0 == r0,
(Self::Geom(l0), Self::Geom(r0)) => l0 == r0,
(Self::Instant(l0), Self::Instant(r0)) => l0 == r0,
(Self::Interval(l0, l1), Self::Interval(r0, r1)) => l0 == r0 && l1 == r1,
(Self::List(l0), Self::List(r0)) => l0 == r0,
_ => mem::discriminant(self) == mem::discriminant(other),
}
}
}
impl Eq for Q {}
impl PartialOrd for Q {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Q::Null, Q::Null) => Some(Ordering::Equal),
(Q::Bool(a), Q::Bool(b)) => a.partial_cmp(b),
(Q::Num(a), Q::Num(b)) => a.partial_cmp(b),
(Q::Str(a), Q::Str(b)) => a.partial_cmp(b),
(Q::Instant(a), Q::Instant(b)) => a.partial_cmp(b),
(Q::Interval(a0, a1), Q::Interval(b0, b1)) => match a0.partial_cmp(b0) {
Some(Ordering::Equal) => match (a1, b1) {
(Bound::None, Bound::None) => Some(Ordering::Equal),
(Bound::None, _) => Some(Ordering::Greater),
(_, Bound::None) => Some(Ordering::Less),
_ => a1.partial_cmp(b1),
},
x => x,
},
(Q::List(a), Q::List(b)) => a.partial_cmp(b),
_ => None,
}
}
}
impl fmt::Display for Q {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Q::Null => write!(f, "Null"),
Q::Bool(x) => write!(f, "{x}"),
Q::Num(x) => write!(f, "{x}"),
Q::Str(x) => write!(f, "{x}"),
Q::Geom(x) => write!(f, "{}", x.to_wkt()),
Q::Instant(x) => write!(f, "{x}"),
Q::Interval(Bound::None, Bound::None) => write!(f, "[...]"),
Q::Interval(Bound::None, y) => write!(f, "[..{y}]"),
Q::Interval(x, Bound::None) => write!(f, "[{x}..]"),
Q::Interval(x, y) => write!(f, "[{x}..{y}]"),
Q::List(x) => write!(f, "{x:?}"),
}
}
}
impl Q {
pub fn new_plain_str(value: &str) -> Self {
Self::Str(QString::plain(value.trim()))
}
pub fn try_from_timestamp_str(value: &str) -> Result<Self, MyError> {
let x = value.parse::<Timestamp>()?;
let z = x.to_zoned(TimeZone::UTC);
Ok(Q::Instant(Bound::Timestamp(z)))
}
pub fn try_from_timestamp_ns(value: i128) -> Result<Self, MyError> {
let x = Timestamp::from_nanosecond(value)?;
let z = x.to_zoned(TimeZone::UTC);
Ok(Q::Instant(Bound::Timestamp(z)))
}
pub fn try_from_timestamp(value: &Zoned) -> Result<Self, MyError> {
Ok(Q::Instant(Bound::Timestamp(value.to_owned())))
}
pub fn try_from_date_str(value: &str) -> Result<Self, MyError> {
let x = value.parse::<Date>()?;
let z = x.to_zoned(TimeZone::UTC)?;
Ok(Q::Instant(Bound::Date(z)))
}
pub fn try_from_date_ns(value: i128) -> Result<Self, MyError> {
let x = Timestamp::from_nanosecond(value)?;
let z = x.to_zoned(TimeZone::UTC);
Ok(Q::Instant(Bound::Date(z)))
}
pub fn try_from_date(value: &Date) -> Result<Self, MyError> {
let z = value.to_zoned(TimeZone::UTC)?;
Ok(Q::Instant(Bound::Date(z)))
}
pub fn try_from_wkt(value: &str) -> Result<Self, MyError> {
let g = G::try_from(value)?;
Ok(Q::Geom(g))
}
pub fn try_from_wkb(value: &[u8]) -> Result<Self, MyError> {
let g = G::try_from(value)?;
Ok(Q::Geom(g))
}
pub fn try_from_ewkb(value: &[u8]) -> Result<Self, MyError> {
let ewkb = PostGisBinary::try_from(value)?;
let g = ewkb.geom();
Ok(Q::Geom(g))
}
pub(crate) fn is_null(&self) -> bool {
matches!(self, Q::Null)
}
pub(crate) fn is_instant(&self) -> bool {
matches!(self, Q::Instant(_))
}
pub fn to_bool(&self) -> Result<bool, MyError> {
match self {
Q::Bool(x) => Ok(*x),
_ => Err(MyError::Runtime(format!("{self} is not a boolean").into())),
}
}
pub fn to_str(&self) -> Result<QString, MyError> {
match self {
Q::Str(x) => Ok(x.to_owned()),
_ => Err(MyError::Runtime(format!("{self} is not a string").into())),
}
}
pub fn to_num(&self) -> Result<f64, MyError> {
match self {
Q::Num(x) => Ok(*x),
_ => Err(MyError::Runtime(format!("{self} is not a number").into())),
}
}
pub fn to_geom(&self) -> Result<G, MyError> {
match self {
Q::Geom(x) => Ok(x.to_owned()),
_ => Err(MyError::Runtime(format!("{self} is not a geometry").into())),
}
}
pub fn to_bound(&self) -> Result<Bound, MyError> {
match self {
Q::Instant(x) => Ok(x.to_owned()),
_ => Err(MyError::Runtime(
format!("{self} is not a bounded instant").into(),
)),
}
}
pub fn to_interval(&self) -> Result<(Bound, Bound), MyError> {
match self {
Q::Interval(x, y) => Ok((x.to_owned(), y.to_owned())),
_ => Err(MyError::Runtime(
format!("{self} is not an interval").into(),
)),
}
}
pub fn to_list(&self) -> Result<Vec<Q>, MyError> {
match self {
Q::List(x) => Ok(x.to_owned()),
_ => Err(MyError::Runtime(format!("{self} is not a list").into())),
}
}
pub(crate) fn same_type(this: &Self, that: &Self) -> bool {
mem::discriminant(this) == mem::discriminant(that)
}
pub(crate) fn literal_type(&self) -> Option<DataType> {
match self {
Q::Bool(_) => Some(DataType::Bool),
Q::Num(_) => Some(DataType::Num),
Q::Str(_) => Some(DataType::Str),
Q::Geom(_) => Some(DataType::Geom),
Q::Instant(x) => match x {
Bound::None => None,
Bound::Date(_) => Some(DataType::Date),
Bound::Timestamp(_) => Some(DataType::Timestamp),
},
_ => None,
}
}
pub(crate) fn contained_by(&self, list: Vec<Self>) -> Result<bool, MyError> {
if list.is_empty() {
return Ok(false);
}
if let Some(z_type) = self.literal_type() {
if matches!(z_type, DataType::Bool) {
let lhs = self.to_bool()?;
let rhs: Result<Vec<bool>, MyError> = list.iter().map(|e| e.to_bool()).collect();
let rhs = rhs?;
Ok(rhs.contains(&lhs))
} else if matches!(z_type, DataType::Num) {
let lhs = self.to_num()?;
let rhs: Result<Vec<f64>, MyError> = list.iter().map(|e| e.to_num()).collect();
let rhs = rhs?;
Ok(rhs.contains(&lhs))
} else if matches!(z_type, DataType::Str) {
let lhs = &self.to_str()?;
let rhs: Result<Vec<QString>, MyError> = list.iter().map(|e| e.to_str()).collect();
let rhs = rhs?;
Ok(rhs.contains(lhs))
} else if matches!(z_type, DataType::Date) || matches!(z_type, DataType::Timestamp) {
let lhs = self.to_bound()?.as_zoned().unwrap();
let rhs: Result<Vec<Zoned>, MyError> = list
.iter()
.map(|e| e.to_bound().and_then(|b| b.to_zoned()))
.collect();
let rhs = rhs?;
Ok(rhs.contains(&lhs))
} else if matches!(z_type, DataType::Geom) {
let lhs = self.to_geom()?;
let rhs: Result<Vec<G>, MyError> = list.iter().map(|e| e.to_geom()).collect();
let rhs = rhs?;
Ok(rhs.contains(&lhs))
} else {
error!("Failed. self = {self:?}; list = {list:?}");
Ok(false)
}
} else {
Ok(false)
}
}
}
impl From<bool> for Q {
fn from(value: bool) -> Self {
Q::Bool(value)
}
}
trait TryToF64<T> {
fn try_to_f64(self) -> Result<f64, MyError>;
}
macro_rules! impl_safe_try_to_f64 {
($($t:ty),*) => {
$(
impl TryToF64<$t> for $t {
fn try_to_f64(self) -> Result<f64, $crate::MyError> {
Ok(self as f64)
}
}
)*
};
}
impl_safe_try_to_f64!(u8, u16, u32, i8, i16, i32);
macro_rules! impl_try_unsigned_to_f64 {
($($t:ty),*) => {
$(
impl TryToF64<$t> for $t {
fn try_to_f64(self) -> Result<f64, $crate::MyError> {
const MAX_LIMIT: $t = (1 << 53) - 1;
if self <= MAX_LIMIT {
Ok(self as f64)
} else {
Err(MyError::PrecisionLoss(self.to_string().into()))
}
}
}
)*
};
}
impl_try_unsigned_to_f64!(u64, u128);
macro_rules! impl_try_signed_to_f64 {
($($t:ty),*) => {
$(
impl TryToF64<$t> for $t {
fn try_to_f64(self) -> Result<f64, $crate::MyError> {
const MAX_LIMIT: $t = (1 << 53) - 1;
const MIN_LIMIT: $t = - MAX_LIMIT;
if (MIN_LIMIT..=MAX_LIMIT).contains(&self) {
Ok(self as f64)
} else {
Err(MyError::PrecisionLoss(self.to_string().into()))
}
}
}
)*
};
}
impl_try_signed_to_f64!(i64, i128);
impl TryToF64<usize> for usize {
fn try_to_f64(self) -> Result<f64, MyError> {
match usize::BITS {
32 => (self as u32).try_to_f64(),
_ => (self as u64).try_to_f64(),
}
}
}
impl TryToF64<isize> for isize {
fn try_to_f64(self) -> Result<f64, MyError> {
match isize::BITS {
32 => (self as i32).try_to_f64(),
_ => (self as i64).try_to_f64(),
}
}
}
macro_rules! impl_try_from_int {
($($t:ty),*) => {
$(
impl TryFrom<$t> for $crate::Q {
type Error = MyError;
fn try_from(value: $t) -> Result<Self, $crate::MyError> {
let x = value.try_to_f64()?;
Ok(Q::Num(x))
}
}
)*
};
}
impl_try_from_int!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
);
impl From<f64> for Q {
fn from(value: f64) -> Self {
Q::Num(value)
}
}
impl TryFrom<Date> for Q {
type Error = MyError;
fn try_from(value: Date) -> Result<Self, Self::Error> {
let z = value.to_zoned(TimeZone::UTC)?;
Ok(Q::Instant(Bound::Date(z)))
}
}
impl From<Timestamp> for Q {
fn from(value: Timestamp) -> Self {
let z = value.to_zoned(TimeZone::UTC);
Q::Instant(Bound::Timestamp(z))
}
}
impl From<Bound> for Q {
fn from(value: Bound) -> Self {
Q::Instant(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::RngExt;
#[test]
fn test_usize_max() {
let x = usize::MAX.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_usize() {
let x: usize = (1 << 53) - 1;
let y1 = x as f64;
let y2 = x.try_to_f64().expect("Failed");
assert_eq!(y1, y2)
}
#[test]
fn test_u128_max() {
let x = u128::MAX.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_u128() {
let x: u128 = (1 << 53) - 1;
let y1 = x as f64;
let y2 = x.try_to_f64().expect("Failed");
assert_eq!(y1, y2)
}
#[test]
fn test_u64_max() {
let x = u64::MAX.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_u64() {
let x: u64 = (1 << 53) - 1;
let y1 = x as f64;
let y2 = x.try_to_f64().expect("Failed");
assert_eq!(y1, y2)
}
#[test]
fn test_isize_max() {
let x = isize::MAX.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_isize() {
let x1: isize = (1 << 53) - 1;
let y1 = x1 as f64;
let y2 = x1.try_to_f64().expect("Failed");
assert_eq!(y1, y2);
let x2 = -x1;
let y1 = x2 as f64;
let y2 = x2.try_to_f64().expect("Failed");
assert_eq!(y1, y2)
}
#[test]
fn test_isize_min() {
let x = isize::MIN.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_i128_max() {
let x = i128::MAX.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_i128() {
let x1: i128 = (1 << 53) - 1;
let y1 = x1 as f64;
let y2 = x1.try_to_f64().expect("Failed");
assert_eq!(y1, y2);
let x2 = -x1;
let y1 = x2 as f64;
let y2 = x2.try_to_f64().expect("Failed");
assert_eq!(y1, y2)
}
#[test]
fn test_i128_min() {
let x = i128::MIN.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_i64_max() {
let x = i64::MAX.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_i64_min() {
let x = i64::MIN.try_to_f64();
assert!(matches!(x.err(), Some(MyError::PrecisionLoss(_))))
}
#[test]
fn test_i64() {
let x1: i64 = (1 << 53) - 1;
let y1 = x1 as f64;
let y2 = x1.try_to_f64().expect("Failed");
assert_eq!(y1, y2);
let x2 = -x1;
let y1 = x2 as f64;
let y2 = x2.try_to_f64().expect("Failed");
assert_eq!(y1, y2)
}
#[test]
fn fuzz_test_i64() {
const LIMIT: i64 = (1 << 53) - 1;
fn random_i64() -> i64 {
let mut rng = rand::rng();
match rng.random_bool(0.5) {
true => LIMIT - rng.random_range(1..=LIMIT.abs()),
false => LIMIT + rng.random_range(1..=LIMIT.abs()),
}
}
let mut expected = 0;
let mut actual = 0;
for _ in 0..1000 {
let x = random_i64();
if !(-LIMIT..=LIMIT).contains(&x) {
expected += 1;
}
match Q::try_from(x) {
Ok(_) => (), Err(MyError::PrecisionLoss(_)) => actual += 1,
Err(x) => panic!("Unexpected {x}"),
}
}
assert_eq!(expected, actual)
}
#[test]
fn fuzz_test_u64() {
const LIMIT: u64 = (1 << 53) - 1;
fn random_u64() -> u64 {
let mut rng = rand::rng();
match rng.random_bool(0.5) {
true => LIMIT.saturating_sub(rng.random_range(1..=LIMIT)),
false => LIMIT + rng.random_range(1..=LIMIT),
}
}
let mut expected = 0;
let mut actual = 0;
for _ in 0..1000 {
let x = random_u64();
if x > LIMIT {
expected += 1;
}
match Q::try_from(x) {
Ok(_) => (), Err(MyError::PrecisionLoss(_)) => actual += 1,
Err(x) => panic!("Unexpected {x}"),
}
}
assert_eq!(expected, actual)
}
#[test]
fn test_like() {
let input = QString::plain("hello");
let pattern = QString::plain("h%o");
let r1 = QString::like(&input, &pattern);
assert!(r1);
let input = QString::icase("HELLO");
let pattern = QString::plain("h%o");
let r2 = QString::like(&input, &pattern);
assert!(r2);
let input = QString::icase("HELLODOLLY");
let pattern = QString::plain("h%odo%y");
let r2p = QString::like(&input, &pattern);
assert!(r2p);
let input = QString::plain("hello");
let pattern = QString::icase("h__lo");
let r3 = QString::like(&input, &pattern);
assert!(r3);
let pattern = QString::icase("h%lo");
let r3p = QString::like(&input, &pattern);
assert!(r3p);
let input = QString::plain("hello");
let pattern = QString::plain("h\\%o");
let r4 = QString::like(&input, &pattern);
assert!(!r4);
let input = QString::plain("h%llo");
let pattern = QString::plain("h\\%llo");
let r5 = QString::like(&input, &pattern);
assert!(r5);
let input = QString::plain("");
let pattern = QString::plain("%");
let r6 = QString::like(&input, &pattern);
assert!(r6);
let input = QString::plain("abc");
let pattern = QString::plain("");
let r7 = QString::like(&input, &pattern);
assert!(!r7);
let input = QString::icase("ß"); let pattern = QString::icase("ẞ"); let u1 = QString::like(&input, &pattern);
assert!(u1);
let input = QString::icase("Σ");
let pattern = QString::plain("σ");
let u2 = QString::like(&input, &pattern);
assert!(u2);
let input = QString::plain("こんにちは");
let pattern = QString::plain("こ%は");
let u4 = QString::like(&input, &pattern);
assert!(u4);
let pattern = QString::icase("こ_にちは");
let u5 = QString::like(&input, &pattern);
assert!(u5);
}
}