saddle-db 0.1.1

Saddle managed asynchronous database access and transactions
Documentation
use std::fmt;

use sqlx::{MySql, Row, Type, ValueRef, mysql::MySqlRow};

use crate::{
    Result,
    error::{invalid_column, result_limit_exceeded, unsupported_column_type},
};

/// Maximum supported decoded field size. The stricter driver allocation bound
/// is enforced by `MAX_INBOUND_PACKET_BYTES` during pool startup.
pub const MAX_FIELD_BYTES: usize = 1_048_576;
/// Maximum decoded payload retained by one multi-row result.
pub const MAX_RESULT_BYTES: usize = 8_388_608;

/// A database row that exposes stable typed access without exposing sqlx.
pub struct DbRow(pub(crate) MySqlRow);

impl fmt::Debug for DbRow {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("DbRow")
            .field("columns", &self.0.len())
            .field("values", &"<redacted>")
            .finish()
    }
}

impl DbRow {
    pub fn bool(&self, column: &str) -> Result<bool> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn i64(&self, column: &str) -> Result<i64> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn u64(&self, column: &str) -> Result<u64> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn f64(&self, column: &str) -> Result<f64> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn string(&self, column: &str) -> Result<String> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn bytes(&self, column: &str) -> Result<Vec<u8>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn optional_i64(&self, column: &str) -> Result<Option<i64>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn optional_bool(&self, column: &str) -> Result<Option<bool>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn optional_u64(&self, column: &str) -> Result<Option<u64>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn optional_f64(&self, column: &str) -> Result<Option<f64>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn optional_string(&self, column: &str) -> Result<Option<String>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
    pub fn optional_bytes(&self, column: &str) -> Result<Option<Vec<u8>>> {
        self.0.try_get(column).map_err(|_| invalid_column())
    }
}

pub(crate) fn row_payload_bytes(row: &MySqlRow) -> Result<usize> {
    let mut total = 0_usize;
    for index in 0..row.len() {
        let raw = row.try_get_raw(index).map_err(|_| invalid_column())?;
        if raw.is_null() {
            continue;
        }
        let type_info = raw.type_info();
        let bytes = if <&[u8] as Type<MySql>>::compatible(type_info.as_ref()) {
            row.try_get::<&[u8], _>(index)
                .map_err(|_| invalid_column())?
                .len()
        } else if <i64 as Type<MySql>>::compatible(type_info.as_ref())
            || <u64 as Type<MySql>>::compatible(type_info.as_ref())
            || <f64 as Type<MySql>>::compatible(type_info.as_ref())
            || <bool as Type<MySql>>::compatible(type_info.as_ref())
        {
            8
        } else {
            return Err(unsupported_column_type());
        };
        if bytes > MAX_FIELD_BYTES {
            return Err(result_limit_exceeded());
        }
        total = total.saturating_add(bytes);
        if total > MAX_RESULT_BYTES {
            return Err(result_limit_exceeded());
        }
    }
    Ok(total)
}

/// Stable write metadata returned by insert, update and delete operations.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WriteResult {
    rows_affected: u64,
    last_insert_id: u64,
}

impl WriteResult {
    pub(crate) const fn new(rows_affected: u64, last_insert_id: u64) -> Self {
        Self {
            rows_affected,
            last_insert_id,
        }
    }
    pub const fn rows_affected(self) -> u64 {
        self.rows_affected
    }
    pub const fn last_insert_id(self) -> u64 {
        self.last_insert_id
    }
}