picodata_plugin/sql/
mod.rs1use crate::internal::ffi;
4use crate::sql::types::SqlValue;
5use abi_stable::derive_macro_reexports::RResult;
6use abi_stable::std_types::{ROk, RVec};
7use serde::de::DeserializeOwned;
8use serde::Deserialize;
9use std::collections::HashMap;
10use tarantool::error::{BoxError, IntoBoxError, TarantoolErrorCode};
11use tarantool::tuple::Tuple;
12
13pub mod types;
14
15pub fn query_raw(query: &str, params: Vec<SqlValue>) -> Result<Tuple, BoxError> {
37 let query_len = query.len();
38 let query_ptr = query.as_ptr();
39
40 match unsafe { ffi::pico_ffi_sql_query(query_ptr, query_len, RVec::from(params)) } {
41 ROk(ptr) => Ok(Tuple::try_from_ptr(ptr).expect("should not be null")),
42 RResult::RErr(_) => Err(BoxError::last()),
43 }
44}
45
46pub struct Query<'a> {
47 query: &'a str,
48 params: Vec<SqlValue>,
49}
50
51impl Query<'_> {
52 #[inline(always)]
54 pub fn bind<T: Into<SqlValue>>(mut self, value: T) -> Self {
55 self.params.push(value.into());
56 self
57 }
58
59 pub fn execute(self) -> Result<u64, BoxError> {
73 let tuple = query_raw(self.query, self.params)?;
74 #[derive(Deserialize)]
75 struct Output {
76 row_count: u64,
77 }
78
79 let result = tuple
80 .decode::<Vec<Output>>()
81 .map_err(|tt| tt.into_box_error())?;
82
83 let result = result.first().ok_or_else(|| {
84 BoxError::new(
85 TarantoolErrorCode::InvalidMsgpack,
86 "sql result should contains at least one row",
87 )
88 })?;
89
90 Ok(result.row_count)
91 }
92
93 pub fn fetch<T: DeserializeOwned>(self) -> Result<Vec<T>, BoxError> {
109 let tuple = query_raw(self.query, self.params)?;
110
111 let mut res = tuple
112 .decode::<Vec<HashMap<String, rmpv::Value>>>()
113 .map_err(|tt| tt.into_box_error())?;
114
115 let Some(mut map) = res.pop() else {
116 return Err(BoxError::new(
117 TarantoolErrorCode::InvalidMsgpack,
118 "fetch result array should contains at least one element",
119 ));
120 };
121 let Some(rows) = map.remove("rows") else {
122 return Err(BoxError::new(
123 TarantoolErrorCode::InvalidMsgpack,
124 "fetch result map should contains `rows` key",
125 ));
126 };
127
128 let data: Vec<T> = rmpv::ext::from_value(rows)
129 .map_err(|e| BoxError::new(TarantoolErrorCode::InvalidMsgpack, e.to_string()))?;
130
131 Ok(data)
132 }
133}
134
135pub fn query(query: &str) -> Query<'_> {
137 Query {
138 query,
139 params: vec![],
140 }
141}