qraft-core 0.1.2

Core type system, query model, decoding, and SQL lowering primitives for qraft.
Documentation
//! Bound parameter storage used by the lowering pipeline.

use std::borrow::Cow;

use quex::{
    Date as QuexDate, DateTime as QuexDateTime, DateTimeTz as QuexDateTimeTz, Encode,
    EncodeTarget, Encoder, Time as QuexTime,
};

use crate::{
    lower::Data,
    span::{Span, TextSource, TextSpan},
};

/// Compact parameter representation stored on compiled queries.
#[derive(Debug, Clone, Copy)]
pub enum Param {
    Null,
    Bool(Option<bool>),
    Float(Option<f32>),
    Double(Option<f64>),
    Int(Option<i32>),
    BigInt(Option<i64>),
    UInt(Option<u32>),
    UBigInt(Option<u64>),
    Text(Option<TextSpan>),
    Blob(Option<Span>),
}

pub fn encode_param(value: &(impl Encode + ?Sized), data: &mut Vec<u8>) -> Param {
    struct ParamEncoder<'a> {
        data: &'a mut Vec<u8>,
        param: Option<Param>,
    }

    impl ParamEncoder<'_> {
        fn set(&mut self, param: Param) {
            assert!(
                self.param.is_none(),
                "Encode must write exactly one SQL parameter"
            );
            self.param = Some(param);
        }
    }

    impl EncodeTarget for ParamEncoder<'_> {
        fn encode_param(&mut self, value: quex::ParamValue<'_>) {
            match value {
                quex::ParamValue::Null => self.encode_null(),
                quex::ParamValue::I64(value) => self.encode_i64(value),
                quex::ParamValue::U64(value) => self.encode_u64(value),
                quex::ParamValue::F64(value) => self.encode_f64(value),
                quex::ParamValue::Str(value) => self.encode_str(value.as_ref()),
                quex::ParamValue::Bytes(value) => self.encode_bytes(value.as_ref()),
            }
        }

        fn encode_null(&mut self) {
            self.set(Param::Null);
        }

        fn encode_i64(&mut self, value: i64) {
            self.set(Param::BigInt(Some(value)));
        }

        fn encode_u64(&mut self, value: u64) {
            self.set(Param::UBigInt(Some(value)));
        }

        fn encode_f64(&mut self, value: f64) {
            self.set(Param::Double(Some(value)));
        }

        fn encode_bool(&mut self, value: bool) {
            self.set(Param::Bool(Some(value)));
        }

        fn encode_date(&mut self, value: QuexDate) {
            let encoded = format!("{:04}-{:02}-{:02}", value.year, value.month, value.day);
            self.encode_string(encoded);
        }

        fn encode_time(&mut self, value: QuexTime) {
            let mut encoded = format!("{:02}:{:02}:{:02}", value.hour, value.minute, value.second);
            if value.microsecond != 0 {
                let mut micros = format!("{:06}", value.microsecond);
                while micros.ends_with('0') {
                    micros.pop();
                }
                encoded.push('.');
                encoded.push_str(&micros);
            }
            self.encode_string(encoded);
        }

        fn encode_datetime(&mut self, value: QuexDateTime) {
            let mut encoded = format!(
                "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
                value.date.year,
                value.date.month,
                value.date.day,
                value.time.hour,
                value.time.minute,
                value.time.second
            );
            if value.time.microsecond != 0 {
                let mut micros = format!("{:06}", value.time.microsecond);
                while micros.ends_with('0') {
                    micros.pop();
                }
                encoded.push('.');
                encoded.push_str(&micros);
            }
            self.encode_string(encoded);
        }

        fn encode_datetime_tz(&mut self, value: QuexDateTimeTz) {
            let sign = if value.offset_seconds < 0 { '-' } else { '+' };
            let offset_seconds = value.offset_seconds.abs();
            let hours = offset_seconds / 3600;
            let minutes = (offset_seconds % 3600) / 60;
            let mut encoded = format!(
                "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
                value.datetime.date.year,
                value.datetime.date.month,
                value.datetime.date.day,
                value.datetime.time.hour,
                value.datetime.time.minute,
                value.datetime.time.second
            );
            if value.datetime.time.microsecond != 0 {
                let mut micros = format!("{:06}", value.datetime.time.microsecond);
                while micros.ends_with('0') {
                    micros.pop();
                }
                encoded.push('.');
                encoded.push_str(&micros);
            }
            encoded.push(sign);
            encoded.push_str(&format!("{hours:02}:{minutes:02}"));
            self.encode_string(encoded);
        }

        fn encode_uuid(&mut self, value: [u8; 16]) {
            #[cfg(feature = "uuid")]
            let encoded = uuid::Uuid::from_bytes(value).hyphenated().to_string();

            #[cfg(not(feature = "uuid"))]
            let encoded = {
                let mut encoded = String::with_capacity(32);
                for byte in value {
                    use std::fmt::Write as _;
                    let _ = write!(&mut encoded, "{byte:02x}");
                }
                encoded
            };

            self.encode_string(encoded);
        }

        fn encode_str(&mut self, value: &str) {
            let span = self.data.intern_text(value);
            self.set(Param::Text(Some(span)));
        }

        fn encode_string(&mut self, value: String) {
            let span = self.data.intern_text(&value);
            self.set(Param::Text(Some(span)));
        }

        fn encode_bytes(&mut self, value: &[u8]) {
            let span = self.data.intern_blob(value);
            self.set(Param::Blob(Some(span)));
        }

        fn encode_bytes_owned(&mut self, value: Vec<u8>) {
            let span = self.data.intern_blob(&value);
            self.set(Param::Blob(Some(span)));
        }
    }

    let mut encoder = ParamEncoder { data, param: None };
    value.encode(Encoder::new(&mut encoder));
    encoder
        .param
        .expect("Encode implementations must write exactly one SQL parameter")
}

impl Param {
    /// Expands the compact parameter into its borrowed runtime representation.
    pub fn into_param_value<'a>(self, data: &'a Vec<u8>) -> ParamValue<'a> {
        match self {
            Param::Null => ParamValue::Text(None),
            Param::Bool(b) => ParamValue::Bool(b),
            Param::Float(f) => ParamValue::Float(f),
            Param::Double(d) => ParamValue::Double(d),
            Param::Int(i) => ParamValue::Int(i),
            Param::BigInt(b) => ParamValue::BigInt(b),
            Param::Text(s) => ParamValue::Text(match s {
                Some(span) => match span.0 {
                    TextSource::StaticText(s) => Some(Cow::Borrowed(s)),
                    TextSource::Text(_) => Some(Cow::Borrowed(data.text(span))),
                },
                None => None,
            }),
            Param::Blob(s) => ParamValue::Blob(match s {
                Some(s) => Some(Cow::Borrowed(data.blob(s))),
                None => None,
            }),
            Param::UInt(value) => ParamValue::UInt(value),
            Param::UBigInt(value) => ParamValue::UBigInt(value),
        }
    }
}

/// Borrowed or owned parameter payload before it is interned into query storage.
#[derive(Debug, Clone)]
pub enum ParamValue<'a> {
    Bool(Option<bool>),
    Float(Option<f32>),
    Double(Option<f64>),
    Int(Option<i32>),
    UInt(Option<u32>),
    UBigInt(Option<u64>),
    BigInt(Option<i64>),
    Text(Option<Cow<'a, str>>),
    Blob(Option<Cow<'a, [u8]>>),
}

macro_rules! from_param_value {
    ($sql:ty, $t:ty) => {
        paste::paste! {
            impl<'v> From<$t> for ParamValue<'v> {
                fn from(value: $t) -> Self {
                    ParamValue::$sql(Some(value))
                }
            }

            impl<'v> From<Option<$t>> for ParamValue<'v> {
                fn from(value: Option<$t>) -> Self {
                    ParamValue::$sql(value)
                }
            }
        }
    };
}

from_param_value!(Bool, bool);
from_param_value!(Float, f32);
from_param_value!(Double, f64);
from_param_value!(Int, i32);
from_param_value!(BigInt, i64);
from_param_value!(UInt, u32);
from_param_value!(UBigInt, u64);

impl<'v> From<&'v str> for ParamValue<'v> {
    fn from(value: &'v str) -> Self {
        ParamValue::Text(Some(Cow::Borrowed(value)))
    }
}

impl<'v> From<Option<&'v str>> for ParamValue<'v> {
    fn from(value: Option<&'v str>) -> Self {
        ParamValue::Text(value.map(Cow::Borrowed))
    }
}

impl<'v> From<&'v [u8]> for ParamValue<'v> {
    fn from(value: &'v [u8]) -> Self {
        ParamValue::Blob(Some(Cow::Borrowed(value)))
    }
}

impl<'v> From<Option<&'v [u8]>> for ParamValue<'v> {
    fn from(value: Option<&'v [u8]>) -> Self {
        ParamValue::Blob(value.map(Cow::Borrowed))
    }
}

impl<'v> From<Cow<'v, str>> for ParamValue<'v> {
    fn from(value: Cow<'v, str>) -> Self {
        ParamValue::Text(Some(value))
    }
}

impl<'v> From<Option<Cow<'v, str>>> for ParamValue<'v> {
    fn from(value: Option<Cow<'v, str>>) -> Self {
        ParamValue::Text(value)
    }
}

impl<'v> From<Cow<'v, [u8]>> for ParamValue<'v> {
    fn from(value: Cow<'v, [u8]>) -> Self {
        ParamValue::Blob(Some(value))
    }
}

impl<'v> From<Option<Cow<'v, [u8]>>> for ParamValue<'v> {
    fn from(value: Option<Cow<'v, [u8]>>) -> Self {
        ParamValue::Blob(value)
    }
}

impl<'a> ParamValue<'a> {
    /// Converts any supported value into a parameter payload.
    pub fn new<T>(value: T) -> Self
    where
        T: Into<ParamValue<'a>>,
    {
        value.into()
    }

    /// Interns owned text/blob data into the query buffers and returns a compact `Param`.
    pub fn into_param(self, data: &mut Vec<u8>) -> Param {
        match self {
            ParamValue::Bool(value) => Param::Bool(value),
            ParamValue::Float(value) => Param::Float(value),
            ParamValue::Double(value) => Param::Double(value),
            ParamValue::Int(value) => Param::Int(value),
            ParamValue::BigInt(value) => Param::BigInt(value),
            ParamValue::Text(text) => Param::Text(text.map(|t| data.intern_text(t.as_ref()))),
            ParamValue::Blob(blob) => Param::Blob(blob.map(|b| data.intern_blob(b.as_ref()))),
            ParamValue::UInt(v) => Param::UInt(v),
            ParamValue::UBigInt(v) => Param::UBigInt(v),
        }
    }
}