DbImpl

Struct DbImpl 

Source
pub struct DbImpl<Store: StorageData> { /* private fields */ }
Expand description

An instance of the agdb database. To create a database:

use agdb::Db;

let mut db = Db::new("db1.agdb").unwrap();

This will try to create or load the database file path db.agdb. If the file does not exist a new database will be initialized creating the given file. If the file does exist the database will try to load it and memory map the data.

These are the available variants of the database to choose from:

  • Db: [default] File based and memory mapped database.
  • DbFile: File based only (no memory mapping).
  • DbMemory: In-memory database only.
  • DbAny: Database variant that can be any of the other variants.

For each of these there are convenient using declarations, e.g. DbTransaction, DbFileTransaction, DbMemoryTransactionMut etc. in case you need to name the related types of the main database type.

You can execute queries or transactions on the database object with

§Examples

use agdb::{Db, QueryBuilder, DbError};

let mut db = Db::new("db2.agdb").unwrap();

// Insert single node
db.exec_mut(QueryBuilder::insert().nodes().count(1).query()).unwrap();

// Insert single node as a transaction
db.transaction_mut(|t| -> Result<(), DbError> { t.exec_mut(QueryBuilder::insert().nodes().count(1).query())?; Ok(()) }).unwrap();

// Select single database element with id 1
db.exec(QueryBuilder::select().ids(1).query()).unwrap();

// Select single database element with id 1 as a transaction
db.transaction(|t| -> Result<(), DbError> { t.exec(QueryBuilder::select().ids(1).query())?; Ok(()) }).unwrap();

// Search the database starting at element 1
db.exec(QueryBuilder::search().from(1).query()).unwrap();

§Transactions

All queries are transactions. Explicit transactions take closures that are passed the transaction object to record & execute queries. You cannot explicitly commit nor rollback transactions. To commit a transaction simply return Ok from the transaction closure. Conversely to rollback a transaction return Err. Nested transactions are not allowed.

§Multithreading

The agdb is multithreading enabled. It is recommended to use Arc<RwLock>:

use std::sync::{Arc, RwLock};
use agdb::Db;

let db = Arc::new(RwLock::new(Db::new("db3.agdb").unwrap()));
db.read().unwrap(); //for a read lock allowing Db::exec() and Db::transaction()
db.write().unwrap(); //for a write lock allowing additionally Db::exec_mut() and Db::transaction_mut()

Using the database in the multi-threaded environment is then the same as in a single threaded application (minus the locking). Nevertheless while Rust does prevent race conditions you still need to be on a lookout for potential deadlocks. This is one of the reasons why nested transactions are not supported by the agdb.

Akin to the Rust borrow checker rules the agdb can handle unlimited number of concurrent reads (transactional or regular) but only single write operation at any one time. For that reason the transactions are not database states or objects but rather a function taking a closure executing the queries in an attempt to limit their scope as much as possible (and therefore the duration of the [exclusive] lock).

§Storage

The agdb is using a single database file to store all of its data. Additionally a single shadow file with a . prefix of the main database file name is used as a write ahead log (WAL). On drop of the Db object the WAL is processed and removed aborting any unfinished transactions. Furthermore the database data is defragmented.

On load, if the WAL file is present (e.g. due to a crash), it will be processed restoring any consistent state that existed before the crash. Data is only written to the main file if the reverse operation has been committed to the WAL file. The WAL is then purged on commit of a transaction (all queries are transactional even if the transaction is not explicitly used).

Implementations§

Source§

impl<Store: StorageData> DbImpl<Store>

Source

pub fn new(filename: &str) -> Result<Self, DbError>

Tries to create or load filename file as Db object. For in-memory storage this will either load the data from file once (if present) or create an empty database. If used with the DbAny variant the database will be of variant Db (memory mapped). Use DbAny::new_*() to construct the other variants.

Source

pub fn backup(&self, filename: &str) -> Result<(), DbError>

Flushes the underlying file and copies it to filename path. Consider calling optimize_storage() prior to this function to reduce the size of the storage file. If speed is of the essence you may omit that operation at expense of the file size. For memory based storage this will dump the internal buffer to the filename which can be used to restore back the database by DbMemory::new().

Source

pub fn copy(&self, filename: &str) -> Result<Self, DbError>

Copies the database to filename path. Consider calling optimize_storage() prior to this function to reduce the size of the storage file to be copied.

Source

pub fn exec<T: Query>(&self, query: T) -> Result<QueryResult, DbError>

Executes immutable query:

  • Select elements
  • Select values
  • Select keys
  • Select key count
  • Select aliases
  • Select all aliases
  • Search

It runs the query as a transaction and returns either the result or error describing what went wrong (e.g. query error, logic error, data error etc.).

Source

pub fn exec_mut<T: QueryMut>( &mut self, query: T, ) -> Result<QueryResult, DbError>

Executes mutable query:

  • Insert nodes
  • Insert edges
  • Insert aliases
  • Insert values
  • Remove elements
  • Remove aliases
  • Remove values

It runs the query as a transaction and returns either the result or error describing what went wrong (e.g. query error, logic error, data error etc.).

Source

pub fn filename(&self) -> &str

Returns the filename that was used to construct the database.

Source

pub fn optimize_storage(&mut self) -> Result<(), DbError>

Reclaims no longer used segments of the database file by packing all used storage segments together. This operation is done automatically when the database goes out of scope. In long running programs it might be desired to perform the storage file optimization without fully shutting down.

Source

pub fn rename(&mut self, filename: &str) -> Result<(), DbError>

Changes the name of the database changing also the names of the files (if the storage is file based).

Source

pub fn transaction<T, E>( &self, f: impl FnOnce(&Transaction<'_, Store>) -> Result<T, E>, ) -> Result<T, E>

Executes immutable transaction. The transaction is running a closure f that will receive &Transaction object to run exec queries as if run on the main database object. You shall specify the return type T (can be ()) and the error type E that must be constructible from the DbError (E can be DbError).

Read transactions cannot be committed or rolled back but their main function is to ensure that the database data does not change during their duration. Through its generic parameters it also allows transforming the query results into a type T.

Source

pub fn transaction_mut<T, E: From<DbError>>( &mut self, f: impl FnOnce(&mut TransactionMut<'_, Store>) -> Result<T, E>, ) -> Result<T, E>

Executes mutable transaction. The transaction is running a closure f that will receive &mut Transaction to execute exec and exec_mut queries as if run on the main database object. You shall specify the return type T (can be ()) and the error type E that must be constructible from the DbError (E can be DbError).

Write transactions are committed if the closure returns Ok and rolled back if the closure returns Err. If the code panics and the program exits the write ahead log (WAL) makes sure the data in the main database file is restored to a consistent state prior to the transaction.

Typical use case for a write transaction is to insert nodes and edges together. When not using a transaction you could end up only with nodes being inserted.

Through its generic parameters the transaction also allows transforming the query results into a type T.

Source

pub fn size(&self) -> u64

Returns the database size in bytes. Depending on the underlying storage the physical size in hardware might be higher. For example for in-memory storage this function reports used storage but actually allocated memory will likely be higher (expecting database growth in order to prevent too frequent allocations). For file based storages this number will be accurate but the actually used space on disk will be higher up to the next file system block size.

Source§

impl DbImpl<AnyStorage>

Source

pub fn new_file(filename: &str) -> Result<Self, DbError>

Creates a new DbAny instance using DbFile.

Source

pub fn new_mapped(filename: &str) -> Result<Self, DbError>

Creates a new DbAny instance using Db.

Source

pub fn new_memory(filename: &str) -> Result<Self, DbError>

Creates a new DbAny instance using DbMemory.

Trait Implementations§

Source§

impl<Store: StorageData> Debug for DbImpl<Store>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Store: StorageData> Drop for DbImpl<Store>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl<Store> Freeze for DbImpl<Store>
where Store: Freeze,

§

impl<Store> RefUnwindSafe for DbImpl<Store>
where Store: RefUnwindSafe,

§

impl<Store> Send for DbImpl<Store>
where Store: Send,

§

impl<Store> Sync for DbImpl<Store>
where Store: Sync,

§

impl<Store> Unpin for DbImpl<Store>
where Store: Unpin,

§

impl<Store> UnwindSafe for DbImpl<Store>
where Store: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.