use std::rc::Rc;
use std::path::Path;
use polodb_bson::{Document, ObjectId};
use super::error::DbErr;
use crate::Config;
use crate::context::DbContext;
use crate::{DbHandle, TransactionType};
use crate::dump::FullDump;
fn consume_handle_to_vec(handle: &mut DbHandle, result: &mut Vec<Rc<Document>>) -> DbResult<()> {
handle.step()?;
while handle.has_row() {
let doc = handle.get().unwrap_document();
result.push(doc.clone());
handle.step()?;
}
Ok(())
}
pub struct Collection<'a> {
db: &'a mut Database,
id: u32,
meta_version: u32,
name: String,
}
impl<'a> Collection<'a> {
fn new(db: &'a mut Database, id: u32, meta_version: u32, name: &str) -> Collection<'a> {
Collection {
db,
id,
meta_version,
name: name.into(),
}
}
pub fn find(&mut self, query: Option<&Document>) -> DbResult<Vec<Rc<Document>>> {
let mut handle = self.db.ctx.find(self.id, self.meta_version, query)?;
let mut result = Vec::new();
consume_handle_to_vec(&mut handle, &mut result)?;
Ok(result)
}
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn count(&mut self) -> DbResult<u64> {
self.db.ctx.count(self.id, self.meta_version)
}
#[inline]
pub fn update(&mut self, query: Option<&Document>, update: &Document) -> DbResult<usize> {
self.db.ctx.update(self.id, self.meta_version, query, update)
}
#[inline]
pub fn insert(&mut self, doc: &mut Document) -> DbResult<bool> {
self.db.ctx.insert(self.id, self.meta_version, doc)
}
#[inline]
pub fn delete(&mut self, query: Option<&Document>) -> DbResult<usize> {
match query {
Some(query) =>
self.db.ctx.delete(self.id, self.meta_version, query),
None =>
self.db.ctx.delete_all(self.id, self.meta_version),
}
}
#[allow(dead_code)]
fn create_index(&mut self, keys: &Document, options: Option<&Document>) -> DbResult<()> {
self.db.ctx.create_index(self.id, keys, options)
}
}
pub struct Database {
ctx: Box<DbContext>,
}
pub type DbResult<T> = Result<T, DbErr>;
impl Database {
#[inline]
pub fn mk_object_id(&mut self) -> ObjectId {
self.ctx.object_id_maker().mk_object_id()
}
pub fn open<P: AsRef<Path>>(path: P) -> DbResult<Database> {
Database::open_with_config(path, Config::default())
}
pub fn open_with_config<P: AsRef<Path>>(path: P, config: Config) -> DbResult<Database> {
let ctx = DbContext::new(path.as_ref(), config)?;
let rc_ctx = Box::new(ctx);
Ok(Database {
ctx: rc_ctx,
})
}
pub fn create_collection(&mut self, name: &str) -> DbResult<Collection> {
let collection_meta = self.ctx.create_collection(name)?;
Ok(Collection::new(self,
collection_meta.id,
collection_meta.meta_version,
name))
}
#[inline]
pub fn get_version() -> String {
DbContext::get_version()
}
pub fn collection(&mut self, col_name: &str) -> DbResult<Collection> {
let info = match self.ctx.get_collection_meta_by_name(col_name) {
Ok(meta) => meta,
Err(DbErr::CollectionNotFound(_)) => self.ctx.create_collection(col_name)?,
Err(err) => return Err(err),
};
Ok(Collection::new(self, info.id, info.meta_version, col_name))
}
#[inline]
pub fn dump(&mut self) -> DbResult<FullDump> {
self.ctx.dump()
}
#[inline]
pub fn start_transaction(&mut self, ty: Option<TransactionType>) -> DbResult<()> {
self.ctx.start_transaction(ty)
}
#[inline]
pub fn commit(&mut self) -> DbResult<()> {
self.ctx.commit()
}
#[inline]
pub fn rollback(&mut self) -> DbResult<()> {
self.ctx.rollback()
}
#[allow(dead_code)]
pub(crate) fn query_all_meta(&mut self) -> DbResult<Vec<Rc<Document>>> {
self.ctx.query_all_meta()
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use std::env;
use polodb_bson::{Document, Value, mk_document};
use crate::{Database, Config};
use std::borrow::Borrow;
static TEST_SIZE: usize = 1000;
fn prepare_db_with_config(db_name: &str, config: Config) -> Database {
let mut db_path = env::temp_dir();
let mut journal_path = env::temp_dir();
let db_filename = String::from(db_name) + ".db";
let journal_filename = String::from(db_name) + ".db.journal";
db_path.push(db_filename);
journal_path.push(journal_filename);
let _ = std::fs::remove_file(db_path.as_path());
let _ = std::fs::remove_file(journal_path);
Database::open_with_config(db_path.as_path().to_str().unwrap(), config).unwrap()
}
fn prepare_db(db_name: &str) -> Database {
prepare_db_with_config(db_name, Config::default())
}
fn create_and_return_db_with_items(db_name: &str, size: usize) -> Database {
let mut db = prepare_db(db_name);
let mut collection = db.create_collection("test").unwrap();
for i in 0..size {
let content = i.to_string();
let mut new_doc = mk_document! {
"content": content,
};
collection.insert(&mut new_doc).unwrap();
}
db
}
#[test]
fn test_create_collection_and_find_all() {
let mut db = create_and_return_db_with_items("test-collection", TEST_SIZE);
let mut test_collection = db.collection("test").unwrap();
let all = test_collection.find( None).unwrap();
for doc in &all {
println!("object: {}", doc);
}
assert_eq!(TEST_SIZE, all.len())
}
#[test]
fn test_transaction_commit() {
let mut db = prepare_db("test-transaction");
db.start_transaction(None).unwrap();
let mut collection = db.create_collection("test").unwrap();
for i in 0..10{
let content = i.to_string();
let mut new_doc = mk_document! {
"_id": i,
"content": content,
};
collection.insert(&mut new_doc).unwrap();
}
db.commit().unwrap()
}
#[test]
fn test_commit_after_commit() {
let mut config = Config::default();
config.journal_full_size = 1;
let mut db = prepare_db_with_config("test-commit-2", config);
db.start_transaction(None).unwrap();
let mut collection = db.create_collection("test").unwrap();
for i in 0..1000 {
let content = i.to_string();
let mut new_doc = mk_document! {
"_id": i,
"content": content,
};
collection.insert(&mut new_doc).unwrap();
}
db.commit().unwrap();
db.start_transaction(None).unwrap();
let mut collection2 = db.create_collection("test-2").unwrap();
for i in 0..10{
let content = i.to_string();
let mut new_doc = mk_document! {
"_id": i,
"content": content,
};
collection2.insert(&mut new_doc).expect(&*format!("insert failed: {}", i));
}
db.commit().unwrap();
}
#[test]
fn test_rollback() {
let mut db = prepare_db("test-rollback");
let mut collection = db.create_collection("test").unwrap();
assert_eq!(collection.count().unwrap(), 0);
db.start_transaction(None).unwrap();
let mut collection = db.collection("test").unwrap();
for i in 0..10{
let content = i.to_string();
let mut new_doc = mk_document! {
"_id": i,
"content": content,
};
collection.insert(new_doc.as_mut()).unwrap();
}
assert_eq!(collection.count().unwrap(), 10);
db.rollback().unwrap();
let mut collection = db.collection("test").unwrap();
assert_eq!(collection.count().unwrap(), 0);
}
#[test]
fn test_create_collection_with_number_pkey() {
let mut db = {
let mut db = prepare_db("test-number-pkey");
let mut collection = db.create_collection("test").unwrap();
for i in 0..TEST_SIZE {
let content = i.to_string();
let mut new_doc = mk_document! {
"_id": i,
"content": content,
};
collection.insert(new_doc.as_mut()).unwrap();
}
db
};
let mut collection = db.collection("test").unwrap();
let count = collection.count().unwrap();
assert_eq!(TEST_SIZE, count as usize);
let all = collection.find( None).unwrap();
for doc in &all {
println!("object: {}", doc);
}
assert_eq!(TEST_SIZE, all.len())
}
#[test]
fn test_find() {
let mut db = create_and_return_db_with_items("test-find", TEST_SIZE);
let mut collection = db.collection("test").unwrap();
let result = collection.find(
Some(mk_document! {
"content": "3",
}.borrow())
).unwrap();
assert_eq!(result.len(), 1);
let one = result[0].clone();
assert_eq!(one.get("content").unwrap().unwrap_string(), "3");
}
#[test]
fn test_create_collection_and_find_by_pkey() {
let mut db = create_and_return_db_with_items("test-find-pkey", 10);
let mut collection = db.collection("test").unwrap();
let all = collection.find(None).unwrap();
assert_eq!(all.len(), 10);
let first_key = &all[0].pkey_id().unwrap();
let result = collection.find(Some(mk_document! {
"_id": first_key.clone(),
}.borrow())).unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_reopen_db() {
{
let _db1 = create_and_return_db_with_items("test-reopen", 5);
}
{
let mut db_path = env::temp_dir();
db_path.push("test-reopen.db");
let _db2 = Database::open(db_path.as_path().to_str().unwrap()).unwrap();
}
}
#[test]
fn test_pkey_type_check() {
let mut db = create_and_return_db_with_items("test-type-check", TEST_SIZE);
let mut doc = mk_document! {
"_id": 10,
"value": "something",
};
let mut collection = db.collection("test").unwrap();
collection.insert(doc.as_mut()).expect_err("should not success");
}
#[test]
fn test_insert_bigger_key() {
let mut db = prepare_db("test-insert-bigger-key");
let mut collection = db.create_collection("test").unwrap();
let mut doc = Document::new_without_id();
let mut new_str: String = String::new();
for _i in 0..32 {
new_str.push('0');
}
doc.insert("_id".into(), Value::String(new_str.into()));
let _ = collection.insert(doc.as_mut()).unwrap();
}
#[test]
fn test_create_index() {
let mut db = prepare_db("test-create-index");
let mut collection = db.create_collection("test").unwrap();
let keys = mk_document! {
"user_id": 1,
};
collection.create_index(&keys, None).unwrap();
for i in 0..10 {
let str: Rc<str> = i.to_string().into();
let mut data = mk_document! {
"name": str.clone(),
"user_id": str.clone(),
};
collection.insert(data.as_mut()).unwrap();
}
let mut data = mk_document! {
"name": "what",
"user_id": 3,
};
collection.insert(data.as_mut()).expect_err("not comparable");
}
#[test]
fn test_one_delete_item() {
let mut db = prepare_db("test-delete-item");
let mut collection = db.create_collection("test").unwrap();
let mut doc_collection = vec![];
for i in 0..100 {
let content = i.to_string();
let mut new_doc = mk_document! {
"content": content,
};
collection.insert(new_doc.as_mut()).unwrap();
doc_collection.push(new_doc);
}
let third = &doc_collection[3];
let third_key = third.get("_id").unwrap();
let delete_doc = mk_document! {
"_id": third_key.clone(),
};
assert!(collection.delete(Some(&delete_doc)).unwrap() > 0);
assert_eq!(collection.delete(Some(&delete_doc)).unwrap(), 0);
}
#[test]
fn test_delete_all_items() {
let mut db = prepare_db("test-delete-all-items");
let mut collection = db.create_collection("test").unwrap();
let mut doc_collection = vec![];
for i in 0..1000 {
let content = i.to_string();
let mut new_doc = mk_document! {
"_id": i,
"content": content,
};
collection.insert(new_doc.as_mut()).unwrap();
doc_collection.push(new_doc);
}
for doc in &doc_collection {
let key = doc.get("_id").unwrap();
let deleted = collection.delete(Some(&mk_document!{
"_id": key.clone(),
})).unwrap();
assert!(deleted > 0, "delete nothing with key: {}", key);
let find_doc = mk_document! {
"_id": key.clone(),
};
let result = collection.find(Some(&find_doc)).unwrap();
assert_eq!(result.len(), 0, "item with key: {}", key);
}
}
}