1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
use_prelude!();
pub mod document;
pub mod document_id;
pub mod pending_cursor_operation;
pub mod pending_id_specific_operation;
pub mod type_traits;
use std::mem::MaybeUninit;
use ::ffi_sdk::{Attachment, AttachmentFileOperation};
pub use document::*;
use pending_cursor_operation::*;
use pending_id_specific_operation::*;
use super::{
ditto_attachment::DittoAttachment, ditto_attachment_fetch_event::DittoAttachmentFetchEvent,
ditto_attachment_fetcher::DittoAttachmentFetcher, ditto_attachment_token::DittoAttachmentToken,
};
use crate::{
ditto::WeakDittoHandleWrapper,
error::{DittoError, ErrorKind},
};
#[derive(Clone, Debug)]
/// A reference to a collection in a [`Store`](crate::prelude::Store).
pub struct Collection {
pub(super) ditto: WeakDittoHandleWrapper,
pub(super) collection_name: char_p::Box,
}
impl Collection {
pub(crate) fn new(ditto: WeakDittoHandleWrapper, collection_name: String) -> Self {
let collection_name = char_p::new(collection_name.as_str());
Self {
ditto,
collection_name,
}
}
/// The name of the collection.
pub fn name(&self) -> &str {
self.collection_name.as_ref().to_str()
}
/// Generates a [`PendingCursorOperation`](crate::prelude::PendingCursorOperation) that can be
/// used to find all documents in the collection at a point in time or you can chain a call to
/// [`observe_local`](crate::prelude::PendingCursorOperation::observe_local) or
/// [`subscribe`](crate::prelude::PendingCursorOperation::subscribe) if you want to get updates
/// about documents in the collection over time. It can also be used to update, remove, or evict
/// documents.
pub fn find_all(&self) -> PendingCursorOperation<'_> {
self.find("true")
}
/// Generates a [`PendingCursorOperation`]((crate::prelude::PendingCursorOperation)) with the
/// provided query that can be used to find the documents matching the query at a point in time
/// or you can chain a call to
/// [`observe_local`](crate::prelude::PendingCursorOperation::observe_local) or
/// [`subscribe`](crate::prelude::PendingCursorOperation::subscribe) if you want to get updates
/// about documents matching the query as they occur. It can also be used to update, remove, or
/// evict documents.
pub fn find(&self, query: &'_ str) -> PendingCursorOperation<'_> {
PendingCursorOperation::<'_>::new(
self.ditto.clone(),
self.collection_name.to_owned(),
query,
None,
)
}
/// Generates a [`PendingCursorOperation`](crate::prelude::PendingCursorOperation) with the
/// provided query and query arguments that can be used to find the documents matching the query
/// at a point in time or you can chain a call to
/// [`observe_local`](crate::prelude::PendingCursorOperation::observe_local) or
/// [`subscribe`](crate::prelude::PendingCursorOperation::subscribe) if you want to get updates
/// about documents matching the query as they occur. It can also be used to update, remove, or
/// evict documents.
///
/// This is the recommended function to use when performing queries on a collection if you have
/// any dynamic data included in the query string. It allows you to provide a query string with
/// placeholders, in the form of `$args.my_arg_name`, along with an accompanying dictionary of
/// arguments, in the form of `{ "my_arg_name": "some value" }`, and the placeholders will be
/// appropriately replaced by the matching provided arguments from the dictionary. This includes
/// handling things like wrapping strings in quotation marks and arrays in square brackets, for
/// example.
pub fn find_with_args<V: ::serde::Serialize, C: Borrow<V>>(
&self,
query: &'_ str,
query_args: C,
) -> PendingCursorOperation<'_> {
PendingCursorOperation::<'_>::new(
self.ditto.clone(),
self.collection_name.to_owned(),
query,
Some(serde_cbor::to_vec(query_args.borrow()).unwrap()),
)
}
/// Generates a [`PendingIdSpecificOperation`](crate::prelude::PendingIdSpecificOperation) with
/// the provided document Id that can be used to find the document at a point in time or you can
/// chain a call to [`observe_local`](crate::prelude::PendingIdSpecificOperation::observe_local)
/// or [`subscribe`](crate::prelude::PendingIdSpecificOperation::subscribe) if you want to get
/// updates about the document over time. It can also be used to update, remove, or evict the
/// document.
pub fn find_by_id(&self, doc_id: impl Into<DocumentId>) -> PendingIdSpecificOperation {
PendingIdSpecificOperation {
ditto: self.ditto.clone(),
collection_name: self.collection_name.to_owned(),
doc_id: doc_id.into(),
}
}
}
/// Convenience impl to avoid having to type `.clone()` everywhere.
impl From<&DocumentId> for DocumentId {
fn from(doc: &DocumentId) -> Self {
doc.clone()
}
}
impl Collection {
/// Inserts a new document into the collection and returns its Id. If the
/// document already exists, the provided document content will be merged
/// with the existing document's content.
pub fn upsert<V: ::serde::Serialize, C: Borrow<V>>(
&self,
content: C,
) -> Result<DocumentId, DittoError> {
self.insert_cbor(
::serde_cbor::to_vec(content.borrow()).unwrap().as_slice(),
WriteStrategy::Merge,
None,
)
}
/// Inserts a new document into the collection and returns its Id. If the
/// document already exists, the behavior is determined by the provided
/// `write_strategy`.
pub fn upsert_with_strategy<V: ::serde::Serialize, C: Borrow<V>>(
&self,
content: C,
write_strategy: WriteStrategy,
) -> Result<DocumentId, DittoError> {
self.insert_cbor(
::serde_cbor::to_vec(content.borrow()).unwrap().as_slice(),
write_strategy,
None,
)
}
/// Inserts a new document into the collection and returns its assigned Id.
/// Use this method when the content has already been serialized externally
/// into a CBOR-formatted byte array
pub(crate) fn insert_cbor(
&self,
cbor: &'_ [u8],
write_strategy: WriteStrategy,
write_txn: Option<&'_ mut ffi_sdk::CWriteTransaction>,
) -> Result<DocumentId, DittoError> {
let ditto = self
.ditto
.upgrade()
.ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
let write_strategy_rs = write_strategy.as_write_strategy_rs();
let hint: Option<char_p::Ref<'_>> = None;
let id = {
ffi_sdk::ditto_collection_insert_value(
&*ditto,
self.collection_name.as_ref(),
cbor.into(),
write_strategy_rs,
hint,
write_txn,
)
}
.ok_or(ErrorKind::InvalidInput)?;
Ok(id.to::<Box<[u8]>>().into())
}
/// Creates a new attachment, which can then be inserted into a document.
///
/// The file residing at the provided path will be copied into the Ditto’s store. The
/// [`DittoAttachment`](crate::prelude::DittoAttachment) object that is returned is what you can
/// then use to insert an attachment into a document.
///
/// You can provide metadata about the attachment, which will be replicated to other peers
/// alongside the file attachment.
///
/// Below is a snippet to show how you can use the new_attachment functionality to insert an
/// attachment into a document.
pub fn new_attachment<P: AsRef<Path>>(
&self,
path: P,
metadata: HashMap<String, String>,
) -> Result<DittoAttachment, DittoError> {
let ditto = self
.ditto
.upgrade()
.ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
use ::safer_ffi::prelude::AsOut;
let source_path = char_p::new(path.as_ref().to_str().unwrap());
let file_operation = AttachmentFileOperation::Copy;
let mut slot = MaybeUninit::<Attachment>::uninit();
let out_attachment: Out<'_, Attachment> = slot.as_out();
let status = {
ffi_sdk::ditto_new_attachment_from_file(
&ditto,
source_path.as_ref(),
file_operation,
out_attachment,
)
};
if status != 0 {
Err(DittoError::from_ffi(ErrorKind::InvalidInput))
} else {
let attachment = unsafe { slot.assume_init() }; // safe assuming above ffi call was successful
let ret = DittoAttachment::new(
attachment.id.into(),
attachment.len,
metadata,
self.ditto.clone(),
attachment.handle,
);
Ok(ret)
}
}
/// Fetch the attachment corresponding to the provided attachment token.
///
/// - `on_fetch_event`: A closure that will be called when the status of the request to fetch
/// the attachment has changed. If the attachment is already available then this will be
/// called almost immediately with a completed status value.
pub fn fetch_attachment<'a>(
&self,
attachment_token: DittoAttachmentToken,
on_fetch_event: impl Fn(DittoAttachmentFetchEvent) + Send + Sync + 'a,
) -> Result<DittoAttachmentFetcher<'a>, DittoError> {
let ditto = self
.ditto
.upgrade()
.ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
DittoAttachmentFetcher::new(attachment_token, ditto, on_fetch_event)
}
}