Skip to main content

saddle_db/
row.rs

1use std::fmt;
2
3use sqlx::{MySql, Row, Type, ValueRef, mysql::MySqlRow};
4
5use crate::{
6    Result,
7    error::{invalid_column, result_limit_exceeded, unsupported_column_type},
8};
9
10/// Maximum supported decoded field size. The stricter driver allocation bound
11/// is enforced by `MAX_INBOUND_PACKET_BYTES` during pool startup.
12pub const MAX_FIELD_BYTES: usize = 1_048_576;
13/// Maximum decoded payload retained by one multi-row result.
14pub const MAX_RESULT_BYTES: usize = 8_388_608;
15
16/// A database row that exposes stable typed access without exposing sqlx.
17pub struct DbRow(pub(crate) MySqlRow);
18
19impl fmt::Debug for DbRow {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        formatter
22            .debug_struct("DbRow")
23            .field("columns", &self.0.len())
24            .field("values", &"<redacted>")
25            .finish()
26    }
27}
28
29impl DbRow {
30    pub fn bool(&self, column: &str) -> Result<bool> {
31        self.0.try_get(column).map_err(|_| invalid_column())
32    }
33    pub fn i64(&self, column: &str) -> Result<i64> {
34        self.0.try_get(column).map_err(|_| invalid_column())
35    }
36    pub fn u64(&self, column: &str) -> Result<u64> {
37        self.0.try_get(column).map_err(|_| invalid_column())
38    }
39    pub fn f64(&self, column: &str) -> Result<f64> {
40        self.0.try_get(column).map_err(|_| invalid_column())
41    }
42    pub fn string(&self, column: &str) -> Result<String> {
43        self.0.try_get(column).map_err(|_| invalid_column())
44    }
45    pub fn bytes(&self, column: &str) -> Result<Vec<u8>> {
46        self.0.try_get(column).map_err(|_| invalid_column())
47    }
48    pub fn optional_i64(&self, column: &str) -> Result<Option<i64>> {
49        self.0.try_get(column).map_err(|_| invalid_column())
50    }
51    pub fn optional_bool(&self, column: &str) -> Result<Option<bool>> {
52        self.0.try_get(column).map_err(|_| invalid_column())
53    }
54    pub fn optional_u64(&self, column: &str) -> Result<Option<u64>> {
55        self.0.try_get(column).map_err(|_| invalid_column())
56    }
57    pub fn optional_f64(&self, column: &str) -> Result<Option<f64>> {
58        self.0.try_get(column).map_err(|_| invalid_column())
59    }
60    pub fn optional_string(&self, column: &str) -> Result<Option<String>> {
61        self.0.try_get(column).map_err(|_| invalid_column())
62    }
63    pub fn optional_bytes(&self, column: &str) -> Result<Option<Vec<u8>>> {
64        self.0.try_get(column).map_err(|_| invalid_column())
65    }
66}
67
68pub(crate) fn row_payload_bytes(row: &MySqlRow) -> Result<usize> {
69    let mut total = 0_usize;
70    for index in 0..row.len() {
71        let raw = row.try_get_raw(index).map_err(|_| invalid_column())?;
72        if raw.is_null() {
73            continue;
74        }
75        let type_info = raw.type_info();
76        let bytes = if <&[u8] as Type<MySql>>::compatible(type_info.as_ref()) {
77            row.try_get::<&[u8], _>(index)
78                .map_err(|_| invalid_column())?
79                .len()
80        } else if <i64 as Type<MySql>>::compatible(type_info.as_ref())
81            || <u64 as Type<MySql>>::compatible(type_info.as_ref())
82            || <f64 as Type<MySql>>::compatible(type_info.as_ref())
83            || <bool as Type<MySql>>::compatible(type_info.as_ref())
84        {
85            8
86        } else {
87            return Err(unsupported_column_type());
88        };
89        if bytes > MAX_FIELD_BYTES {
90            return Err(result_limit_exceeded());
91        }
92        total = total.saturating_add(bytes);
93        if total > MAX_RESULT_BYTES {
94            return Err(result_limit_exceeded());
95        }
96    }
97    Ok(total)
98}
99
100/// Stable write metadata returned by insert, update and delete operations.
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub struct WriteResult {
103    rows_affected: u64,
104    last_insert_id: u64,
105}
106
107impl WriteResult {
108    pub(crate) const fn new(rows_affected: u64, last_insert_id: u64) -> Self {
109        Self {
110            rows_affected,
111            last_insert_id,
112        }
113    }
114    pub const fn rows_affected(self) -> u64 {
115        self.rows_affected
116    }
117    pub const fn last_insert_id(self) -> u64 {
118        self.last_insert_id
119    }
120}