Integrate Query Parser

Integrate the query parser to allow filters to be provided to collection
end points.

Refactor the shared state to better encapsulate the model.
This commit is contained in:
2019-09-12 11:33:44 +02:00
parent 29e78454a0
commit 485e6d342b
13 changed files with 295 additions and 2009 deletions

1872
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ include = ["Cargo.toml", "README.md", "LICENSE", "ACKNOWLEDGEMENTS", "src/**/*.r
[dependencies]
mercator_db = "^0.1"
#mercator_parser = "^0.1"
mercator_parser = "^0.1"
actix = "^0.7"
actix-web = "^0.7"

View File

@@ -70,8 +70,8 @@ paths:
summary: >
Retrieve a list of space definition names.
operationId: post_spaces
# parameters:
# - $ref: '#/parameters/Filters'
parameters:
- $ref: '#/parameters/Filters'
responses:
'200':
$ref: '#/responses/ArrayOfStrings'
@@ -204,8 +204,8 @@ paths:
summary: >
Retrieve a list of core names.
operationId: post_cores
# parameters:
# - $ref: '#/parameters/Filters'
parameters:
- $ref: '#/parameters/Filters'
responses:
'200':
$ref: '#/responses/ArrayOfStrings'
@@ -356,8 +356,8 @@ paths:
summary: >
Retrieve a list of spatial object.
operationId: post_spatial_objects
# parameters:
# - $ref: '#/parameters/Filters'
parameters:
- $ref: '#/parameters/Filters'
responses:
'200':
$ref: '#/responses/ArrayOfStrings'
@@ -667,6 +667,7 @@ parameters:
Filters:
name: filters
in: body
required: false
description: >
Filter string to use to select the data.

View File

@@ -5,6 +5,7 @@ extern crate measure_time;
extern crate serde_derive;
mod rest_api;
mod shared_state;
use std::process::exit;
use std::sync::Arc;
@@ -14,7 +15,7 @@ use mercator_db::json::model;
use mercator_db::json::storage;
use mercator_db::DataBase;
pub type SharedState = DataBase;
use shared_state::SharedState;
/*
fn into_bool(string: &str) -> bool {
@@ -126,48 +127,20 @@ fn main() {
}
};
// Convert to binary the JSON data:
if true {
info_time!("Converting to binary JSON data");
storage::convert(&import);
}
// Build a Database Index:
if true {
info_time!("Building database index");
storage::build(&import);
}
// Load a Database:
{
info_time!("Loading database index");
db = DataBase::load(&import).unwrap();
}
/*
let core = db.core(&import).unwrap();
let space = db.space("std").unwrap();
let lower = space.encode(&[0.2, 0.2, 0.2]).unwrap();
let higher = space.encode(&[0.8, 0.8, 0.8]).unwrap();
let shape = Shape::BoundingBox(lower.clone(), higher.clone());
let r;
{
info_time!("Query by box {:?} - {:?}", lower, higher);
r = core.get_by_shape(&shape, 0.0).unwrap();
}
println!("get_by_shape {:?}: {}", shape, r.len());
println!("{:?}: {:?}\n", shape, r[0]);
*/
// END of Temporary bloc
}
let state = SharedState::new(db);
rest_api::run(
hostname,
port,
base,
allowed_origins,
Arc::new(RwLock::new(db)),
Arc::new(RwLock::new(state)),
);
}

View File

@@ -1,15 +1,19 @@
use actix_web::HttpRequest;
use actix_web::HttpResponse;
use actix_web::Json;
use super::error_400;
use super::AppState;
use super::Filters;
use super::StringOrStaticFileResult;
pub fn health(_req: &HttpRequest<AppState>) -> HttpResponse {
HttpResponse::Ok().finish()
}
pub fn query(_req: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn query(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("query Triggered!");
error_400()
}

View File

@@ -1,27 +1,23 @@
use actix_web::HttpRequest;
use actix_web::Json;
use actix_web::Path;
use super::error_400;
use super::error_404;
use super::ok_200;
use super::AppState;
use super::StringOrStaticFileResult;
use actix_web::HttpRequest;
use actix_web::Path;
/*
pub fn post(_req: &HttpRequest<AppState>) -> StringOrStaticFileResult {
trace!("POST Triggered!");
error_400()
}
*/
#[derive(Clone, Deserialize, Serialize)]
struct Core {
pub struct Core {
name: String,
version: String,
scales: Vec<Vec<i32>>,
}
pub fn put((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
pub fn put(
(_path, _core, _state): (Path<String>, Json<Core>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("PUT Triggered!");
error_400()
}
@@ -29,9 +25,9 @@ pub fn put((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrSt
pub fn get((core, state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
trace!("GET Triggered!");
let core = core.to_string();
let db = state.state().shared.read().unwrap();
let context = state.state().shared.read().unwrap();
match db.core(core) {
match context.db().core(core) {
Ok(core) => ok_200(&Core {
name: core.name().clone(),
version: core.version().clone(),
@@ -43,7 +39,9 @@ pub fn get((core, state): (Path<String>, HttpRequest<AppState>)) -> StringOrStat
}
}
pub fn patch((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
pub fn patch(
(_path, _core, _state): (Path<String>, Json<Core>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("PATCH Triggered!");
error_400()
}

View File

@@ -1,34 +1,55 @@
use actix_web::HttpRequest;
use actix_web::Json;
use super::error_400;
use super::ok_200;
use super::AppState;
use super::Filters;
use super::StringOrStaticFileResult;
pub fn post(state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn post(
(parameters, state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("POST Triggered!");
let db = state.state().shared.read().unwrap();
let context = state.state().shared.read().unwrap();
let parameters = Filters::get(parameters);
ok_200(db.core_keys())
let mut results = match parameters.filters {
None => context.db().core_keys().clone(),
Some(filter) => context
.db()
.core_keys()
.iter()
.filter_map(|core| match context.filter(&filter, core, None, None) {
Err(_) => None, //FIXME: Return errors here instead!!
Ok(_) => Some(core.to_string()),
})
.collect(),
};
results.sort_unstable();
results.dedup();
ok_200(&results)
}
pub fn put(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn put(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("PUT Triggered!");
error_400()
}
/*
pub fn get(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
trace!("GET Triggered!");
error400()
}*/
pub fn patch(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn patch(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("PATCH Triggered!");
error_400()
}
pub fn delete(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn delete(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("DELETE Triggered!");
error_400()
}

View File

@@ -18,6 +18,7 @@ use actix_web::fs;
use actix_web::http;
use actix_web::http::Method;
use actix_web::http::StatusCode;
use actix_web::middleware;
use actix_web::middleware::cors::Cors;
use actix_web::pred;
use actix_web::server;
@@ -25,6 +26,7 @@ use actix_web::server::HttpHandler;
use actix_web::server::HttpHandlerTask;
use actix_web::App;
use actix_web::Either;
use actix_web::Json;
use serde::Serialize;
use crate::SharedState;
@@ -34,21 +36,25 @@ pub struct AppState {
shared: Arc<RwLock<SharedState>>,
}
/* EXAMPLE FOR STATE USAGE
// simple handle
fn index(req: &HttpRequest<AppState>) -> HttpResponse {
println!("{:?}", req);
{
// So that we release ASAP the exclusive lock.
*(req.state().shared.write().unwrap()) += 1;
}
HttpResponse::BadRequest().body(format!(
"Num of requests: {}",
req.state().shared.read().unwrap()
))
#[derive(Debug, Deserialize)]
pub struct Filters {
filters: Option<String>,
ids_only: Option<bool>,
}
impl Filters {
pub fn get(parameters: Option<Json<Filters>>) -> Self {
trace!("PARAMETERS {:#?}", parameters);
match parameters {
None => Filters {
filters: None,
ids_only: Some(true),
},
Some(p) => p.0,
}
}
}
*/
type StringOrStaticFileResult = Either<String, fs::NamedFile>;
@@ -120,19 +126,19 @@ where
cors.allowed_methods(vec!["GET", "POST", "UPDATE", "PATCH", "DELETE", "OPTIONS"])
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
.allowed_header(http::header::CONTENT_TYPE)
.max_age(60)
.max_age(600)
.resource("/queries", |r| {
r.method(Method::POST).f(actions::query);
r.method(Method::POST).with(actions::query);
r.route()
.filter(pred::Not(pred::Post()))
.f(default::page_400);
})
// SPACES -------------------------------------------------------------------
.resource("/spaces", |r| {
r.method(Method::POST).f(spaces::post);
r.method(Method::PUT).f(spaces::put);
r.method(Method::PATCH).f(spaces::patch);
r.method(Method::DELETE).f(spaces::delete);
r.method(Method::POST).with(spaces::post);
r.method(Method::PUT).with(spaces::put);
r.method(Method::PATCH).with(spaces::patch);
r.method(Method::DELETE).with(spaces::delete);
})
.resource("/spaces/{name}", |r| {
r.method(Method::PUT).with(space::put);
@@ -142,10 +148,10 @@ where
})
// DATASETS -------------------------------------------------------------------
.resource("/cores", |r| {
r.method(Method::POST).f(&cores::post);
r.method(Method::PUT).f(&cores::put);
r.method(Method::PATCH).f(&cores::patch);
r.method(Method::DELETE).f(&cores::delete);
r.method(Method::POST).with(&cores::post);
r.method(Method::PUT).with(&cores::put);
r.method(Method::PATCH).with(&cores::patch);
r.method(Method::DELETE).with(&cores::delete);
})
.resource("/cores/{name}", |r| {
r.method(Method::PUT).with(core::put);

View File

@@ -9,13 +9,6 @@ use super::ok_200;
use super::AppState;
use super::StringOrStaticFileResult;
/*
pub fn post(_req: &HttpRequest<AppState>) ->StringOrStaticFileResult {
info!("POST Triggered!");
error_400()
}
*/
pub fn put((path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
trace!("PUT Triggered on {}", path);
error_400()
@@ -24,9 +17,9 @@ pub fn put((path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrSta
pub fn get((path, state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
trace!("GET Triggered on '{}'", path);
let name = path.to_string();
let db = state.state().shared.read().unwrap();
let context = state.state().shared.read().unwrap();
match db.space(name) {
match context.db().space(name) {
Ok(space) => {
let space: model::Space = space.into();
ok_200(&space)

View File

@@ -1,34 +1,59 @@
use actix_web::HttpRequest;
use actix_web::Json;
use super::error_400;
use super::ok_200;
use super::AppState;
use super::Filters;
use super::StringOrStaticFileResult;
pub fn post(state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn post(
(parameters, state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("POST Triggered!");
let db = state.state().shared.read().unwrap();
let context = state.state().shared.read().unwrap();
let parameters = Filters::get(parameters);
ok_200(db.space_keys())
let mut results = match parameters.filters {
None => context.db().space_keys().clone(),
Some(filter) => context
.db()
.core_keys()
.iter()
.flat_map(|core| match context.filter(&filter, core, None, None) {
Err(_) => vec![], //FIXME: Return errors here instead!!
Ok(r) => {
let mut r = r.into_iter().map(|o| o.space_id).collect::<Vec<_>>();
r.sort_unstable();
r.dedup();
r
}
})
.collect(),
};
results.sort_unstable();
results.dedup();
ok_200(&results)
}
pub fn put(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn put(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("PUT Triggered!");
error_400()
}
/*
pub fn get(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
trace!("GET Triggered!");
error_400()
}*/
pub fn patch(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn patch(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("PATCH Triggered!");
error_400()
}
pub fn delete(_state: &HttpRequest<AppState>) -> StringOrStaticFileResult {
pub fn delete(
(_parameters, _state): (Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("DELETE Triggered!");
error_400()
}

View File

@@ -1,3 +1,7 @@
use actix_web::HttpRequest;
use actix_web::Json;
use actix_web::Path;
use super::error_400;
use super::error_404;
use super::ok_200;
@@ -5,16 +9,6 @@ use super::AppState;
use super::StringOrStaticFileResult;
use crate::model::to_spatial_objects;
use actix_web::HttpRequest;
use actix_web::Path;
/*
pub fn post((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
info!("POST Triggered!");
error_400()
}
*/
pub fn put(
(core, id, state): (Path<String>, Path<String>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
@@ -29,11 +23,12 @@ pub fn get(
let (core, id) = path.into_inner();
let core = core.to_string();
let id = id.to_string();
let db = state.state().shared.read().unwrap();
let context = state.state().shared.read().unwrap();
let db = context.db();
match db.core(core) {
Ok(core) => match core.get_by_id(&db, &id, None, 0.0) {
Ok(objects) => ok_200(&to_spatial_objects(&db, objects)),
Ok(core) => match core.get_by_id(db, &id, None, 0.0) {
Ok(objects) => ok_200(&to_spatial_objects(db, objects)),
Err(_) => error_404(),
},
Err(_) => error_404(),

View File

@@ -1,23 +1,37 @@
use actix_web::HttpRequest;
use actix_web::Json;
use actix_web::Path;
use super::error_400;
use super::error_404;
use super::ok_200;
use super::AppState;
use super::Filters;
use super::StringOrStaticFileResult;
pub fn post((core, state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
pub fn post(
(core_id, parameters, state): (Path<String>, Option<Json<Filters>>, HttpRequest<AppState>),
) -> StringOrStaticFileResult {
trace!("POST Triggered!");
let core = core.to_string();
let db = state.state().shared.read().unwrap();
let core = core_id.to_string();
let context = state.state().shared.read().unwrap();
match db.core(core) {
match context.db().core(core) {
Ok(core) => {
// Generate a list of oid.
let v: Vec<&String> = core.keys().iter().map(|o| o.id()).collect();
let parameters = Filters::get(parameters);
ok_200(&v)
// Generate a list of oid.
let mut results = match parameters.filters {
None => core.keys().iter().map(|o| o.id().clone()).collect(),
Some(filter) => match context.filter(&filter, &core_id, None, None) {
Err(_) => vec![], //FIXME: Return errors here instead!!
Ok(objects) => objects.iter().map(|o| o.value.id().clone()).collect(),
},
};
results.sort_unstable();
results.dedup();
ok_200(&results)
}
Err(_) => error_404(),
}
@@ -28,12 +42,6 @@ pub fn put((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrSt
error_400()
}
/*
pub fn get((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
trace!("GET Triggered!");
error400()
*/
pub fn patch((_path, _state): (Path<String>, HttpRequest<AppState>)) -> StringOrStaticFileResult {
trace!("PATCH Triggered!");
error_400()

134
src/shared_state.rs Normal file
View File

@@ -0,0 +1,134 @@
use mercator_db::DataBase;
use parser::Executor;
use parser::FiltersParser;
use parser::QueryParser;
use parser::Validator;
pub struct SharedState {
db: DataBase,
query_parser: QueryParser,
filter_parser: FiltersParser,
}
impl SharedState {
pub fn new(db: DataBase) -> Self {
SharedState {
db,
query_parser: QueryParser::new(),
filter_parser: FiltersParser::new(),
}
}
pub fn db(&self) -> &DataBase {
&self.db
}
pub fn filter_parser(&self) -> &FiltersParser {
&self.filter_parser
}
pub fn query_parser(&self) -> &QueryParser {
&self.query_parser
}
pub fn filter(
&self,
input: &str,
core: &str,
output_space: Option<&str>,
threshold_volume: Option<f64>,
) -> mercator_db::ResultSet {
let parser = self.filter_parser();
let parse;
// Parse Input
{
info_time!("Parsing");
parse = parser.parse(input);
}
match parse {
Err(e) => {
debug!("Parsing failed: \n{:?}", e);
Err(format!("{}", e))
}
Ok(tree) => {
let validation;
let execution;
// Check type coherence & validate tree
{
info_time!("Type check");
validation = tree.validate();
}
if validation.is_err() {
debug!("Type check failed");
return Err("Type check failed".to_string());
}
// Execute filter.
{
info_time!("Execution");
execution = tree.execute(self.db(), core, output_space, threshold_volume);
}
match execution {
Err(e) => {
debug!("Parsing failed: \n{:?}", e);
Err(e.to_string())
}
results @ Ok(_) => results,
}
}
}
}
pub fn query(
&self,
input: &str,
core: &str,
output_space: Option<&str>,
threshold_volume: Option<f64>,
) -> mercator_db::ResultSet {
let parser = self.query_parser();
let parse;
// Parse Input
{
info_time!("Parsing");
parse = parser.parse(input);
}
match parse {
Err(e) => {
debug!("Parsing failed: \n{:?}", e);
Err(e.to_string())
}
Ok(None) => Ok(vec![]),
Ok(Some(tree)) => {
let validation;
let execution;
// Check type coherence & validate tree
{
info_time!("Type check");
validation = tree.validate();
}
if validation.is_err() {
debug!("Type check failed");
return Err("Type check failed".to_string());
}
// Execute filter.
{
info_time!("Execution");
execution = tree.execute(self.db(), core, output_space, threshold_volume);
}
match execution {
Err(e) => {
debug!("Parsing failed: \n{:?}", e);
Err(e.to_string())
}
results @ Ok(_) => results,
}
}
}
}
}