Skip to content

Commit

Permalink
Create and validate abstract queries (#302)
Browse files Browse the repository at this point in the history
* Introduce query module with find and find_many methods

* First sketches on how the API could look like

* Add filtering options, automatically merge them

* Add tests for merging elements

* Add test converting to single element

* Add more general filter tests

* Add doc-string to upsert method

* Correct doc-string

* Rename Range to Interval

* Share Field type between Order and Filter structs, try different names

* Delete wrong doc-string line

* Clean up a little bit

* Derive PartialEq, add some more constructors

* Separate files into own module

* Derive Default for Find struct as well

* Prepare validation method for query

* Expose struct fields

* Allow all of this to be unused for now

* Make Cursor a trait

* Implement Cursor for String only for tests

* Add a few more tests

* Implement validation logic

* Convert from string and query value to filter

* Make clippy happy

* Add TODO

* Remove store for now, we do this in a separate PR

* Rename helpers to test_utils

* Move code a little

* Formatting

* Add Select struct to select fields in document

* Validate selected fields as well

* Extend validation test case a little

* Simplfy test helpers

* Move errors over to query module

* Add some tests for validation

* Add documentation and some more tests

* Add entry to CHANGELOG.md
  • Loading branch information
adzialocha committed Mar 24, 2023
1 parent 89ff11e commit 0dfddcd
Show file tree
Hide file tree
Showing 12 changed files with 1,353 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Introduce libp2p networking service and configuration [#282](https://github.com/p2panda/aquadoggo/pull/282)
- Create and validate abstract queries [#302](https://github.com/p2panda/aquadoggo/pull/302)

### Changed

Expand Down
5 changes: 3 additions & 2 deletions aquadoggo/src/db/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
use p2panda_rs::schema::error::{SchemaError, SchemaIdError};
use p2panda_rs::schema::system::SystemSchemaError;
use p2panda_rs::storage_provider::error::{DocumentStorageError, OperationStorageError};
use thiserror::Error;

/// `SQLStorage` errors.
#[derive(thiserror::Error, Debug)]
#[derive(Error, Debug)]
pub enum SqlStoreError {
#[error("SQL query failed: {0}")]
Transaction(String),
Expand All @@ -15,7 +16,7 @@ pub enum SqlStoreError {
}

/// `SchemaStore` errors.
#[derive(thiserror::Error, Debug)]
#[derive(Error, Debug)]
pub enum SchemaStoreError {
/// Catch all error which implementers can use for passing their own errors up the chain.
#[error("Error occured in DocumentStore: {0}")]
Expand Down
4 changes: 3 additions & 1 deletion aquadoggo/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
//! Persistent storage for an `aquadoggo` node supporting both Postgres and SQLite databases.
//!
//! The main interface is [`SqlStore`] which offers an interface onto the database by implementing
//! the storage traits defined in `p2panda-rs` as well as some implementation specific features.
//! the storage traits defined in `p2panda-rs` as well as some implementation specific features.
use anyhow::{Error, Result};
use sqlx::any::{Any, AnyPool, AnyPoolOptions};
use sqlx::migrate;
use sqlx::migrate::MigrateDatabase;

pub mod errors;
pub mod models;
#[allow(unused)]
pub mod query;
pub mod stores;
pub mod types;

Expand Down
35 changes: 35 additions & 0 deletions aquadoggo/src/db/query/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

use thiserror::Error;

/// Validation errors for "abstract" queries.
#[derive(Error, Debug)]
pub enum QueryError {
/// Selection contains a field which is not part of the given schema.
#[error("Can't select unknown field '{0}'")]
SelectFieldUnknown(String),

/// Filter contains a field which is not part of the given schema.
#[error("Can't apply filter on unknown field '{0}'")]
FilterFieldUnknown(String),

/// Ordering is based on a field which is not part of the given schema.
#[error("Can't apply ordering on unknown field '{0}'")]
OrderFieldUnknown(String),

/// Filter can not be applied to a field of given type.
#[error("Filter type '{0}' for field '{1}' is not matching schema type '{2}'")]
FilterInvalidType(String, String, String),

/// Set filters are not possible for boolean values.
#[error("Can't apply set filter as field '{0}' is of type boolean")]
FilterInvalidSet(String),

/// Interval filters arae not possible for booleans and relations.
#[error("Can't apply interval filter as field '{0}' is not of type string, float or integer")]
FilterInvalidInterval(String),

/// Search filters can only be applied on strings.
#[error("Can't apply search filter as field '{0}' is not of type string")]
FilterInvalidSearch(String),
}
92 changes: 92 additions & 0 deletions aquadoggo/src/db/query/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

use std::convert::TryFrom;

use anyhow::bail;
use p2panda_rs::schema::FieldName;

/// Pre-defined constant fields for every document.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MetaField {
/// Identifier of the document.
DocumentId,

/// Latest version of the document.
DocumentViewId,

/// Public key of the original author (owner) of the document.
Owner,

/// Flag indicating if document was edited at least once.
Edited,

/// Flag indicating if document was deleted.
Deleted,
}

impl TryFrom<&str> for MetaField {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"documentId" => Ok(MetaField::DocumentId),
"viewId" => Ok(MetaField::DocumentViewId),
"owner" => Ok(MetaField::Owner),
"edited" => Ok(MetaField::Edited),
"deleted" => Ok(MetaField::Deleted),
_ => bail!("Unknown meta field"),
}
}
}

impl ToString for MetaField {
fn to_string(&self) -> String {
match self {
MetaField::DocumentId => "documentId",
MetaField::DocumentViewId => "viewId",
MetaField::Owner => "owner",
MetaField::Edited => "edited",
MetaField::Deleted => "deleted",
}
.to_string()
}
}

/// Fields can be either defined by the regarding schema (application fields) or are constants
/// (meta fields) like the identifier of the document itself.
///
/// Fields can be selected, ordered or filtered. Use the regarding structs to apply settings on top
/// of these fields.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Field {
/// Pre-defined, constant fields for every document.
Meta(MetaField),

/// Field defined by the schema.
Field(FieldName),
}

impl Field {
/// Returns a new application field.
pub fn new(name: &str) -> Self {
Self::Field(name.to_string())
}
}

impl From<&str> for Field {
fn from(value: &str) -> Self {
Self::Field(value.to_string())
}
}

#[cfg(test)]
mod tests {
use super::Field;

#[test]
fn create_field_from_str() {
let field: Field = "message".into();
assert_eq!(field, Field::new("message"));
assert_eq!(Field::Field("favorite".to_string()), Field::new("favorite"));
}
}
Loading

0 comments on commit 0dfddcd

Please sign in to comment.