use std::sync::{Arc, Mutex};
use ffi_sdk::{self, ffi_utils::repr_c};
use serde::de::DeserializeOwned;
use crate::{
error::DittoError, store::collection::document_id::DocumentId,
utils::extension_traits::FfiResultIntoRustResult,
};
type CborObject = ::std::collections::HashMap<Box<str>, ::serde_cbor::Value>;
pub struct QueryResult {
raw: repr_c::Box<ffi_sdk::FfiQueryResult>,
count: usize,
}
impl From<repr_c::Box_<ffi_sdk::FfiQueryResult>> for QueryResult {
fn from(raw: repr_c::Box<ffi_sdk::FfiQueryResult>) -> QueryResult {
let count = ffi_sdk::dittoffi_query_result_item_count(&raw);
QueryResult { raw, count }
}
}
impl QueryResult {
pub fn get_item(&self, index: usize) -> Option<QueryResultItem> {
if index >= self.count {
return None;
}
Some(QueryResultItem::from(
ffi_sdk::dittoffi_query_result_item_at(&self.raw, index),
))
}
pub fn item_count(&self) -> usize {
self.count
}
pub fn mutated_document_ids(&self) -> Vec<DocumentId> {
let mutated_document_number =
ffi_sdk::dittoffi_query_result_mutated_document_id_count(&self.raw);
(0..mutated_document_number)
.map(|idx| ffi_sdk::dittoffi_query_result_mutated_document_id_at(&self.raw, idx))
.map(|raw_slice| DocumentId::from(Box::<[u8]>::from(raw_slice)))
.collect()
}
}
impl QueryResult {
pub fn iter(&self) -> impl '_ + Iterator<Item = QueryResultItem> {
self.into_iter()
}
}
mod sealed {
pub struct QueryResultIterator<'iter> {
pub(super) query_result: &'iter super::QueryResult,
pub(super) idx: usize,
}
}
use self::sealed::QueryResultIterator;
impl<'iter> IntoIterator for &'iter QueryResult {
type IntoIter = QueryResultIterator<'iter>;
type Item = QueryResultItem;
fn into_iter(self) -> QueryResultIterator<'iter> {
QueryResultIterator {
query_result: self,
idx: 0,
}
}
}
impl Iterator for QueryResultIterator<'_> {
type Item = QueryResultItem;
fn next(&mut self) -> Option<Self::Item> {
let return_value = self.query_result.get_item(self.idx);
if return_value.is_some() {
self.idx += 1;
}
return_value
}
}
pub struct QueryResultItem {
pub(super) raw: repr_c::Arc_<ffi_sdk::FfiQueryResultItem>,
materialized_value: Mutex<Option<Arc<CborObject>>>,
}
impl From<repr_c::Arc_<ffi_sdk::FfiQueryResultItem>> for QueryResultItem {
fn from(raw: repr_c::Arc<ffi_sdk::FfiQueryResultItem>) -> Self {
Self {
raw,
materialized_value: <_>::default(),
}
}
}
impl QueryResultItem {
pub fn value(&self) -> Arc<CborObject> {
let cache = &mut *self.materialized_value.lock().unwrap();
Self::materialize_(&self.raw, cache).clone()
}
pub fn is_materialized(&self) -> bool {
self.materialized_value.lock().unwrap().is_some()
}
fn materialize_<'cache>(
raw: &ffi_sdk::FfiQueryResultItem,
cache: &'cache mut Option<Arc<CborObject>>,
) -> &'cache Arc<CborObject> {
cache.get_or_insert_with(|| {
let cbor_data = ffi_sdk::dittoffi_query_result_item_cbor(raw);
Arc::new(::serde_cbor::from_slice(&cbor_data[..]).expect(
"internal inconsistency, couldn't materialize query result item due to CBOR \
decoding error",
))
})
}
pub fn materialize(&mut self) {
Self::materialize_(&self.raw, self.materialized_value.get_mut().unwrap());
}
pub fn dematerialize(&mut self) {
*self.materialized_value.get_mut().unwrap() = None;
}
pub fn cbor_data(&self) -> Vec<u8> {
let c_slice = ffi_sdk::dittoffi_query_result_item_cbor(&self.raw);
Box::<[u8]>::from(c_slice).into()
}
pub fn json_string(&self) -> String {
let raw_string = ffi_sdk::dittoffi_query_result_item_json(&self.raw);
raw_string.into_string()
}
pub fn deserialize_value<T: DeserializeOwned>(&self) -> Result<T, DittoError> {
::serde_cbor::from_slice(&self.cbor_data()).map_err(Into::into)
}
#[doc(hidden)]
pub fn unstable_try_from_serde_json_value(
value: serde_json::Value,
) -> Result<Self, DittoError> {
let json_data = value.to_string().into_bytes().to_vec();
let raw = ffi_sdk::dittoffi_query_result_item_new(json_data.as_slice().into())
.into_rust_result()?;
Ok(QueryResultItem {
raw,
materialized_value: Mutex::new(None),
})
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
use crate::prelude::CborValueGetters;
#[test]
fn create_query_result_item_from_json() {
let json_dict = json!({"_id": "1", "data": "A"});
let query_result_item =
QueryResultItem::unstable_try_from_serde_json_value(json_dict).unwrap();
let value = query_result_item.value();
assert_eq!(value["_id"].as_str().unwrap(), "1");
assert_eq!(value["data"].as_str().unwrap(), "A");
}
}