dittolive-ditto 4.5.3

Ditto is a peer to peer cross-platform database that allows mobile, web, IoT and server apps to sync with or without an internet connection.
Documentation
use ::ffi_sdk::{self, ffi_utils::repr_c};
use ::serde::de::DeserializeOwned;
use ::std::sync::{Arc, Mutex};

use crate::{error::DittoError, store::DocumentId};

type CborObject = ::std::collections::HashMap<Box<str>, ::serde_cbor::Value>;

/// Represents results returned when executing a DQL query containing
/// a [`QueryResultItem`] for each match.
///
/// > Note: More info such as metrics, affected document IDs, etc. will be
/// provided in the near future.
pub struct QueryResult {
    raw: repr_c::Box<ffi_sdk::DqlResponse>,
    count: usize,
}

impl From<repr_c::Box_<ffi_sdk::DqlResponse>> for QueryResult {
    fn from(raw: repr_c::Box<ffi_sdk::DqlResponse>) -> QueryResult {
        let count = ffi_sdk::ditto_dql_response_result_count(&raw);
        QueryResult { raw, count }
    }
}

impl QueryResult {
    /// Get the [`QueryResultItem`] at the given index.
    /// Return [`None`] if out of bounds.
    pub fn get_item(&self, index: usize) -> Option<QueryResultItem> {
        if index >= self.count {
            return None;
        }
        Some(QueryResultItem::from(
            ffi_sdk::ditto_dql_response_result_at(&self.raw, index),
        ))
    }

    /// Return the number of available [`QueryResultItem`].
    pub fn item_count(&self) -> usize {
        self.count
    }

    /// IDs of documents that were mutated by the DQL query. Empty
    /// array if no documents have been mutated.
    ///
    /// > Important: The returned document IDs are not cached, make sure to call
    /// this method once and keep the return value for as long as needed.
    pub fn mutated_document_ids(&self) -> Vec<DocumentId> {
        let mutated_document_number =
            ffi_sdk::ditto_dql_response_affected_document_id_count(&self.raw);

        (0..mutated_document_number)
            .map(|idx| ffi_sdk::ditto_dql_response_affected_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
    }
}

/// Represents a single match of a DQL query, similar to a "row" in SQL terms.
/// It's a reference type serving as a "cursor", allowing for efficient access
/// of the underlying data in various formats.
///
/// The row is lazily materialized and kept in memory until it goes out of scope.
/// To reduce the **memory footprint**, access the items using the provided iterator.
///
/// ```
/// # use serde::Deserialize;
/// # use dittolive_ditto::store::dql::QueryResult;
/// # #[derive(Deserialize)]
/// # struct Car{}
/// # fn scope(all_cars_query_result: QueryResult) {
/// let cars: Vec<Car> = all_cars_query_result
///     .iter()
///     .map(|query_result_item| query_result_item.deserialize_value().unwrap())
///     .collect();
/// # }
/// ```
pub struct QueryResultItem {
    /// Raw pointer to the core DqlResult
    raw: repr_c::Box<ffi_sdk::DqlResult>,
    materialized_value: Mutex<Option<Arc<CborObject>>>,
}

impl From<repr_c::Box_<ffi_sdk::DqlResult>> for QueryResultItem {
    fn from(raw: repr_c::Box<ffi_sdk::DqlResult>) -> Self {
        Self {
            raw,
            materialized_value: <_>::default(),
        }
    }
}

impl QueryResultItem {
    /// Returns the content as a materialized object.
    ///
    /// The item's value is [`.materialize()`]-ed on first access and
    /// subsequently on each access after performing [`.dematerialize()`]. Once
    /// materialized, the value is kept in memory until explicitly
    /// [`.dematerialize()`]-ed or the item goes out of scope.
    ///
    /// [`.materialize()`]: Self::materialize
    /// [`.dematerialize()`]: Self::dematerialize
    pub fn value(&self) -> Arc<CborObject> {
        let cache = &mut *self.materialized_value.lock().unwrap();
        Self::materialize_(&self.raw, cache).clone()
    }

    /// Returns `true` if value is currently held materialized in memory,
    /// otherwise returns `false`.
    ///
    /// ### See Also
    ///
    /// - [`Self::materialize()`]
    /// - [`Self::dematerialize()`]
    pub fn is_materialized(&self) -> bool {
        self.materialized_value.lock().unwrap().is_some()
    }

    /// Common helper to `.value()` and `.materialize()`.
    fn materialize_<'cache>(
        raw: &ffi_sdk::DqlResult,
        cache: &'cache mut Option<Arc<CborObject>>,
    ) -> &'cache Arc<CborObject> {
        cache.get_or_insert_with(|| {
            let cbor_data = ffi_sdk::ditto_result_cbor(raw);
            Arc::new(::serde_cbor::from_slice(&cbor_data[..]).expect(
                "internal inconsistency, couldn't materialize query result item due to CBOR \
                 decoding error",
            ))
        })
    }

    /// Loads the CBOR representation of the item's content, decodes it as a
    /// dictionary so it can be accessed via [`.value()`]. Keeps the dictionary in
    /// memory until [`.dematerialize()`] is called. No-op if `value` is already
    /// materialized.
    ///
    /// [`.value()`]: Self::value
    /// [`.dematerialize()`]: Self::dematerialize
    pub fn materialize(&mut self) {
        Self::materialize_(&self.raw, self.materialized_value.get_mut().unwrap());
    }

    /// Releases the materialized value from memory. No-op if item is not
    /// materialized.
    pub fn dematerialize(&mut self) {
        *self.materialized_value.get_mut().unwrap() = None;
    }

    /// Return the content of the item as a CBOR slice.
    ///
    /// *Important*: The returned CBOR slice is not cached, make sure to call this method once and
    /// keep it for as long as needed.
    pub fn cbor_data(&self) -> Vec<u8> {
        let c_slice = ffi_sdk::ditto_result_cbor(&self.raw);
        Box::<[u8]>::from(c_slice).into()
    }

    /// Return the content of the item as a JSON string.
    ///
    /// *Important*: The returned JSON string is not cached, make sure to call this method once and
    /// keep it for as long as needed.
    pub fn json_string(&self) -> String {
        let raw_string = ffi_sdk::ditto_result_json(&self.raw);
        raw_string.into_string()
    }

    /// Convenience around [`Self::cbor_data()`] `deserialize`-ing the value.
    ///
    /// *Important*: The returned value is not cached, make sure to call this method once and
    /// keep it for as long as needed.
    pub fn deserialize_value<T: DeserializeOwned>(&self) -> Result<T, DittoError> {
        ::serde_cbor::from_slice(&self.cbor_data()).map_err(Into::into)
    }
}