tank-sqlite 0.32.0

SQLite driver implementation for Tank: the Rust data layer
Documentation
use crate::{CBox, sql_writer::SQLiteSqlWriter};
use libsqlite3_sys::*;
use rust_decimal::prelude::ToPrimitive;
use std::{
    ffi::{CStr, c_int},
    fmt::{self, Display},
    os::raw::{c_char, c_void},
};
use tank_core::{
    AsValue, Context, DynQuery, Error, Fragment, Prepared, Result, SqlWriter, Value,
    error_message_from_ptr, truncate_long,
};

/// Prepared statement wrapper for SQLite.
///
/// Manages the native statement pointer and binding index, and implements  the `Prepared` trait to bind parameters from `tank_core::Value`.
#[derive(Debug)]
pub struct SQLitePrepared {
    pub(crate) statement: CBox<*mut sqlite3_stmt>,
    pub(crate) index: u64,
}

impl SQLitePrepared {
    const WRITER: SQLiteSqlWriter = SQLiteSqlWriter {};
    pub(crate) fn new(statement: CBox<*mut sqlite3_stmt>) -> Self {
        Self {
            statement: statement.into(),
            index: 1,
        }
    }
    pub(crate) fn statement(&self) -> *mut sqlite3_stmt {
        *self.statement
    }
    pub fn last_error(&self) -> String {
        unsafe {
            let db = sqlite3_db_handle(self.statement());
            let errcode = sqlite3_errcode(db);
            format!(
                "Error ({errcode}): {}",
                error_message_from_ptr(&sqlite3_errmsg(db)),
            )
        }
    }
}

impl Prepared for SQLitePrepared {
    fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
        self
    }
    fn clear_bindings(&mut self) -> Result<&mut Self> {
        self.index = 1;
        unsafe {
            let rc = sqlite3_reset(self.statement());
            let error = || {
                let e = Error::msg(self.last_error())
                    .context("Could not clear the bindings from Sqlite statement");
                log::error!("{e:#}");
                e
            };
            if rc != SQLITE_OK {
                return Err(error());
            }
            let rc = sqlite3_clear_bindings(self.statement());
            if rc != SQLITE_OK {
                return Err(error());
            }
        }
        Ok(self)
    }
    fn bind(&mut self, value: impl AsValue) -> Result<&mut Self> {
        self.bind_index(value, self.index)?;
        Ok(self)
    }
    fn bind_index(&mut self, v: impl AsValue, index: u64) -> Result<&mut Self> {
        let index = index as c_int;
        unsafe {
            let value = v.as_value();
            let statement = self.statement();
            let rc = match value {
                _ if value.is_null() => sqlite3_bind_null(statement, index),
                Value::Boolean(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::Int8(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::Int16(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::Int32(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::Int64(Some(v), ..) => sqlite3_bind_int64(statement, index, v),
                Value::Int128(Some(v), ..) => {
                    if v as sqlite3_int64 as i128 != v {
                        return Err(Error::msg(format!(
                            "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
                        )));
                    }
                    sqlite3_bind_int64(statement, index, v as sqlite3_int64)
                }
                Value::UInt8(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::UInt16(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::UInt32(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
                Value::UInt64(Some(v), ..) => {
                    if v as sqlite3_int64 as u64 != v {
                        return Err(Error::msg(format!(
                            "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
                        )));
                    }
                    sqlite3_bind_int64(statement, index, v as sqlite3_int64)
                }
                Value::UInt128(Some(v), ..) => {
                    if v as sqlite3_int64 as u128 != v {
                        return Err(Error::msg(format!(
                            "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
                        )));
                    }
                    sqlite3_bind_int64(statement, index, v as sqlite3_int64)
                }
                Value::Float32(Some(v), ..) => sqlite3_bind_double(statement, index, v as f64),
                Value::Float64(Some(v), ..) => sqlite3_bind_double(statement, index, v),
                Value::Decimal(Some(v), ..) => sqlite3_bind_double(
                    statement,
                    index,
                    v.to_f64().ok_or_else(|| {
                        Error::msg(format!("Cannot bind the Decimal value `{v}` to f64"))
                    })?,
                ),
                Value::Char(Some(v), ..) => {
                    let v = v.to_string();
                    sqlite3_bind_text(
                        statement,
                        index,
                        v.as_ptr() as *const c_char,
                        v.len() as c_int,
                        SQLITE_TRANSIENT(),
                    )
                }
                Value::Varchar(Some(v), ..) => sqlite3_bind_text(
                    statement,
                    index,
                    v.as_ptr() as *const c_char,
                    v.len() as c_int,
                    SQLITE_TRANSIENT(),
                ),
                Value::Blob(Some(v), ..) => sqlite3_bind_blob(
                    statement,
                    index,
                    v.as_ptr() as *const c_void,
                    v.len() as c_int,
                    SQLITE_TRANSIENT(),
                ),
                Value::Date(Some(v), ..) => {
                    let mut out = DynQuery::with_capacity(32);
                    Self::WRITER.write_date(
                        &mut Context::fragment(Fragment::ParameterBinding),
                        &mut out,
                        &v,
                    );
                    sqlite3_bind_text(
                        statement,
                        index,
                        out.as_str().as_ptr() as *const c_char,
                        out.len() as c_int,
                        SQLITE_TRANSIENT(),
                    )
                }
                Value::Time(Some(v), ..) => {
                    let mut out = DynQuery::with_capacity(32);
                    Self::WRITER.write_time(
                        &mut Context::fragment(Fragment::ParameterBinding),
                        &mut out,
                        &v,
                    );
                    sqlite3_bind_text(
                        statement,
                        index,
                        out.as_str().as_ptr() as *const c_char,
                        out.len() as c_int,
                        SQLITE_TRANSIENT(),
                    )
                }
                Value::Timestamp(Some(v), ..) => {
                    let mut out = DynQuery::with_capacity(32);
                    Self::WRITER.write_timestamp(
                        &mut Context::fragment(Fragment::ParameterBinding),
                        &mut out,
                        &v,
                    );
                    sqlite3_bind_text(
                        statement,
                        index,
                        out.as_str().as_ptr() as *const c_char,
                        out.len() as c_int,
                        SQLITE_TRANSIENT(),
                    )
                }
                Value::TimestampWithTimezone(Some(v), ..) => {
                    let mut out = DynQuery::with_capacity(32);
                    Self::WRITER.write_timestamptz(
                        &mut Context::fragment(Fragment::ParameterBinding),
                        &mut out,
                        &v,
                    );
                    sqlite3_bind_text(
                        statement,
                        index,
                        out.as_str().as_ptr() as *const c_char,
                        out.len() as c_int,
                        SQLITE_TRANSIENT(),
                    )
                }
                Value::Uuid(Some(v), ..) => {
                    let v = v.to_string();
                    sqlite3_bind_text(
                        statement,
                        index,
                        v.as_ptr() as *const c_char,
                        v.len() as c_int,
                        SQLITE_TRANSIENT(),
                    )
                }
                _ => {
                    let error =
                        Error::msg(format!("Cannot use a {:?} as a query parameter", value));
                    log::error!("{:#}", error);
                    return Err(error);
                }
            };
            if rc != SQLITE_OK {
                let db = sqlite3_db_handle(statement);
                let query = sqlite3_sql(statement);
                let error = Error::msg(error_message_from_ptr(&sqlite3_errmsg(db)).to_string())
                    .context(format!(
                        "Cannot bind parameter {index} to query:\n{}",
                        truncate_long!(CStr::from_ptr(query).to_string_lossy())
                    ));
                log::error!("{:#}", error);
                return Err(error);
            }
            self.index = index as u64 + 1;
            Ok(self)
        }
    }
}

impl Display for SQLitePrepared {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:p}", self.statement())
    }
}