boxdb-sqlite-sys 0.3.2

BoxDB SQLite engine and native bindings (fork of libsqlite3-sys 0.38.2)
Documentation
//! Native value codecs and operations behind SQLite's common type dispatch.
pub use crate::native_types::Kind;
use crate::{
    decimal::{Error, Result},
    native_types::TYPES,
};
use chrono::Datelike;
use std::ffi::c_char;
use std::hash::{Hash, Hasher};

pub fn encode(kind: Kind, text: &str) -> Result<Vec<u8>> {
    let payload = match kind {
        Kind::Uuid => uuid::Uuid::parse_str(text)
            .map_err(|e| Error(e.to_string()))?
            .as_bytes()
            .to_vec(),
        Kind::Decimal => return crate::decimal::encode(text),
        Kind::Uint8 | Kind::Uint16 | Kind::Uint32 | Kind::Uint64 => {
            return crate::unsigned::encode(kind, text);
        }
        _ => crate::temporal::encode(kind, text)?,
    };
    let descriptor = TYPES.iter().find(|d| d.kind == kind).unwrap();
    let mut bytes = vec![descriptor.tag];
    bytes.extend(payload);
    Ok(bytes)
}

pub fn text(kind: Kind, bytes: &[u8]) -> Result<String> {
    match kind {
        Kind::Uuid => Ok(uuid::Uuid::from_bytes(
            bytes
                .try_into()
                .map_err(|_| Error("invalid UUID storage".into()))?,
        )
        .to_string()),
        Kind::Uint8 | Kind::Uint16 | Kind::Uint32 | Kind::Uint64 => {
            Ok(crate::unsigned::read(bytes).to_string())
        }
        Kind::Decimal => std::str::from_utf8(bytes)
            .map(str::to_owned)
            .map_err(|e| Error(e.to_string())),
        _ => crate::temporal::text(kind, bytes),
    }
}

pub fn decode(bytes: &[u8]) -> Option<(Kind, String)> {
    if bytes.is_empty() {
        return None;
    }
    let descriptor = TYPES
        .iter()
        .find(|d| d.tag == bytes[0] && d.kind != Kind::Decimal)?;
    if bytes.len() != 1 + descriptor.payload_size {
        return None;
    }
    Some((descriptor.kind, text(descriptor.kind, &bytes[1..]).ok()?))
}

pub fn canonical(kind: Kind, value: &str) -> Result<String> {
    let bytes = encode(kind, value)?;
    text(kind, &bytes[1..])
}

pub fn cast(target: Kind, source: Kind, value: &str) -> Result<String> {
    let bytes = encode(source, value)?;
    if crate::unsigned::is_unsigned(target as i32) {
        let converted =
            crate::unsigned::evaluate(0, target as i32, source as i32, &bytes[1..], -3, &[])?;
        return text(target, &converted[1..]);
    }
    if target == Kind::Decimal && crate::unsigned::is_unsigned(source as i32) {
        return canonical(target, &text(source, &bytes[1..])?);
    }
    let payload = if target == source {
        bytes[1..].to_vec()
    } else {
        crate::temporal::cast(target, source, &bytes[1..], false)?
    };
    text(target, &payload)
}

#[repr(C)]
struct FunctionArgument {
    kind: i32,
    data: *const c_char,
    length: i32,
}

#[unsafe(no_mangle)]
unsafe extern "C" fn boxdb_native_function(
    function: i32,
    argc: i32,
    args: *const FunctionArgument,
    out: *mut *mut c_char,
    out_len: *mut i32,
) -> i32 {
    let result = (|| {
        if !(0..=7).contains(&argc) {
            return Err(Error("invalid temporal function argument count".into()));
        }
        let args = unsafe { std::slice::from_raw_parts(args, argc as usize) }
            .iter()
            .map(|arg| crate::temporal_functions::Argument {
                kind: arg.kind,
                bytes: unsafe { input(arg.data, arg.length) },
            })
            .collect::<Vec<_>>();
        let (kind, payload) = crate::temporal_functions::evaluate(function, &args)?;
        let descriptor = TYPES.iter().find(|d| d.kind == kind).unwrap();
        let mut bytes = vec![descriptor.tag];
        bytes.extend(payload);
        Ok(bytes)
    })();
    match result {
        Ok(bytes) => unsafe { crate::decimal::output(&bytes, out, out_len) },
        Err(error) => {
            let rc = unsafe { crate::decimal::output(error.0.as_bytes(), out, out_len) };
            if rc == crate::SQLITE_OK {
                crate::SQLITE_ERROR
            } else {
                rc
            }
        }
    }
}

fn ordering_key(kind: i32, bytes: &[u8]) -> i128 {
    match kind {
        3 | 5 => {
            i128::from(i64::from_be_bytes(bytes[..8].try_into().unwrap())) * crate::temporal::SECOND
                + i128::from(u32::from_be_bytes(bytes[8..12].try_into().unwrap()))
        }
        4 => {
            (i128::from(i32::from_be_bytes(bytes[..4].try_into().unwrap()))
                - i128::from(
                    chrono::NaiveDate::from_ymd_opt(1970, 1, 1)
                        .unwrap()
                        .num_days_from_ce(),
                ))
                * crate::temporal::DAY
        }
        6 => {
            let value = crate::temporal::read_interval(bytes).unwrap();
            (i128::from(value.months) * 30 + i128::from(value.days)) * crate::temporal::DAY
                + i128::from(value.micros)
        }
        _ => 0,
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn boxdb_native_compare(
    kind: i32,
    a: *const c_char,
    na: i32,
    kind_b: i32,
    b: *const c_char,
    nb: i32,
) -> i32 {
    let a = unsafe { input(a, na) };
    let b = unsafe { input(b, nb) };
    if kind == 2 {
        a.cmp(b) as i32
    } else {
        ordering_key(kind, a).cmp(&ordering_key(kind_b, b)) as i32
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn boxdb_native_hash(kind: i32, data: *const c_char, length: i32) -> u64 {
    let bytes = unsafe { input(data, length) };
    if crate::unsigned::is_unsigned(kind) {
        // SQLite's numeric Bloom hash saturates REAL/DECIMAL at INT64_MAX.
        // Equal cross-type numbers must land in the same bucket.
        return crate::unsigned::read(bytes).min(i64::MAX as u64);
    }
    let mut hash = std::collections::hash_map::DefaultHasher::new();
    (if matches!(kind, 4 | 5) { 4 } else { kind }).hash(&mut hash);
    if matches!(kind, 4 | 5 | 6) {
        ordering_key(kind, bytes).hash(&mut hash);
    } else {
        bytes.hash(&mut hash);
    }
    hash.finish()
}

unsafe fn input<'a>(data: *const c_char, length: i32) -> &'a [u8] {
    unsafe { std::slice::from_raw_parts(data.cast(), length as usize) }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn boxdb_native_text(
    kind: i32,
    data: *const c_char,
    length: i32,
    out: *mut *mut c_char,
    out_len: *mut i32,
) -> i32 {
    let result = (|| {
        let kind = TYPES
            .iter()
            .find(|d| d.kind as i32 == kind)
            .ok_or_else(|| Error("unknown native type".into()))?
            .kind;
        text(kind, unsafe { input(data, length) })
    })();
    match result {
        Ok(value) => unsafe { crate::decimal::output(value.as_bytes(), out, out_len) },
        Err(error) => {
            let rc = unsafe { crate::decimal::output(error.0.as_bytes(), out, out_len) };
            if rc == crate::SQLITE_OK {
                crate::SQLITE_ERROR
            } else {
                rc
            }
        }
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn boxdb_native_eval(
    operation: i32,
    target: i32,
    kind_a: i32,
    data_a: *const c_char,
    len_a: i32,
    kind_b: i32,
    data_b: *const c_char,
    len_b: i32,
    out: *mut *mut c_char,
    out_len: *mut i32,
    out_kind: *mut i32,
) -> i32 {
    let result = (|| {
        if crate::unsigned::is_unsigned(target)
            || crate::unsigned::is_unsigned(kind_a)
            || crate::unsigned::is_unsigned(kind_b)
        {
            if target == 0 || crate::unsigned::is_unsigned(target) {
                unsafe {
                    *out_kind = if target == 0 {
                        kind_a.max(kind_b)
                    } else {
                        target
                    };
                }
                return crate::unsigned::evaluate(
                    operation,
                    target,
                    kind_a,
                    unsafe { input(data_a, len_a) },
                    kind_b,
                    unsafe { input(data_b, len_b) },
                );
            }
        }
        if operation != 0 && operation != 8 {
            let (kind, payload) = crate::temporal::operation(
                operation,
                kind_a,
                unsafe { input(data_a, len_a) },
                kind_b,
                unsafe { input(data_b, len_b) },
            )?;
            unsafe {
                *out_kind = kind;
            }
            if kind < 0 {
                return Ok(payload);
            }
            let descriptor = TYPES.iter().find(|d| d.kind as i32 == kind).unwrap();
            let mut bytes = vec![descriptor.tag];
            bytes.extend(payload);
            return Ok(bytes);
        }
        let kind = TYPES
            .iter()
            .find(|d| d.kind as i32 == target)
            .ok_or_else(|| Error("unknown native type".into()))?
            .kind;
        unsafe {
            *out_kind = kind as i32;
        }
        if kind_a > 0 {
            let source = TYPES
                .iter()
                .find(|d| d.kind as i32 == kind_a)
                .ok_or_else(|| Error("unknown native type".into()))?
                .kind;
            let input = unsafe { input(data_a, len_a) };
            let payload = if kind == source {
                text(kind, input)?;
                input.to_vec()
            } else {
                crate::temporal::cast(kind, source, input, operation == 8)?
            };
            let descriptor = TYPES.iter().find(|d| d.kind == kind).unwrap();
            let mut bytes = vec![descriptor.tag];
            bytes.extend(payload);
            return Ok(bytes);
        }
        if kind_a != 0 {
            return Err(Error("unsupported native conversion".into()));
        }
        let text = std::str::from_utf8(unsafe { input(data_a, len_a) })
            .map_err(|e| Error(e.to_string()))?;
        encode(kind, text)
    })();
    match result {
        Ok(bytes) => unsafe { crate::decimal::output(&bytes, out, out_len) },
        Err(error) => {
            let rc = unsafe { crate::decimal::output(error.0.as_bytes(), out, out_len) };
            if rc == crate::SQLITE_OK {
                crate::SQLITE_ERROR
            } else {
                rc
            }
        }
    }
}