vtx_sdk/modules/data/
sql.rs

1//! Host-side SQL helpers.
2
3use crate::bindings::vtx::api::vtx_sql::{self, DbValue};
4use crate::error::{VtxError, VtxResult};
5use serde::de::DeserializeOwned;
6
7/// Trait: Converts Rust types to the `DbValue` defined by WIT.
8///
9/// Suitable for passing parameters across database boundaries.
10pub trait ToDbValue {
11    fn to_db_value(&self) -> DbValue;
12}
13
14// --- Implementation of mapping basic types to DbValue ---
15
16impl ToDbValue for String {
17    fn to_db_value(&self) -> DbValue {
18        DbValue::Text(self.clone())
19    }
20}
21
22impl ToDbValue for &str {
23    fn to_db_value(&self) -> DbValue {
24        DbValue::Text(self.to_string())
25    }
26}
27
28impl ToDbValue for i64 {
29    fn to_db_value(&self) -> DbValue {
30        DbValue::Integer(*self)
31    }
32}
33
34impl ToDbValue for i32 {
35    fn to_db_value(&self) -> DbValue {
36        DbValue::Integer(*self as i64)
37    }
38}
39
40impl ToDbValue for f64 {
41    fn to_db_value(&self) -> DbValue {
42        DbValue::Real(*self)
43    }
44}
45
46impl ToDbValue for f32 {
47    fn to_db_value(&self) -> DbValue {
48        DbValue::Real(*self as f64)
49    }
50}
51
52impl ToDbValue for u64 {
53    fn to_db_value(&self) -> DbValue {
54        DbValue::Integer(*self as i64)
55    }
56}
57
58impl ToDbValue for u32 {
59    fn to_db_value(&self) -> DbValue {
60        DbValue::Integer(*self as i64)
61    }
62}
63
64impl ToDbValue for bool {
65    fn to_db_value(&self) -> DbValue {
66        DbValue::Integer(if *self { 1 } else { 0 })
67    }
68}
69
70impl ToDbValue for () {
71    fn to_db_value(&self) -> DbValue {
72        DbValue::NullVal
73    }
74}
75
76impl<T: ToDbValue> ToDbValue for Option<T> {
77    fn to_db_value(&self) -> DbValue {
78        match self {
79            Some(v) => v.to_db_value(),
80            None => DbValue::NullVal,
81        }
82    }
83}
84
85/// Executes non-query SQL statements (INSERT / UPDATE / DELETE).
86///
87/// # Parameters
88/// - `sql`: Raw SQL string, supports `?` placeholders.
89/// - `params`: Array of parameters, elements must implement `ToDbValue`.
90///
91/// # Returns
92/// - Success: Returns the number of affected rows.
93/// - Failure: Maps to `VtxError::DatabaseError`.
94///
95/// # Note
96/// Calling this interface is prohibited under the Restricted security policy.
97pub fn execute(sql: &str, params: &[&dyn ToDbValue]) -> VtxResult<u64> {
98    let wit_params: Vec<DbValue> = params.iter().map(|p| p.to_db_value()).collect();
99
100    vtx_sql::execute(sql, &wit_params).map_err(|e| {
101        if e.to_lowercase().contains("permission denied") {
102            VtxError::PermissionDenied(e)
103        } else {
104            VtxError::DatabaseError(e)
105        }
106    })
107}
108
109/// Executes query SQL statements (SELECT) and deserializes the result into a list of target types.
110///
111/// # Parameters
112/// - `sql`: SQL string (supports `?` placeholders).
113/// - `params`: Array of parameters (implementing `ToDbValue`).
114///
115/// # Returns
116/// - Success: Collection of deserialized results.
117/// - Failure: `DatabaseError` or `SerializationError`.
118///
119/// # Notes
120/// - The host interface returns a JSON string.
121/// - To ensure performance, it is recommended to limit a single response to within 1MB (pagination via LIMIT is suggested).
122/// - Generic `T` must implement `DeserializeOwned` (lifetimes are not required).
123pub fn query<T: DeserializeOwned>(sql: &str, params: &[&dyn ToDbValue]) -> VtxResult<Vec<T>> {
124    let wit_params: Vec<DbValue> = params.iter().map(|p| p.to_db_value()).collect();
125
126    let json_str = vtx_sql::query_json(sql, &wit_params).map_err(|e| {
127        if e.to_lowercase().contains("permission denied") {
128            VtxError::PermissionDenied(e)
129        } else {
130            VtxError::DatabaseError(e)
131        }
132    })?;
133
134    serde_json::from_str(&json_str).map_err(|e| VtxError::SerializationError(e.to_string()))
135}