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 let res = unsafe { ffi::pico_ffi_sql_query(query_ptr, query_len, RVec::from(params)) };
42 let ptr = match res {
43 ROk(v) => v,
44 RResult::RErr(_) => return Err(BoxError::last()),
45 };
46
47 let tuple = unsafe { Tuple::try_from_ptr_dont_ref(ptr) };
51 let tuple = tuple.expect("always non null");
52
53 Ok(tuple)
54}
55
56pub struct Query<'a> {
57 query: &'a str,
58 params: Vec<SqlValue>,
59}
60
61impl Query<'_> {
62 #[inline(always)]
64 pub fn bind<T: Into<SqlValue>>(mut self, value: T) -> Self {
65 self.params.push(value.into());
66 self
67 }
68
69 pub fn execute(self) -> Result<u64, BoxError> {
83 let tuple = query_raw(self.query, self.params)?;
84 #[derive(Deserialize)]
85 struct Output {
86 row_count: u64,
87 }
88
89 let result = tuple
90 .decode::<Vec<Output>>()
91 .map_err(|tt| tt.into_box_error())?;
92
93 let result = result.first().ok_or_else(|| {
94 BoxError::new(
95 TarantoolErrorCode::InvalidMsgpack,
96 "sql result should contains at least one row",
97 )
98 })?;
99
100 Ok(result.row_count)
101 }
102
103 pub fn fetch<T: DeserializeOwned>(self) -> Result<Vec<T>, BoxError> {
119 let tuple = query_raw(self.query, self.params)?;
120
121 let mut res = tuple
122 .decode::<Vec<HashMap<String, rmpv::Value>>>()
123 .map_err(|tt| tt.into_box_error())?;
124
125 let Some(mut map) = res.pop() else {
126 return Err(BoxError::new(
127 TarantoolErrorCode::InvalidMsgpack,
128 "fetch result array should contains at least one element",
129 ));
130 };
131 let Some(rows) = map.remove("rows") else {
132 return Err(BoxError::new(
133 TarantoolErrorCode::InvalidMsgpack,
134 "fetch result map should contains `rows` key",
135 ));
136 };
137
138 let data: Vec<T> = rmpv::ext::from_value(rows)
139 .map_err(|e| BoxError::new(TarantoolErrorCode::InvalidMsgpack, e.to_string()))?;
140
141 Ok(data)
142 }
143}
144
145pub fn query(query: &str) -> Query<'_> {
147 Query {
148 query,
149 params: vec![],
150 }
151}