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
use_prelude!();
pub mod document;
pub mod document_id;
pub mod pending_cursor_operation;
pub mod pending_id_specific_operation;
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::error::{DittoError, ErrorKind};
#[derive(Clone, Debug)]
pub struct Collection {
pub(super) ditto: Arc<BoxedDitto>,
pub(super) collection_name: char_p::Box,
}
impl Collection {
pub(crate) fn new(ditto: Arc<BoxedDitto>, collection_name: String) -> Self {
let collection_name = char_p::new(collection_name.as_str());
Self {
ditto,
collection_name,
}
}
pub fn name(&self) -> &str {
self.collection_name.as_ref().to_str()
}
/// Generates a DittoPendingCursorOperation that can be used to find all
/// documents in the collection at a point in time or you can chain a
/// call to observe, observeLocal, or 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 DittoPendingCursorOperation 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, observeLocal, or 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.retain(),
self.collection_name.to_owned(),
query,
None,
)
}
/// Generates a DittoPendingCursorOperation 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,
/// observeLocal, or 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>(
&self,
query: &'_ str,
query_args: impl Borrow<V>,
) -> PendingCursorOperation {
PendingCursorOperation::new(
self.ditto.retain(),
self.collection_name.to_owned(),
query,
Some(serde_cbor::to_vec(query_args.borrow()).unwrap()),
)
}
/// Generates a DittoPendingIDSpecificOperation 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, observeLocal, or 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: DocumentId) -> PendingIdSpecificOperation {
PendingIdSpecificOperation {
ditto: self.ditto.retain(), // just an alias for clone
collection_name: self.collection_name.to_owned(),
doc_id,
}
}
}
impl Collection {
/// Inserts a new document into the collection and returns its assigned ID.
/// A DocumentId may be provided or if None, a document ID will be
/// auto-generated
pub fn insert<V: ::serde::Serialize>(
&self,
content: impl Borrow<V>,
id: Option<&DocumentId>,
is_default: bool,
) -> Result<DocumentId, DittoError> {
let strategy = if is_default {
WriteStrategy::InsertDefaultIfAbsent
} else {
WriteStrategy::Overwrite
};
self.insert_cbor(
&::serde_cbor::to_vec(content.borrow()).unwrap()[..],
id,
strategy,
None,
)
}
/// Inserts a new document into the collection and returns its assigned ID.
/// A DocumentId may be provided or if None, a document ID will be
/// auto-generated
pub fn insert_with_strategy<V: ::serde::Serialize>(
&self,
content: impl Borrow<V>,
id: Option<&DocumentId>,
write_strategy: WriteStrategy,
) -> Result<DocumentId, DittoError> {
self.insert_cbor(
&::serde_cbor::to_vec(content.borrow()).unwrap()[..],
id,
write_strategy,
None,
)
}
/// Inserts a new document into the collection and returns its assigned ID.
/// Use this method when the content as already been serialized externally
/// into a CBOR-formatted byte array
pub(in crate) fn insert_cbor(
&self,
cbor: &'_ [u8],
id: Option<&DocumentId>,
write_strategy: WriteStrategy,
write_txn: Option<&'_ mut ffi_sdk::CWriteTransaction>,
) -> Result<DocumentId, DittoError> {
let id: Option<c_slice::Ref<'_, u8>> = id.map(|id| id.as_ref().into());
let write_strategy_rs = write_strategy.as_write_strategy_rs();
let id = unsafe {
ffi_sdk::ditto_collection_insert_value(
&*self.ditto,
self.collection_name.as_ref(),
cbor.into(),
id,
write_strategy_rs,
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 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> {
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 = unsafe {
ffi_sdk::ditto_new_attachment_from_file(
&self.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.retain(),
attachment.handle,
);
Ok(ret)
}
}
/// Fetch the attachment corresponding to the provided attachment token.
/// * `onchange` - 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> {
DittoAttachmentFetcher::new(attachment_token, self.ditto.retain(), on_fetch_event)
}
}