Initial version

pull/1/merge
Thijs Cadier 9 years ago
commit d686ba240f

4
.gitignore vendored

@ -0,0 +1,4 @@
target
Cargo.lock
mongo_c_driver_wrapper/mongo-c-driver*
mongo_c_driver_wrapper/src/bindings.rs

@ -0,0 +1,13 @@
[package]
name = "mongo_driver"
version = "0.1.0"
authors = ["Thijs Cadier <thijs@appsignal.com>"]
[dependencies]
libc = "*"
[dependencies.bson]
git = "https://github.com/zonyitoo/bson-rs.git"
[dependencies.mongo_c_driver_wrapper]
path = "mongo_c_driver_wrapper"

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Thijs Cadier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,21 @@
# Mongo Rust Driver
## About
Mongo Rust driver built on top of the [Mongo C driver](https://github.com/mongodb/mongo-c-driver).
This drivers aims to be a thin wrapper around the production-ready C driver, while providing a safe
and ergonomic Rust interface that handles all the gnarly usage details of the C driver for you.
Bson encoding and decoding is handled by the [bson crate](https://github.com/zonyitoo/bson-rs), the bindings
are generated using [bindgen](https://github.com/crabtw/rust-bindgen).
The API is experimental, it might change at any time.
## Compatibility
The driver currently only builds on Unix, only tested on Mac Os X so far.
## Contributing
Contributions are very welcome, only the parts of the C driver we need have been wrapped so far. Please
write a test for any behavior you add.

@ -0,0 +1,14 @@
[package]
name = "mongo_c_driver_wrapper"
version = "1.1.8"
build = "build.rs"
authors = ["Thijs Cadier <thijs@appsignal.com>"]
[dependencies]
libc = "*"
[build-dependencies]
pkg-config = "*"
[build-dependencies.bindgen]
git = "https://github.com/crabtw/rust-bindgen.git"

@ -0,0 +1,86 @@
#![feature(path_ext)]
extern crate bindgen;
extern crate pkg_config;
use std::env;
use std::fs::PathExt;
use std::path::Path;
use std::process::Command;
static VERSION: &'static str = "1.1.8"; // Should be the same as the version in the manifest
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let current_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let driver_path = format!(
"mongo-c-driver-{}",
VERSION
);
let libmongoc_path = Path::new(&out_dir).join("lib/libmongoc-1.0.a");
if !libmongoc_path.exists() { // TODO: This should check if we're at the right version
// Download and extract driver archive
let url = format!(
"https://github.com/mongodb/mongo-c-driver/releases/download/{}/mongo-c-driver-{}.tar.gz",
VERSION,
VERSION
);
assert!(Command::new("curl").arg("-O") // Save to disk
.arg("-L") // Follow redirects
.arg(url)
.status()
.unwrap()
.success());
let archive_name = format!(
"mongo-c-driver-{}.tar.gz",
VERSION
);
assert!(Command::new("tar").arg("xzf")
.arg(&archive_name)
.status()
.unwrap()
.success());
// Configure and install
assert!(Command::new("sh").arg("configure")
.arg("--enable-ssl=yes")
.arg("--enable-sasl=no")
.arg("--enable-static=yes")
.arg("--enable-shared=no")
.arg("--with-libbson=bundled")
.arg(format!("--prefix={}", &out_dir))
.current_dir(&driver_path)
.status()
.unwrap()
.success());
assert!(Command::new("make").current_dir(&driver_path).status().unwrap().success());
assert!(Command::new("make").arg("install").current_dir(&driver_path).status().unwrap().success());
// Generate bindings
let bindings_rs_path = Path::new(&current_dir).join("src/bindings.rs");
let mongo_h_path = Path::new(&current_dir).join(&driver_path).join("src/mongoc/mongoc.h");
let bson_path = Path::new(&current_dir).join(&driver_path).join("src/libbson/src/bson");
bindgen::builder()
.emit_builtins()
.header(mongo_h_path.to_str().unwrap())
.clang_arg(format!("-I{}", bson_path.to_str().unwrap()))
.generate()
.unwrap()
.write_to_file(&bindings_rs_path)
.unwrap();
}
// Output to Cargo
println!("cargo:root={}", &out_dir);
println!("cargo:libdir={}/lib", &out_dir);
println!("cargo:include={}/include", &out_dir);
println!("cargo:rustc-link-search={}/lib", &out_dir);
println!("cargo:rustc-link-lib=static=bson-1.0");
println!("cargo:rustc-link-lib=static=mongoc-1.0");
for link_path in pkg_config::find_library("openssl").unwrap().link_paths.iter(){
println!("cargo:rustc-link-search=framework={}", &link_path.display());
}
}

@ -0,0 +1,4 @@
extern crate libc;
#[allow(non_camel_case_types,non_snake_case)]
pub mod bindings;

@ -0,0 +1,145 @@
use std::ffi::{CStr,CString};
use std::ptr;
use std::borrow::Cow;
use std::fmt;
use std::slice;
use libc::types::common::c95::c_void;
use super::BsoncError;
use mongo_c_driver_wrapper::bindings;
use bson;
use super::Result;
pub struct Bsonc {
inner: *mut bindings::bson_t
}
impl Bsonc {
pub fn from_ptr(inner: *const bindings::bson_t) -> Bsonc {
assert!(!inner.is_null());
Bsonc { inner: inner as *mut bindings::bson_t }
}
pub fn from_document(document: &bson::Document) -> Result<Bsonc> {
let mut buffer = Vec::new();
try!(bson::encode_document(&mut buffer, document));
let inner = unsafe {
bindings::bson_new_from_data(
buffer[..].as_ptr(),
buffer.len() as u64
)
};
// Inner will be null if there was an error converting the data.
// We're assuming the bson crate works and therefore assert here.
// See: http://api.mongodb.org/libbson/current/bson_new_from_data.html
assert!(!inner.is_null());
Ok(Bsonc{ inner: inner })
}
pub fn from_json<S: Into<Vec<u8>>>(json: S) -> Result<Bsonc> {
let json_cstring = CString::new(json).unwrap();
let mut error = BsoncError::empty();
let inner = unsafe {
bindings::bson_new_from_json(
json_cstring.as_ptr() as *const u8,
json_cstring.as_bytes().len() as i64,
error.mut_inner()
)
};
if error.is_empty() {
Ok(Bsonc{ inner: inner })
} else {
Err(error.into())
}
}
pub fn as_document(&self) -> Result<bson::Document> {
assert!(!self.inner.is_null());
// This pointer should not be modified or freed
// See: http://api.mongodb.org/libbson/current/bson_get_data.html
let data_ptr = unsafe { bindings::bson_get_data(self.inner) };
assert!(!data_ptr.is_null());
let data_len = unsafe {
let bson = *self.inner;
bson.len
} as usize;
let mut slice = unsafe {
slice::from_raw_parts(data_ptr, data_len)
};
let document = try!(bson::decode_document(&mut slice));
Ok(document)
}
pub fn as_json(&self) -> Cow<str> {
assert!(!self.inner.is_null());
let json_ptr = unsafe { bindings::bson_as_json(self.inner, ptr::null_mut()) };
assert!(!json_ptr.is_null());
let json_cstr = unsafe { CStr::from_ptr(json_ptr) };
let out = String::from_utf8_lossy(json_cstr.to_bytes());
unsafe { bindings::bson_free(json_ptr as *mut c_void); }
out
}
pub fn inner(&self) -> *const bindings::bson_t {
self.inner
}
}
impl fmt::Debug for Bsonc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Bsonc: {}", self.as_json())
}
}
impl Drop for Bsonc {
fn drop(&mut self) {
unsafe {
bindings::bson_destroy(self.inner);
}
}
}
#[cfg(test)]
mod tests {
use bson;
#[test]
fn test_bsonc_from_and_as_document() {
let mut document = bson::Document::new();
document.insert("key".to_string(), bson::Bson::String("value".to_string()));
let bsonc = super::Bsonc::from_document(&document).unwrap();
let decoded = bsonc.as_document().unwrap();
assert!(decoded.contains_key("key"));
}
#[test]
fn test_bsonc_from_and_as_json() {
let json = "{ \"key\" : \"value\" }";
let bsonc = super::Bsonc::from_json(json).unwrap();
assert_eq!(json.to_string(), bsonc.as_json().into_owned());
}
#[test]
fn test_invalid_json() {
let malformed_json = "{ \"key\" : \"val }";
let bsonc_result = super::Bsonc::from_json(malformed_json);
assert!(bsonc_result.is_err());
let error_message = format!("{:?}", bsonc_result.err().unwrap());
assert!(error_message.starts_with("MongoError (BsoncError: parse error: premature EOF"));
}
}

@ -0,0 +1,161 @@
use std::fmt;
use std::ffi::CString;
use mongo_c_driver_wrapper::bindings;
use super::uri::Uri;
use super::collection::Collection;
// TODO: We're using a sort of poor man's Arc here
// with this root bool, there must be a better way.
pub struct ClientPool {
root_instance: bool,
uri: Uri,
inner: *mut bindings::mongoc_client_pool_t
}
impl ClientPool {
/// Create a new ClientPool
/// See: http://api.mongodb.org/c/current/mongoc_client_pool_t.html
pub fn new(uri: Uri) -> ClientPool {
let pool = unsafe {
let pool_ptr = bindings::mongoc_client_pool_new(uri.inner());
assert!(!pool_ptr.is_null());
pool_ptr
};
ClientPool {
root_instance: true,
uri: uri, // Become owner of uri so it doesn't go out of scope
inner: pool
}
}
/// 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) };
Client{
client_pool: self,
inner: client
}
}
/// 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());
bindings::mongoc_client_pool_push(
self.inner,
mongo_client
);
}
}
unsafe impl Send for ClientPool { }
unsafe impl Sync for ClientPool { }
impl fmt::Debug for ClientPool {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ClientPool for {}", self.uri.as_str())
}
}
impl Clone for ClientPool {
fn clone(&self) -> ClientPool {
assert!(!self.inner.is_null());
ClientPool {
root_instance: false,
uri: self.uri.clone(),
inner: self.inner.clone()
}
}
}
impl Drop for ClientPool {
fn drop(&mut self) {
if self.root_instance {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_client_pool_destroy(self.inner);
}
}
}
}
pub struct Client<'a> {
client_pool: &'a ClientPool,
inner: *mut bindings::mongoc_client_t
}
impl<'a> Client<'a> {
pub fn get_collection<S: Into<Vec<u8>>>(&'a self, db: S, collection: S) -> Collection<'a> {
assert!(!self.inner.is_null());
let mut coll;
unsafe {
let db_cstring = CString::new(db).unwrap();
let collection_cstring = CString::new(collection).unwrap();
coll = bindings::mongoc_client_get_collection(
self.inner,
db_cstring.as_ptr(),
collection_cstring.as_ptr()
);
}
Collection::new(self, coll)
}
}
impl<'a> Drop for Client<'a> {
fn drop(&mut self) {
assert!(!self.inner.is_null());
unsafe {
self.client_pool.push(self.inner);
}
}
}
#[cfg(test)]
mod tests {
use std::thread;
use super::super::uri::Uri;
use super::super::client::ClientPool;
#[test]
fn test_new_pool_and_pop_client() {
super::super::init();
let uri = Uri::new("mongodb://localhost:27017/");
let pool = ClientPool::new(uri);
// Pop a client and insert a couple of times
for _ in 0..10 {
let client = pool.pop();
pool.pop();
client.get_collection("rust_test", "items");
}
}
#[test]
fn test_new_pool_and_pop_client_in_threads() {
super::super::init();
let uri = Uri::new("mongodb://localhost:27017/");
let pool = ClientPool::new(uri);
let pool1 = pool.clone();
let guard1 = thread::scoped(move || {
let client = pool1.pop();
client.get_collection("test", "items");
});
let pool2 = pool.clone();
let guard2 = thread::scoped(move || {
let client = pool2.pop();
client.get_collection("test", "items");
});
guard1.join();
guard2.join();
}
}

@ -0,0 +1,379 @@
use std::ptr;
use mongo_c_driver_wrapper::bindings;
use bson::Document;
use super::Result;
use super::{BsoncError,InvalidParamsError};
use super::bsonc::Bsonc;
use super::client::Client;
use super::cursor;
use super::cursor::Cursor;
use super::flags::{Flags,FlagsValue,InsertFlag,QueryFlag,RemoveFlag};
use super::write_concern::WriteConcern;
use super::read_prefs::ReadPrefs;
pub enum CreatedBy<'a> {
Client(&'a Client<'a>)
}
pub struct Collection<'a> {
_created_by: CreatedBy<'a>,
inner: *mut bindings::mongoc_collection_t
}
impl<'a> Collection<'a> {
pub fn new(
client: &'a Client<'a>,
inner: *mut bindings::mongoc_collection_t
) -> Collection<'a> {
assert!(!inner.is_null());
Collection {
_created_by: CreatedBy::Client(client),
inner: inner
}
}
pub fn count_with_options(
&self,
query_flags: &Flags<QueryFlag>,
query: &Document,
skip: u32,
limit: u32,
opts: Option<&Document>,
read_prefs: Option<&ReadPrefs>
) -> Result<i64> {
assert!(!self.inner.is_null());
let mut error = BsoncError::empty();
let count = unsafe {
bindings::mongoc_collection_count_with_opts(
self.inner,
query_flags.flags(),
try!(Bsonc::from_document(query)).inner(),
skip as i64,
limit as i64,
match opts {
Some(o) => try!(Bsonc::from_document(o)).inner(),
None => ptr::null()
},
match read_prefs {
Some(prefs) => prefs.inner(),
None => ptr::null()
},
error.mut_inner()
)
};
if error.is_empty() {
Ok(count)
} else {
Err(error.into())
}
}
pub fn count(
&self,
query: &Document
) -> Result<i64> {
self.count_with_options(
&Flags::new(),
query,
0,
0,
None,
None
)
}
pub fn drop(&mut self) -> Result<()> {
assert!(!self.inner.is_null());
let mut error = BsoncError::empty();
let success = unsafe {
bindings::mongoc_collection_drop(
self.inner,
error.mut_inner()
)
};
if success == 0 {
assert!(!error.is_empty());
return Err(error.into())
}
Ok(())
}
pub fn find_with_options(
&'a self,
query_flags: &Flags<QueryFlag>,
skip: u32,
limit: u32,
batch_size: u32,
query: &Document,
fields: Option<&Document>,
read_prefs: Option<&ReadPrefs>
) -> Result<Cursor<'a>> {
assert!(!self.inner.is_null());
let inner = unsafe {
bindings::mongoc_collection_find(
self.inner,
query_flags.flags(),
skip,
limit,
batch_size,
try!(Bsonc::from_document(query)).inner(),
match fields {
Some(f) => {
try!(Bsonc::from_document(f)).inner()
},
None => ptr::null()
},
match read_prefs {
Some(prefs) => prefs.inner(),
None => ptr::null()
}
)
};
if inner.is_null() {
return Err(InvalidParamsError.into())
}
Ok(Cursor::new(cursor::CreatedBy::Collection(self), inner))
}
pub fn find(
&'a self,
query: &Document
) -> Result<Cursor<'a>> {
self.find_with_options(
&Flags::new(),
0,
0,
0,
&query,
None,
None
)
}
pub fn insert_with_options(
&'a self,
insert_flags: &Flags<InsertFlag>,
document: &Document,
write_concern: &WriteConcern
) -> Result<()> {
assert!(!self.inner.is_null());
let mut error = BsoncError::empty();
let success = unsafe {
bindings::mongoc_collection_insert(
self.inner,
insert_flags.flags(),
try!(Bsonc::from_document(&document)).inner(),
write_concern.inner(),
error.mut_inner()
)
};
if success == 1 {
Ok(())
} else {
Err(error.into())
}
}
pub fn insert(&'a self, document: &Document) -> Result<()> {
self.insert_with_options(
&Flags::new(),
document,
&WriteConcern::new()
)
}
pub fn remove_with_options(
&self,
remove_flags: &Flags<RemoveFlag>,
selector: &Document,
write_concern: &WriteConcern
) -> Result<()> {
assert!(!self.inner.is_null());
let mut error = BsoncError::empty();
let success = unsafe {
bindings::mongoc_collection_remove(
self.inner,
remove_flags.flags(),
try!(Bsonc::from_document(&selector)).inner(),
write_concern.inner(),
error.mut_inner()
)
};
if success == 1 {
Ok(())
} else {
Err(error.into())
}
}
pub fn remove(
&self,
selector: &Document
) -> Result<()> {
self.remove_with_options(
&Flags::new(),
selector,
&WriteConcern::new()
)
}
pub fn save_with_options(
&self,
document: &Document,
write_concern: &WriteConcern
) -> Result<()> {
assert!(!self.inner.is_null());
let mut error = BsoncError::empty();
let success = unsafe {
bindings::mongoc_collection_save(
self.inner,
try!(Bsonc::from_document(&document)).inner(),
write_concern.inner(),
error.mut_inner()
)
};
if success == 1 {
Ok(())
} else {
Err(error.into())
}
}
pub fn save(
&self,
document: &Document,
) -> Result<()> {
self.save_with_options(
document,
&WriteConcern::new()
)
}
}
impl<'a> Drop for Collection<'a> {
fn drop(&mut self) {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_collection_destroy(self.inner);
}
}
}
#[cfg(test)]
mod tests {
use bson;
use super::super::uri::Uri;
use super::super::client::ClientPool;
use super::super::flags::{Flags};
#[test]
fn test_mutation_and_finding() {
let uri = Uri::new("mongodb://localhost:27017/");
let pool = ClientPool::new(uri);
let client = pool.pop();
let mut collection = client.get_collection("rust_driver_test", "items");
collection.drop().unwrap_or(());
let mut document = bson::Document::new();
document.insert("key_1".to_string(), bson::Bson::String("Value 1".to_string()));
document.insert("key_2".to_string(), bson::Bson::String("Value 2".to_string()));
assert!(collection.insert(&document).is_ok());
let mut second_document = bson::Document::new();
second_document.insert("key_1".to_string(), bson::Bson::String("Value 3".to_string()));
assert!(collection.insert(&second_document).is_ok());
let query = bson::Document::new();
// Count the documents in the collection
assert_eq!(2, collection.count(&query).unwrap());
// Find the documents
assert_eq!(
collection.find(&document).unwrap().next().unwrap().unwrap().get("key_1").unwrap().to_json(),
bson::Bson::String("Value 1".to_string()).to_json()
);
let mut found_document = collection.find(&second_document).unwrap().next().unwrap().unwrap();
assert_eq!(
found_document.get("key_1").unwrap().to_json(),
bson::Bson::String("Value 3".to_string()).to_json()
);
// Update the second document
found_document.insert("key_1".to_string(), bson::Bson::String("Value 4".to_string()));
assert!(collection.save(&found_document).is_ok());
// Reload and check value
let found_document = collection.find(&found_document).unwrap().next().unwrap().unwrap();
assert_eq!(
found_document.get("key_1").unwrap().to_json(),
bson::Bson::String("Value 4".to_string()).to_json()
);
// Remove one
assert!(collection.remove(&found_document).is_ok());
// Count again
assert_eq!(1, collection.count(&query).unwrap());
// Find the document and see if it has the keys we expect
{
let mut cursor = collection.find(&query).unwrap();
let next_document = cursor.next().unwrap().unwrap();
assert!(next_document.contains_key("key_1"));
assert!(next_document.contains_key("key_2"));
}
// Find the document with fields set
let mut fields = bson::Document::new();
fields.insert("key_1".to_string(), bson::Bson::Boolean(true));
{
let mut cursor = collection.find_with_options(
&Flags::new(),
0,
0,
0,
&query,
Some(&fields),
None
).unwrap();
let next_document = cursor.next().unwrap().unwrap();
assert!(next_document.contains_key("key_1"));
assert!(!next_document.contains_key("key_2"));
}
// Drop collection
collection.drop().unwrap();
assert_eq!(0, collection.count(&query).unwrap());
}
#[test]
fn test_insert_failure() {
let uri = Uri::new("mongodb://localhost:27018/"); // There should be no mongo server here
let pool = ClientPool::new(uri);
let client = pool.pop();
let collection = client.get_collection("rust_driver_test", "items");
let document = bson::Document::new();
let result = collection.insert(&document);
assert!(result.is_err());
assert_eq!(
"MongoError (BsoncError: Failed to connect to target host: localhost:27018)",
format!("{:?}", result.err().unwrap())
);
}
}

@ -0,0 +1,139 @@
use std::iter::Iterator;
use std::ptr;
use mongo_c_driver_wrapper::bindings;
use bson::Document;
use super::BsoncError;
use super::bsonc;
use super::client::Client;
use super::collection::Collection;
use super::Result;
pub enum CreatedBy<'a> {
Collection(&'a Collection<'a>),
Client(&'a Client<'a>)
}
pub struct Cursor<'a> {
_created_by: CreatedBy<'a>,
inner: *mut bindings::mongoc_cursor_t,
}
impl<'a> Cursor<'a> {
pub fn new(
created_by: CreatedBy<'a>,
inner: *mut bindings::mongoc_cursor_t
) -> Cursor<'a> {
assert!(!inner.is_null());
Cursor {
_created_by: created_by,
inner: inner
}
}
pub fn is_alive(&self) -> bool {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_cursor_is_alive(self.inner) == 1
}
}
pub fn more(&self) -> bool {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_cursor_more(self.inner) == 1
}
}
fn error(&self) -> BsoncError {
assert!(!self.inner.is_null());
let mut error = BsoncError::empty();
unsafe {
bindings::mongoc_cursor_error(
self.inner,
error.mut_inner()
)
};
error
}
}
impl<'a> Iterator for Cursor<'a> {
type Item = Result<Document>;
fn next(&mut self) -> Option<Self::Item> {
assert!(!self.inner.is_null());
// The C driver writes the document to memory and sets an
// already existing pointer to it.
let mut bson_ptr: *const bindings::bson_t = ptr::null();
let success = unsafe {
bindings::mongoc_cursor_next(
self.inner,
&mut bson_ptr
)
};
if success == 0 {
let error = self.error();
if error.is_empty() {
return None
} else {
return Some(Err(error.into()))
}
}
assert!(!bson_ptr.is_null());
let bsonc = bsonc::Bsonc::from_ptr(bson_ptr);
let document = bsonc.as_document();
match document {
Ok(document) => Some(Ok(document)),
Err(error) => Some(Err(error.into()))
}
}
}
impl<'a> Drop for Cursor<'a> {
fn drop(&mut self) {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_cursor_destroy(self.inner);
}
}
}
#[cfg(test)]
mod tests {
use bson;
use super::super::uri::Uri;
use super::super::client::ClientPool;
use super::super::Result;
#[test]
fn test_cursor() {
let uri = Uri::new("mongodb://localhost:27017/");
let pool = ClientPool::new(uri);
let client = pool.pop();
let mut collection = client.get_collection("rust_driver_test", "cursor_items");
let mut document = bson::Document::new();
document.insert("key".to_string(), bson::Bson::String("value".to_string()));
collection.drop().unwrap();
for _ in 0..10 {
assert!(collection.insert(&document).is_ok());
}
let query = bson::Document::new();
let cursor = collection.find(&query).unwrap();
assert!(cursor.is_alive());
let documents = cursor.into_iter().collect::<Vec<Result<bson::Document>>>();
// See if we got 10 results and the iterator then stopped
assert_eq!(10, documents.len());
}
}

@ -0,0 +1,296 @@
use std::error;
use std::fmt;
use std::borrow::Cow;
use std::ffi::CStr;
use bson::{DecoderError,EncoderError};
use mongo_c_driver_wrapper::bindings;
pub enum MongoError {
Bsonc(BsoncError),
Decoder(DecoderError),
Encoder(EncoderError),
InvalidParams(InvalidParamsError)
}
impl fmt::Display for MongoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MongoError::Bsonc(ref err) => write!(f, "{}", err),
MongoError::Encoder(ref err) => write!(f, "{}", err),
MongoError::Decoder(ref err) => write!(f, "{}", err),
MongoError::InvalidParams(ref err) => write!(f, "{}", err)
}
}
}
impl fmt::Debug for MongoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MongoError::Bsonc(ref err) => write!(f, "MongoError ({:?})", err),
MongoError::Decoder(ref err) => write!(f, "MongoError ({:?})", err),
MongoError::Encoder(ref err) => write!(f, "MongoError ({:?})", err),
MongoError::InvalidParams(ref err) => write!(f, "MongoError ({:?})", err)
}
}
}
impl error::Error for MongoError {
fn description(&self) -> &str {
match *self {
MongoError::Bsonc(ref err) => err.description(),
MongoError::Decoder(ref err) => err.description(),
MongoError::Encoder(ref err) => err.description(),
MongoError::InvalidParams(ref err) => err.description()
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
MongoError::Bsonc(ref err) => Some(err),
MongoError::Decoder(ref err) => Some(err),
MongoError::Encoder(ref err) => Some(err),
MongoError::InvalidParams(ref err) => Some(err)
}
}
}
impl From<DecoderError> for MongoError {
fn from(error: DecoderError) -> MongoError {
MongoError::Decoder(error)
}
}
impl From<EncoderError> for MongoError {
fn from(error: EncoderError) -> MongoError {
MongoError::Encoder(error)
}
}
pub struct BsoncError {
inner: bindings::bson_error_t,
}
#[derive(Debug,PartialEq)]
pub enum MongoErrorDomain {
Blank,
Client,
Stream,
Protocol,
Cursor,
Query,
Insert,
Sasl,
Bson,
Matcher,
Namespace,
Command,
Collection,
Gridfs,
Scram,
Unknown
}
#[derive(Debug,PartialEq)]
pub enum MongoErrorCode {
Blank,
StreamInvalidType,
StreamInvalidState,
StreamNameResolution,
StreamSocket,
StreamConnect,
StreamNotEstablished,
ClientNotReady,
ClientTooBig,
ClientTooSmall,
ClientGetnonce,
ClientAuthenticate,
ClientNoAcceptablePeer,
ClientInExhaust,
ProtocolInvalidReply,
ProtocolBadWireVersion,
CursorInvalidCursor,
QueryFailure,
BsonInvalid,
MatcherInvalid,
NamespaceInvalid,
NamespaceInvalidFilterType,
CommandInvalidArg,
CollectionInsertFailed,
CollectionUpdateFailed,
CollectionDeleteFailed,
CollectionDoesNotExist,
GridfsInvalidFilename,
ScramNotDone,
ScramProtocolError,
QueryCommandNotFound,
QueryNotTailable,
Unknown
}
impl BsoncError {
pub fn empty() -> BsoncError {
BsoncError {
inner: bindings::bson_error_t {
domain: 0,
code: 0,
message: [0; 504]
}
}
}
pub fn is_empty(&self) -> bool {
self.inner.domain == 0 && self.inner.code == 0
}
pub fn domain(&self) -> MongoErrorDomain {
match self.inner.domain {
0 => MongoErrorDomain::Blank,
bindings::MONGOC_ERROR_CLIENT => MongoErrorDomain::Client,
bindings::MONGOC_ERROR_STREAM => MongoErrorDomain::Stream,
bindings::MONGOC_ERROR_PROTOCOL => MongoErrorDomain::Protocol,
bindings::MONGOC_ERROR_CURSOR => MongoErrorDomain::Cursor,
bindings::MONGOC_ERROR_QUERY => MongoErrorDomain::Query,
bindings::MONGOC_ERROR_INSERT => MongoErrorDomain::Insert,
bindings::MONGOC_ERROR_SASL => MongoErrorDomain::Sasl,
bindings::MONGOC_ERROR_BSON => MongoErrorDomain::Bson,
bindings::MONGOC_ERROR_MATCHER => MongoErrorDomain::Matcher,
bindings::MONGOC_ERROR_NAMESPACE => MongoErrorDomain::Namespace,
bindings::MONGOC_ERROR_COMMAND => MongoErrorDomain::Command,
bindings::MONGOC_ERROR_COLLECTION => MongoErrorDomain::Collection,
bindings::MONGOC_ERROR_GRIDFS => MongoErrorDomain::Gridfs,
bindings::MONGOC_ERROR_SCRAM => MongoErrorDomain::Scram,
_ => MongoErrorDomain::Unknown
}
}
pub fn code(&self) -> MongoErrorCode {
match self.inner.code {
0 => MongoErrorCode::Blank,
bindings::MONGOC_ERROR_STREAM_INVALID_TYPE => MongoErrorCode::StreamInvalidType,
bindings::MONGOC_ERROR_STREAM_INVALID_STATE => MongoErrorCode::StreamInvalidState,
bindings::MONGOC_ERROR_STREAM_NAME_RESOLUTION => MongoErrorCode::StreamNameResolution,
bindings::MONGOC_ERROR_STREAM_SOCKET => MongoErrorCode::StreamSocket,
bindings::MONGOC_ERROR_STREAM_CONNECT => MongoErrorCode::StreamConnect,
bindings::MONGOC_ERROR_STREAM_NOT_ESTABLISHED => MongoErrorCode::StreamNotEstablished,
bindings::MONGOC_ERROR_CLIENT_NOT_READY => MongoErrorCode::ClientNotReady,
bindings::MONGOC_ERROR_CLIENT_TOO_BIG => MongoErrorCode::ClientTooBig,
bindings::MONGOC_ERROR_CLIENT_TOO_SMALL => MongoErrorCode::ClientTooSmall,
bindings::MONGOC_ERROR_CLIENT_GETNONCE => MongoErrorCode::ClientGetnonce,
bindings::MONGOC_ERROR_CLIENT_AUTHENTICATE => MongoErrorCode::ClientAuthenticate,
bindings::MONGOC_ERROR_CLIENT_NO_ACCEPTABLE_PEER => MongoErrorCode::ClientNoAcceptablePeer,
bindings::MONGOC_ERROR_CLIENT_IN_EXHAUST => MongoErrorCode::ClientInExhaust,
bindings::MONGOC_ERROR_PROTOCOL_INVALID_REPLY => MongoErrorCode::ProtocolInvalidReply,
bindings::MONGOC_ERROR_PROTOCOL_BAD_WIRE_VERSION => MongoErrorCode::ProtocolBadWireVersion,
bindings::MONGOC_ERROR_CURSOR_INVALID_CURSOR => MongoErrorCode::CursorInvalidCursor,
bindings::MONGOC_ERROR_QUERY_FAILURE => MongoErrorCode::QueryFailure,
bindings::MONGOC_ERROR_BSON_INVALID => MongoErrorCode::BsonInvalid,
bindings::MONGOC_ERROR_MATCHER_INVALID => MongoErrorCode::MatcherInvalid,
bindings::MONGOC_ERROR_NAMESPACE_INVALID => MongoErrorCode::NamespaceInvalid,
bindings::MONGOC_ERROR_NAMESPACE_INVALID_FILTER_TYPE => MongoErrorCode::NamespaceInvalidFilterType,
bindings::MONGOC_ERROR_COMMAND_INVALID_ARG => MongoErrorCode::CommandInvalidArg,
bindings::MONGOC_ERROR_COLLECTION_INSERT_FAILED => MongoErrorCode::CollectionInsertFailed,
bindings::MONGOC_ERROR_COLLECTION_UPDATE_FAILED => MongoErrorCode::CollectionUpdateFailed,
bindings::MONGOC_ERROR_COLLECTION_DELETE_FAILED => MongoErrorCode::CollectionDeleteFailed,
bindings::MONGOC_ERROR_COLLECTION_DOES_NOT_EXIST => MongoErrorCode::CollectionDoesNotExist,
bindings::MONGOC_ERROR_GRIDFS_INVALID_FILENAME => MongoErrorCode::GridfsInvalidFilename,
bindings::MONGOC_ERROR_SCRAM_NOT_DONE => MongoErrorCode::ScramNotDone,
bindings::MONGOC_ERROR_SCRAM_PROTOCOL_ERROR => MongoErrorCode::ScramProtocolError,
bindings::MONGOC_ERROR_QUERY_COMMAND_NOT_FOUND => MongoErrorCode::QueryCommandNotFound,
bindings::MONGOC_ERROR_QUERY_NOT_TAILABLE => MongoErrorCode::QueryNotTailable,
_ => MongoErrorCode::Unknown
}
}
pub fn get_message(&self) -> Cow<str> {
let cstr = unsafe { CStr::from_ptr(&self.inner.message as *const i8) };
String::from_utf8_lossy(cstr.to_bytes())
}
pub fn mut_inner(&mut self) -> &mut bindings::bson_error_t {
&mut self.inner
}
}
impl fmt::Debug for BsoncError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "BsoncError: {}", &self.get_message())
}
}
impl fmt::Display for BsoncError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self.get_message())
}
}
impl error::Error for BsoncError {
fn description(&self) -> &str {
"Error reported by the underlying Mongo C driver"
}
}
impl From<BsoncError> for MongoError {
fn from(error: BsoncError) -> MongoError {
MongoError::Bsonc(error)
}
}
pub struct InvalidParamsError;
impl fmt::Debug for InvalidParamsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "InvalidParamsError: Invalid params supplied")
}
}
impl fmt::Display for InvalidParamsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Invalid params supplied")
}
}
impl error::Error for InvalidParamsError {
fn description(&self) -> &str {
"Invalid params reported by the underlying Mongo C driver, no more information is available"
}
}
impl From<InvalidParamsError> for MongoError {
fn from(error: InvalidParamsError) -> MongoError {
MongoError::InvalidParams(error)
}
}
#[cfg(test)]
mod tests {
use super::{BsoncError,MongoErrorDomain,MongoErrorCode};
#[test]
fn test_bson_error_empty() {
let mut error = BsoncError::empty();
assert!(error.is_empty());
error.mut_inner().code = 1;
assert!(!error.is_empty());
error.mut_inner().domain = 1;
error.mut_inner().code = 0;
assert!(!error.is_empty());
}
#[test]
fn test_bson_error_domain() {
let mut error = BsoncError::empty();
assert_eq!(MongoErrorDomain::Blank, error.domain());
error.mut_inner().domain = 1;
assert_eq!(MongoErrorDomain::Client, error.domain());
}
#[test]
fn test_bson_error_code() {
let mut error = BsoncError::empty();
assert_eq!(MongoErrorCode::Blank, error.code());
error.mut_inner().code = 1;
assert_eq!(MongoErrorCode::StreamInvalidType, error.code());
}
}

@ -0,0 +1,120 @@
use mongo_c_driver_wrapper::bindings;
pub struct Flags<T> {
flags: Vec<T>
}
impl <T> Flags<T> {
pub fn new() -> Flags<T> {
Flags {
flags: Vec::new()
}
}
pub fn add(&mut self, flag: T) {
self.flags.push(flag);
}
}
pub trait FlagsValue {
fn flags(&self) -> u32;
}
/// Flags for insert operations
/// See: http://api.mongodb.org/c/current/mongoc_insert_flags_t.html
pub enum InsertFlag {
ContinueOnError,
NoValidate
}
impl FlagsValue for Flags<InsertFlag> {
fn flags(&self) -> u32 {
if self.flags.is_empty() {
bindings::MONGOC_INSERT_NONE
} else {
self.flags.iter().fold(0, { |flags, flag|
flags | match flag {
&InsertFlag::ContinueOnError => bindings::MONGOC_INSERT_CONTINUE_ON_ERROR,
&InsertFlag::NoValidate => 1 | 31 // MONGOC_INSERT_NO_VALIDATE defined in macro
}
})
}
}
}
/// Flags for query operations
/// See: http://api.mongodb.org/c/current/mongoc_query_flags_t.html
pub enum QueryFlag {
TailableCursor,
SlaveOk,
OplogReplay,
NoCursorTimeout,
AwaitData,
Exhaust,
Partial
}
impl FlagsValue for Flags<QueryFlag> {
fn flags(&self) -> u32 {
if self.flags.is_empty() {
bindings::MONGOC_QUERY_NONE
} else {
self.flags.iter().fold(0, { |flags, flag|
flags | match flag {
&QueryFlag::TailableCursor => bindings::MONGOC_QUERY_TAILABLE_CURSOR,
&QueryFlag::SlaveOk => bindings::MONGOC_QUERY_SLAVE_OK,
&QueryFlag::OplogReplay => bindings::MONGOC_QUERY_OPLOG_REPLAY,
&QueryFlag::NoCursorTimeout => bindings::MONGOC_QUERY_NO_CURSOR_TIMEOUT,
&QueryFlag::AwaitData => bindings::MONGOC_QUERY_AWAIT_DATA,
&QueryFlag::Exhaust => bindings::MONGOC_QUERY_EXHAUST,
&QueryFlag::Partial => bindings::MONGOC_QUERY_PARTIAL
}
})
}
}
}
/// Flags for deletion operations
/// See: http://api.mongodb.org/c/1.1.8/mongoc_remove_flags_t.html
pub enum RemoveFlag {
SingleRemove
}
impl FlagsValue for Flags<RemoveFlag> {
fn flags(&self) -> u32 {
if self.flags.is_empty() {
bindings::MONGOC_REMOVE_NONE
} else {
bindings::MONGOC_REMOVE_SINGLE_REMOVE
}
}
}
#[cfg(test)]
mod tests {
use super::FlagsValue;
#[test]
pub fn test_insert_flags() {
let mut flags = super::Flags::new();
assert_eq!(0, flags.flags());
flags.add(super::InsertFlag::ContinueOnError);
assert_eq!(1, flags.flags());
flags.add(super::InsertFlag::NoValidate);
assert_eq!(31, flags.flags());
}
#[test]
pub fn test_query_flags() {
let mut flags = super::Flags::new();
assert_eq!(0, flags.flags());
flags.add(super::QueryFlag::TailableCursor);
assert_eq!(2, flags.flags());
flags.add(super::QueryFlag::Partial);
assert_eq!(130, flags.flags());
}
}

@ -0,0 +1,58 @@
#![feature(scoped)]
extern crate libc;
extern crate mongo_c_driver_wrapper;
extern crate bson;
use std::result;
use mongo_c_driver_wrapper::bindings;
pub mod bsonc;
pub mod client;
pub mod collection;
pub mod cursor;
pub mod error;
pub mod flags;
pub mod read_prefs;
pub mod uri;
pub mod write_concern;
pub use error::{MongoError,BsoncError,InvalidParamsError};
pub type Result<T> = result::Result<T, MongoError>;
static mut INITIALIZED: bool = false;
/// Init mongo driver, needs to be called once before doing
/// anything else.
pub fn init() {
unsafe {
bindings::mongoc_init();
INITIALIZED = true;
}
}
/// Clean up mongo driver's resources
pub fn cleanup() {
unsafe {
bindings::mongoc_cleanup();
INITIALIZED = false;
}
}
pub fn is_initialized() -> bool {
unsafe { INITIALIZED }
}
#[cfg(test)]
mod tests {
#[test]
fn test_init_and_cleanup() {
super::init();
assert!(super::is_initialized());
super::cleanup();
assert!(!super::is_initialized());
}
}

@ -0,0 +1,59 @@
use mongo_c_driver_wrapper::bindings;
pub enum ReadMode {
Primary,
Secondary,
PrimaryPreferred,
SecondaryPreferred,
Nearest
}
fn read_mode_value(read_mode: &ReadMode) -> bindings::mongoc_read_mode_t {
match read_mode {
&ReadMode::Primary => bindings::MONGOC_READ_PRIMARY,
&ReadMode::Secondary => bindings::MONGOC_READ_SECONDARY,
&ReadMode::PrimaryPreferred => bindings::MONGOC_READ_PRIMARY_PREFERRED,
&ReadMode::SecondaryPreferred => bindings::MONGOC_READ_SECONDARY_PREFERRED,
&ReadMode::Nearest => bindings::MONGOC_READ_NEAREST
}
}
pub struct ReadPrefs {
inner: *mut bindings::mongoc_read_prefs_t
}
impl ReadPrefs {
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) };
assert!(!inner.is_null());
ReadPrefs { inner: inner }
}
pub fn default() -> ReadPrefs{
ReadPrefs::new(&ReadMode::Primary)
}
pub fn inner(&self) -> *const bindings::mongoc_read_prefs_t {
assert!(!self.inner.is_null());
self.inner
}
}
impl Drop for ReadPrefs {
fn drop(&mut self) {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_read_prefs_destroy(self.inner);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_read_prefs() {
let read_prefs = super::ReadPrefs::default();
assert!(!read_prefs.inner().is_null());
}
}

@ -0,0 +1,69 @@
use std::borrow::Cow;
use std::ffi::{CStr,CString};
use std::fmt;
use mongo_c_driver_wrapper::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 {
pub fn new<T: Into<Vec<u8>>>(uri_string: T) -> Uri {
let uri_cstring = CString::new(uri_string).unwrap();
let uri = unsafe { bindings::mongoc_uri_new(uri_cstring.as_ptr()) };
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())
}
}
// TODO add various methods that are available on uri
}
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())
}
}
impl Drop for Uri {
fn drop(&mut self) {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_uri_destroy(self.inner);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_new_uri() {
let uri = super::Uri::new("mongodb://localhost:27017/");
assert_eq!("mongodb://localhost:27017/", uri.as_str());
}
}

@ -0,0 +1,36 @@
use mongo_c_driver_wrapper::bindings;
pub struct WriteConcern {
inner: *mut bindings::mongoc_write_concern_t
}
impl WriteConcern {
pub fn new() -> WriteConcern {
let inner = unsafe { bindings::mongoc_write_concern_new() };
assert!(!inner.is_null());
WriteConcern { inner: inner }
}
pub fn inner(&self) -> *const bindings::mongoc_write_concern_t {
assert!(!self.inner.is_null());
self.inner
}
}
impl Drop for WriteConcern {
fn drop(&mut self) {
assert!(!self.inner.is_null());
unsafe {
bindings::mongoc_write_concern_destroy(self.inner);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_write_concern() {
let write_concern = super::WriteConcern::new();
assert!(!write_concern.inner().is_null());
}
}
Loading…
Cancel
Save