From c5d0509549baa72453e40c4691bdca88500c4327 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Sun, 9 Jul 2023 08:51:04 +0200 Subject: [PATCH] [docs] Add code level docs for public types #620 (#633) * update guide * update guide * add reference to main links * rename guide to efficient agdb * Update db_search_handlers.rs * add db docs * wip * update --- .gitignore | 2 + README.md | 6 +- docs/concepts.md | 41 +++ docs/queries.md | 71 ++++- src/agdb/db.rs | 139 ++++++++- src/agdb/db/db_element.rs | 5 + src/agdb/db/db_error.rs | 3 + src/agdb/db/db_float.rs | 6 + src/agdb/db/db_id.rs | 5 + src/agdb/db/db_key.rs | 14 + src/agdb/db/db_key_value.rs | 6 + src/agdb/db/db_value.rs | 27 ++ src/agdb/lib.rs | 20 ++ src/agdb/query.rs | 4 + src/agdb/query/insert_aliases_query.rs | 11 + src/agdb/query/insert_edges_query.rs | 20 ++ src/agdb/query/insert_nodes_query.rs | 16 + src/agdb/query/insert_values_query.rs | 12 + src/agdb/query/query_aliases.rs | 3 + src/agdb/query/query_condition.rs | 89 +++++- src/agdb/query/query_error.rs | 5 + src/agdb/query/query_id.rs | 6 + src/agdb/query/query_ids.rs | 14 +- src/agdb/query/query_result.rs | 10 + src/agdb/query/query_values.rs | 20 +- src/agdb/query/remove_aliases_query.rs | 6 + src/agdb/query/remove_query.rs | 7 + src/agdb/query/remove_values_query.rs | 5 + src/agdb/query/search_query.rs | 28 ++ src/agdb/query/select_aliases_query.rs | 6 + src/agdb/query/select_all_aliases_query.rs | 5 + src/agdb/query/select_key_count_query.rs | 6 + src/agdb/query/select_keys_query.rs | 5 + src/agdb/query/select_query.rs | 5 + src/agdb/query/select_values_query.rs | 6 + src/agdb/query_builder.rs | 69 ++++ src/agdb/query_builder/insert.rs | 61 ++++ src/agdb/query_builder/insert_aliases.rs | 8 + src/agdb/query_builder/insert_edge.rs | 74 ++++- src/agdb/query_builder/insert_nodes.rs | 47 ++- src/agdb/query_builder/insert_values.rs | 6 + src/agdb/query_builder/remove.rs | 23 ++ src/agdb/query_builder/remove_aliases.rs | 3 + src/agdb/query_builder/remove_ids.rs | 3 + src/agdb/query_builder/remove_values.rs | 7 + src/agdb/query_builder/search.rs | 346 ++++++++++++++++++++- src/agdb/query_builder/select.rs | 15 + src/agdb/query_builder/select_aliases.rs | 7 + src/agdb/query_builder/select_ids.rs | 3 + src/agdb/query_builder/select_key_count.rs | 6 + src/agdb/query_builder/select_keys.rs | 6 + src/agdb/query_builder/select_values.rs | 6 + src/agdb/query_builder/where_.rs | 211 ++++++++++++- src/agdb/transaction.rs | 20 +- src/agdb/transaction_mut.rs | 29 +- tests/insert_nodes_test.rs | 19 -- tests/search_test.rs | 51 ++- 57 files changed, 1586 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 088ba6ba..4da9efa6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +db.agdb +.db.agdb \ No newline at end of file diff --git a/README.md b/README.md index 36383a35..04113c0b 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ The Agnesoft Graph Database (aka _agdb_) is persistent memory mapped graph datab # Key Features - Data plotted on a graph -- Typed key-value properties of graph elements (nodes & edges) +- Typed [key-value properties](docs/concepts.md#data-types) attached to graph elements (nodes & edges) - Persistent file based storage - ACID compliant -- Object queries with builder pattern (no text, no query language) +- [Object queries](docs/queries.md) with builder pattern (no text, no query language) - Memory mapped for fast querying - _No dependencies_ @@ -84,7 +84,7 @@ println!("{:?}", user); // ] } ``` -For comprehensive overview of all queries see the [queries](docs/queries.md) reference or continue with more in-depth [efficient agdb](docs/efficient_agdb.md). +For database concepts and **supported data** types see [concepts](docs/concepts.md). For comprehensive overview of all queries see the [queries](docs/queries.md) reference or continue with more in-depth [efficient agdb](docs/efficient_agdb.md). # Roadmap diff --git a/docs/concepts.md b/docs/concepts.md index f89901b3..3426cf00 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -5,6 +5,7 @@ - [Query](#query) - [Transaction](#transaction) - [Storage](#storage) + - [Data types](#data-types) ## Graph @@ -68,7 +69,47 @@ The database durability is provided by the write ahead log (WAL) file which reco Just like the memory the main database file will get fragmented over time. Sectors of the file used for the data that was later reallocated will remain unused (fragmented) until the database file is defragmented. That operation is performed automatically on database object instance drop. +The storage taken by individual elements are properties is generally as follows: + +- node: 32 bytes +- edge: 32 bytes +- single key or value (<=15 bytes): 16 bytes +- single key or value (>15 bytes): 32 bytes (+) +- key-value pair: 32 bytes (+) + +The size of the graph elements (nodes & edges) is fixed. The size of the properties (key-value pairs) is at least 32 bytes (16 per key and 16 per value) but can be greater if the value itself is greater. This creates some inefficiency for small values (e.g. integers) but it also allows application of small value optimization where values up to 15 bytes in size (e.g. strings) do not allocate or take extra space. When a value is larger than 15 bytes it will be stored separately with another 16 bytes overhead making it at least `32 + value length` bytes. + +The reason for values taking 16 bytes at minimum instead of 8 is that the value needs to store a type information for which 1 byte is required. 9 bytes is an awkward and very inefficient (as measured where 16 byte values were much faster) size even if it could save some file space. The next alignment is therefore 16 bytes which also allows the aforementioned small value optimization. + **Terminology:** - File storage (underlying single data file) - Write ahead log (WAL, shadowing file storage to provide durability) + +## Data types + +Supported types of both keys and values are: + +- `i64` +- `u64` +- `f64` +- `String` +- `Vec` +- `Vec` +- `Vec` +- `Vec` +- `Vec` + +It is an enum of limited number of supported types that are universal across all platforms and programming languages. They are serialized in file as follows: + +| Type | Layout | Size | +| ------------- | ------------------------------------------------------------ | -------- | +| `i64` | little endian | 8 bytes | +| `u64` | little endian | 8 bytes | +| `f64` | little endian | 8 bytes | +| `String` | size as `u64` followed by UTF-8 encoded string as `u8` bytes | 8+ bytes | +| `Vec` | size as `u64` followed by individual `u8` bytes | 8+ bytes | +| `Vec` | size as `u64` followed by individual `i64` elements | 8+ bytes | +| `Vec` | size as `u64` followed by individual `u64` elements | 8+ bytes | +| `Vec` | size as `u64` followed by individual `i64` elements | 8+ bytes | +| `Vec` | size as `u64` followed by individual `String` elements | 8+ bytes | diff --git a/docs/queries.md b/docs/queries.md index e144b3ac..fe2a8de1 100644 --- a/docs/queries.md +++ b/docs/queries.md @@ -26,6 +26,11 @@ - [Select all aliases](#select-all-aliases) - [Search](#search) - [Conditions](#conditions) + - [Truth tables](#truth-tables) + - [And](#and) + - [Or](#or) + - [Modifiers](#modifiers) + - [Results](#results) - [Paths](#paths) All interactions with the `agdb` are realized through queries. There are two kinds of queries: @@ -677,9 +682,73 @@ The conditions are applied one at a time to each visited element and chained usi The condition `Distance` and the condition modifiers `Beyond` and `NotBeyond` are particularly important because they can directly influence the search. The former (`Distance`) can limit the depth of the search and can help with constructing more elaborate queries (or sequence thereof) extracting only fine grained elements (e.g. nodes whose edges have particular properties or are connected to other nodes with some properties). The latter (`Beyond` and `NotBeyond`) can limit search to only certain areas of an otherwise larger graph. Its most basic usage would be with condition `ids` to flat out stop the search at certain elements or continue only beyond certain elements. +### Truth tables + +The following information should help with reasoning about the query conditions. Most of it should be intuitive but there are some aspects that might not be obvious especially when combining logic operators and condition modifiers. The search is using the following `enum` when evaluating conditions: + +```Rust +pub enum SearchControl { + Continue(bool), + Finish(bool), + Stop(bool), +} +``` + +The type controls the search and the boolean value controls if the given element should be included in a search result. The `Stop` will prevent the search expanding beyond current element (stopping the search in that direction). `Finish` will immediately exit the search returning accumulated elements (ids) and is only used internally with `offset` and `limit` (NOTE: path search and `order_by` still require complete search regardless of `limit`). + +Each condition contributes to the final control result as follows with the starting/default value being always `Continue(true)`: + +#### And + +| Left | Right | Result | +| -------------- | --------------- | ----------------------- | +| Continue(left) | Continue(right) | Continue(left && right) | +| Continue(left) | Stop(right) | Stop(left && right) | +| Continue(left) | Finish(right) | Finish(left && right) | +| Stop(left) | Stop(right) | Stop(left && right) | +| Stop(left) | Finish(right) | Finish(left && right) | +| Finish(left) | Finish(right) | Finish(left && right) | + +#### Or + +| Left | Right | Result | +| -------------- | --------------- | ------------------------- | +| Continue(left) | Continue(right) | Continue(left \|\| right) | +| Continue(left) | Stop(right) | Continue(left \|\| right) | +| Continue(left) | Finish(right) | Continue(left \|\| right) | +| Stop(left) | Stop(right) | Stop(left \|\| right) | +| Stop(left) | Finish(right) | Stop(left \|\| right) | +| Finish(left) | Finish(right) | Finish(left \|\| right) | + +#### Modifiers + +Modifiers will change the result of a condition based on the control value (the boolean) as follows: + +| Modifier | TRUE | FALSE | +| --------- | ------------------- | ---------------------- | +| None | - | - | +| Beyond | `&& Continue(true)` | `\|\| Stop(false)` | +| Not | `!` | `!` | +| NotBeyond | `&& Stop(true)` | `\|\| Continue(false)` | + +#### Results + +Most conditions result in `Continue(bool)` except for `distance()` and nested `where()` which can also result in `Stop(bool)`: + +| Condition | Continue | Stop | +| ----------- | -------- | ---- | +| Where | YES | YES | +| Edge | YES | NO | +| Node | YES | NO | +| Distance | YES | YES | +| EdgeCount\* | YES | NO | +| Ids | YES | NO | +| Key(Value) | YES | NO | +| Keys | YES | NO | + ### Paths -Path search (`from().to()`) uses A\* algorithm. Every element (node or edge) has a cost of `1` by default. If it passes all the conditions the cost will remain `1` and would be included in the result (if the path it is on would be selected). If it fails any of the conditions its cost will be `2`. This means that the algorithm will prefer paths where elements match the conditions rather than the absolutely shortest path (that can be achieved with no conditions). If the search is not to continue beyond certain element (through `beyond()` or `not_beyond()` conditions) its cost will be `0` and the paths it is on will no longer be considered for that search. +Path search (`from().to()`) uses A\* algorithm. Every element (node or edge) has a cost of `1` by default. If it passes all the conditions (the `SearchControl` value `true`) the cost will remain `1` and would be included in the result (if the path it is on would be selected). If it fails any of the conditions (the `SearchControl` value `false`) its cost will be `2`. This means that the algorithm will prefer paths where elements match the conditions rather than the absolutely shortest path (that can be achieved with no conditions). If the search is not to continue beyond certain element (through `beyond()`, `not_beyond()` or `distance()` conditions) its cost will be `0` and the paths it is on will no longer be considered for that search. --- diff --git a/src/agdb/db.rs b/src/agdb/db.rs index cd10028b..98e7a0a8 100644 --- a/src/agdb/db.rs +++ b/src/agdb/db.rs @@ -84,6 +84,91 @@ impl Serialize for DbStorageIndex { } } +/// An instance of the `agdb` database. To create a database: +/// +/// ``` +/// use agdb::Db; +/// +/// let mut db = Db::new("db.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. +/// +/// You can execute queries or transactions on the database object with +/// +/// - exec() //immutable queries +/// - exec_mut() //mutable queries +/// - transaction() //immutable transactions +/// - transaction_mut() // mutable transaction +/// +/// # Examples +/// +/// ``` +/// use agdb::{Db, QueryBuilder, QueryError}; +/// +/// let mut db = Db::new("db.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<(), QueryError> { 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<(), QueryError> { 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`: +/// +/// ``` +/// use std::sync::{Arc, RwLock}; +/// use agdb::Db; +/// +/// let db = Arc::new(RwLock::new(Db::new("db.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). pub struct Db { storage: Rc>, graph: DbGraph, @@ -94,11 +179,12 @@ pub struct Db { impl std::fmt::Debug for Db { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Db").finish_non_exhaustive() + f.debug_struct("agdb::Db").finish_non_exhaustive() } } impl Db { + /// Tries to create or load `filename` file as `Db` object. pub fn new(filename: &str) -> Result { match Self::try_new(filename) { Ok(db) => Ok(db), @@ -110,20 +196,71 @@ impl Db { } } + /// 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.). pub fn exec(&self, query: &T) -> Result { self.transaction(|transaction| transaction.exec(query)) } + /// 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.). pub fn exec_mut(&mut self, query: &T) -> Result { self.transaction_mut(|transaction| transaction.exec_mut(query)) } + /// 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 `QueryError` + /// (`E` can be `QueryError`). + /// + /// 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`. pub fn transaction(&self, f: impl Fn(&Transaction) -> Result) -> Result { let transaction = Transaction::new(self); f(&transaction) } + /// 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 `QueryError` + /// (`E` can be `QueryError`). + /// + /// 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`. pub fn transaction_mut>( &mut self, f: impl Fn(&mut TransactionMut) -> Result, diff --git a/src/agdb/db/db_element.rs b/src/agdb/db/db_element.rs index 4eb2b5ba..1e68285f 100644 --- a/src/agdb/db/db_element.rs +++ b/src/agdb/db/db_element.rs @@ -1,9 +1,14 @@ use super::db_key_value::DbKeyValue; use crate::DbId; +/// Database element used in `QueryResult` +/// that represents a node or an edge. #[derive(Debug, PartialEq)] pub struct DbElement { + /// Element id. pub id: DbId, + + /// List of key-value pairs associated with the element. pub values: Vec, } diff --git a/src/agdb/db/db_error.rs b/src/agdb/db/db_error.rs index ae567b15..f3eefcd9 100644 --- a/src/agdb/db/db_error.rs +++ b/src/agdb/db/db_error.rs @@ -8,6 +8,9 @@ use std::num::TryFromIntError; use std::panic::Location; use std::string::FromUtf8Error; +/// Universal `agdb` database error. It represents +/// any error caused by the database processing such as +/// loading a database, writing data etc. #[derive(Debug)] pub struct DbError { pub description: String, diff --git a/src/agdb/db/db_float.rs b/src/agdb/db/db_float.rs index fdf0d638..624aa840 100644 --- a/src/agdb/db/db_float.rs +++ b/src/agdb/db/db_float.rs @@ -5,6 +5,12 @@ use std::cmp::Ordering; use std::hash::Hash; use std::hash::Hasher; +/// Database float is a wrapper around `f64` to provide +/// functionality like comparison. The comparison is +/// using `total_cmp` standard library function. See its +/// [docs](https://doc.rust-lang.org/std/primitive.f64.html#method.total_cmp) +/// to understand how it handles NaNs and other edge cases +/// of floating point numbers. #[derive(Clone, Debug)] pub struct DbFloat(f64); diff --git a/src/agdb/db/db_id.rs b/src/agdb/db/db_id.rs index f4cc868a..0b139d28 100644 --- a/src/agdb/db/db_id.rs +++ b/src/agdb/db/db_id.rs @@ -5,6 +5,11 @@ use crate::utilities::serialize::Serialize; use crate::utilities::serialize::SerializeStatic; use crate::utilities::stable_hash::StableHash; +/// Database id is a wrapper around `i64`. +/// The id is an identifier of a database element +/// both nodes and edges. The positive ids represent nodes, +/// negative ids represent edges. The value of `0` is +/// logically invalid (there cannot be element with id 0) and a default. #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)] pub struct DbId(pub i64); diff --git a/src/agdb/db/db_key.rs b/src/agdb/db/db_key.rs index 64b7a36e..4b3fa277 100644 --- a/src/agdb/db/db_key.rs +++ b/src/agdb/db/db_key.rs @@ -1,10 +1,15 @@ use super::db_value::DbValue; +/// Alias to `DbValue` pub type DbKey = DbValue; +/// Ordering for search queries #[derive(Debug, Clone, PartialEq)] pub enum DbKeyOrder { + /// Ascending order (from smallest) Asc(DbKey), + + /// Descending order (from largest) Desc(DbKey), } @@ -17,6 +22,15 @@ mod tests { format!("{:?}", DbKeyOrder::Asc(DbKey::default())); } + #[test] + #[allow(clippy::redundant_clone)] + fn derived_from_clone() { + let order = DbKeyOrder::Asc(1.into()); + let other = order.clone(); + + assert_eq!(order, other); + } + #[test] fn derived_from_partial_eq() { assert_eq!( diff --git a/src/agdb/db/db_key_value.rs b/src/agdb/db/db_key_value.rs index 29383953..da21d323 100644 --- a/src/agdb/db/db_key_value.rs +++ b/src/agdb/db/db_key_value.rs @@ -8,9 +8,15 @@ use crate::utilities::serialize::SerializeStatic; use crate::DbKey; use crate::DbValue; +/// Database key-value pair (aka property) attached to +/// database elements. It can be constructed from a +/// tuple of types that are convertible to `DbValue`. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct DbKeyValue { + /// Key of the property pub key: DbKey, + + /// Value of the property pub value: DbValue, } diff --git a/src/agdb/db/db_value.rs b/src/agdb/db/db_value.rs index 4146bfec..738d04b9 100644 --- a/src/agdb/db/db_value.rs +++ b/src/agdb/db/db_value.rs @@ -8,16 +8,43 @@ use std::fmt::Display; use std::fmt::Formatter; use std::fmt::Result as DisplayResult; +/// Database value is a strongly types value. +/// +/// It is an enum of limited number supported types +/// that are universal across all platforms +/// and programming languages. +/// +/// The value is constructible from large number of +/// raw types or associated types (e.g. i32, &str, etc.). +/// Getting the raw value back as string can be done +/// with `to_string()` but otherwise requires a `match`. #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum DbValue { + /// Byte array, sometimes referred to as blob Bytes(Vec), + + /// 64-bit wide signed integer Int(i64), + + /// 64-bit wide unsigned integer Uint(u64), + + /// 64-bit floating point number Float(DbFloat), + + /// UTF-8 string String(String), + + /// List of 64-bit wide signed integers VecInt(Vec), + + /// List of 64-bit wide unsigned integers VecUint(Vec), + + /// List of 64-bit floating point numbers VecFloat(Vec), + + /// List of UTF-8 strings VecString(Vec), } diff --git a/src/agdb/lib.rs b/src/agdb/lib.rs index e1159b59..0a615e83 100644 --- a/src/agdb/lib.rs +++ b/src/agdb/lib.rs @@ -1,3 +1,23 @@ +//! Persistent embedded memory mapped graph database with native object queries. +//! +//! [Readme](https://github.com/agnesoft/agdb) | +//! [Quickstart](https://github.com/agnesoft/agdb#quickstart) | +//! [Queries](https://github.com/agnesoft/agdb/blob/main/docs/queries.md) | +//! [Efficient agdb](https://github.com/agnesoft/agdb/blob/main/docs/efficient_agdb.md) +//! +//! # Example +//! +//! ``` +//! use agdb::{Db, QueryBuilder}; +//! +//! let mut db = Db::new("db.agdb").unwrap(); +//! db.exec_mut(&QueryBuilder::insert().nodes().values(vec![vec![("key", 123).into()]]).query()).unwrap(); +//! +//! let result = db.exec(&QueryBuilder::select().ids(1).query()).unwrap(); +//! println!("{:?}", result); +//! // QueryResult { result: 1, elements: [ DbElement { id: DbId(1), values: [ DbKeyValue { key: String("key"), value: Int(123) } ] } ] } +//! ``` + mod collections; mod command; mod db; diff --git a/src/agdb/query.rs b/src/agdb/query.rs index 2c5c21be..f5a2da2c 100644 --- a/src/agdb/query.rs +++ b/src/agdb/query.rs @@ -24,10 +24,14 @@ use crate::Db; use crate::QueryError; use crate::QueryResult; +/// Trait for immutable `agdb` database queries. This +/// trait is unlikely to be implementable for user types. pub trait Query { fn process(&self, db: &Db) -> Result; } +/// Trait for mutable `agdb` database queries. This +/// trait is unlikely to be implementable for user types. pub trait QueryMut { fn process(&self, db: &mut Db) -> Result; } diff --git a/src/agdb/query/insert_aliases_query.rs b/src/agdb/query/insert_aliases_query.rs index 9129599d..8f459248 100644 --- a/src/agdb/query/insert_aliases_query.rs +++ b/src/agdb/query/insert_aliases_query.rs @@ -4,8 +4,19 @@ use crate::Db; use crate::QueryError; use crate::QueryResult; +/// Query to insert or update aliases of existing nodes. +/// All `ids` must exist. None of the `aliases` can be empty. +/// If there is an existing alias for any of the elements it +/// will be overwritten with a new one. +/// +/// NOTE: Setting `ids` to a search query will result in an error. +/// +/// The result will contain number of aliases inserted/updated but no elements. pub struct InsertAliasesQuery { + /// Ids to be aliased pub ids: QueryIds, + + /// Aliases to be inserted pub aliases: Vec, } diff --git a/src/agdb/query/insert_edges_query.rs b/src/agdb/query/insert_edges_query.rs index 3b2d609b..b5445b58 100644 --- a/src/agdb/query/insert_edges_query.rs +++ b/src/agdb/query/insert_edges_query.rs @@ -8,10 +8,30 @@ use crate::DbKeyValue; use crate::QueryError; use crate::QueryResult; +/// Query to inserts edges to the database. The `from` +/// and `to` ids must exist in the database. There must be +/// enough `values` for all new edges unless set to `Single` +/// in which case they will be uniformly applied to all new +/// edges. The `each` flag is only useful if `from and `to` are +/// symmetric (same length) but you still want to connect every +/// origin to every destination. By default it would connect only +/// the pairs. For asymmetric inserts `each` is assumed. +/// +/// The result will contain number of edges inserted and elements with +/// their ids but no properties. pub struct InsertEdgesQuery { + /// Origins pub from: QueryIds, + + /// Destinations pub to: QueryIds, + + /// Key value pairs to be associated with + /// the new edges. pub values: QueryValues, + + /// If `true` create an edge between each origin + /// and destination. pub each: bool, } diff --git a/src/agdb/query/insert_nodes_query.rs b/src/agdb/query/insert_nodes_query.rs index 19ae6c57..d111d2ae 100644 --- a/src/agdb/query/insert_nodes_query.rs +++ b/src/agdb/query/insert_nodes_query.rs @@ -5,9 +5,25 @@ use crate::DbElement; use crate::QueryError; use crate::QueryResult; +/// Query to insert nodes to the database. Only one of +/// `count`, `values` or `aliases` need to be given as the +/// implementation will derive the count from the other +/// parameters. If `values` is set to `Single` either `count` +/// or `aliases` must be provided however. If `values` are not +/// set to `Single` there must be enough value for `count/aliases` +/// unless they are not se and the count is derived from `values. +/// +/// The result will contain number of nodes inserted and elements with +/// their ids but no properties. pub struct InsertNodesQuery { + /// Number of nodes to be inserted. pub count: u64, + + /// Key value pairs to be associated with + /// the new nodes. pub values: QueryValues, + + /// Aliases of the new nodes. pub aliases: Vec, } diff --git a/src/agdb/query/insert_values_query.rs b/src/agdb/query/insert_values_query.rs index 68aaf707..b72da37d 100644 --- a/src/agdb/query/insert_values_query.rs +++ b/src/agdb/query/insert_values_query.rs @@ -5,8 +5,20 @@ use crate::QueryError; use crate::QueryMut; use crate::QueryResult; +/// Query to insert or update key-value pairs (properties) +/// to existing elements in the database. All `ids` must exist +/// in the database. If `values` is set to `Single` the properties +/// will be inserted uniformly to all `ids` otherwise there must be +/// enough `values` for all `ids`. +/// +/// The result will be number of inserted/update values and no elements. +/// +/// NOTE: The result is NOT number of affected elements but individual properties. pub struct InsertValuesQuery { + /// Ids whose properties should be updated pub ids: QueryIds, + + /// Key value pairs to be inserted to the existing elements. pub values: QueryValues, } diff --git a/src/agdb/query/query_aliases.rs b/src/agdb/query/query_aliases.rs index 699f3a5e..3e7353de 100644 --- a/src/agdb/query/query_aliases.rs +++ b/src/agdb/query/query_aliases.rs @@ -1,3 +1,6 @@ +/// Wrapper around `Vec` to provide +/// several convenient conversions for the +/// `QueryBuilder`. pub struct QueryAliases(pub Vec); impl From> for QueryAliases { diff --git a/src/agdb/query/query_condition.rs b/src/agdb/query/query_condition.rs index 95798d60..09712a2b 100644 --- a/src/agdb/query/query_condition.rs +++ b/src/agdb/query/query_condition.rs @@ -3,58 +3,143 @@ use crate::graph_search::SearchControl; use crate::DbKey; use crate::DbValue; +/// Logical operator for query conditions #[derive(Clone, Copy, Debug, PartialEq)] pub enum QueryConditionLogic { + /// Logical AND (&&) And, + + /// Logical Or (||) Or, } +/// Query condition modifier #[derive(Clone, Copy, Debug, PartialEq)] pub enum QueryConditionModifier { + /// No modifier None, + + /// Continues the search beyond the current element + /// if the condition being modified passes. + Beyond, + + /// Reversal of the result (equivalent to `!`). Not, + + /// Stops the search beyond the current element + /// if the condition being modified passes. NotBeyond, - Beyond, } +/// Query condition data #[derive(Debug, Clone, PartialEq)] pub enum QueryConditionData { + /// Distance from the search origin. Takes count comparison + /// (e.g. Equal, GreaterThan). Distance(CountComparison), + + /// Is the current element an edge? I.e. `id < 0`. Edge, + + /// Tests number of edges (from+to) of the current element. + /// Only nodes will pass. Self-referential edges are + /// counted twice. Takes count comparison + /// (e.g. Equal, GreaterThan). EdgeCount(CountComparison), + + /// Tests the number of outgoing edges (from) of the + /// current element. Takes count comparison + /// (e.g. Equal, GreaterThan). EdgeCountFrom(CountComparison), + + /// Tests the number of incoming edges (to) of the + /// current element. Takes count comparison + /// (e.g. Equal, GreaterThan). EdgeCountTo(CountComparison), + + /// Tests if the current id is in the list of ids. Ids(Vec), - KeyValue { key: DbKey, value: Comparison }, + + /// Tests if the current element has a property `key` + /// with a value that evaluates true against `comparison`. + KeyValue { + /// Property key + key: DbKey, + + /// Comparison operator (e.g. Equal, GreaterThan etc.) + value: Comparison, + }, + + /// Test if the current element has **all** of the keys listed. Keys(Vec), + + /// Is the current element a node? I.e. `0 < id`. Node, + + /// Nested list of conditions (equivalent to brackets). Where(Vec), } +/// Query condition. The condition consists of +/// `data`, logic operator and a modifier. #[derive(Debug, Clone, PartialEq)] pub struct QueryCondition { + /// Logic operator (e.g. And, Or) pub logic: QueryConditionLogic, + + /// Condition modifier (e.g. None, Beyond, Not, NotBeyond) pub modifier: QueryConditionModifier, + + /// Condition data (or type) defining what type + /// of validation is to be performed. pub data: QueryConditionData, } +/// Comparison of unsigned integers (`u64`) used +/// by `distance()` and `edge_count*()` conditions. Supports +/// the usual set of named comparisons: `==, !=, <, <=, >, =>`. #[derive(Debug, Clone, PartialEq)] pub enum CountComparison { + /// property == this Equal(u64), + + /// property > this GreaterThan(u64), + + /// property >= this GreaterThanOrEqual(u64), + + /// property < this LessThan(u64), + + /// property <= this LessThanOrEqual(u64), + + /// property != this NotEqual(u64), } +/// Comparison of database values (`DbValue`) used +/// by `key()` condition. Supports +/// the usual set of named comparisons: `==, !=, <, <=, >, =>`. #[derive(Debug, Clone, PartialEq)] pub enum Comparison { + /// property == this Equal(DbValue), + + /// property > this GreaterThan(DbValue), + + /// property >= this GreaterThanOrEqual(DbValue), + + /// property < this LessThan(DbValue), + + /// property <= this LessThanOrEqual(DbValue), + + /// property != this NotEqual(DbValue), } diff --git a/src/agdb/query/query_error.rs b/src/agdb/query/query_error.rs index 11d4b6ee..a4dfee51 100644 --- a/src/agdb/query/query_error.rs +++ b/src/agdb/query/query_error.rs @@ -1,6 +1,11 @@ use crate::db::db_error::DbError; use std::sync::PoisonError; +/// Universal `query` error returned from all query operations. +/// It represents mainly errors from executing queries but the +/// cause of the error may be in exceptional cases a `DbError`. +/// Typically however it will contain description of a problem with +/// running a query such as "id/alias does not exist". #[derive(Default, Debug, PartialEq)] pub struct QueryError { pub description: String, diff --git a/src/agdb/query/query_id.rs b/src/agdb/query/query_id.rs index c9698294..d57d260c 100644 --- a/src/agdb/query/query_id.rs +++ b/src/agdb/query/query_id.rs @@ -1,8 +1,14 @@ use crate::DbId; +/// Database id used in queries that lets +/// you refer to a database element as numerical +/// id or a string alias. #[derive(Debug, Clone, PartialEq)] pub enum QueryId { + /// Numerical id as `DbId` Id(DbId), + + /// Alias Alias(String), } diff --git a/src/agdb/query/query_ids.rs b/src/agdb/query/query_ids.rs index 27a4e2aa..137e2d6b 100644 --- a/src/agdb/query/query_ids.rs +++ b/src/agdb/query/query_ids.rs @@ -1,10 +1,20 @@ use super::query_id::QueryId; use super::search_query::SearchQuery; -use crate::{DbId, QueryResult}; - +use crate::DbId; +use crate::QueryResult; + +/// List of database ids used in queries. It +/// can either represent a list of `QueryId`s +/// or a search query. Search query allows query +/// nesting and sourcing the ids dynamically for +/// another query most commonly with the +/// select queries. #[derive(Debug, Clone, PartialEq)] pub enum QueryIds { + /// List of `QueryId`s Ids(Vec), + + /// Search query Search(SearchQuery), } diff --git a/src/agdb/query/query_result.rs b/src/agdb/query/query_result.rs index 1ab4d99f..08caf9c9 100644 --- a/src/agdb/query/query_result.rs +++ b/src/agdb/query/query_result.rs @@ -1,9 +1,19 @@ use crate::db::db_element::DbElement; use crate::DbId; +/// Universal database result. Successful +/// execution of a query will always yield +/// this type. The `result` field is a numerical +/// representation of the result while the +/// `elements` are the list of `DbElement`s +/// with database ids and properties (key-value pairs). #[derive(Debug, Default)] pub struct QueryResult { + /// Query result pub result: i64, + + /// List of elements yielded by the query + /// possibly with a list of properties. pub elements: Vec, } diff --git a/src/agdb/query/query_values.rs b/src/agdb/query/query_values.rs index 2d58de9e..453a3950 100644 --- a/src/agdb/query/query_values.rs +++ b/src/agdb/query/query_values.rs @@ -1,13 +1,31 @@ -use crate::{db::db_key_value::DbKeyValue, DbKey}; +use crate::db::db_key_value::DbKeyValue; +use crate::DbKey; +/// Helper type distinguishing uniform (`Single`) values +/// and multiple (`Multi`) values in database queries. pub enum QueryValues { + /// Single list of properties (key-value pairs) + /// to be applied to all elements in a query. Single(Vec), + + /// List of lists of properties (key-value pairs) + /// to be applied to all elements in a query. There + /// must be as many lists of properties as ids + /// in a query. Multi(Vec>), } +/// Convenient wrapper for the `QueryBuilder` to +/// allow properties conversions. Represents `QueryValues::Single`. pub struct SingleValues(pub Vec); + +/// Convenient wrapper for the `QueryBuilder` to +/// allow properties conversions. Represents `QueryValues::Multi`. pub struct MultiValues(pub Vec>); +/// Convenient wrapper for the `QueryBuilder` to +/// allow properties conversions. Represents list +/// of property keys. pub struct QueryKeys(pub Vec); impl From> for SingleValues { diff --git a/src/agdb/query/remove_aliases_query.rs b/src/agdb/query/remove_aliases_query.rs index 5ec056bf..4fe447b5 100644 --- a/src/agdb/query/remove_aliases_query.rs +++ b/src/agdb/query/remove_aliases_query.rs @@ -3,6 +3,12 @@ use crate::Db; use crate::QueryError; use crate::QueryResult; +/// Query to remove aliases from the database. It +/// is not an error if an alias to be removed already +/// does not exist. +/// +/// The result will be a negative number signifying how +/// many aliases have been actually removed. pub struct RemoveAliasesQuery(pub Vec); impl QueryMut for RemoveAliasesQuery { diff --git a/src/agdb/query/remove_query.rs b/src/agdb/query/remove_query.rs index 239f01f0..f890eaf3 100644 --- a/src/agdb/query/remove_query.rs +++ b/src/agdb/query/remove_query.rs @@ -4,6 +4,13 @@ use crate::Db; use crate::QueryError; use crate::QueryResult; +/// Query to remove database elements (nodes & edges). It +/// is not an error if any of the `ids` do not already exist. +/// +/// All properties associated with a given element are also removed. +/// +/// If removing nodes all of its incoming and outgoing edges are +/// also removed along with their properties. pub struct RemoveQuery(pub QueryIds); impl QueryMut for RemoveQuery { diff --git a/src/agdb/query/remove_values_query.rs b/src/agdb/query/remove_values_query.rs index e94eec5c..7dc2a42f 100644 --- a/src/agdb/query/remove_values_query.rs +++ b/src/agdb/query/remove_values_query.rs @@ -5,6 +5,11 @@ use crate::QueryError; use crate::QueryMut; use crate::QueryResult; +/// Query to remove properties from existing elements +/// in the database. All of the specified `ids` must +/// exist in the database however they do not need to have +/// all the listed keys (it is NOT an error if any or all keys +/// do not exist on any of the elements). pub struct RemoveValuesQuery(pub SelectValuesQuery); impl QueryMut for RemoveValuesQuery { diff --git a/src/agdb/query/search_query.rs b/src/agdb/query/search_query.rs index 68ae7e57..41b07032 100644 --- a/src/agdb/query/search_query.rs +++ b/src/agdb/query/search_query.rs @@ -10,20 +10,48 @@ use crate::QueryError; use crate::QueryResult; use std::cmp::Ordering; +/// Search algorithm to be used #[derive(Debug, Clone, Copy, PartialEq)] pub enum SearchQueryAlgorithm { + /// Examines each distance level from the search origin in full + /// before continuing with the next level. E.g. when starting at + /// a node it first examines all the edges and then nodes they lead + /// to. BreadthFirst, + + /// Examines maximum distance it can reach following every element. + /// E.g. when starting at anode it will go `edge -> node -> edge -> node` + /// until it reaches dead end or encounters already visited element. DepthFirst, } +/// Query to search for ids in the database following the graph. #[derive(Debug, Clone, PartialEq)] pub struct SearchQuery { + /// Search algorithm to be used. Will be bypassed for path + /// searches that unconditionally use A*. pub algorithm: SearchQueryAlgorithm, + + /// Starting element of the search. pub origin: QueryId, + + /// Target element of the path search (if origin is specified) + /// or starting element of the reverse search (if origin is not specified). pub destination: QueryId, + + /// How many elements maximum to return. pub limit: u64, + + /// How many elements that would be returned should be + /// skipped in the result. pub offset: u64, + + /// Order of the elements in the result. The sorting happens before + /// `offset` and `limit` are applied. pub order_by: Vec, + + /// Set of conditions every element must satisfy to be included in the + /// result. Some conditions also influence the search path as well. pub conditions: Vec, } diff --git a/src/agdb/query/select_aliases_query.rs b/src/agdb/query/select_aliases_query.rs index 3dabbce7..cb987979 100644 --- a/src/agdb/query/select_aliases_query.rs +++ b/src/agdb/query/select_aliases_query.rs @@ -6,6 +6,12 @@ use crate::Query; use crate::QueryError; use crate::QueryResult; +/// Query to select aliases of given ids. All of the ids +/// must exist in the database and have an alias. +/// +/// The result will be number of returned aliases and list +/// of elements with a single property `String("alias")` holding +/// the value `String`. pub struct SelectAliasesQuery(pub QueryIds); impl Query for SelectAliasesQuery { diff --git a/src/agdb/query/select_all_aliases_query.rs b/src/agdb/query/select_all_aliases_query.rs index 42f69122..c07d06b2 100644 --- a/src/agdb/query/select_all_aliases_query.rs +++ b/src/agdb/query/select_all_aliases_query.rs @@ -4,6 +4,11 @@ use crate::Query; use crate::QueryError; use crate::QueryResult; +/// Query to select all aliases in the database. +/// +/// The result will be number of returned aliases and list +/// of elements with a single property `String("alias")` holding +/// the value `String`. pub struct SelectAllAliases {} impl Query for SelectAllAliases { diff --git a/src/agdb/query/select_key_count_query.rs b/src/agdb/query/select_key_count_query.rs index ddd6c4bd..363cbfe9 100644 --- a/src/agdb/query/select_key_count_query.rs +++ b/src/agdb/query/select_key_count_query.rs @@ -5,6 +5,12 @@ use crate::Query; use crate::QueryError; use crate::QueryResult; +/// Query to select number of properties (key count) of +/// given ids. All of the ids must exist in the database. +/// +/// The result will be number of elements returned and the list +/// of elements with a single property `String("key_count")` with +/// a value `u64`. pub struct SelectKeyCountQuery(pub QueryIds); impl Query for SelectKeyCountQuery { diff --git a/src/agdb/query/select_keys_query.rs b/src/agdb/query/select_keys_query.rs index daea12c5..12ae2cb1 100644 --- a/src/agdb/query/select_keys_query.rs +++ b/src/agdb/query/select_keys_query.rs @@ -5,6 +5,11 @@ use crate::Query; use crate::QueryError; use crate::QueryResult; +/// Query to select only property keys of given ids. All +/// of the ids must exist in the database. +/// +/// The result will be number of elements returned and the list +/// of elements with all properties except all values will be empty. pub struct SelectKeysQuery(pub QueryIds); impl Query for SelectKeysQuery { diff --git a/src/agdb/query/select_query.rs b/src/agdb/query/select_query.rs index c30a3f1e..454b6032 100644 --- a/src/agdb/query/select_query.rs +++ b/src/agdb/query/select_query.rs @@ -5,6 +5,11 @@ use crate::Query; use crate::QueryError; use crate::QueryResult; +/// Query to select elements with all properties of +/// given ids. All ids must exist in the database. +/// +/// The result will be number of elements and the +/// list of elements with all properties. pub struct SelectQuery(pub QueryIds); impl Query for SelectQuery { diff --git a/src/agdb/query/select_values_query.rs b/src/agdb/query/select_values_query.rs index 4f29f9ac..391a4928 100644 --- a/src/agdb/query/select_values_query.rs +++ b/src/agdb/query/select_values_query.rs @@ -6,6 +6,12 @@ use crate::Query; use crate::QueryError; use crate::QueryResult; +/// Query to select elements with only certain properties of +/// given ids. All ids must exist in the database and all +/// of them must have the requested properties. +/// +/// The result will be number of elements and the +/// list of elements with the requested properties. pub struct SelectValuesQuery { pub keys: Vec, pub ids: QueryIds, diff --git a/src/agdb/query_builder.rs b/src/agdb/query_builder.rs index 7874d9f4..0c25dd14 100644 --- a/src/agdb/query_builder.rs +++ b/src/agdb/query_builder.rs @@ -21,21 +21,90 @@ use self::remove::Remove; use self::search::Search; use self::select::Select; +/// The starting point of all queries. +/// +/// Options: +/// +/// ``` +/// use agdb::QueryBuilder; +/// +/// QueryBuilder::insert(); +/// QueryBuilder::remove(); +/// QueryBuilder::search(); +/// QueryBuilder::select(); +/// ``` pub struct QueryBuilder {} impl QueryBuilder { + /// Allows inserting data into the database: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().nodes(); + /// QueryBuilder::insert().edges(); + /// QueryBuilder::insert().aliases("a"); + /// QueryBuilder::insert().aliases(vec!["a", "b"]); + /// QueryBuilder::insert().values(vec![vec![("k", 1).into()]]); + /// QueryBuilder::insert().values_uniform(vec![("k", 1).into()]); + /// ``` pub fn insert() -> Insert { Insert {} } + /// Allows removing data from the database: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::remove().ids(1); + /// QueryBuilder::remove().ids(vec![1, 2]); + /// QueryBuilder::remove().ids(QueryBuilder::search().from(1).query()); + /// QueryBuilder::remove().aliases("a"); + /// QueryBuilder::remove().aliases(vec!["a", "b"]); + /// QueryBuilder::remove().values(vec!["k".into()]); + /// ``` pub fn remove() -> Remove { Remove {} } + /// Search teh database by traversing the graph + /// and returns element ids using breadth first, + /// depth first or A* algorithm: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::search().from(1); // BDS + /// QueryBuilder::search().to(1); // BDS + /// QueryBuilder::search().breadth_first(); + /// QueryBuilder::search().depth_first(); + /// ``` pub fn search() -> Search { Search {} } + /// Selects data from the database: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::select().ids(1); + /// QueryBuilder::select().ids(vec![1, 2]); + /// QueryBuilder::select().ids(QueryBuilder::search().from(1).query()); + /// QueryBuilder::select().aliases(); + /// QueryBuilder::select().keys(); + /// QueryBuilder::select().key_count(); + /// QueryBuilder::select().values(vec!["k".into()]); + /// ``` pub fn select() -> Select { Select {} } diff --git a/src/agdb/query_builder/insert.rs b/src/agdb/query_builder/insert.rs index dd3bb9df..763a70d4 100644 --- a/src/agdb/query_builder/insert.rs +++ b/src/agdb/query_builder/insert.rs @@ -12,9 +12,21 @@ use crate::query::query_values::MultiValues; use crate::query::query_values::QueryValues; use crate::query::query_values::SingleValues; +/// Insert builder for inserting various data +/// into the database. pub struct Insert {} impl Insert { + /// Inserts aliases `names` into the database: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().aliases("a").ids(1); + /// QueryBuilder::insert().aliases(vec!["a", "b"]).ids(vec![1, 2]); + /// ``` pub fn aliases>(self, names: T) -> InsertAliases { InsertAliases(InsertAliasesQuery { ids: QueryIds::Ids(vec![]), @@ -22,6 +34,17 @@ impl Insert { }) } + /// Inserts edges into the database: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().edges().from(1); + /// QueryBuilder::insert().edges().from(vec![1, 2]); + /// QueryBuilder::insert().edges().from(QueryBuilder::search().from(1).query()); + /// ``` pub fn edges(self) -> InsertEdges { InsertEdges(InsertEdgesQuery { from: QueryIds::Ids(vec![]), @@ -31,6 +54,18 @@ impl Insert { }) } + /// Inserts nodes into the database: + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().nodes().count(1); + /// QueryBuilder::insert().nodes().aliases("a"); + /// QueryBuilder::insert().nodes().aliases(vec!["a", "b"]); + /// QueryBuilder::insert().nodes().values(vec![vec![("k", 1).into()]]); + /// ``` pub fn nodes(self) -> InsertNodes { InsertNodes(InsertNodesQuery { count: 0, @@ -39,6 +74,19 @@ impl Insert { }) } + /// Inserts or updates list of lists `key_values` into the database. + /// Each item in the list represents a list of key-value pairs to + /// be inserted into database elements identified by ids in the next step. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().values(vec![vec![("k", 1).into()]]).ids(1); + /// QueryBuilder::insert().values(vec![vec![("k", 1).into()], vec![("k", 2).into()]]).ids(vec![1, 2]); + /// QueryBuilder::insert().values(vec![vec![("k", 1).into()]]).ids(QueryBuilder::search().from(1).query()); + /// ``` pub fn values>(self, key_values: T) -> InsertValues { InsertValues(InsertValuesQuery { ids: QueryIds::Ids(vec![]), @@ -46,6 +94,19 @@ impl Insert { }) } + /// Inserts or updates list of `key_values` into the database. + /// The list represents a list of key-value pairs to be inserted + /// into every database elements identified by ids in the next step. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().values_uniform(vec![("k", 1).into()]).ids(1); + /// QueryBuilder::insert().values_uniform(vec![("k", 1).into()]).ids(vec![1, 2]); + /// QueryBuilder::insert().values_uniform(vec![("k", 1).into()]).ids(QueryBuilder::search().from(1).query()); + /// ``` pub fn values_uniform>(self, key_values: T) -> InsertValues { InsertValues(InsertValuesQuery { ids: QueryIds::Ids(vec![0.into()]), diff --git a/src/agdb/query_builder/insert_aliases.rs b/src/agdb/query_builder/insert_aliases.rs index 2e58dec7..62c07589 100644 --- a/src/agdb/query_builder/insert_aliases.rs +++ b/src/agdb/query_builder/insert_aliases.rs @@ -1,11 +1,18 @@ use crate::query::insert_aliases_query::InsertAliasesQuery; use crate::query::query_ids::QueryIds; +/// Insert aliases builder to select `ids` +/// of the aliases. pub struct InsertAliases(pub InsertAliasesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct InsertAliasesIds(pub InsertAliasesQuery); impl InsertAliases { + /// Ids of the db elements to be aliased. Only nodes can be aliased + /// (positive ids) and the ids must exist in the database. NOTE: Search + /// query in place of ids is not allowed and will be ignored if used. pub fn ids>(mut self, ids: T) -> InsertAliasesIds { self.0.ids = ids.into(); @@ -14,6 +21,7 @@ impl InsertAliases { } impl InsertAliasesIds { + /// Returns the built `InsertAliasesQuery` object. pub fn query(self) -> InsertAliasesQuery { self.0 } diff --git a/src/agdb/query_builder/insert_edge.rs b/src/agdb/query_builder/insert_edge.rs index 1bd023ab..1f8e5c59 100644 --- a/src/agdb/query_builder/insert_edge.rs +++ b/src/agdb/query_builder/insert_edge.rs @@ -4,27 +4,59 @@ use crate::query::query_values::MultiValues; use crate::query::query_values::QueryValues; use crate::query::query_values::SingleValues; -pub struct InsertEdgesEach(pub InsertEdgesQuery); - +/// Insert edges builder that lets you add `from` +/// (origin) nodes. pub struct InsertEdges(pub InsertEdgesQuery); +/// Insert edges builder that lets you add values. +pub struct InsertEdgesEach(pub InsertEdgesQuery); + +/// Insert edges builder that lets you add `to` +/// (destination) nodes. pub struct InsertEdgesFrom(pub InsertEdgesQuery); +/// Insert edges builder that lets you add values +/// or set `each`. pub struct InsertEdgesFromTo(pub InsertEdgesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct InsertEdgesValues(pub InsertEdgesQuery); +impl InsertEdges { + /// An id or list of ids or search query from where the edges should come from (origin). + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().edges().from(1).to(2); + /// QueryBuilder::insert().edges().from(1).to(vec![2, 3]); + /// QueryBuilder::insert().edges().from(1).to(QueryBuilder::search().from(1).query()); + /// ``` + pub fn from>(mut self, ids: T) -> InsertEdgesFrom { + self.0.from = ids.into(); + + InsertEdgesFrom(self.0) + } +} + impl InsertEdgesEach { + /// Returns the built `InsertEdgesQuery` object. pub fn query(self) -> InsertEdgesQuery { self.0 } + /// List of lists of `key_values` to be inserted into the edges. There must be exactly + /// as many lists as the number of created edges. pub fn values>(mut self, key_values: T) -> InsertEdgesValues { self.0.values = QueryValues::Multi(Into::::into(key_values).0); InsertEdgesValues(self.0) } + /// List of `key_values` to be inserted into all created edges. pub fn values_uniform>(mut self, key_values: T) -> InsertEdgesValues { self.0.values = QueryValues::Single(Into::::into(key_values).0); @@ -32,15 +64,19 @@ impl InsertEdgesEach { } } -impl InsertEdges { - pub fn from>(mut self, ids: T) -> InsertEdgesFrom { - self.0.from = ids.into(); - - InsertEdgesFrom(self.0) - } -} - impl InsertEdgesFrom { + /// An id or list of ids or search query to where the edges should go (destination). + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().edges().from(1).to(2).query(); + /// QueryBuilder::insert().edges().from(1).to(2).each(); + /// QueryBuilder::insert().edges().from(1).to(2).values(vec![vec![("k", 1).into()]]); + /// QueryBuilder::insert().edges().from(1).to(2).values_uniform(vec![("k", 1).into()]); + /// ``` pub fn to>(mut self, ids: T) -> InsertEdgesFromTo { self.0.to = ids.into(); @@ -49,22 +85,39 @@ impl InsertEdgesFrom { } impl InsertEdgesFromTo { + /// A modifier to create edges from each origin (from) to each destination (to) + /// even if the number of origins and destinations is the same. This modifier is assumed + /// and thus not needed if they are already asymmetric. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().edges().from(1).to(2).each().query(); + /// QueryBuilder::insert().edges().from(1).to(2).each().values(vec![vec![("k", 1).into()]]); + /// QueryBuilder::insert().edges().from(1).to(2).each().values_uniform(vec![("k", 1).into()]); + /// ``` pub fn each(mut self) -> InsertEdgesEach { self.0.each = true; InsertEdgesEach(self.0) } + /// Returns the built `InsertEdgesQuery` object. pub fn query(self) -> InsertEdgesQuery { self.0 } + /// List of lists of `key_values` to be inserted into the edges. There must be exactly + /// as many lists as the number of created edges. pub fn values>(mut self, key_values: T) -> InsertEdgesValues { self.0.values = QueryValues::Multi(Into::::into(key_values).0); InsertEdgesValues(self.0) } + /// List of `key_values` to be inserted into all created edges. pub fn values_uniform>(mut self, key_values: T) -> InsertEdgesValues { self.0.values = QueryValues::Single(Into::::into(key_values).0); @@ -73,6 +126,7 @@ impl InsertEdgesFromTo { } impl InsertEdgesValues { + /// Returns the built `InsertEdgesQuery` object. pub fn query(self) -> InsertEdgesQuery { self.0 } diff --git a/src/agdb/query_builder/insert_nodes.rs b/src/agdb/query_builder/insert_nodes.rs index 8d3404f2..a40bbd67 100644 --- a/src/agdb/query_builder/insert_nodes.rs +++ b/src/agdb/query_builder/insert_nodes.rs @@ -1,26 +1,38 @@ use crate::query::insert_nodes_query::InsertNodesQuery; use crate::query::query_aliases::QueryAliases; -use crate::query::query_values::{MultiValues, QueryValues, SingleValues}; +use crate::query::query_values::MultiValues; +use crate::query::query_values::QueryValues; +use crate::query::query_values::SingleValues; +/// Insert nodes builder to add aliases or count +/// or values. pub struct InsertNodes(pub InsertNodesQuery); +/// Insert nodes builder to add values. pub struct InsertNodesAliases(pub InsertNodesQuery); +/// Insert nodes builder to add uniform values. pub struct InsertNodesCount(pub InsertNodesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct InsertNodesValues(pub InsertNodesQuery); impl InsertNodesAliases { + /// Returns the built `InsertNodesQuery` object. pub fn query(self) -> InsertNodesQuery { self.0 } + /// List of lists of `key_values` to be inserted into the aliased nodes. + /// The number of lists mut be the same as number of aliases. pub fn values>(mut self, key_values: T) -> InsertNodesValues { self.0.values = QueryValues::Multi(Into::::into(key_values).0); InsertNodesValues(self.0) } + /// List of `key_values` to be inserted into the all nodes that are being created. pub fn values_uniform>(mut self, key_values: T) -> InsertNodesValues { self.0.values = QueryValues::Single(Into::::into(key_values).0); @@ -29,10 +41,12 @@ impl InsertNodesAliases { } impl InsertNodesCount { + /// Returns the built `InsertNodesQuery` object. pub fn query(self) -> InsertNodesQuery { self.0 } + /// List of `key_values` to be inserted into the all nodes that are being created. pub fn values_uniform>(mut self, key_values: T) -> InsertNodesValues { self.0.values = QueryValues::Single(Into::::into(key_values).0); @@ -41,33 +55,52 @@ impl InsertNodesCount { } impl InsertNodesValues { + /// Returns the built `InsertNodesQuery` object. pub fn query(self) -> InsertNodesQuery { self.0 } } impl InsertNodes { + /// A list of `names` of the inserted nodes that will work as aliases + /// instead of the numerical ids. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().nodes().aliases("a").query(); + /// QueryBuilder::insert().nodes().aliases("a").values(vec![vec![("k", 1).into()]]); + /// QueryBuilder::insert().nodes().aliases("a").values_uniform(vec![("k", 1).into()]); + /// ``` pub fn aliases>(mut self, names: T) -> InsertNodesAliases { self.0.aliases = Into::::into(names).0; InsertNodesAliases(self.0) } + /// Number of nodes to insert. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::insert().nodes().count(1).query(); + /// QueryBuilder::insert().nodes().count(1).values_uniform(vec![("k", 1).into()]); + /// ``` pub fn count(mut self, num: u64) -> InsertNodesCount { self.0.count = num; InsertNodesCount(self.0) } + /// List of lists of `key_values` to be inserted into the nodes. The number of lists + /// will be number created nodes. pub fn values>(mut self, key_values: T) -> InsertNodesValues { self.0.values = QueryValues::Multi(Into::::into(key_values).0); InsertNodesValues(self.0) } - - pub fn values_uniform>(mut self, key_values: T) -> InsertNodesValues { - self.0.values = QueryValues::Single(Into::::into(key_values).0); - - InsertNodesValues(self.0) - } } diff --git a/src/agdb/query_builder/insert_values.rs b/src/agdb/query_builder/insert_values.rs index 6b254d75..a47ac26a 100644 --- a/src/agdb/query_builder/insert_values.rs +++ b/src/agdb/query_builder/insert_values.rs @@ -1,11 +1,16 @@ use crate::query::insert_values_query::InsertValuesQuery; use crate::query::query_ids::QueryIds; +/// Insert values builder to set ids to which the values +/// should be inserted. pub struct InsertValues(pub InsertValuesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct InsertValuesIds(pub InsertValuesQuery); impl InsertValues { + /// An id or list of ids or search query from to which to insert the values. pub fn ids>(mut self, ids: T) -> InsertValuesIds { self.0.ids = ids.into(); @@ -14,6 +19,7 @@ impl InsertValues { } impl InsertValuesIds { + /// Returns the built `InsertValuesQuery` object. pub fn query(self) -> InsertValuesQuery { self.0 } diff --git a/src/agdb/query_builder/remove.rs b/src/agdb/query_builder/remove.rs index 59a1f596..aeca74a7 100644 --- a/src/agdb/query_builder/remove.rs +++ b/src/agdb/query_builder/remove.rs @@ -9,17 +9,40 @@ use crate::query::remove_query::RemoveQuery; use crate::query::remove_values_query::RemoveValuesQuery; use crate::query::select_values_query::SelectValuesQuery; +/// Remove builder to choose what to delete from the database. pub struct Remove {} impl Remove { + /// List of aliases to delete from the database. It is not an error + /// if any of the aliases does not exist in the database. pub fn aliases>(self, names: T) -> RemoveAliases { RemoveAliases(RemoveAliasesQuery(Into::::into(names).0)) } + /// Id, list of ids or search of the database elements to delete + /// from the database. + /// + /// NOTE: all properties (key-value pairs) associated + /// with the elements will be also deleted. If deleting nodes its outgoing + /// and incoming edges will also be deleted along with their properties. + /// + /// It is not an error if not all of the ids exist in the database. pub fn ids>(self, ids: T) -> RemoveIds { RemoveIds(RemoveQuery(ids.into())) } + /// List of keys to delete from ids selected in the next step. It is not an + /// error if not all of the keys exist on the elements. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::remove().values(vec!["k".into()]).ids(1); + /// QueryBuilder::remove().values(vec!["k".into()]).ids(vec![1]); + /// QueryBuilder::remove().values(vec!["k".into()]).ids(QueryBuilder::search().from(1).query()); + /// ``` pub fn values>(self, keys: T) -> RemoveValues { RemoveValues(RemoveValuesQuery(SelectValuesQuery { keys: Into::::into(keys).0, diff --git a/src/agdb/query_builder/remove_aliases.rs b/src/agdb/query_builder/remove_aliases.rs index 10adf042..a997faef 100644 --- a/src/agdb/query_builder/remove_aliases.rs +++ b/src/agdb/query_builder/remove_aliases.rs @@ -1,8 +1,11 @@ use crate::query::remove_aliases_query::RemoveAliasesQuery; +/// Final builder that lets you create +/// an actual query object. pub struct RemoveAliases(pub RemoveAliasesQuery); impl RemoveAliases { + /// Returns the built `RemoveAliasesQuery` object. pub fn query(self) -> RemoveAliasesQuery { self.0 } diff --git a/src/agdb/query_builder/remove_ids.rs b/src/agdb/query_builder/remove_ids.rs index ea05e9eb..acb500c2 100644 --- a/src/agdb/query_builder/remove_ids.rs +++ b/src/agdb/query_builder/remove_ids.rs @@ -1,8 +1,11 @@ use crate::query::remove_query::RemoveQuery; +/// Final builder that lets you create +/// an actual query object. pub struct RemoveIds(pub RemoveQuery); impl RemoveIds { + /// Returns the built `RemoveQuery` object. pub fn query(self) -> RemoveQuery { self.0 } diff --git a/src/agdb/query_builder/remove_values.rs b/src/agdb/query_builder/remove_values.rs index a7e53d03..2652a16a 100644 --- a/src/agdb/query_builder/remove_values.rs +++ b/src/agdb/query_builder/remove_values.rs @@ -1,11 +1,17 @@ use crate::query::query_ids::QueryIds; use crate::query::remove_values_query::RemoveValuesQuery; +/// Remove values builder that lets you select the ids from +/// which to remove the values. pub struct RemoveValues(pub RemoveValuesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct RemoveValuesIds(pub RemoveValuesQuery); impl RemoveValues { + /// Id, list of ids or search of the database elements to delete + /// the values from. All of the ids must exist in the database. pub fn ids>(mut self, ids: T) -> RemoveValuesIds { self.0 .0.ids = ids.into(); @@ -14,6 +20,7 @@ impl RemoveValues { } impl RemoveValuesIds { + /// Returns the built `RemoveValuesQuery` object. pub fn query(self) -> RemoveValuesQuery { self.0 } diff --git a/src/agdb/query_builder/search.rs b/src/agdb/query_builder/search.rs index 39fb2200..146590be 100644 --- a/src/agdb/query_builder/search.rs +++ b/src/agdb/query_builder/search.rs @@ -4,21 +4,45 @@ use crate::query::query_id::QueryId; use crate::query::search_query::SearchQuery; use crate::SearchQueryAlgorithm; +/// Search builder query. pub struct Search {} +/// Search builder query that lets you choose search origin +/// and other parameters. pub struct SearchFrom(pub SearchQuery); +/// Search builder query that lets you choose search destination +/// and other parameters. pub struct SearchTo(pub SearchQuery); +/// Search builder query that lets you choose limit and offset. pub struct SearchOrderBy(pub SearchQuery); +/// Search builder query that lets you choose conditions. pub struct SelectLimit(pub SearchQuery); +/// Search builder query that lets you choose limit. pub struct SelectOffset(pub SearchQuery); +/// Search builder query that lets you choose search origin +/// and other parameters. pub struct SearchAlgorithm(pub SearchQuery); impl Search { + /// Use breadth-first (BFS) search algorithm. This option is redundant as + /// BFS is the default. BFS means each level of the graph is examined in full + /// before advancing to the next level. E.g. all edges coming from a node, + /// then all the nodes connected to them, then all edges coming from each of the + /// nodes etc. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::search().breadth_first().from(1); + /// QueryBuilder::search().breadth_first().to(1); + /// ``` pub fn breadth_first(self) -> SearchAlgorithm { SearchAlgorithm(SearchQuery { algorithm: SearchQueryAlgorithm::BreadthFirst, @@ -31,6 +55,18 @@ impl Search { }) } + /// Use depth-first (DFS) search algorithm. DFS means each element is followed + /// up to its dead end (or already visited element) before examining a next element. + /// E.g. first edge, its connected node, its first outgoing edge etc. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::search().depth_first().from(1); + /// QueryBuilder::search().depth_first().to(1); + /// ``` pub fn depth_first(self) -> SearchAlgorithm { SearchAlgorithm(SearchQuery { algorithm: SearchQueryAlgorithm::DepthFirst, @@ -43,6 +79,20 @@ impl Search { }) } + /// Sets the origin of the search. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().from(1).query(); + /// QueryBuilder::search().from(1).to(2); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]); + /// QueryBuilder::search().from(1).offset(5); + /// QueryBuilder::search().from(1).limit(10); + /// QueryBuilder::search().from(1).where_(); + /// ``` pub fn from>(self, id: T) -> SearchFrom { SearchFrom(SearchQuery { algorithm: SearchQueryAlgorithm::BreadthFirst, @@ -55,6 +105,19 @@ impl Search { }) } + /// Reverses the search setting only the destination. Reverse search + /// follows the edges in reverse (to<-from). + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]); + /// QueryBuilder::search().to(1).offset(5); + /// QueryBuilder::search().to(1).limit(10); + /// QueryBuilder::search().to(1).where_(); + /// ``` pub fn to>(self, id: T) -> SearchTo { SearchTo(SearchQuery { algorithm: SearchQueryAlgorithm::BreadthFirst, @@ -69,11 +132,38 @@ impl Search { } impl SearchAlgorithm { + /// Sets the origin of the search. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().depth_first().from(1).query(); + /// QueryBuilder::search().depth_first().from(1).to(2); + /// QueryBuilder::search().depth_first().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]); + /// QueryBuilder::search().depth_first().from(1).offset(5); + /// QueryBuilder::search().depth_first().from(1).limit(10); + /// QueryBuilder::search().depth_first().from(1).where_(); + /// ``` pub fn from>(mut self, id: T) -> SearchFrom { self.0.origin = id.into(); SearchFrom(self.0) } + /// Reverses the search setting only the destination. Reverse search + /// follows the edges in reverse (to<-x<-from). + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().depth_first().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]); + /// QueryBuilder::search().depth_first().to(1).offset(5); + /// QueryBuilder::search().depth_first().to(1).limit(10); + /// QueryBuilder::search().depth_first().to(1).where_(); + /// ``` pub fn to>(mut self, id: T) -> SearchTo { self.0.destination = id.into(); SearchTo(self.0) @@ -81,110 +171,358 @@ impl SearchAlgorithm { } impl SearchFrom { + /// Sets the limit to number of ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is returned. + /// However when doing a path search or requesting ordering of the result + /// the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::search().from(1).limit(10).query(); + /// QueryBuilder::search().from(1).limit(10).where_(); + /// ``` pub fn limit(mut self, value: u64) -> SelectLimit { self.0.limit = value; SelectLimit(self.0) } + /// Sets the offset to the ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is + /// returned. The `offset` ids will be skipped in the result. + /// However when doing a path search or requesting ordering of the + /// result the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// QueryBuilder::search().from(1).offset(10).query(); + /// QueryBuilder::search().from(1).offset(10).limit(5); + /// QueryBuilder::search().from(1).offset(10).where_(); + /// ``` pub fn offset(mut self, value: u64) -> SelectOffset { self.0.offset = value; SelectOffset(self.0) } + /// Orders the result by `keys`. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).query(); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).limit(5); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).where_(); + /// ``` pub fn order_by(mut self, keys: Vec) -> SearchOrderBy { self.0.order_by = keys; SearchOrderBy(self.0) } + /// Returns the built `SearchQuery` object. pub fn query(self) -> SearchQuery { self.0 } - pub fn to>(mut self, id: T) -> SearchOrderBy { + /// Sets the destination (to) and changes the search algorithm to path search + /// using the A* algorithm. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().from(1).to(2).query(); + /// QueryBuilder::search().from(1).to(2).order_by(vec![DbKeyOrder::Asc("k".into())]); + /// QueryBuilder::search().from(1).to(2).offset(10); + /// QueryBuilder::search().from(1).to(2).limit(5); + /// QueryBuilder::search().from(1).to(2).where_(); + /// ``` + pub fn to>(mut self, id: T) -> SearchTo { self.0.destination = id.into(); - SearchOrderBy(self.0) + SearchTo(self.0) } + /// Starts the condition builder. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().node(); + /// QueryBuilder::search().from(1).where_().edge(); + /// QueryBuilder::search().from(1).where_().distance(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().ids(1); + /// QueryBuilder::search().from(1).where_().keys(vec!["k".into()]); + /// QueryBuilder::search().from(1).where_().key("k"); + /// QueryBuilder::search().from(1).where_().edge_count(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_from(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_to(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().where_(); + /// QueryBuilder::search().from(1).where_().not(); + /// QueryBuilder::search().from(1).where_().beyond(); + /// QueryBuilder::search().from(1).where_().not_beyond(); + /// ``` pub fn where_(self) -> Where { Where::new(self.0) } } impl SearchOrderBy { + /// Sets the limit to number of ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is returned. + /// However when doing a path search or requesting ordering of the result + /// the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).limit(10).query(); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).limit(10).where_(); + /// ``` pub fn limit(mut self, value: u64) -> SelectLimit { self.0.limit = value; SelectLimit(self.0) } + /// Sets the offset to the ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is + /// returned. The `offset` ids will be skipped in the result. + /// However when doing a path search or requesting ordering of the + /// result the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).query(); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).limit(5); + /// QueryBuilder::search().from(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).where_(); + /// ``` pub fn offset(mut self, value: u64) -> SelectOffset { self.0.offset = value; SelectOffset(self.0) } + /// Returns the built `SearchQuery` object. pub fn query(self) -> SearchQuery { self.0 } + /// Starts the condition builder. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().node(); + /// QueryBuilder::search().from(1).where_().edge(); + /// QueryBuilder::search().from(1).where_().distance(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().ids(1); + /// QueryBuilder::search().from(1).where_().keys(vec!["k".into()]); + /// QueryBuilder::search().from(1).where_().key("k"); + /// QueryBuilder::search().from(1).where_().edge_count(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_from(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_to(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().where_(); + /// QueryBuilder::search().from(1).where_().not(); + /// QueryBuilder::search().from(1).where_().beyond(); + /// QueryBuilder::search().from(1).where_().not_beyond(); + /// ``` pub fn where_(self) -> Where { Where::new(self.0) } } impl SearchTo { + /// Sets the limit to number of ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is returned. + /// However when doing a path search or requesting ordering of the result + /// the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).limit(10).query(); + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).limit(10).where_(); + /// ``` pub fn limit(mut self, value: u64) -> SelectLimit { self.0.limit = value; SelectLimit(self.0) } + /// Sets the offset to the ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is + /// returned. The `offset` ids will be skipped in the result. + /// However when doing a path search or requesting ordering of the + /// result the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).query(); + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).limit(5); + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).where_(); + /// ``` pub fn offset(mut self, value: u64) -> SelectOffset { self.0.offset = value; SelectOffset(self.0) } - pub fn order_by(mut self, keys: &[DbKeyOrder]) -> SearchOrderBy { - self.0.order_by = keys.to_vec(); + /// Sets the offset to the ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is + /// returned. The `offset` ids will be skipped in the result. + /// However when doing a path search or requesting ordering of the + /// result the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).query(); + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).limit(5); + /// QueryBuilder::search().to(1).order_by(vec![DbKeyOrder::Asc("k".into())]).offset(10).where_(); + /// ``` + pub fn order_by(mut self, keys: Vec) -> SearchOrderBy { + self.0.order_by = keys; SearchOrderBy(self.0) } + /// Returns the built `SearchQuery` object. pub fn query(self) -> SearchQuery { self.0 } + /// Starts the condition builder. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().node(); + /// QueryBuilder::search().from(1).where_().edge(); + /// QueryBuilder::search().from(1).where_().distance(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().ids(1); + /// QueryBuilder::search().from(1).where_().keys(vec!["k".into()]); + /// QueryBuilder::search().from(1).where_().key("k"); + /// QueryBuilder::search().from(1).where_().edge_count(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_from(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_to(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().where_(); + /// QueryBuilder::search().from(1).where_().not(); + /// QueryBuilder::search().from(1).where_().beyond(); + /// QueryBuilder::search().from(1).where_().not_beyond(); + /// ``` pub fn where_(self) -> Where { Where::new(self.0) } } impl SelectLimit { + /// Returns the built `SearchQuery` object. pub fn query(self) -> SearchQuery { self.0 } + /// Starts the condition builder. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().node(); + /// QueryBuilder::search().from(1).where_().edge(); + /// QueryBuilder::search().from(1).where_().distance(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().ids(1); + /// QueryBuilder::search().from(1).where_().keys(vec!["k".into()]); + /// QueryBuilder::search().from(1).where_().key("k"); + /// QueryBuilder::search().from(1).where_().edge_count(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_from(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_to(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().where_(); + /// QueryBuilder::search().from(1).where_().not(); + /// QueryBuilder::search().from(1).where_().beyond(); + /// QueryBuilder::search().from(1).where_().not_beyond(); + /// ``` pub fn where_(self) -> Where { Where::new(self.0) } } impl SelectOffset { + /// Sets the limit to number of ids returned. If during the search + /// the `limit + offset` is hit the search ends and the result is returned. + /// However when doing a path search or requesting ordering of the result + /// the search is first completed before the limit is applied. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, DbKeyOrder}; + /// + /// QueryBuilder::search().from(1).offset(10).limit(10).query(); + /// QueryBuilder::search().from(1).offset(10).limit(10).where_(); + /// ``` pub fn limit(mut self, value: u64) -> SelectLimit { self.0.limit = value; SelectLimit(self.0) } + /// Returns the built `SearchQuery` object. pub fn query(self) -> SearchQuery { self.0 } + /// Starts the condition builder. + /// + /// Options: + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().node(); + /// QueryBuilder::search().from(1).where_().edge(); + /// QueryBuilder::search().from(1).where_().distance(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().ids(1); + /// QueryBuilder::search().from(1).where_().keys(vec!["k".into()]); + /// QueryBuilder::search().from(1).where_().key("k"); + /// QueryBuilder::search().from(1).where_().edge_count(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_from(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().edge_count_to(CountComparison::Equal(1)); + /// QueryBuilder::search().from(1).where_().where_(); + /// QueryBuilder::search().from(1).where_().not(); + /// QueryBuilder::search().from(1).where_().beyond(); + /// QueryBuilder::search().from(1).where_().not_beyond(); + /// ``` pub fn where_(self) -> Where { Where::new(self.0) } diff --git a/src/agdb/query_builder/select.rs b/src/agdb/query_builder/select.rs index e9244eda..e3cb1bb9 100644 --- a/src/agdb/query_builder/select.rs +++ b/src/agdb/query_builder/select.rs @@ -11,25 +11,40 @@ use crate::query::select_keys_query::SelectKeysQuery; use crate::query::select_query::SelectQuery; use crate::query::select_values_query::SelectValuesQuery; +/// Select builder that lets you choose what +/// data you want to select form the database. pub struct Select {} impl Select { + /// Select aliases. If no ids are given all aliases + /// in the database will be selected. Each element + /// of the result will have a property `String("alias")` + /// holding the alias. If `ids` are specified and any + /// of them does not have an alias an error will occur + /// when running such query. pub fn aliases(self) -> SelectAliases { SelectAliases(SelectAliasesQuery(QueryIds::Ids(vec![]))) } + /// Select elements with `ids` with all properties (key-values). + /// All ids specified must exist in the database. pub fn ids>(self, ids: T) -> SelectIds { SelectIds(SelectQuery(ids.into())) } + /// Select keys only (values will be empty). pub fn keys(self) -> SelectKeys { SelectKeys(SelectKeysQuery(QueryIds::Ids(vec![0.into()]))) } + /// Select number of keys. Each element of the result will have + /// a property `String("key_count")` with `i64` as the value. pub fn key_count(self) -> SelectKeyCount { SelectKeyCount(SelectKeyCountQuery(QueryIds::Ids(vec![0.into()]))) } + /// Select elements with `ids` with only `keys` properties (key-values). + /// All ids specified must exist in the database. pub fn values>(self, keys: T) -> SelectValues { SelectValues(SelectValuesQuery { keys: Into::::into(keys).0, diff --git a/src/agdb/query_builder/select_aliases.rs b/src/agdb/query_builder/select_aliases.rs index 03246f19..746636e0 100644 --- a/src/agdb/query_builder/select_aliases.rs +++ b/src/agdb/query_builder/select_aliases.rs @@ -2,23 +2,30 @@ use crate::query::query_ids::QueryIds; use crate::query::select_aliases_query::SelectAliasesQuery; use crate::query::select_all_aliases_query::SelectAllAliases; +/// Select aliases builder. pub struct SelectAliases(pub SelectAliasesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct SelectAliasesIds(pub SelectAliasesQuery); impl SelectAliases { + /// An id or list of ids or search query to select aliases of. + /// All ids specified must exist in the database. pub fn ids>(mut self, ids: T) -> SelectAliasesIds { self.0 .0 = ids.into(); SelectAliasesIds(self.0) } + /// Returns the built `SelectAllAliases` object. pub fn query(self) -> SelectAllAliases { SelectAllAliases {} } } impl SelectAliasesIds { + /// Returns the built `SelectAllAliases` object. pub fn query(self) -> SelectAliasesQuery { self.0 } diff --git a/src/agdb/query_builder/select_ids.rs b/src/agdb/query_builder/select_ids.rs index bb6ea807..c0b5a77b 100644 --- a/src/agdb/query_builder/select_ids.rs +++ b/src/agdb/query_builder/select_ids.rs @@ -1,8 +1,11 @@ use crate::query::select_query::SelectQuery; +/// Final builder that lets you create +/// an actual query object. pub struct SelectIds(pub SelectQuery); impl SelectIds { + /// Returns the built `SelectQuery` object. pub fn query(self) -> SelectQuery { self.0 } diff --git a/src/agdb/query_builder/select_key_count.rs b/src/agdb/query_builder/select_key_count.rs index b7f1f2ca..aaeb4263 100644 --- a/src/agdb/query_builder/select_key_count.rs +++ b/src/agdb/query_builder/select_key_count.rs @@ -1,11 +1,16 @@ use crate::query::query_ids::QueryIds; use crate::query::select_key_count_query::SelectKeyCountQuery; +/// Select key count builder. pub struct SelectKeyCount(pub SelectKeyCountQuery); +/// Final builder that lets you create +/// an actual query object. pub struct SelectKeyCountIds(pub SelectKeyCountQuery); impl SelectKeyCount { + /// An id or list of ids or search query to select key count of. + /// All ids specified must exist in the database. pub fn ids>(mut self, ids: T) -> SelectKeyCountIds { self.0 .0 = ids.into(); @@ -14,6 +19,7 @@ impl SelectKeyCount { } impl SelectKeyCountIds { + /// Returns the built `SelectKeyCountQuery` object. pub fn query(self) -> SelectKeyCountQuery { self.0 } diff --git a/src/agdb/query_builder/select_keys.rs b/src/agdb/query_builder/select_keys.rs index 8a4bc5d0..c9f0022f 100644 --- a/src/agdb/query_builder/select_keys.rs +++ b/src/agdb/query_builder/select_keys.rs @@ -1,11 +1,16 @@ use crate::query::query_ids::QueryIds; use crate::query::select_keys_query::SelectKeysQuery; +/// Select keys builder. pub struct SelectKeys(pub SelectKeysQuery); +/// Final builder that lets you create +/// an actual query object. pub struct SelectKeysIds(pub SelectKeysQuery); impl SelectKeys { + /// An id or list of ids or search query to select keys of. + /// All ids specified must exist in the database. pub fn ids>(mut self, ids: T) -> SelectKeysIds { self.0 .0 = ids.into(); @@ -14,6 +19,7 @@ impl SelectKeys { } impl SelectKeysIds { + /// Returns the built `SelectKeysQuery` object. pub fn query(self) -> SelectKeysQuery { self.0 } diff --git a/src/agdb/query_builder/select_values.rs b/src/agdb/query_builder/select_values.rs index 06370309..33969d43 100644 --- a/src/agdb/query_builder/select_values.rs +++ b/src/agdb/query_builder/select_values.rs @@ -1,11 +1,16 @@ use crate::query::query_ids::QueryIds; use crate::query::select_values_query::SelectValuesQuery; +/// Select values builder. pub struct SelectValues(pub SelectValuesQuery); +/// Final builder that lets you create +/// an actual query object. pub struct SelectValuesIds(pub SelectValuesQuery); impl SelectValues { + /// An id or list of ids or search query to select values of. + /// All ids specified must exist in the database. pub fn ids>(mut self, ids: T) -> SelectValuesIds { self.0.ids = ids.into(); @@ -14,6 +19,7 @@ impl SelectValues { } impl SelectValuesIds { + /// Returns the built `SelectValuesQuery` object. pub fn query(self) -> SelectValuesQuery { self.0 } diff --git a/src/agdb/query_builder/where_.rs b/src/agdb/query_builder/where_.rs index 8904a7ee..a9f4e75f 100644 --- a/src/agdb/query_builder/where_.rs +++ b/src/agdb/query_builder/where_.rs @@ -9,6 +9,7 @@ use crate::Comparison; use crate::DbKey; use crate::QueryIds; +/// Condition builder pub struct Where { logic: QueryConditionLogic, modifier: QueryConditionModifier, @@ -16,29 +17,57 @@ pub struct Where { query: SearchQuery, } +/// Condition builder for `key` condition. pub struct WhereKey { key: DbKey, where_: Where, } +/// Condition builder setting the logic operator. pub struct WhereLogicOperator(pub Where); impl Where { - pub fn new(query: SearchQuery) -> Self { - Self { - logic: QueryConditionLogic::And, - modifier: QueryConditionModifier::None, - conditions: vec![vec![]], - query, - } - } - + /// Sets the condition modifier for the following condition so + /// that the search will continue beyond current element only + /// if the condition is satisfied. For the opposite effect see + /// `not_beyond()`. + /// + /// NOTE: This condition applies to the starting element as well. + /// A common issue is that the starting element is not followed + /// as it may not pass this condition. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// // Only elements with `k` key will be followed during search. + /// QueryBuilder::search().from(1).where_().beyond().keys(vec!["k".into()]).query(); + /// + /// // Only edges or nodes with exactly 1 edge are followed. + /// QueryBuilder::search().from(1).where_().beyond().edge().or().edge_count(CountComparison::Equal(1)); + /// ``` pub fn beyond(mut self) -> Where { self.modifier = QueryConditionModifier::Beyond; self } + /// Sets the distance condition. It can be used both for accepting + /// elements during search and for limiting the area of the search + /// on the graph. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// // Search at most to distance 2 (1 = first edge, 2 = neighbouring node) + /// QueryBuilder::search().from(1).where_().distance(CountComparison::LessThanOrEqual(2)).query(); + /// + /// // Start accepting elements at distance greater than 1 (2+) + /// QueryBuilder::search().from(1).where_().distance(CountComparison::GreaterThan(1)).query(); + /// ``` pub fn distance(mut self, comparison: CountComparison) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -49,6 +78,15 @@ impl Where { WhereLogicOperator(self) } + /// Only elements that are edges will pass this condition. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().edge().query(); + /// ``` pub fn edge(mut self) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -59,6 +97,18 @@ impl Where { WhereLogicOperator(self) } + /// Only nodes can pass this condition and only if `edge_count` + /// (from + to edges) is compared true against `comparison`. Note that self-referential + /// edges are counted twice (e.g. node with an edge to itself will appear to have + /// "2" edges, one outgoing and one incoming). + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().edge_count(CountComparison::Equal(1)).query(); + /// ``` pub fn edge_count(mut self, comparison: CountComparison) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -69,6 +119,16 @@ impl Where { WhereLogicOperator(self) } + /// Only nodes can pass this condition and only if `edge_count_from` + /// (outgoing/from edges) is compared true against `comparison`. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().edge_count_from(CountComparison::Equal(1)).query(); + /// ``` pub fn edge_count_from(mut self, comparison: CountComparison) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -79,6 +139,16 @@ impl Where { WhereLogicOperator(self) } + /// Only nodes can pass this condition and only if `edge_count_to` + /// (incoming/to edges) is compared true against `comparison`. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().edge_count_to(CountComparison::Equal(1)).query(); + /// ``` pub fn edge_count_to(mut self, comparison: CountComparison) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -89,6 +159,22 @@ impl Where { WhereLogicOperator(self) } + /// Only elements listed in `ids` will pass this condition. It is usually combined + /// with a modifier like `not_beyond()` or `not()`. + /// + /// NOTE: Search query is NOT supported here and will be ignored. + /// + /// # Examples + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// // Exclude element 1 from result (the starting element) + /// QueryBuilder::search().from(1).where_().not().ids(1).query(); + /// + /// // Do not continue the search beyond "alias" element + /// QueryBuilder::search().from(1).where_().not_beyond().ids("alias").query(); + /// ``` pub fn ids>(mut self, ids: T) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -99,6 +185,17 @@ impl Where { WhereLogicOperator(self) } + /// Initiates the `key` condition that tests the key for a + /// particular value set in the next step. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, Comparison}; + /// + /// // Includes only elements with property `String("k") == 1_i64` + /// QueryBuilder::search().from(1).where_().key("k").value(Comparison::Equal(1.into())).query(); + /// ``` pub fn key>(self, key: T) -> WhereKey { WhereKey { key: key.into(), @@ -106,6 +203,21 @@ impl Where { } } + /// Only elements with all properties listed in `keys` (regardless of values) + /// will pass this condition ("all"). To achieve "any" you need to chain the + /// `keys()` condition with `or()`. + /// + /// # Examples + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// // Include only elements with "k" property (key) + /// QueryBuilder::search().from(1).where_().keys(vec!["k".into()]).query(); + /// + /// // Includes only elements with either "a" or "b" properties (keys). + /// QueryBuilder::search().from(1).where_().keys(vec!["a".into()]).or().keys(vec!["b".into()]).query(); + /// ``` pub fn keys>(mut self, keys: T) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -116,6 +228,15 @@ impl Where { WhereLogicOperator(self) } + /// Only elements that are nodes will pass this condition. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// QueryBuilder::search().from(1).where_().node().query(); + /// ``` pub fn node(mut self) -> WhereLogicOperator { self.add_condition(QueryCondition { logic: self.logic, @@ -126,18 +247,70 @@ impl Where { WhereLogicOperator(self) } + /// Sets the condition modifier reversing the outcome of the following + /// condition. + /// + /// # Examples + /// + /// ``` + /// use agdb::QueryBuilder; + /// + /// // Includes elements WITHOUT the "k" property (key). + /// QueryBuilder::search().from(1).where_().not().keys(vec!["k".into()]).query(); + /// ``` pub fn not(mut self) -> Self { self.modifier = QueryConditionModifier::Not; self } + /// Sets the condition modifier for the following condition so + /// that the search will NOT continue beyond current element only + /// if the condition IS satisfied. For the opposite effect see + /// `beyond()`. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// // Elements with `k` key will NOT be followed during search. + /// QueryBuilder::search().from(1).where_().not_beyond().keys(vec!["k".into()]).query(); + /// + /// // Elements 1 and 2 will NOT be followed during search. + /// QueryBuilder::search().from(1).where_().not_beyond().ids(vec![1, 2]); + /// ``` pub fn not_beyond(mut self) -> Self { self.modifier = QueryConditionModifier::NotBeyond; self } + /// Starts a sub-condition (it semantically represents an open bracket). The + /// conditions in a sub-condition are collapsed into single condition when + /// evaluated and passed to the previous level. Any condition modifiers can still + /// apply to the collapsed condition if applied on the `where_()` condition. + /// + /// # Examples + /// + /// ``` + /// use agdb::{QueryBuilder, CountComparison}; + /// + /// // Select only elements at distance 2 (= nodes) + /// // but only follow elements with "k" property + /// // or nodes (this is to follow the starting node) + /// QueryBuilder::search() + /// .from(1) + /// .where_() + /// .distance(CountComparison::Equal(2)) + /// .and() + /// .beyond() + /// .where_() + /// .keys(vec!["k".into()]) + /// .or() + /// .node() + /// .query(); + /// ``` pub fn where_(mut self) -> Self { self.add_condition(QueryCondition { logic: self.logic, @@ -154,6 +327,15 @@ impl Where { } } + pub(crate) fn new(query: SearchQuery) -> Self { + Self { + logic: QueryConditionLogic::And, + modifier: QueryConditionModifier::None, + conditions: vec![vec![]], + query, + } + } + fn add_condition(&mut self, condition: QueryCondition) { self.conditions.last_mut().unwrap().push(condition); } @@ -179,6 +361,7 @@ impl Where { } impl WhereKey { + /// Sets the value of the `key` condition to `comparison`. pub fn value(mut self, comparison: Comparison) -> WhereLogicOperator { let condition = QueryCondition { logic: self.where_.logic, @@ -194,6 +377,9 @@ impl WhereKey { } impl WhereLogicOperator { + /// Sets the logic operator for the following condition + /// to logical AND (&&). The condition passes only if + /// both sides evaluates to `true`. pub fn and(self) -> Where { Where { logic: QueryConditionLogic::And, @@ -203,12 +389,18 @@ impl WhereLogicOperator { } } + /// Closes the current level condition level returning + /// to the previous one. It semantically represents a + /// closing bracket. pub fn end_where(mut self) -> WhereLogicOperator { self.0.collapse_conditions(); WhereLogicOperator(self.0) } + /// Sets the logic operator for the following condition + /// to logical OR (||). The condition passes only if + /// both sides evaluates to `false`. pub fn or(self) -> Where { Where { logic: QueryConditionLogic::Or, @@ -218,6 +410,7 @@ impl WhereLogicOperator { } } + /// Returns the built `SearchQuery` object. pub fn query(mut self) -> SearchQuery { while self.0.collapse_conditions() {} std::mem::swap(&mut self.0.query.conditions, &mut self.0.conditions[0]); diff --git a/src/agdb/transaction.rs b/src/agdb/transaction.rs index c5a64df7..fbc3dd6f 100644 --- a/src/agdb/transaction.rs +++ b/src/agdb/transaction.rs @@ -3,16 +3,28 @@ use crate::Db; use crate::QueryError; use crate::QueryResult; +/// The `Transaction` is a proxy struct that +/// encapsulates an immutably borrowed `Db`. +/// It allows running queries via `exec()`. pub struct Transaction<'a> { db: &'a Db, } impl<'a> Transaction<'a> { - pub fn new(data: &'a Db) -> Self { - Self { db: data } - } - + /// Executes immutable query: + /// + /// - Select elements + /// - Select values + /// - Select keys + /// - Select key count + /// - Select aliases + /// - Select all aliases + /// - Search pub fn exec(&self, query: &T) -> Result { query.process(self.db) } + + pub(crate) fn new(data: &'a Db) -> Self { + Self { db: data } + } } diff --git a/src/agdb/transaction_mut.rs b/src/agdb/transaction_mut.rs index 3ce020e5..231ed8df 100644 --- a/src/agdb/transaction_mut.rs +++ b/src/agdb/transaction_mut.rs @@ -5,23 +5,44 @@ use crate::QueryError; use crate::QueryResult; use crate::Transaction; +/// The `TransactionMut` is a proxy struct that +/// encapsulates a mutably borrowed `Db`. +/// It allows running queries via `exec()` and `exec_mut()`. pub struct TransactionMut<'a> { db: &'a mut Db, } impl<'a> TransactionMut<'a> { - pub fn new(data: &'a mut Db) -> Self { - Self { db: data } - } - + /// Executes immutable query: + /// + /// - Select elements + /// - Select values + /// - Select keys + /// - Select key count + /// - Select aliases + /// - Select all aliases + /// - Search pub fn exec(&self, query: &T) -> Result { Transaction::new(self.db).exec(query) } + /// Executes mutable query: + /// + /// - Insert nodes + /// - Insert edges + /// - Insert aliases + /// - Insert values + /// - Remove elements + /// - Remove aliases + /// - Remove values pub fn exec_mut(&mut self, query: &T) -> Result { query.process(self.db) } + pub(crate) fn new(data: &'a mut Db) -> Self { + Self { db: data } + } + pub(crate) fn commit(self) -> Result<(), QueryError> { self.db.commit()?; Ok(()) diff --git a/tests/insert_nodes_test.rs b/tests/insert_nodes_test.rs index 333cbea2..5780fb9c 100644 --- a/tests/insert_nodes_test.rs +++ b/tests/insert_nodes_test.rs @@ -208,22 +208,3 @@ fn insert_nodes_values() { ], ); } - -#[test] -fn insert_nodes_values_uniform() { - let mut db = TestDb::new(); - db.exec_mut( - QueryBuilder::insert() - .nodes() - .values_uniform(vec![("key", "value").into(), ("key2", "value2").into()]) - .query(), - 1, - ); - db.exec_elements( - QueryBuilder::select().ids(1).query(), - &[DbElement { - id: DbId(1), - values: vec![("key", "value").into(), ("key2", "value2").into()], - }], - ); -} diff --git a/tests/search_test.rs b/tests/search_test.rs index 4f1a6db8..21b9cbed 100644 --- a/tests/search_test.rs +++ b/tests/search_test.rs @@ -321,6 +321,55 @@ fn search_from_ordered_by() { 10, ); + db.exec_ids( + QueryBuilder::select() + .ids( + QueryBuilder::search() + .from("users") + .order_by(vec![ + DbKeyOrder::Desc("age".into()), + DbKeyOrder::Asc("name".into()), + ]) + .query(), + ) + .query(), + &[ + 5, 4, 2, 8, 7, 3, 9, 10, 11, 6, 1, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, + ], + ); +} + +#[test] +fn search_from_ordered_by_limit_offset() { + let mut db = TestDb::new(); + db.exec_mut(QueryBuilder::insert().nodes().aliases("users").query(), 1); + + let users = db.exec_mut_result( + QueryBuilder::insert() + .nodes() + .values(vec![ + vec![("name", "z").into(), ("age", 31).into(), ("id", 1).into()], + vec![("name", "x").into(), ("age", 12).into(), ("id", 2).into()], + vec![("name", "y").into(), ("age", 57).into(), ("id", 3).into()], + vec![("name", "a").into(), ("age", 60).into(), ("id", 4).into()], + vec![("name", "f").into(), ("age", 4).into(), ("id", 5).into()], + vec![("name", "s").into(), ("age", 18).into(), ("id", 6).into()], + vec![("name", "y").into(), ("age", 28).into(), ("id", 7).into()], + vec![("name", "k").into(), ("age", 9).into(), ("id", 8).into()], + vec![("name", "w").into(), ("age", 6).into(), ("id", 9).into()], + vec![("name", "c").into(), ("age", 5).into(), ("id", 10).into()], + ]) + .query(), + ); + db.exec_mut( + QueryBuilder::insert() + .edges() + .from("users") + .to(users) + .query(), + 10, + ); + db.exec_elements( QueryBuilder::select() .ids( @@ -388,7 +437,7 @@ fn search_to_ordered_by() { .ids( QueryBuilder::search() .to("users") - .order_by(&[ + .order_by(vec![ DbKeyOrder::Asc("age".into()), DbKeyOrder::Desc("name".into()), ])