diff --git a/README.md b/README.md index 80f1d59..6e0c5a4 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,12 @@ Bson encoding and decoding is handled by the [bson crate](https://github.com/zon The API should still be considered experimental, but I'm not expecting changes at the moment. +[Documentation](http://thijsc.github.io/mongo-rust-driver/mongo_driver/) + ## Compatibility -The driver currently only builds on Unix, tested on Mac Os X and Linux so far. +The driver currently only builds on Unix, tested on Mac Os X and Linux so far. It's compatible with MongoDB 2.4 up to 3.2 and +has full replica set and SSL support. ## Logging diff --git a/src/bulk_operation.rs b/src/bulk_operation.rs deleted file mode 100644 index 42cedc3..0000000 --- a/src/bulk_operation.rs +++ /dev/null @@ -1,196 +0,0 @@ -use mongoc::bindings; -use bson::Document; - -use super::BsoncError; -use super::bsonc::Bsonc; -use super::collection::Collection; - -use super::Result; - -pub struct BulkOperation<'a> { - _collection: &'a Collection<'a>, - inner: *mut bindings::mongoc_bulk_operation_t -} - -impl<'a> BulkOperation<'a> { - pub fn new( - collection: &'a Collection<'a>, - inner: *mut bindings::mongoc_bulk_operation_t - ) -> BulkOperation<'a> { - assert!(!inner.is_null()); - BulkOperation { - _collection: collection, - inner: inner - } - } - - /// Queue an insert of a single document into a bulk operation. - /// The insert is not performed until `execute` is called. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_insert.html - pub fn insert( - &self, - document: &Document - ) -> Result<()> { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_insert( - self.inner, - try!(Bsonc::from_document(&document)).inner() - ) - } - Ok(()) - } - - /// Queue removal of al documents matching selector into a bulk operation. - /// The removal is not performed until `execute` is called. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_remove.html - pub fn remove( - &self, - selector: &Document - ) -> Result<()> { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_remove( - self.inner, - try!(Bsonc::from_document(&selector)).inner() - ) - } - Ok(()) - } - - /// Queue removal of a single document into a bulk operation. - /// The removal is not performed until `execute` is called. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_remove_one.html - pub fn remove_one( - &self, - selector: &Document - ) -> Result<()> { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_remove_one( - self.inner, - try!(Bsonc::from_document(&selector)).inner() - ) - } - Ok(()) - } - - /// Queue replacement of a single document into a bulk operation. - /// The replacement is not performed until `execute` is called. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_remove_one.html - pub fn replace_one( - &self, - selector: &Document, - document: &Document, - upsert: bool - ) -> Result<()> { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_replace_one( - self.inner, - try!(Bsonc::from_document(&selector)).inner(), - try!(Bsonc::from_document(&document)).inner(), - upsert as u8 - ) - } - Ok(()) - } - - /// Queue update of a single documents into a bulk operation. - /// The update is not performed until `execute` is called. - /// - /// TODO: document must only contain fields whose key starts - /// with $, these is no error handling for this. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_update_one.html - pub fn update_one( - &self, - selector: &Document, - document: &Document, - upsert: bool - ) -> Result<()> { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_update_one( - self.inner, - try!(Bsonc::from_document(&selector)).inner(), - try!(Bsonc::from_document(&document)).inner(), - upsert as u8 - ) - } - Ok(()) - } - - /// Queue update of multiple documents into a bulk operation. - /// The update is not performed until `execute` is called. - /// - /// TODO: document must only contain fields whose key starts - /// with $, these is no error handling for this. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_update_one.html - pub fn update( - &self, - selector: &Document, - document: &Document, - upsert: bool - ) -> Result<()> { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_update( - self.inner, - try!(Bsonc::from_document(&selector)).inner(), - try!(Bsonc::from_document(&document)).inner(), - upsert as u8 - ) - } - Ok(()) - } - - /// This function executes all operations queued into this bulk operation. - /// If ordered was set true, forward progress will be stopped upon the first error. - /// - /// This function takes ownership because it is not possible to execute a bulk operation - /// multiple times. - /// - /// Returns a document with an overview of the bulk operation if successfull. - /// - /// See: http://api.mongodb.org/c/current/mongoc_bulk_operation_execute.html - pub fn execute(self) -> Result { - // Bsonc to store the reply - let mut reply = Bsonc::new(); - // Empty error that might be filled - let mut error = BsoncError::empty(); - - // Execute the operation. This returns a non-zero hint of the peer node on - // success, otherwise 0 and error is set. - let return_value = unsafe { - bindings::mongoc_bulk_operation_execute( - self.inner, - reply.mut_inner(), - error.mut_inner() - ) - }; - - if return_value != 0 { - match reply.as_document() { - Ok(document) => return Ok(document), - Err(error) => return Err(error.into()) - } - } else { - Err(error.into()) - } - } -} - -impl<'a> Drop for BulkOperation<'a> { - fn drop(&mut self) { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_bulk_operation_destroy(self.inner); - } - } -} diff --git a/src/client.rs b/src/client.rs index 7acef8e..48ddedb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,10 @@ +//! Client to access a MongoDB node, replica set or sharded cluster. +//! +//! Get started by creating a `ClientPool` you can use to pop a `Client`. + +use std::borrow::Cow; use std::fmt; -use std::ffi::CString; +use std::ffi::{CStr,CString}; use std::path::PathBuf; use std::mem; use std::ptr; @@ -17,16 +22,16 @@ use super::collection; use super::collection::Collection; use super::database; use super::database::Database; -use super::uri::Uri; use super::read_prefs::ReadPrefs; -/// Client pool to a MongoDB cluster. +/// Pool that allows usage of clients out of a single pool from multiple threads. /// -/// This client pool cannot be cloned, but it can be shared between threads by using an `Arc`. /// Use the pool to pop a client and do operations. The client will be automatically added /// back to the pool when it goes out of scope. /// -/// See: http://api.mongodb.org/c/current/mongoc_client_pool_t.html +/// This client pool cannot be cloned, but it can be use from different threads by using an `Arc`. +/// Clients cannot be shared between threads, pop a client from the pool for very single thread +/// where you need a connection. pub struct ClientPool { // Uri and SslOptions need to be present for the lifetime of this pool otherwise the C driver // loses access to resources it needs. @@ -36,10 +41,9 @@ pub struct ClientPool { } impl ClientPool { - /// Create a new ClientPool with optionally SSL options - /// - /// See: http://api.mongodb.org/c/current/mongoc_client_pool_t.html - /// And: http://api.mongodb.org/c/current/mongoc_ssl_opt_t.html + /// Create a new ClientPool with that can provide clients pointing to the specified uri. + /// The pool will connect via SSL if you add `?ssl=true` to the uri. You can optionally pass + /// in SSL options to configure SSL certificate usage and so on. pub fn new(uri: Uri, ssl_options: Option) -> ClientPool { super::init(); let pool = unsafe { @@ -65,13 +69,12 @@ impl ClientPool { } } - /// Get a reference to this pool's Uri + /// Get a reference to this pool's Uri. pub fn get_uri(&self) -> &Uri { &self.uri } /// Retrieve a client from the client pool, possibly blocking until one is available. - /// See: http://api.mongodb.org/c/current/mongoc_client_pool_pop.html pub fn pop(&self) -> Client { assert!(!self.inner.is_null()); let client = unsafe { bindings::mongoc_client_pool_pop(self.inner) }; @@ -82,7 +85,6 @@ impl ClientPool { } /// Return a client back to the client pool, called from drop of client. - /// See: http://api.mongodb.org/c/current/mongoc_client_pool_push.html unsafe fn push(&self, mongo_client: *mut bindings::mongoc_client_t) { assert!(!self.inner.is_null()); assert!(!mongo_client.is_null()); @@ -111,6 +113,7 @@ impl Drop for ClientPool { } } +/// Optional SSL configuration for a `ClientPool`. pub struct SslOptions { inner: bindings::mongoc_ssl_opt_t, // We need to store everything so both memory sticks around @@ -129,6 +132,8 @@ pub struct SslOptions { } impl SslOptions { + /// Create a new ssl options instance that can be used to configured + /// a `ClientPool`. pub fn new( pem_file: Option, pem_password: Option, @@ -219,6 +224,11 @@ impl Clone for SslOptions { } } +/// Client that provides access to a MongoDB MongoDB node, replica-set, or sharded-cluster. +/// +/// It maintains management of underlying sockets and routing to individual nodes based on +/// `ReadPrefs` or `WriteConcern`. Clients cannot be shared between threads, pop a new one from +/// a `ClientPool` in every thread that needs a connection instead. pub struct Client<'a> { client_pool: &'a ClientPool, inner: *mut bindings::mongoc_client_t @@ -273,9 +283,7 @@ impl<'a> Client<'a> { ) } - /// Queries the server for the current server status. - /// - /// See: http://api.mongodb.org/c/current/mongoc_client_get_server_status.html + /// Queries the server for the current server status, returns a document with this information. pub fn get_server_status(&self, read_prefs: Option) -> Result { assert!(!self.inner.is_null()); @@ -315,3 +323,81 @@ impl<'a> Drop for Client<'a> { } } } + +/// Abstraction on top of MongoDB connection URI format. +pub struct Uri { + inner: *mut bindings::mongoc_uri_t +} + +impl Uri { + /// Parses a string containing a MongoDB style URI connection string. + /// + /// Returns None if the uri is not in the correct format, there is no + /// further information available if this is not the case. + pub fn new>>(uri_string: T) -> Option { + let uri_cstring = CString::new(uri_string).unwrap(); + let uri = unsafe { bindings::mongoc_uri_new(uri_cstring.as_ptr()) }; + if uri.is_null() { + None + } else { + Some(Uri { inner: uri }) + } + } + + unsafe fn inner(&self) -> *const bindings::mongoc_uri_t { + assert!(!self.inner.is_null()); + self.inner + } + + pub fn as_str<'a>(&'a self) -> Cow<'a, str> { + assert!(!self.inner.is_null()); + unsafe { + let cstr = CStr::from_ptr( + bindings::mongoc_uri_get_string(self.inner) + ); + String::from_utf8_lossy(cstr.to_bytes()) + } + } + + pub fn get_database<'a>(&'a self) -> Option> { + assert!(!self.inner.is_null()); + unsafe { + let ptr = bindings::mongoc_uri_get_database(self.inner); + if ptr.is_null() { + None + } else { + let cstr = CStr::from_ptr(ptr); + Some(String::from_utf8_lossy(cstr.to_bytes())) + } + } + } + + // TODO add various methods that are available on uri +} + +impl PartialEq for Uri { + fn eq(&self, other: &Uri) -> bool { + self.as_str() == other.as_str() + } +} + +impl fmt::Debug for Uri { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl Clone for Uri { + fn clone(&self) -> Uri { + Uri::new(self.as_str().into_owned()).unwrap() + } +} + +impl Drop for Uri { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_uri_destroy(self.inner); + } + } +} diff --git a/src/collection.rs b/src/collection.rs index 6db1cb6..3310641 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -1,3 +1,7 @@ +//! Access to a MongoDB collection. +//! +//! `Collection` is the main type used when accessing collections. + use std::ptr; use std::ffi::CStr; use std::borrow::Cow; @@ -12,7 +16,6 @@ use super::Result; use super::CommandAndFindOptions; use super::{BsoncError,InvalidParamsError}; use super::bsonc::Bsonc; -use super::bulk_operation::BulkOperation; use super::client::Client; use super::cursor; use super::cursor::{Cursor,TailingCursor}; @@ -21,6 +24,7 @@ use super::flags::{Flags,FlagsValue,InsertFlag,QueryFlag,RemoveFlag,UpdateFlag}; use super::write_concern::WriteConcern; use super::read_prefs::ReadPrefs; +#[doc(hidden)] pub enum CreatedBy<'a> { BorrowedClient(&'a Client<'a>), OwnedClient(Client<'a>), @@ -28,32 +32,46 @@ pub enum CreatedBy<'a> { OwnedDatabase(Database<'a>) } +/// Provides access to a collection for most CRUD operations, I.e. insert, update, delete, find, etc. +/// +/// A collection instance can be created by calling `get_collection` or `take_database` on a `Client` or `Database` +/// instance. pub struct Collection<'a> { _created_by: CreatedBy<'a>, inner: *mut bindings::mongoc_collection_t } +/// Options to configure a bulk operation. pub struct BulkOperationOptions { + /// If the operations must be performed in order pub ordered: bool, + /// `WriteConcern` to use pub write_concern: WriteConcern } impl BulkOperationOptions { + /// Default options that are used if no options are specified + /// when creating a `BulkOperation`. pub fn default() -> BulkOperationOptions { BulkOperationOptions { ordered: false, - write_concern: WriteConcern::new() + write_concern: WriteConcern::default() } } } +/// Options to configure a find and modify operation. pub struct FindAndModifyOptions { + /// Sort order for the query pub sort: Option, + /// If the new version of the document should be returned pub new: bool, + /// The fields to return pub fields: Option } impl FindAndModifyOptions { + /// Default options used if none are provided. pub fn default() -> FindAndModifyOptions { FindAndModifyOptions { sort: None, @@ -70,21 +88,32 @@ impl FindAndModifyOptions { } } +/// Possible find and modify operations. pub enum FindAndModifyOperation<'a> { + /// Update the matching documents Update(&'a Document), + /// Upsert the matching documents Upsert(&'a Document), + /// Remove the matching documents Remove } +/// Options to configure a count operation. pub struct CountOptions { + /// The query flags to use pub query_flags: Flags, + /// Number of results to skip, zero to ignore pub skip: u32, + /// Limit to the number of results, zero to ignore pub limit: u32, + /// Optional extra keys to add to the count pub opts: Option, + /// Read prefs to use pub read_prefs: Option } impl CountOptions { + /// Default options used if none are provided. pub fn default() -> CountOptions { CountOptions { query_flags: Flags::new(), @@ -96,54 +125,70 @@ impl CountOptions { } } +/// Options to configure an insert operation. pub struct InsertOptions { + /// Flags to use pub insert_flags: Flags, + /// Write concern to use pub write_concern: WriteConcern } impl InsertOptions { + /// Default options used if none are provided. pub fn default() -> InsertOptions { InsertOptions { insert_flags: Flags::new(), - write_concern: WriteConcern::new() + write_concern: WriteConcern::default() } } } +/// Options to configure a remove operation. pub struct RemoveOptions { + /// Flags to use pub remove_flags: Flags, + /// Write concern to use pub write_concern: WriteConcern } impl RemoveOptions { + /// Default options used if none are provided. pub fn default() -> RemoveOptions { RemoveOptions { remove_flags: Flags::new(), - write_concern: WriteConcern::new() + write_concern: WriteConcern::default() } } } +/// Options to configure an update operation. pub struct UpdateOptions { + /// Flags to use pub update_flags: Flags, + /// Write concern to use pub write_concern: WriteConcern } impl UpdateOptions { + /// Default options used if none are provided. pub fn default() -> UpdateOptions { UpdateOptions { update_flags: Flags::new(), - write_concern: WriteConcern::new() + write_concern: WriteConcern::default() } } } +/// Options to configure a tailing query. pub struct TailOptions { + /// Duration to wait before checking for new results pub wait_duration: Duration, + /// Maximum number of retries if there is an error pub max_retries: u32 } impl TailOptions { + /// Default options used if none are provided. pub fn default() -> TailOptions { TailOptions { wait_duration: Duration::from_millis(500), @@ -153,6 +198,7 @@ impl TailOptions { } impl<'a> Collection<'a> { + #[doc(hidden)] pub fn new( created_by: CreatedBy<'a>, inner: *mut bindings::mongoc_collection_t @@ -164,9 +210,8 @@ impl<'a> Collection<'a> { } } - /// Execute a command on the collection - /// - /// See: http://api.mongodb.org/c/current/mongoc_collection_command.html + /// Execute a command on the collection. + /// This is performed lazily and therefore requires calling `next` on the resulting cursor. pub fn command( &'a self, command: Document, @@ -208,19 +253,14 @@ impl<'a> Collection<'a> { )) } - /// Simplified version of command that returns the first document - /// - /// See: http://api.mongodb.org/c/current/mongoc_database_command_simple.html + /// Simplified version of `command` that returns the first document immediately. pub fn command_simple( &'a self, command: Document, - options: Option<&CommandAndFindOptions> + read_prefs: Option<&ReadPrefs> ) -> Result { assert!(!self.inner.is_null()); - let default_options = CommandAndFindOptions::default(); - let options = options.unwrap_or(&default_options); - // Bsonc to store the reply let mut reply = Bsonc::new(); // Empty error that might be filled @@ -230,7 +270,7 @@ impl<'a> Collection<'a> { bindings::mongoc_collection_command_simple( self.inner, try!(Bsonc::from_document(&command)).inner(), - match options.read_prefs { + match read_prefs { Some(ref prefs) => prefs.inner(), None => ptr::null() }, @@ -249,6 +289,10 @@ impl<'a> Collection<'a> { } } + /// Execute a count query on the underlying collection. + /// The `query` bson is not validated, simply passed along to the server. As such, compatibility and errors should be validated in the appropriate server documentation. + /// + /// For more information, see the [query reference](https://docs.mongodb.org/manual/reference/operator/query/) at the MongoDB website. pub fn count( &self, query: &Document, @@ -290,6 +334,9 @@ impl<'a> Collection<'a> { } } + /// Create a bulk operation. After creating call various functions such as `update`, + /// `insert` and others. When calling `execute` these operations will be executed in + /// batches. pub fn create_bulk_operation( &'a self, options: Option<&BulkOperationOptions> @@ -310,6 +357,7 @@ impl<'a> Collection<'a> { BulkOperation::new(self, inner) } + /// Request that a collection be dropped, including all indexes associated with the collection. pub fn drop(&mut self) -> Result<()> { assert!(!self.inner.is_null()); let mut error = BsoncError::empty(); @@ -326,6 +374,10 @@ impl<'a> Collection<'a> { Ok(()) } + /// Execute a query on the underlying collection. + /// If no options are necessary, query can simply contain a query such as `{a:1}`. + /// If you would like to specify options such as a sort order, the query must be placed inside of `{"$query": {}}` + /// as specified by the server documentation. See the example below for how to properly specify additional options to query. pub fn find( &'a self, query: &Document, @@ -367,10 +419,9 @@ impl<'a> Collection<'a> { )) } - // Update and return an object. - // - // This is a thin wrapper around the findAndModify command. Pass in - // an operation that either updates, upserts or removes. + /// Update and return an object. + /// This is a thin wrapper around the findAndModify command. Pass in + /// an operation that either updates, upserts or removes. pub fn find_and_modify( &'a self, query: &Document, @@ -443,6 +494,7 @@ impl<'a> Collection<'a> { } } + /// Get the name of the collection. pub fn get_name(&self) -> Cow { let cstr = unsafe { CStr::from_ptr(bindings::mongoc_collection_get_name(self.inner)) @@ -450,6 +502,9 @@ impl<'a> Collection<'a> { String::from_utf8_lossy(cstr.to_bytes()) } + /// Insert document into collection. + /// If no `_id` element is found in document, then an id will be generated locally and added to the document. + // TODO: You can retrieve a generated _id from mongoc_collection_get_last_error(). pub fn insert( &'a self, document: &Document, @@ -478,6 +533,9 @@ impl<'a> Collection<'a> { } } + /// Remove documents in the given collection that match selector. + /// The bson `selector` is not validated, simply passed along as appropriate to the server. As such, compatibility and errors should be validated in the appropriate server documentation. + /// If you want to limit deletes to a single document, add the `SingleRemove` flag. pub fn remove( &self, selector: &Document, @@ -506,6 +564,8 @@ impl<'a> Collection<'a> { } } + /// Save a document into the collection. If the document has an `_id` field it will be updated. + /// Otherwise it will be inserted. pub fn save( &self, document: &Document, @@ -513,7 +573,7 @@ impl<'a> Collection<'a> { ) -> Result<()> { assert!(!self.inner.is_null()); - let default_write_concern = WriteConcern::new(); + let default_write_concern = WriteConcern::default(); let write_concern = write_concern.unwrap_or(&default_write_concern); let mut error = BsoncError::empty(); @@ -533,9 +593,8 @@ impl<'a> Collection<'a> { } } - /// This function shall update documents in collection that match selector. - /// - /// See: http://api.mongodb.org/c/current/mongoc_collection_update.html + /// This function updates documents in collection that match selector. + /// By default, updates only a single document. Add `MultiUpdate` flag to update multiple documents. pub fn update( &self, selector: &Document, @@ -607,3 +666,183 @@ impl<'a> Drop for Collection<'a> { } } } + +/// Provides an abstraction for submitting multiple write operations as a single batch. +/// +/// Create a `BulkOperation` by calling `create_bulk_operation` on a `Collection`. After adding all of +/// the write operations using the functions on this struct, `execute` to execute the operation on +/// the server in batches. After executing the bulk operation is consumed and cannot be used anymore. +pub struct BulkOperation<'a> { + _collection: &'a Collection<'a>, + inner: *mut bindings::mongoc_bulk_operation_t +} + +impl<'a>BulkOperation<'a> { + /// Create a new bulk operation, only for internal usage. + fn new( + collection: &'a Collection<'a>, + inner: *mut bindings::mongoc_bulk_operation_t + ) -> BulkOperation<'a> { + assert!(!inner.is_null()); + BulkOperation { + _collection: collection, + inner: inner + } + } + + /// Queue an insert of a single document into a bulk operation. + /// The insert is not performed until `execute` is called. + pub fn insert( + &self, + document: &Document + ) -> Result<()> { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_insert( + self.inner, + try!(Bsonc::from_document(&document)).inner() + ) + } + Ok(()) + } + + /// Queue removal of all documents matching the provided selector into a bulk operation. + /// The removal is not performed until `execute` is called. + pub fn remove( + &self, + selector: &Document + ) -> Result<()> { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_remove( + self.inner, + try!(Bsonc::from_document(&selector)).inner() + ) + } + Ok(()) + } + + /// Queue removal of a single document into a bulk operation. + /// The removal is not performed until `execute` is called. + pub fn remove_one( + &self, + selector: &Document + ) -> Result<()> { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_remove_one( + self.inner, + try!(Bsonc::from_document(&selector)).inner() + ) + } + Ok(()) + } + + /// Queue replacement of a single document into a bulk operation. + /// The replacement is not performed until `execute` is called. + pub fn replace_one( + &self, + selector: &Document, + document: &Document, + upsert: bool + ) -> Result<()> { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_replace_one( + self.inner, + try!(Bsonc::from_document(&selector)).inner(), + try!(Bsonc::from_document(&document)).inner(), + upsert as u8 + ) + } + Ok(()) + } + + /// Queue update of a single documents into a bulk operation. + /// The update is not performed until `execute` is called. + /// + /// TODO: document must only contain fields whose key starts + /// with $, these is no error handling for this. + pub fn update_one( + &self, + selector: &Document, + document: &Document, + upsert: bool + ) -> Result<()> { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_update_one( + self.inner, + try!(Bsonc::from_document(&selector)).inner(), + try!(Bsonc::from_document(&document)).inner(), + upsert as u8 + ) + } + Ok(()) + } + + /// Queue update of multiple documents into a bulk operation. + /// The update is not performed until `execute` is called. + /// + /// TODO: document must only contain fields whose key starts + /// with $, these is no error handling for this. + pub fn update( + &self, + selector: &Document, + document: &Document, + upsert: bool + ) -> Result<()> { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_update( + self.inner, + try!(Bsonc::from_document(&selector)).inner(), + try!(Bsonc::from_document(&document)).inner(), + upsert as u8 + ) + } + Ok(()) + } + + /// This function executes all operations queued into this bulk operation. + /// If ordered was set true, forward progress will be stopped upon the first error. + /// + /// This function takes ownership because it is not possible to execute a bulk operation + /// multiple times. + /// + /// Returns a document with an overview of the bulk operation if successfull. + pub fn execute(self) -> Result { + // Bsonc to store the reply + let mut reply = Bsonc::new(); + // Empty error that might be filled + let mut error = BsoncError::empty(); + + // Execute the operation. This returns a non-zero hint of the peer node on + // success, otherwise 0 and error is set. + let return_value = unsafe { + bindings::mongoc_bulk_operation_execute( + self.inner, + reply.mut_inner(), + error.mut_inner() + ) + }; + + if return_value != 0 { + match reply.as_document() { + Ok(document) => return Ok(document), + Err(error) => return Err(error.into()) + } + } else { + Err(error.into()) + } + } +} + +impl<'a> Drop for BulkOperation<'a> { + fn drop(&mut self) { + assert!(!self.inner.is_null()); + unsafe { + bindings::mongoc_bulk_operation_destroy(self.inner); + } + } +} diff --git a/src/cursor.rs b/src/cursor.rs index 7e6f89f..6439398 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,3 +1,5 @@ +//! Access to a MongoDB query cursor. + use std::iter::Iterator; use std::ptr; use std::thread; @@ -16,12 +18,22 @@ use super::CommandAndFindOptions; use super::Result; +#[doc(hidden)] pub enum CreatedBy<'a> { Client(&'a Client<'a>), Database(&'a Database<'a>), Collection(&'a Collection<'a>) } +/// Provides access to a MongoDB cursor for a normal operation. +/// +/// It wraps up the wire protocol negotiation required to initiate a query and +/// retrieve an unknown number of documents. Cursors are lazy, meaning that no network +/// traffic occurs until the first call to `next`. At this point various functions to get +/// information about the state of the cursor are available. +/// +/// `Cursor` implements the `Iterator` trait, so you can use with all normal Rust means +/// of iteration and looping. pub struct Cursor<'a> { _created_by: CreatedBy<'a>, inner: *mut bindings::mongoc_cursor_t, @@ -33,6 +45,7 @@ pub struct Cursor<'a> { } impl<'a> Cursor<'a> { + #[doc(hidden)] pub fn new( created_by: CreatedBy<'a>, inner: *mut bindings::mongoc_cursor_t, @@ -140,6 +153,10 @@ impl<'a> Drop for Cursor<'a> { /// Cursor that will reconnect and resume tailing a collection /// at the right point if the connection fails. +/// +/// This cursor will wait for new results when there are none, so calling `next` +/// is a blocking operation. If an error occurs the iterator will retry, if errors +/// keep occuring it will eventually return an error result. pub struct TailingCursor<'a> { collection: &'a Collection<'a>, query: Document, @@ -151,6 +168,7 @@ pub struct TailingCursor<'a> { } impl<'a> TailingCursor<'a> { + #[doc(hidden)] pub fn new( collection: &'a Collection<'a>, query: Document, diff --git a/src/database.rs b/src/database.rs index 2563f9e..bf97f31 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,3 +1,5 @@ +//! Access to a MongoDB database. + use std::ffi::{CString,CStr}; use std::borrow::Cow; use std::ptr; @@ -14,19 +16,25 @@ use super::collection; use super::collection::Collection; use super::cursor; use super::cursor::Cursor; +use super::read_prefs::ReadPrefs; use flags::FlagsValue; +#[doc(hidden)] pub enum CreatedBy<'a> { BorrowedClient(&'a Client<'a>), OwnedClient(Client<'a>) } +/// Provides access to a MongoDB database. +/// +/// A database instance can be created by calling `get_database` or `take_database` on a `Client` instance. pub struct Database<'a> { _created_by: CreatedBy<'a>, inner: *mut bindings::mongoc_database_t } impl<'a> Database<'a> { + #[doc(ignore)] pub fn new( created_by: CreatedBy<'a>, inner: *mut bindings::mongoc_database_t @@ -38,9 +46,8 @@ impl<'a> Database<'a> { } } - /// Execute a command on the database - /// - /// See: http://api.mongodb.org/c/current/mongoc_database_command.html + /// Execute a command on the database. + /// This is performed lazily and therefore requires calling `next` on the resulting cursor. pub fn command( &'a self, command: Document, @@ -82,19 +89,14 @@ impl<'a> Database<'a> { )) } - /// Simplified version of command that returns the first document - /// - /// See: http://api.mongodb.org/c/current/mongoc_database_command_simple.html + /// Simplified version of `command` that returns the first document immediately. pub fn command_simple( &'a self, command: Document, - options: Option<&CommandAndFindOptions> + read_prefs: Option<&ReadPrefs> ) -> Result { assert!(!self.inner.is_null()); - let default_options = CommandAndFindOptions::default(); - let options = options.unwrap_or(&default_options); - // Bsonc to store the reply let mut reply = Bsonc::new(); // Empty error that might be filled @@ -104,7 +106,7 @@ impl<'a> Database<'a> { bindings::mongoc_database_command_simple( self.inner, try!(Bsonc::from_document(&command)).inner(), - match options.read_prefs { + match read_prefs { Some(ref prefs) => prefs.inner(), None => ptr::null() }, @@ -123,6 +125,7 @@ impl<'a> Database<'a> { } } + /// Create a new collection in this database. pub fn create_collection>>( &self, name: S, @@ -179,6 +182,7 @@ impl<'a> Database<'a> { ) } + /// Get the name of this database. pub fn get_name(&self) -> Cow { let cstr = unsafe { CStr::from_ptr(bindings::mongoc_database_get_name(self.inner)) diff --git a/src/flags.rs b/src/flags.rs index 7a3445f..60880c3 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -1,23 +1,29 @@ +//! Flags to configure various MongoDB operations. + use mongoc::bindings; use std::collections::BTreeSet; +/// Structure to hold flags for various flag types pub struct Flags { flags: BTreeSet } impl Flags where T: Ord { + /// Creare a new empty flags instance pub fn new() -> Flags { Flags { flags: BTreeSet::new() } } + /// Add a flag to this instance pub fn add(&mut self, flag: T) { self.flags.insert(flag); } } +/// To provide the combined value of all flags. pub trait FlagsValue { fn flags(&self) -> u32; } diff --git a/src/lib.rs b/src/lib.rs index 35a6761..2166724 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,27 @@ +//! This driver is a thin wrapper around the production-ready [Mongo C driver](https://github.com/mongodb/mongo-c-driver). +//! +//! It aims to provide a safe and ergonomic Rust interface which handles all the gnarly usage details of +//! the C driver for you. We use Rust's type system to make sure that we can only use the +//! underlying C driver in the recommended way specified in it's [documentation](http://api.mongodb.org/c/current/). +//! +//! To get started create a client pool wrapped in an `Arc` so we can share it between threads. Then pop a client from it +//! you can use to perform operations. +//! +//! # Example +//! +//! ``` +//! use std::sync::Arc; +//! use mongo_driver::client::{ClientPool,Uri}; +//! +//! let uri = Uri::new("mongodb://localhost:27017/").unwrap(); +//! let pool = Arc::new(ClientPool::new(uri.clone(), None)); +//! let client = pool.pop(); +//! client.get_server_status(None).unwrap(); +//! ``` +//! +//! See the documentation for the available modules to find out how you can use the driver beyond +//! this. + extern crate libc; extern crate mongoc_sys as mongoc; @@ -14,14 +38,12 @@ use std::sync::{Once,ONCE_INIT}; use mongoc::bindings; -pub mod bulk_operation; pub mod client; pub mod collection; pub mod cursor; pub mod database; pub mod flags; pub mod read_prefs; -pub mod uri; pub mod write_concern; mod bsonc; @@ -72,16 +94,24 @@ unsafe extern "C" fn mongoc_log_handler( } } +/// Options to configure both command and find operations. pub struct CommandAndFindOptions { + /// Flags to use pub query_flags: flags::Flags, + /// Number of documents to skip, zero to ignore pub skip: u32, + /// Max number of documents to return, zero to ignore pub limit: u32, + /// Number of documents in each batch, zero to ignore (default is 100) pub batch_size: u32, + /// Fields to return, not all commands support this option pub fields: Option, + /// Read prefs to use pub read_prefs: Option } impl CommandAndFindOptions { + /// Default options used if none are provided. pub fn default() -> CommandAndFindOptions { CommandAndFindOptions { query_flags: flags::Flags::new(), diff --git a/src/read_prefs.rs b/src/read_prefs.rs index fb483ea..5cc9888 100644 --- a/src/read_prefs.rs +++ b/src/read_prefs.rs @@ -1,10 +1,18 @@ +//! Abstraction on top of the MongoDB connection read prefences. + use mongoc::bindings; +/// Describes how reads should be dispatched. pub enum ReadMode { + /// Default mode. All operations read from the current replica set primary. Primary, + /// All operations read from among the nearest secondary members of the replica set. Secondary, + /// In most situations, operations read from the primary but if it is unavailable, operations read from secondary members. PrimaryPreferred, + /// In most situations, operations read from among the nearest secondary members, but if no secondaries are available, operations read from the primary. SecondaryPreferred, + /// Operations read from among the nearest members of the replica set, irrespective of the member’s type. Nearest } @@ -18,11 +26,16 @@ fn read_mode_value(read_mode: &ReadMode) -> bindings::mongoc_read_mode_t { } } +/// Provides an abstraction on top of the MongoDB connection read prefences. +/// +/// It allows for hinting to the driver which nodes in a replica set should be accessed first. +/// Generally, it makes the most sense to stick with the global default, `Primary`. All of the other modes come with caveats that won't be covered in great detail here. pub struct ReadPrefs { inner: *mut bindings::mongoc_read_prefs_t } impl ReadPrefs { + /// Create a new empty read prefs. pub fn new(read_mode: &ReadMode) -> ReadPrefs { let read_mode_value = read_mode_value(read_mode); let inner = unsafe { bindings::mongoc_read_prefs_new(read_mode_value) }; @@ -30,15 +43,18 @@ impl ReadPrefs { ReadPrefs { inner: inner } } + /// Get a new instance of the default read pref. pub fn default() -> ReadPrefs{ ReadPrefs::new(&ReadMode::Primary) } + #[doc(hidden)] pub fn inner(&self) -> *const bindings::mongoc_read_prefs_t { assert!(!self.inner.is_null()); self.inner } + #[doc(hidden)] pub fn mut_inner(&self) -> *mut bindings::mongoc_read_prefs_t { assert!(!self.inner.is_null()); self.inner as *mut bindings::mongoc_read_prefs_t diff --git a/src/uri.rs b/src/uri.rs deleted file mode 100644 index 0525d2f..0000000 --- a/src/uri.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::borrow::Cow; -use std::ffi::{CStr,CString}; -use std::fmt; - -use mongoc::bindings; - -/// Abstraction on top of MongoDB connection URI format. -/// See: http://api.mongodb.org/c/current/mongoc_uri_t.html - -pub struct Uri { - inner: *mut bindings::mongoc_uri_t -} - -impl Uri { - /// Parses a string containing a MongoDB style URI connection string. - /// - /// Returns None if the uri is not in the correct format, there is no - /// further information available if this is not the case. - /// - /// See: http://api.mongodb.org/c/current/mongoc_uri_new.html - pub fn new>>(uri_string: T) -> Option { - let uri_cstring = CString::new(uri_string).unwrap(); - let uri = unsafe { bindings::mongoc_uri_new(uri_cstring.as_ptr()) }; - if uri.is_null() { - None - } else { - Some(Uri { inner: uri }) - } - } - - pub unsafe fn inner(&self) -> *const bindings::mongoc_uri_t { - assert!(!self.inner.is_null()); - self.inner - } - - pub fn as_str<'a>(&'a self) -> Cow<'a, str> { - assert!(!self.inner.is_null()); - unsafe { - let cstr = CStr::from_ptr( - bindings::mongoc_uri_get_string(self.inner) - ); - String::from_utf8_lossy(cstr.to_bytes()) - } - } - - pub fn get_database<'a>(&'a self) -> Option> { - assert!(!self.inner.is_null()); - unsafe { - let ptr = bindings::mongoc_uri_get_database(self.inner); - if ptr.is_null() { - None - } else { - let cstr = CStr::from_ptr(ptr); - Some(String::from_utf8_lossy(cstr.to_bytes())) - } - } - } - - // TODO add various methods that are available on uri -} - -impl PartialEq for Uri { - fn eq(&self, other: &Uri) -> bool { - self.as_str() == other.as_str() - } -} - -impl fmt::Debug for Uri { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl Clone for Uri { - fn clone(&self) -> Uri { - Uri::new(self.as_str().into_owned()).unwrap() - } -} - -impl Drop for Uri { - fn drop(&mut self) { - assert!(!self.inner.is_null()); - unsafe { - bindings::mongoc_uri_destroy(self.inner); - } - } -} diff --git a/src/write_concern.rs b/src/write_concern.rs index 219bb6b..9e1f3cd 100644 --- a/src/write_concern.rs +++ b/src/write_concern.rs @@ -1,16 +1,44 @@ +//! Abstraction on top of the MongoDB connection write concern. + use mongoc::bindings; +/// Possible write concern levels, only default is supported at the moment. +pub enum WriteConcernLevel { + /// By default, writes block awaiting acknowledgment from MongoDB. Acknowledged write concern allows clients to catch network, duplicate key, and other errors. + Default, + + // We'd like to support the following write concerns too at some point, pull request welcome: + + // With this write concern, MongoDB does not acknowledge the receipt of write operation. Unacknowledged is similar to errors ignored; however, mongoc attempts to receive and handle network errors when possible. + // WriteUnacknowledged, + // Block until a write has been propagated to a majority of the nodes in the replica set. + // Majority, + // Block until a write has been propagated to at least n nodes in the replica set. + // AtLeastNumberOfNodes(u32), + // Block until the node receiving the write has committed the journal. + // Journal +} + +/// This tells the driver what level of acknowledgment to await from the server. +/// The default, `Default`, is right for the great majority of applications. pub struct WriteConcern { inner: *mut bindings::mongoc_write_concern_t } impl WriteConcern { - pub fn new() -> WriteConcern { + /// Get the default write concern + pub fn default() -> WriteConcern { + Self::new(WriteConcernLevel::Default) + } + + /// Create a new write concern + pub fn new(_: WriteConcernLevel) -> WriteConcern { let inner = unsafe { bindings::mongoc_write_concern_new() }; assert!(!inner.is_null()); WriteConcern { inner: inner } } + #[doc(hidden)] pub fn inner(&self) -> *const bindings::mongoc_write_concern_t { assert!(!self.inner.is_null()); self.inner diff --git a/tests/bson_encode_decode.rs b/tests/bson_encode_decode.rs index 2268769..9227e6e 100644 --- a/tests/bson_encode_decode.rs +++ b/tests/bson_encode_decode.rs @@ -1,7 +1,6 @@ use chrono::*; -use mongo_driver::uri::Uri; -use mongo_driver::client::ClientPool; +use mongo_driver::client::{ClientPool,Uri}; use bson::oid::ObjectId; use bson::spec::BinarySubtype; diff --git a/tests/bulk_operation.rs b/tests/bulk_operation.rs index ed7696c..cc8447c 100644 --- a/tests/bulk_operation.rs +++ b/tests/bulk_operation.rs @@ -2,8 +2,7 @@ use std::env; use bson; -use mongo_driver::uri::Uri; -use mongo_driver::client::ClientPool; +use mongo_driver::client::{ClientPool,Uri}; #[test] fn test_execute_error() { diff --git a/tests/client.rs b/tests/client.rs index 3710b70..8cd90bc 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -3,8 +3,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::thread; -use mongo_driver::uri::Uri; -use mongo_driver::client::{ClientPool,SslOptions}; +use mongo_driver::client::{ClientPool,SslOptions,Uri}; #[test] fn test_new_pool_pop_client_and_borrow_collection() { diff --git a/tests/collection.rs b/tests/collection.rs index 35e6fa8..2e52a0b 100644 --- a/tests/collection.rs +++ b/tests/collection.rs @@ -2,8 +2,7 @@ use bson; use mongo_driver::CommandAndFindOptions; use mongo_driver::collection::{CountOptions,FindAndModifyOperation}; -use mongo_driver::uri::Uri; -use mongo_driver::client::ClientPool; +use mongo_driver::client::{ClientPool,Uri}; use mongo_driver::flags; #[test] diff --git a/tests/cursor.rs b/tests/cursor.rs index 7f4b0e3..8e73723 100644 --- a/tests/cursor.rs +++ b/tests/cursor.rs @@ -4,8 +4,7 @@ use std::time::Duration; use bson; -use mongo_driver::uri::Uri; -use mongo_driver::client::ClientPool; +use mongo_driver::client::{ClientPool,Uri}; use mongo_driver::Result; #[test] diff --git a/tests/database.rs b/tests/database.rs index b255bfa..8769a64 100644 --- a/tests/database.rs +++ b/tests/database.rs @@ -1,5 +1,4 @@ -use mongo_driver::uri::Uri; -use mongo_driver::client::ClientPool; +use mongo_driver::client::{ClientPool,Uri}; #[test] fn test_command() { diff --git a/tests/uri.rs b/tests/uri.rs index a191ea9..ab65a9b 100644 --- a/tests/uri.rs +++ b/tests/uri.rs @@ -1,4 +1,4 @@ -use mongo_driver::uri::Uri; +use mongo_driver::client::Uri; #[test] fn test_new_uri() { diff --git a/tests/write_concern.rs b/tests/write_concern.rs index f55cd3c..04987b9 100644 --- a/tests/write_concern.rs +++ b/tests/write_concern.rs @@ -1,7 +1,7 @@ use mongo_driver::write_concern::WriteConcern; #[test] -fn test_write_concern() { - let write_concern = WriteConcern::new(); +fn test_default_write_concern() { + let write_concern = WriteConcern::default(); assert!(!write_concern.inner().is_null()); }