Servers
Your Code
What this talk is on
Database
Database
Collection
Collection
Collection
Collection
Collection
Document
Document
Document
Document
Document
Document
Document
Document
Document
Document
All basic functionality can be described by the "CRUD" spec:
C reate - insert new documents
R ead - query existing documents
U pdate - change existing documents
D elete - remove existing documents
How would you query all the players on a certain team?
{
"_id" : ObjectId("55a02f52648dca06dce7e5d0"),
"first_name" : "Jose",
"last_name" : "Alvarez",
"bats" : "L",
"throws" : "L",
"team" : "LAA",
"position" : "P",
"avg" : null,
"tags" : [ ]
}
Sample
Document
let db = client.db("mlb");
let coll = db.collection("players");
let filter = Some(doc! { "team" => team });
let mut options = FindOptions::new();
options.projection = Some(doc! {
"_id" => 0,
"first_name" => 1,
"last_name" => 1,
"position" => 1
});
match coll.find(filter, Some(options)) {
Ok(cursor) => Ok(cursor),
Err(e) => err_as_string!(e),
}
Note: The code segment:
produces the BSON equivalent to the JSON object:
(more on that later)
doc! {
"team" => "BOS"
}
Step 1
{ "team": "BOS" }
let db = client.db("mlb");
let coll = db.collection("players");
let filter = Some(doc! { "team" => team });
let mut options = FindOptions::new();
options.projection = Some(doc! {
"_id" => 0,
"first_name" => 1,
"last_name" => 1,
"position" => 1
});
match coll.find(filter, Some(options)) {
Ok(cursor) => Ok(cursor),
Err(e) => err_as_string!(e),
}
Step 1
Step 2.2
Step 2.1
let db = client.db("mlb");
let coll = db.collection("players");
let filter = Some(doc! { "team" => team });
let mut options = FindOptions::new();
options.projection = Some(doc! {
"_id" => 0,
"first_name" => 1,
"last_name" => 1,
"position" => 1
});
match coll.find(filter, Some(options)) {
Ok(cursor) => Ok(cursor),
Err(e) => err_as_string!(e),
}
Step 1
Step 2.2
Step 2.1
Step 3
let mut string = "{\"result\":[".to_owned();
for (i, doc_result) in cursor.enumerate() {
match json_string_from_doc_result(doc_result) {
Ok(json_string) => {
let new_string = if i == 0 {
json_string
} else {
format!(",{}"), json_string)
};
string.push_str(&new_string);
},
Err(e) => return e,
}
}
string.push_str("]}");
`Cursor`
implements
`Iterator`
For Instance:
By default, all variables in Rust are immutable.
However, mutability is a bit different in Rust...
By default, all structs and variables in Rust have exterior immutability.
Immutable structures can still hold mutable components, as long as they follow the ownership system of Rust:
You may have one or the other of these two kinds of borrows, but not both at the same time:
one or more references (&T) to a resource.
exactly one mutable reference (&mut T).
One approach to guaranteeing these rules at compile time is to use RAII locks.
Each database operation requires a connection to the database.
Sometimes, they take a long time.
Connection pools handle the creation and reuse of sockets
connected to a MongoDB instance.
Acquire socket lock, Use socket, Release socket lock.
An immutable connection pool requires explicit lock and socket initialization on creation.
A mutable connection pool requires a mutex to guarantee thread safety.
Lock the pool
Pop a stream (S)
Return the stream
with a pool reference
The Good:
Variable number of lazily-connected sockets
Almost lock-free
One master lock on the connection pool
The Bad:
The number of open sockets is uncapped.
Recycling vulnerability: Old sockets can be dropped as new ones are constantly made to take their place.
Not very readable!
let mut update = bson::Document::new();
let mut set = bson::Document::new();
set.insert("director".to_owned(), Bson::String("Robert Zemeckis".to_owned()));
update.insert("$set".to_owned(), Bson::Document(set));
Do we really need a second macro?
let update = doc! {
"$set" => nested_doc! {
"director" => Bson::String("Robert Zemeckis".to_owned())
}
};
#[macro_export]
macro_rules! doc {
( $( $k:expr => $v: expr),* ) => {
{
let mut doc = Document::new();
$(
doc.insert($k.to_owned(), $v);
)*
doc
}
};
}
#[macro_export]
macro_rules! nested_doc {
( $( $k:expr => $v: expr),* ) => {
Bson::Document(doc!(
$( $k => $v),*
))
}
}
Explicit types still needed
let update = doc! {
"$set" => {
"director" => Bson::String("Robert Zemeckis".to_owned())
}
};
#[macro_export]
macro_rules! add_to_doc {
($doc:expr, $key:expr => ($val:expr)) => {{
$doc.insert($key.to_owned(), $val);
}};
($doc:expr, $key:expr => [$($val:expr),*]) => {{
let vec = vec![$($val),*];
$doc.insert($key.to_owned(), Bson::Array(vec));
}};
($doc:expr, $key:expr => { $($k:expr => $v:tt),* }) => {{
$doc.insert($key.to_owned(), Bson::Document(doc! {
$(
$k => $v
),*
}));
}};
}
#[macro_export]
macro_rules! doc {
( $($key:expr => $val:tt),* ) => {{
let mut document = Document::new();
$(
add_to_doc!(document, $key => $val);
)*
document
}};
}
let doc1 = doc! { "tags" => ["a", "b", "c"] };
let doc2 = doc! { "tags" => ["a", "b", "d"] };
let doc3 = doc! { "tags" => ["d", "e", "f"] };
coll.insert_many(vec![doc1.clone(), doc2.clone(), doc3.clone()], false, None)
.ok().expect("Failed to execute insert_many command.");
// Build aggregation pipeline to unwind tag arrays and group distinct tags
let project = doc! { "$project" => { "tags" => 1 } };
let unwind = doc! { "$unwind" => ("$tags") };
let group = doc! { "$group" => { "_id" => "$tags" } };
#[macro_export]
macro_rules! bson {
([$($val:tt),*]) => {{
let mut array = Vec::new();
$(
array.push(bson!($val));
)*
$crate::Bson::Array(array)
}};
([$val:expr]) => {{
$crate::Bson::Array(vec!(::std::convert::From::from($val)))
}};
({ $($k:expr => $v:tt),* }) => {{
$crate::Bson::Document(doc! {
$(
$k => $v
),*
})
}};
($val:expr) => {{
::std::convert::From::from($val)
}};
}
#[macro_export]
macro_rules! doc {
() => {{ $crate::Document::new() }};
( $($key:expr => $val:tt),* ) => {{
let mut document = $crate::Document::new();
$(
document.insert($key.to_owned(), bson!($val));
)*
document
}};
}
Auto-converted types!
Conversion Traits: Auto-convert types.
Encoder Traits: Auto-encode structs.
Error Traits: Handle errors unobtrusively.
Dereference Traits: Utilize the auto-dereferencing system.
Iterator Traits: Iterate naturally over custom structs.
I/O Traits: Perform I/O ops over custom structs.
Rust makes it easy to auto-convert types.
Encoding traits allow arbitrary structs to be converted.
BSON can be decoded into structs and vice versa without any matching involved.
All errors in rust are handled by Result<T, Err>.
Defining your own Error type is easy!
Unobtrusive error handling and control paths.
To provide concurrency and safety, Rust relies on a number of wrapping structs: Arc, Box, MutexGuard, Ref, etc.
An auto-dereferencing system makes this as transparent and user-friendly as possible.
Iterate with control.
Read and write anything.
...and speed!
bson library
OID generation
stable macro
wire protocol
CRUD, commands, and cursors
bulk writes
connection pooling
error handling
automated testing suites
replica set failover
server discovery and monitoring (SDAM)
SCRAM-SHA-1 auth
shard tagging, indexes, and other server commands