Skip to content

Commit

Permalink
query api filter applied to db operations #14
Browse files Browse the repository at this point in the history
  • Loading branch information
serayuzgur committed May 5, 2017
1 parent bdbad4c commit 034988a
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 15 deletions.
3 changes: 2 additions & 1 deletion src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod read;
mod insert;
mod update;
mod delete;
pub mod query_api;

pub mod errors;

Expand Down Expand Up @@ -85,7 +86,7 @@ impl Database {
}

/// This is the main access function to reach the desired data from the whole database.
/// Tries to find the keys provided in the database recursively.
/// Tries to find the keys provided in the database recursively.
/// Returns mutable references to allow manipulation.
pub fn get_object<'per_req>(keys: &mut Vec<String>,
json_object: &'per_req mut Value)
Expand Down
271 changes: 271 additions & 0 deletions src/database/query_api/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
//! # query_api
//! All necessery functions for appliying query api to json results.
use database::Database;
use database::Errors;
use std::vec::Vec;
use serde_json::{Value, Error};
use serde_json;
use serde_json::error::ErrorCode::Message;
use service::query_api::Queries;
use database::query_api;

/// filter array according to the query api
pub fn apply(obj: &mut Value, queries: &Queries) -> Value {
let ref filters = queries.filter;
if let &mut Value::Array(ref mut arr) = obj {
let mut size = arr.len();
let mut i = 0;
while i < size {
let mut valid = true;
if let Some(item) = arr.get(i) {
for q in filters {
if let Some(field) = item.get(&q.key) {
valid = is_valid(&q.op, field, &q.value);
if !valid {
break;
}
}
}
} else {
break;
}
if !valid {
arr.remove(i);
size -= 1;
} else {
i += 1;
}
}
} else {}
obj.clone()
}

fn convert_2_same_type(field_value: &Value, query_value: &str) -> Result<Value, Error> {
match field_value {
&Value::Bool(_) => return serde_json::to_value(query_value == "true"),
&Value::Number(_) => return serde_json::to_value(i64::from_str_radix(query_value, 10).ok()),
&Value::String(_) => return serde_json::to_value(query_value),
_ => {
let error = Message("Filter is not applicable for this column".to_string());
Err(Error::syntax(error, 1, 1))
}
}
}

fn is_valid(op: &str, field_value: &Value, query_value: &str) -> bool {
let mut valid = true;
let parsed_q = convert_2_same_type(field_value, query_value);
if let Ok(qval) = parsed_q {
match op {
"=" => valid = field_value == &qval,
"!=" => valid = field_value != &qval,
">" => {
if let Some(num_f) = field_value.as_i64() {
if let Some(num_q) = qval.as_i64() {
valid = num_f > num_q;
}
}
}
">=" => {
if let Some(num_f) = field_value.as_i64() {
if let Some(num_q) = qval.as_i64() {
valid = num_f >= num_q;
}
}
}
"<" => {
if let Some(num_f) = field_value.as_i64() {
if let Some(num_q) = qval.as_i64() {
valid = num_f < num_q;
}
}
}
"<=" => {
if let Some(num_f) = field_value.as_i64() {
if let Some(num_q) = qval.as_i64() {
valid = num_f <= num_q;
}
}
}
"~=" => {
println!("checking {:?}~={:?}", field_value, &qval);
if let Some(str_f) = field_value.as_str() {
if let Some(str_q) = qval.as_str() {
valid = str_f.starts_with(str_q);
}
}
}
"|=" => {
let parts = query_value.split("|");
for part in parts {
valid = false;
if let Ok(qval) = convert_2_same_type(field_value, part) {
println!("checking {:?}|={:?}", field_value, &qval);
if field_value == &qval{
valid = true;
break;
}
}
}
}
_ => {}
}
}
valid
}




#[cfg(test)]
mod tests {
use service::query_api::Query;
use super::*;

fn get_json() -> Value {
let json_string = r#"[
{
"name":"seray",
"age":31,
"active":true,
"password":"123"
},
{
"name":"kamil",
"age":900,
"active":false,
"password":"333"
},
{
"name":"hasan",
"age":25,
"active":true,
"password":"321"
}
]"#;
serde_json::from_str(&json_string).unwrap()
}

#[test]
fn apply_eq_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("name", "=", "seray"));
filter.push(Query::new("active", "=", "true"));
}
let expected: Value = serde_json::from_str(&r#"[
{
"name":"seray",
"age":31,
"active":true,
"password":"123"
}]"#)
.unwrap();
assert_eq!(apply(&mut get_json(), &queries), expected);
}
#[test]
fn apply_ne_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("name", "!=", "seray"));
filter.push(Query::new("active", "!=", "true"));
}
let expected: Value = serde_json::from_str(&r#"[
{
"name":"kamil",
"age":900,
"active":false,
"password":"333"
}]"#)
.unwrap();
assert_eq!(apply(&mut get_json(), &queries), expected);
}

#[test]
fn apply_gt_lt_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("age", "<", "500"));
filter.push(Query::new("age", ">", "26"));
}
let expected: Value = serde_json::from_str(&r#"[
{
"name":"seray",
"age":31,
"active":true,
"password":"123"
}]"#)
.unwrap();
assert_eq!(apply(&mut get_json(), &queries), expected);
}
#[test]
fn apply_gte_lte_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("age", "<=", "31"));
filter.push(Query::new("age", ">=", "31"));
}
let expected: Value = serde_json::from_str(&r#"[
{
"name":"seray",
"age":31,
"active":true,
"password":"123"
}]"#)
.unwrap();
assert_eq!(apply(&mut get_json(), &queries), expected);
}
#[test]
fn apply_like_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("password", "~=", "3"));
}
let expected: Value = serde_json::from_str(&r#"[
{
"name":"kamil",
"age":900,
"active":false,
"password":"333"
},
{
"name":"hasan",
"age":25,
"active":true,
"password":"321"
}
]"#)
.unwrap();
assert_eq!(apply(&mut get_json(), &queries), expected);
}

#[test]
fn apply_in_test() {
let mut queries = Queries::new();
{
let filter = &mut queries.filter;
filter.push(Query::new("name", "|=", "kamil|hasan"));
}
let expected: Value = serde_json::from_str(&r#"[
{
"name":"kamil",
"age":900,
"active":false,
"password":"333"
},
{
"name":"hasan",
"age":25,
"active":true,
"password":"321"
}
]"#)
.unwrap();
assert_eq!(apply(&mut get_json(), &queries), expected);
}
}
8 changes: 8 additions & 0 deletions src/database/query_api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod filter;
// TODO:
// Get the result
// If it is List than do the ops
// filter & operations & full text
// Sort
// Paginate
// Slice
17 changes: 8 additions & 9 deletions src/database/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::vec::Vec;
use serde_json::Value;
use serde_json;
use service::query_api::Queries;
use database::query_api;

impl Database {
/// Retuns the list of the tables (outmost keys) from the database.
Expand All @@ -22,18 +23,16 @@ impl Database {
queries: Option<Queries>)
-> Result<Value, Errors> {
let mut data = &mut self.data;
println!("{:?}", queries);
// TODO: If path is db return db
match Self::get_object(keys, data) {
Ok(obj) => Ok(obj.clone()),
Ok(obj) => {
println!("{:?}", queries);
if let Some(q) = queries {
query_api::filter::apply(obj, &q);
}
Ok(obj.clone())
}
Err(ref msg) => Err(msg.clone()),
}
// TODO:
// Get the result
// If it is List than do the ops
// filter & operations & full text
// Sort
// Paginate
// Slice
}
}
2 changes: 1 addition & 1 deletion src/service/query_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ fn set_where_it_belongs(queries: &mut Queries, q: Query) {
}

/// A simple struct to hold query parameters well structured.
#[derive(Debug)]
#[derive(Debug,Clone)]
pub struct Queries {
/// field names to return
pub fields: Vec<String>,
Expand Down
2 changes: 1 addition & 1 deletion src/service/query_api/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! All paging related codes contained under this module.
use service::query_api::query::Query;
/// An enum to hold sort parameters with the direction.
#[derive(Debug)]
#[derive(Debug,Copy,Clone)]
pub enum Page {
/// Ascending sorting.
OFFSET(u8),
Expand Down
3 changes: 1 addition & 2 deletions src/service/query_api/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
use std::cmp::PartialEq;

/// A simple struct to hold necessary information about a query parameter.
#[derive(Eq)]
#[derive(Debug)]
#[derive(Debug,Clone,Eq)]
pub struct Query {
/// key of the parameter. It holds pure key without any `_eq` etc.
pub key: String,
Expand Down
2 changes: 1 addition & 1 deletion src/service/query_api/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::cmp::PartialEq;

/// An enum to hold sort parameters with the direction.
#[derive(Debug)]
#[derive(Debug,Clone)]
pub enum Sort {
/// Ascending sorting.
ASC(String),
Expand Down

0 comments on commit 034988a

Please sign in to comment.