Skip to main content

azul_core/
db.rs

1//! POD types for the SQL database surface (SUPER_PLAN_2 §4 P4.3).
2//!
3//! Engine-agnostic: the public API is SQL strings plus typed value arrays,
4//! so the engine (bundled SQLite via `rusqlite`) stays fully hidden behind
5//! the `db-sqlite` feature in `azul-dll`. The handle type (`Db`, wrapping a
6//! `rusqlite::Connection`) lives in the dll — like `App` — because it
7//! carries an engine resource; these param/result *data* types live here in
8//! `azul-core` (no engine dep) so they're always present and codegen-able.
9//!
10//! Shape: `db.execute(sql, params: DbValueVec) -> rows_affected` and
11//! `db.query(sql, params) -> DbRows`. `DbValue` maps onto SQLite's five
12//! storage classes.
13
14use azul_css::{AzString, StringVec, U8Vec};
15
16/// A single SQL value — a bound statement parameter or a result cell.
17/// Mirrors `SQLite`'s storage classes (Null / Integer / Real / Text / Blob)
18/// but names nothing engine-specific.
19#[repr(C, u8)]
20#[derive(Debug, Clone, PartialEq)]
21pub enum DbValue {
22    /// SQL `NULL`.
23    Null,
24    /// 64-bit signed integer.
25    Integer(i64),
26    /// 64-bit IEEE float.
27    Real(f64),
28    /// UTF-8 text.
29    Text(AzString),
30    /// Raw bytes.
31    Blob(U8Vec),
32}
33
34impl DbValue {
35    #[must_use]
36    pub const fn is_null(&self) -> bool {
37        matches!(self, Self::Null)
38    }
39    #[must_use]
40    pub const fn as_integer(&self) -> Option<i64> {
41        if let Self::Integer(i) = self {
42            Some(*i)
43        } else {
44            None
45        }
46    }
47    #[must_use]
48    pub const fn as_real(&self) -> Option<f64> {
49        if let Self::Real(r) = self {
50            Some(*r)
51        } else {
52            None
53        }
54    }
55    #[must_use]
56    pub const fn as_text(&self) -> Option<&AzString> {
57        if let Self::Text(t) = self {
58            Some(t)
59        } else {
60            None
61        }
62    }
63}
64
65impl_vec!(
66    DbValue,
67    DbValueVec,
68    DbValueVecDestructor,
69    DbValueVecDestructorType,
70    DbValueVecSlice,
71    OptionDbValue
72);
73impl_vec_debug!(DbValue, DbValueVec);
74impl_vec_clone!(DbValue, DbValueVec, DbValueVecDestructor);
75impl_vec_partialeq!(DbValue, DbValueVec);
76impl_option!(
77    DbValue,
78    OptionDbValue,
79    copy = false,
80    [Debug, Clone, PartialEq]
81);
82
83/// The result of `db.query(...)` — a column-named, row-major value grid.
84/// Flat (not nested vectors) for a simple FFI shape: cell `(row, col)` is
85/// `values[row * num_columns + col]`.
86#[repr(C)]
87#[derive(Debug, Clone, PartialEq)]
88pub struct DbRows {
89    /// Column names; `len()` is the number of columns.
90    pub columns: StringVec,
91    /// All cells, row-major. `len()` is `num_rows * num_columns`.
92    pub values: DbValueVec,
93}
94
95impl DbRows {
96    /// Number of result columns.
97    #[must_use]
98    pub fn num_columns(&self) -> usize {
99        self.columns.as_ref().len()
100    }
101    /// Number of result rows (`0` when there are no columns).
102    #[must_use]
103    pub fn num_rows(&self) -> usize {
104        let cols = self.num_columns();
105        if cols == 0 {
106            0
107        } else {
108            self.values.as_ref().len() / cols
109        }
110    }
111    /// The cell at `(row, col)`, or `None` if out of range.
112    #[must_use]
113    pub fn get(&self, row: usize, col: usize) -> Option<&DbValue> {
114        let cols = self.num_columns();
115        if col >= cols {
116            return None;
117        }
118        // Checked so an out-of-range `row` (whose `row * cols + col` overflows
119        // usize) resolves to None instead of panicking (debug) / wrapping to a
120        // real cell (release).
121        let idx = row.checked_mul(cols)?.checked_add(col)?;
122        self.values.as_ref().get(idx)
123    }
124}
125
126#[cfg(test)]
127#[path = "db_test.rs"]
128mod db_test;