orbis_plugin_api/sdk/
db.rs1use super::error::{Error, Result};
24use serde::{de::DeserializeOwned, Deserialize, Serialize};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(untagged)]
29pub enum DbValue {
30 Null,
32 Bool(bool),
34 Int(i64),
36 Float(f64),
38 String(String),
40 Bytes(Vec<u8>),
42 Json(serde_json::Value),
44}
45
46impl From<()> for DbValue {
47 fn from(_: ()) -> Self {
48 Self::Null
49 }
50}
51
52impl From<bool> for DbValue {
53 fn from(v: bool) -> Self {
54 Self::Bool(v)
55 }
56}
57
58impl From<i32> for DbValue {
59 fn from(v: i32) -> Self {
60 Self::Int(i64::from(v))
61 }
62}
63
64impl From<i64> for DbValue {
65 fn from(v: i64) -> Self {
66 Self::Int(v)
67 }
68}
69
70impl From<f64> for DbValue {
71 fn from(v: f64) -> Self {
72 Self::Float(v)
73 }
74}
75
76impl From<&str> for DbValue {
77 fn from(v: &str) -> Self {
78 Self::String(v.to_string())
79 }
80}
81
82impl From<String> for DbValue {
83 fn from(v: String) -> Self {
84 Self::String(v)
85 }
86}
87
88impl From<Vec<u8>> for DbValue {
89 fn from(v: Vec<u8>) -> Self {
90 Self::Bytes(v)
91 }
92}
93
94impl From<serde_json::Value> for DbValue {
95 fn from(v: serde_json::Value) -> Self {
96 Self::Json(v)
97 }
98}
99
100pub trait ToDbParams {
102 fn to_db_params(&self) -> Vec<DbValue>;
103}
104
105impl ToDbParams for () {
106 fn to_db_params(&self) -> Vec<DbValue> {
107 vec![]
108 }
109}
110
111impl<T: Into<DbValue> + Clone> ToDbParams for &[T] {
112 fn to_db_params(&self) -> Vec<DbValue> {
113 self.iter().map(|v| v.clone().into()).collect()
114 }
115}
116
117impl<T: Into<DbValue> + Clone, const N: usize> ToDbParams for [T; N] {
118 fn to_db_params(&self) -> Vec<DbValue> {
119 self.iter().map(|v| v.clone().into()).collect()
120 }
121}
122
123impl ToDbParams for Vec<DbValue> {
124 fn to_db_params(&self) -> Vec<DbValue> {
125 self.clone()
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct DbRow {
132 #[serde(flatten)]
134 pub columns: std::collections::HashMap<String, serde_json::Value>,
135}
136
137impl DbRow {
138 #[inline]
140 pub fn get(&self, name: &str) -> Option<&serde_json::Value> {
141 self.columns.get(name)
142 }
143
144 pub fn get_as<T: DeserializeOwned>(&self, name: &str) -> Result<Option<T>> {
146 match self.columns.get(name) {
147 Some(v) => serde_json::from_value(v.clone()).map(Some).map_err(Error::from),
148 None => Ok(None),
149 }
150 }
151
152 pub fn get_required<T: DeserializeOwned>(&self, name: &str) -> Result<T> {
154 self.get_as(name)?
155 .ok_or_else(|| Error::database(format!("Missing column: {}", name)))
156 }
157
158 pub fn into_typed<T: DeserializeOwned>(self) -> Result<T> {
160 let value = serde_json::to_value(self.columns)?;
161 serde_json::from_value(value).map_err(Error::from)
162 }
163}
164
165#[derive(Debug, Serialize)]
167#[allow(dead_code)]
168struct QueryRequest<'a> {
169 sql: &'a str,
170 params: Vec<DbValue>,
171}
172
173#[derive(Debug, Deserialize)]
175#[allow(dead_code)]
176struct QueryResponse {
177 rows: Vec<DbRow>,
178 #[serde(default)]
179 error: Option<String>,
180}
181
182#[derive(Debug, Deserialize)]
184#[allow(dead_code)]
185struct ExecuteResponse {
186 rows_affected: i64,
187 #[serde(default)]
188 last_insert_id: Option<i64>,
189 #[serde(default)]
190 error: Option<String>,
191}
192
193#[cfg(target_arch = "wasm32")]
211pub fn query<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<Vec<T>> {
212 let request = QueryRequest {
213 sql,
214 params: params.to_db_params(),
215 };
216
217 let request_json = serde_json::to_vec(&request)?;
218
219 let result_ptr = unsafe {
220 super::ffi::db_query(
221 sql.as_ptr() as i32,
222 sql.len() as i32,
223 request_json.as_ptr() as i32,
224 request_json.len() as i32,
225 )
226 };
227
228 if result_ptr == 0 {
229 return Err(Error::database("Database query failed"));
230 }
231
232 let result_bytes = unsafe { super::ffi::read_length_prefixed(result_ptr) };
233 let response: QueryResponse = serde_json::from_slice(&result_bytes)?;
234
235 if let Some(err) = response.error {
236 return Err(Error::database(err));
237 }
238
239 response
240 .rows
241 .into_iter()
242 .map(|row| row.into_typed())
243 .collect()
244}
245
246#[cfg(not(target_arch = "wasm32"))]
248pub fn query<T: DeserializeOwned>(_sql: &str, _params: impl ToDbParams) -> Result<Vec<T>> {
249 Ok(vec![])
250}
251
252#[cfg(target_arch = "wasm32")]
254pub fn query_raw(sql: &str, params: impl ToDbParams) -> Result<Vec<DbRow>> {
255 let request = QueryRequest {
256 sql,
257 params: params.to_db_params(),
258 };
259
260 let request_json = serde_json::to_vec(&request)?;
261
262 let result_ptr = unsafe {
263 super::ffi::db_query(
264 sql.as_ptr() as i32,
265 sql.len() as i32,
266 request_json.as_ptr() as i32,
267 request_json.len() as i32,
268 )
269 };
270
271 if result_ptr == 0 {
272 return Err(Error::database("Database query failed"));
273 }
274
275 let result_bytes = unsafe { super::ffi::read_length_prefixed(result_ptr) };
276 let response: QueryResponse = serde_json::from_slice(&result_bytes)?;
277
278 if let Some(err) = response.error {
279 return Err(Error::database(err));
280 }
281
282 Ok(response.rows)
283}
284
285#[cfg(not(target_arch = "wasm32"))]
287pub fn query_raw(_sql: &str, _params: impl ToDbParams) -> Result<Vec<DbRow>> {
288 Ok(vec![])
289}
290
291pub fn query_one<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<Option<T>> {
293 let results = query::<T>(sql, params)?;
294 Ok(results.into_iter().next())
295}
296
297pub fn query_one_required<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<T> {
299 query_one::<T>(sql, params)?.ok_or_else(|| Error::not_found("No rows found"))
300}
301
302pub fn query_scalar<T: DeserializeOwned>(sql: &str, params: impl ToDbParams) -> Result<Option<T>> {
304 let rows = query_raw(sql, params)?;
305 if let Some(row) = rows.into_iter().next() {
306 if let Some((_, value)) = row.columns.into_iter().next() {
307 return Ok(Some(serde_json::from_value(value)?));
308 }
309 }
310 Ok(None)
311}
312
313#[cfg(target_arch = "wasm32")]
326pub fn execute(sql: &str, params: impl ToDbParams) -> Result<i64> {
327 let params_json = serde_json::to_vec(¶ms.to_db_params())?;
328
329 let result = unsafe {
330 super::ffi::db_execute(
331 sql.as_ptr() as i32,
332 sql.len() as i32,
333 params_json.as_ptr() as i32,
334 params_json.len() as i32,
335 )
336 };
337
338 if result < 0 {
339 return Err(Error::database("Database execute failed"));
340 }
341
342 Ok(i64::from(result))
343}
344
345#[cfg(not(target_arch = "wasm32"))]
347pub fn execute(_sql: &str, _params: impl ToDbParams) -> Result<i64> {
348 Ok(0)
349}
350
351#[cfg(target_arch = "wasm32")]
353pub fn insert_returning_id(sql: &str, params: impl ToDbParams) -> Result<i64> {
354 let returning_sql = if sql.to_uppercase().contains("RETURNING") {
356 sql.to_string()
357 } else {
358 format!("{} RETURNING id", sql)
359 };
360
361 query_scalar::<i64>(&returning_sql, params)?
362 .ok_or_else(|| Error::database("Insert did not return an ID"))
363}
364
365#[cfg(not(target_arch = "wasm32"))]
367pub fn insert_returning_id(_sql: &str, _params: impl ToDbParams) -> Result<i64> {
368 Ok(0)
369}
370
371pub struct Transaction {
373 operations: Vec<(String, Vec<DbValue>)>,
374}
375
376impl Transaction {
377 #[must_use]
379 pub const fn new() -> Self {
380 Self {
381 operations: Vec::new(),
382 }
383 }
384
385 pub fn add(&mut self, sql: impl Into<String>, params: impl ToDbParams) -> &mut Self {
387 self.operations.push((sql.into(), params.to_db_params()));
388 self
389 }
390
391 pub fn commit(self) -> Result<()> {
395 for (sql, params) in self.operations {
396 execute(&sql, params.as_slice())?;
397 }
398 Ok(())
399 }
400}
401
402impl Default for Transaction {
403 fn default() -> Self {
404 Self::new()
405 }
406}
407
408#[inline]
410#[must_use]
411pub const fn transaction() -> Transaction {
412 Transaction::new()
413}