1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//! Safe and idiomatic Rust bindings for EJDB, a MongoDB-like embedded database library.
//!
//! ejdb.rs provides an interface for [EJDB], an embeddable JSON-based document database
//! library. Think of it as SQLite-like MongoDB. EJDB attempts to be compatible (to some extent)
//! with MongoDB basic concepts and query language, so if you have any experience with MongoDB,
//! learning EJDB will be easy.
//!
//! EJDB uses BSON internally, just like MongoDB, so ejdb.rs uses [bson-rs] crate, which is
//! reexported as `ejdb::bson` module. Please note that it is important to use types and
//! functions from `ejdb::bson` module, not loading `bson` with `extern crate`, because of
//! possible version incompatibilities. `bson!` macro provided by this crate also uses
//! types from `ejdb::bson`.
//!
//! The central type in this library is `Database` structure. It represents an opened
//! EJDB database. An EJDB database usually consists of several files: the database itself,
//! a file for each collection and a file for each index. Therefore, it makes sense to
//! dedicate a whole directory for EJDB database files. However, `Database::open()` method
//! and its comrades accepts a path to the database file itself, not the directory.
//!
//! ```no_run
//! use ejdb::Database;
//!
//! let db = Database::open("/path/to/db").unwrap();
//! ```
//!
//! `Database`'s `Drop` implementation closes the database automatically according to RAII pattern.
//!
//! The database can be opened in various modes; see `DatabaseOpenMode` structure for
//! more information.
//!
//! After the database is opened, you can obtain collections out of it. It is done
//! primarily with `Database::collection()` method:
//!
//! ```no_run
//! # use ejdb::Database;
//! # let db = Database::open("/path/to/db").unwrap();
//! let coll = db.collection("some_collection").unwrap();
//! ```
//!
//! `Database::collection()` method returns an existing collection or creates a new one
//! with the default options. See `CollectionOptions` structure for more information about
//! which options collections have.
//!
//! A collection may be used to perform queries, initiate transactions or save/load BSON
//! documents by their identifiers directly, without using queries. Collection objects
//! can also be used to manage indices.
//!
//! ## Saving/loading BSON documents
//!
//! You can use `Collection::save()` or `Collection::save_all()` methods to store BSON documents
//! directly into the collection, and `Collection::load()` to load a document by its id:
//!
//! ```no_run
//! # #[macro_use] extern crate ejdb;
//! # use ejdb::Database;
//! # fn main() {
//! # let db = Database::open("/path/to/db").unwrap();
//! # let coll = db.collection("some_collection").unwrap();
//! let mut d = bson! {
//!     "name" => "Foo Bar",
//!     "count" => 10
//! };
//! let inserted_id = coll.save(&d).unwrap();
//!
//! d.insert("_id", inserted_id.clone());
//! let d2 = coll.load(&inserted_id).unwrap().unwrap();
//! assert_eq!(d, d2);
//! # }
//! ```
//!
//! If the `_id` field is not present in the BSON document, it will be generated and added
//! automatically.
//!
//! `Collection::save_all()` method is implemented over `Collection::save()` and returns a
//! special kind of error which contains information about errors for each save operation,
//! if any.
//!
//! ## Performing queries
//!
//! EJDB supports a pretty large subset of operations provided by MongoDB, and even has
//! its own unique queries, like joins.
//!
//! Queries are performed with `Collection::query()` method which accepts, two arguments:
//! anything which can be borrowed into a `Query` and anything which can be borrowed into
//! a `QueryHints`. `Query` is the actual query, i.e. constraints on the data in a collection,
//! and `QueryHints` alter the way the query is processed and returned.
//!
//! Both query and query hints are just BSON documents of [special format][ejdb-ql], therefore
//! ejdb.rs provides the respective `From<bson::Document>`/`Into<bson::Document>` for both
//! `Query` and `QueryHints`; however, it is recommended to use the builder API instead
//! of constructing queries manually because this way it is much harder to create invalid queries.
//! Naturally, invalid queries are by no means unsafe in Rust sense - if such a query is passed
//! for execution, an error will be returned.
//!
//! Query builder API provides two entry points, `ejdb::query::Q` and `ejdb::query::QH`, which
//! are kind of aliases for `Query::new()` and `QueryHints::new()` but look arguably nicer.
//! To run a query, pass an instance of `Query` and `QueryHints` to `Collection::query()` method.
//! The latter returns a `ejdb::PreparedQuery` instance which can be used to execute the query
//! in various ways.
//!
//! ```no_run
//! # #[macro_use] extern crate ejdb;
//! # use ejdb::Database;
//! use ejdb::query::{Q, QH};
//! use ejdb::bson;
//! use ejdb::Result;
//! # fn main() {
//! # let db = Database::open("/path/to/db").unwrap();
//! # let coll = db.collection("some_collection").unwrap();
//!
//! let n = coll.query(Q.field("name").eq("Foo").set("count", 10), QH.empty()).update().unwrap();
//! // `n` is the number of affected rows
//!
//! let names = ["foo", "bar", "baz"];
//! let items = coll.query(Q.field("name").contained_in(names.iter().cloned()), QH.max(12))
//!     .find().unwrap();
//! // `items` is an iterator which contains at maximum 12 records whose `name`
//! // field is either "foo", "bar" or "baz"
//! let items: Result<Vec<bson::Document>> = items.collect();  // collect them into a vector
//!
//! let item = coll.query(Q.field("count").between(-10, 10.2), QH.field("name").include())
//!     .find_one().unwrap();
//! // `item` is an `Option<bson::Document>` which contains a record whose `count` field
//! // is between -10 and 10.2, inclusive, if there is one, and this document will only contain
//! // `name` field.
//!
//! let n = coll.query(Q.field("name").exists(true), QH.empty()).count().unwrap();
//! // `n` is the number of records which contain `name` field
//! # }
//! ```
//!
//! ## Transactions
//!
//! You can use `Collection::begin_transaction()` method which will start a transaction over
//! this collection. Citing the official documentation:
//!
//! > EJDB provides atomic and durable non parallel and read-uncommitted collection level
//! > transactions, i.e., There is only one transaction for collection is active for a single
//! > point in a time. The data written in a transaction is visible for other non transactional
//! > readers. EJDB transaction system utilizes write ahead logging to provide consistent
//! > transaction rollbacks.
//!
//! Transactions in ejdb.rs are implemented with RAII pattern: a transaction is represented
//! by a guard object. When this object is dropped, the transaction is committed or aborted.
//! By default it is aborted; but you can change the default behavior with corresponding methods.
//! Alternatively, you can explicitly commit or abort the transaction with `Transaction::commit()`
//! or `Transaction::abort()`, respectively. Additionally, these methods return a `Result<()>`
//! which can be used to track errors; when the transaction is closed on its drop, the result
//! is ignored.
//!
//! ```no_run
//! # use ejdb::Database;
//! # let db = Database::open("/path/to/db").unwrap();
//! # let coll = db.collection("some_collection").unwrap();
//! loop {
//!     let tx = coll.begin_transaction().unwrap();
//!     // execute queries and other operations
//!     // if some error happens and the loop exits prematurely, e.g. through unwinding,
//!     // the transaction will be aborted automatically
//!
//!     // try to commit the transaction and try again if there is an error
//!     if let Ok(_) = tx.commit() {
//!         break;
//!     }
//! }
//! ```
//!
//! ## Indices
//!
//! It is also possible to use `Collection::index()` method to configure indices in the collection.
//! `index()` accepts the name of the field on which the user needs to configure indices; it
//! returns a builder-like object which can be used to tweak indices on this field.
//!
//! In EJDB a field can have several associated indices of different types, which is important
//! for heterogeneous fields. It is also possible to rebuild and optimize indices. This can
//! be done with the respective methods on `Index` structure returned by `Collection::index()`.
//!
//! ```no_run
//! # use ejdb::Database;
//! # let db = Database::open("/path/to/db").unwrap();
//! # let coll = db.collection("some_collection").unwrap();
//! // create a case-sensitive string index on field `name`
//! coll.index("name").string(true).set().unwrap();
//!
//! // create case-insensitive string and numeric indices on field `title`
//! coll.index("title").number().string(false).set().unwrap();
//!
//! // remove number and array indices from field `items`
//! coll.index("items").number().array().drop().unwrap();
//!
//! // optimize string index on field `name`
//! coll.index("name").string(true).optimize().unwrap();
//!
//! // drop all indices on field `properties`
//! coll.index("properties").drop_all();
//! ```
//!
//! All consuming methods except for `Index::drop_all()` will panic if index type is not
//! specified before their invocation:
//!
//! ```no_run
//! # use ejdb::Database;
//! # let db = Database::open("/path/to/db").unwrap();
//! # let coll = db.collection("some_collection").unwrap();
//! coll.index("name").set();  // will panic
//! ```
//!
//!   [EJDB]: http://ejdb.org/
//!   [bson-rs]: https://crates.io/crates/bson
//!   [ejdb-ql]: http://ejdb.org/doc/ql/ql.html

#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate quick_error;
pub extern crate bson as bson_crate;
pub extern crate ejdb_sys;
extern crate itertools;
extern crate libc;

/// A reexport of `bson` crate used by this crate in public interface.
pub use bson_crate as bson;

pub use database::indices::Index;
pub use database::meta;
pub use database::open_mode::{self, DatabaseOpenMode};
pub use database::query;
pub use database::tx::Transaction;
pub use database::{Collection, CollectionOptions, Database, PreparedQuery, QueryResult};
pub use types::{Error, Result};

#[macro_use]
mod macros;
mod database;
mod utils;

pub mod ejdb_bson;
pub mod types;