dittolive_ditto/dql/
query_result.rs1use std::{
2 num::NonZeroU64,
3 sync::{Arc, Mutex},
4};
5
6use ffi_sdk::{self, ffi_utils::repr_c};
7use serde::de::DeserializeOwned;
8
9#[doc(hidden)]
11#[allow(deprecated)]
12use crate::store::collection::document_id::DocumentId;
13use crate::{error::DittoError, utils::extension_traits::FfiResultIntoRustResult};
14
15type CborObject = ::std::collections::HashMap<Box<str>, ::serde_cbor::Value>;
16
17pub struct QueryResult {
23 raw: repr_c::Box<ffi_sdk::FfiQueryResult>,
24 count: usize,
25}
26
27impl From<repr_c::Box_<ffi_sdk::FfiQueryResult>> for QueryResult {
28 fn from(raw: repr_c::Box<ffi_sdk::FfiQueryResult>) -> QueryResult {
29 let count = ffi_sdk::dittoffi_query_result_item_count(&raw);
30 QueryResult { raw, count }
31 }
32}
33
34impl QueryResult {
35 pub fn get_item(&self, index: usize) -> Option<QueryResultItem> {
38 if index >= self.count {
39 return None;
40 }
41 Some(QueryResultItem::from(
42 ffi_sdk::dittoffi_query_result_item_at(&self.raw, index),
43 ))
44 }
45
46 pub fn item_count(&self) -> usize {
48 self.count
49 }
50
51 pub fn mutated_document_ids(&self) -> Vec<DocumentId> {
57 let mutated_document_number =
58 ffi_sdk::dittoffi_query_result_mutated_document_id_count(&self.raw);
59
60 (0..mutated_document_number)
61 .map(|idx| ffi_sdk::dittoffi_query_result_mutated_document_id_at(&self.raw, idx))
62 .map(|raw_slice| DocumentId::from(Box::<[u8]>::from(raw_slice)))
63 .collect()
64 }
65
66 pub fn commit_id(&self) -> Option<NonZeroU64> {
77 NonZeroU64::new(ffi_sdk::dittoffi_query_result_commit_id(&self.raw))
78 }
79}
80
81impl QueryResult {
82 pub fn iter(&self) -> impl '_ + Iterator<Item = QueryResultItem> {
84 self.into_iter()
85 }
86}
87
88mod sealed {
89 pub struct QueryResultIterator<'iter> {
90 pub(super) query_result: &'iter super::QueryResult,
91 pub(super) idx: usize,
92 }
93}
94use self::sealed::QueryResultIterator;
95
96impl<'iter> IntoIterator for &'iter QueryResult {
97 type IntoIter = QueryResultIterator<'iter>;
98 type Item = QueryResultItem;
99
100 fn into_iter(self) -> QueryResultIterator<'iter> {
101 QueryResultIterator {
102 query_result: self,
103 idx: 0,
104 }
105 }
106}
107
108impl Iterator for QueryResultIterator<'_> {
109 type Item = QueryResultItem;
110
111 fn next(&mut self) -> Option<Self::Item> {
112 let return_value = self.query_result.get_item(self.idx);
113 if return_value.is_some() {
114 self.idx += 1;
115 }
116 return_value
117 }
118}
119
120pub struct QueryResultItem {
140 pub(super) raw: repr_c::Arc_<ffi_sdk::FfiQueryResultItem>,
142 materialized_value: Mutex<Option<Arc<CborObject>>>,
143}
144
145impl From<repr_c::Arc_<ffi_sdk::FfiQueryResultItem>> for QueryResultItem {
146 fn from(raw: repr_c::Arc<ffi_sdk::FfiQueryResultItem>) -> Self {
147 Self {
148 raw,
149 materialized_value: <_>::default(),
150 }
151 }
152}
153
154impl QueryResultItem {
155 pub fn value(&self) -> Arc<CborObject> {
165 let cache = &mut *self.materialized_value.lock().unwrap();
166 Self::materialize_(&self.raw, cache).clone()
167 }
168
169 pub fn is_materialized(&self) -> bool {
177 self.materialized_value.lock().unwrap().is_some()
178 }
179
180 fn materialize_<'cache>(
182 raw: &ffi_sdk::FfiQueryResultItem,
183 cache: &'cache mut Option<Arc<CborObject>>,
184 ) -> &'cache Arc<CborObject> {
185 cache.get_or_insert_with(|| {
186 let cbor_data = ffi_sdk::dittoffi_query_result_item_cbor(raw);
187 Arc::new(::serde_cbor::from_slice(&cbor_data[..]).expect(
188 "internal inconsistency, couldn't materialize query result item due to CBOR \
189 decoding error",
190 ))
191 })
192 }
193
194 pub fn materialize(&mut self) {
202 Self::materialize_(&self.raw, self.materialized_value.get_mut().unwrap());
203 }
204
205 pub fn dematerialize(&mut self) {
208 *self.materialized_value.get_mut().unwrap() = None;
209 }
210
211 pub fn cbor_data(&self) -> Vec<u8> {
216 let c_slice = ffi_sdk::dittoffi_query_result_item_cbor(&self.raw);
217 Box::<[u8]>::from(c_slice).into()
218 }
219
220 pub fn json_string(&self) -> String {
225 let raw_string = ffi_sdk::dittoffi_query_result_item_json(&self.raw);
226 raw_string.into_string()
227 }
228
229 pub fn deserialize_value<T: DeserializeOwned>(&self) -> Result<T, DittoError> {
234 ::serde_cbor::from_slice(&self.cbor_data()).map_err(Into::into)
235 }
236
237 #[doc(hidden)]
242 pub fn unstable_try_from_serde_json_value(
243 value: serde_json::Value,
244 ) -> Result<Self, DittoError> {
245 let json_data = value.to_string().into_bytes().to_vec();
246 let raw = ffi_sdk::dittoffi_query_result_item_new(json_data.as_slice().into())
247 .into_rust_result()?;
248 Ok(QueryResultItem {
249 raw,
250 materialized_value: Mutex::new(None),
251 })
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use serde_json::json;
258
259 use super::*;
260 use crate::prelude::CborValueGetters;
261
262 #[test]
263 fn create_query_result_item_from_json() {
264 let json_dict = json!({"_id": "1", "data": "A"});
265 let query_result_item =
266 QueryResultItem::unstable_try_from_serde_json_value(json_dict).unwrap();
267 let value = query_result_item.value();
268 assert_eq!(value["_id"].as_str().unwrap(), "1");
269 assert_eq!(value["data"].as_str().unwrap(), "A");
270 }
271}