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;