#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::checked_conversions)]
use crate::ffi;
use sqlmodel_core::Value;
use std::ffi::{CStr, c_int};
pub unsafe fn bind_value(stmt: *mut ffi::sqlite3_stmt, index: c_int, value: &Value) -> c_int {
unsafe {
match value {
Value::Null => ffi::sqlite3_bind_null(stmt, index),
Value::Bool(b) => ffi::sqlite3_bind_int(stmt, index, if *b { 1 } else { 0 }),
Value::TinyInt(v) => ffi::sqlite3_bind_int(stmt, index, i32::from(*v)),
Value::SmallInt(v) => ffi::sqlite3_bind_int(stmt, index, i32::from(*v)),
Value::Int(v) => ffi::sqlite3_bind_int(stmt, index, *v),
Value::BigInt(v) => ffi::sqlite3_bind_int64(stmt, index, *v),
Value::Float(v) => ffi::sqlite3_bind_double(stmt, index, f64::from(*v)),
Value::Double(v) => ffi::sqlite3_bind_double(stmt, index, *v),
Value::Decimal(s) => {
let bytes = s.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Text(s) => {
let bytes = s.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Bytes(b) => ffi::sqlite3_bind_blob(
stmt,
index,
b.as_ptr().cast(),
b.len() as c_int,
ffi::sqlite_transient(),
),
Value::Date(days) => {
let date = days_to_date(*days);
let bytes = date.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Time(micros) => {
let time = micros_to_time(*micros);
let bytes = time.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Timestamp(micros) | Value::TimestampTz(micros) => {
let ts = micros_to_timestamp(*micros);
let bytes = ts.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Uuid(bytes) => ffi::sqlite3_bind_blob(
stmt,
index,
bytes.as_ptr().cast(),
16,
ffi::sqlite_transient(),
),
Value::Json(json) => {
let s = json.to_string();
let bytes = s.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Array(arr) => {
let json = serde_json::Value::Array(arr.iter().map(value_to_json).collect());
let s = json.to_string();
let bytes = s.as_bytes();
ffi::sqlite3_bind_text(
stmt,
index,
bytes.as_ptr().cast(),
bytes.len() as c_int,
ffi::sqlite_transient(),
)
}
Value::Default => ffi::sqlite3_bind_null(stmt, index),
}
}
}
pub unsafe fn read_column(stmt: *mut ffi::sqlite3_stmt, index: c_int) -> Value {
unsafe {
let col_type = ffi::sqlite3_column_type(stmt, index);
match col_type {
ffi::SQLITE_NULL => Value::Null,
ffi::SQLITE_INTEGER => {
let v = ffi::sqlite3_column_int64(stmt, index);
if v >= i32::MIN as i64 && v <= i32::MAX as i64 {
Value::Int(v as i32)
} else {
Value::BigInt(v)
}
}
ffi::SQLITE_FLOAT => {
let v = ffi::sqlite3_column_double(stmt, index);
Value::Double(v)
}
ffi::SQLITE_TEXT => {
let ptr = ffi::sqlite3_column_text(stmt, index);
let len = ffi::sqlite3_column_bytes(stmt, index);
if ptr.is_null() {
Value::Null
} else {
let slice = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
let s = String::from_utf8_lossy(slice).into_owned();
Value::Text(s)
}
}
ffi::SQLITE_BLOB => {
let ptr = ffi::sqlite3_column_blob(stmt, index);
let len = ffi::sqlite3_column_bytes(stmt, index);
if ptr.is_null() || len == 0 {
Value::Bytes(Vec::new())
} else {
let slice = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
Value::Bytes(slice.to_vec())
}
}
_ => Value::Null,
}
}
}
pub unsafe fn column_name(stmt: *mut ffi::sqlite3_stmt, index: c_int) -> Option<String> {
unsafe {
let ptr = ffi::sqlite3_column_name(stmt, index);
if ptr.is_null() {
None
} else {
CStr::from_ptr(ptr).to_str().ok().map(String::from)
}
}
}
fn days_to_date(days: i32) -> String {
sqlmodel_core::value::iso_date(days)
}
fn micros_to_time(micros: i64) -> String {
sqlmodel_core::value::iso_time(micros)
}
fn micros_to_timestamp(micros: i64) -> String {
sqlmodel_core::value::iso_timestamp(micros)
}
fn value_to_json(value: &Value) -> serde_json::Value {
match value {
Value::Null => serde_json::Value::Null,
Value::Bool(b) => serde_json::Value::Bool(*b),
Value::TinyInt(v) => serde_json::Value::Number((*v).into()),
Value::SmallInt(v) => serde_json::Value::Number((*v).into()),
Value::Int(v) => serde_json::Value::Number((*v).into()),
Value::BigInt(v) => serde_json::Value::Number((*v).into()),
Value::Float(v) => serde_json::Number::from_f64(f64::from(*v))
.map_or(serde_json::Value::Null, serde_json::Value::Number),
Value::Double(v) => serde_json::Number::from_f64(*v)
.map_or(serde_json::Value::Null, serde_json::Value::Number),
Value::Decimal(s) | Value::Text(s) => serde_json::Value::String(s.clone()),
Value::Bytes(b) => serde_json::Value::String(base64_encode(b)),
Value::Date(d) => serde_json::Value::String(days_to_date(*d)),
Value::Time(t) => serde_json::Value::String(micros_to_time(*t)),
Value::Timestamp(ts) | Value::TimestampTz(ts) => {
serde_json::Value::String(micros_to_timestamp(*ts))
}
Value::Uuid(bytes) => serde_json::Value::String(uuid_to_string(bytes)),
Value::Json(j) => j.clone(),
Value::Array(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
Value::Default => serde_json::Value::Null,
}
}
fn base64_encode(data: &[u8]) -> String {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
for chunk in data.chunks(3) {
let b0 = chunk[0];
let b1 = chunk.get(1).copied().unwrap_or(0);
let b2 = chunk.get(2).copied().unwrap_or(0);
result.push(ALPHABET[(b0 >> 2) as usize] as char);
result.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize] as char);
if chunk.len() > 1 {
result.push(ALPHABET[(((b1 & 0x0f) << 2) | (b2 >> 6)) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(ALPHABET[(b2 & 0x3f) as usize] as char);
} else {
result.push('=');
}
}
result
}
fn uuid_to_string(bytes: &[u8; 16]) -> String {
format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
bytes[6],
bytes[7],
bytes[8],
bytes[9],
bytes[10],
bytes[11],
bytes[12],
bytes[13],
bytes[14],
bytes[15]
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_days_to_date() {
assert_eq!(days_to_date(0), "1970-01-01");
assert_eq!(days_to_date(1), "1970-01-02");
}
#[test]
fn test_micros_to_time() {
assert_eq!(micros_to_time(0), "00:00:00");
assert_eq!(micros_to_time(3_600_000_000), "01:00:00");
assert_eq!(micros_to_time(3_661_123_000), "01:01:01.123000");
assert_eq!(micros_to_time(3_661_123_456), "01:01:01.123456");
assert_eq!(
micros_to_timestamp(-2_208_988_800_000_000),
"1900-01-01T00:00:00"
);
}
#[test]
fn test_base64_encode() {
assert_eq!(base64_encode(b""), "");
assert_eq!(base64_encode(b"f"), "Zg==");
assert_eq!(base64_encode(b"fo"), "Zm8=");
assert_eq!(base64_encode(b"foo"), "Zm9v");
assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
}
#[test]
fn test_uuid_to_string() {
let uuid = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
assert_eq!(
uuid_to_string(&uuid),
"01020304-0506-0708-090a-0b0c0d0e0f10"
);
}
}