Add query session and authentication logic
This commit is contained in:
parent
9e5e6efa6d
commit
c51e60e706
6 changed files with 400 additions and 40 deletions
59
src/dbs/auth.rs
Normal file
59
src/dbs/auth.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
|
||||||
|
pub enum Level {
|
||||||
|
No,
|
||||||
|
Kv,
|
||||||
|
Ns,
|
||||||
|
Db,
|
||||||
|
Sc,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
|
||||||
|
pub enum Auth {
|
||||||
|
No,
|
||||||
|
Kv,
|
||||||
|
Ns(String),
|
||||||
|
Db(String, String),
|
||||||
|
Sc(String, String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Auth {
|
||||||
|
fn default() -> Self {
|
||||||
|
Auth::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Auth {
|
||||||
|
pub fn check(&self, level: Level) -> bool {
|
||||||
|
match self {
|
||||||
|
Auth::No => match level {
|
||||||
|
Level::No => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Auth::Kv => match level {
|
||||||
|
Level::No => true,
|
||||||
|
Level::Kv => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Auth::Ns(_) => match level {
|
||||||
|
Level::No => true,
|
||||||
|
Level::Kv => true,
|
||||||
|
Level::Ns => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Auth::Db(_, _) => match level {
|
||||||
|
Level::No => true,
|
||||||
|
Level::Kv => true,
|
||||||
|
Level::Ns => true,
|
||||||
|
Level::Db => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Auth::Sc(_, _, _) => match level {
|
||||||
|
Level::No => true,
|
||||||
|
Level::Kv => true,
|
||||||
|
Level::Ns => true,
|
||||||
|
Level::Db => true,
|
||||||
|
Level::Sc => true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,45 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::executor::Executor;
|
use crate::dbs::executor::Executor;
|
||||||
use crate::dbs::response::Responses;
|
use crate::dbs::response::Responses;
|
||||||
|
use crate::dbs::session::Session;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql;
|
use crate::sql;
|
||||||
use crate::sql::query::Query;
|
use crate::sql::query::Query;
|
||||||
|
use crate::sql::value::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub type Vars<'a> = Option<HashMap<&'a str, String>>;
|
pub type Variables = Option<HashMap<String, Value>>;
|
||||||
|
|
||||||
pub fn execute(txt: &str, vars: Vars) -> Result<Responses, Error> {
|
pub async fn execute(txt: &str, session: Session, vars: Variables) -> Result<Responses, Error> {
|
||||||
// Parse the SQL query into an AST
|
// Create a new query executor
|
||||||
|
let mut exe = Executor::new();
|
||||||
|
// Create a new execution context
|
||||||
|
let ctx = session.context();
|
||||||
|
// Parse the SQL query text
|
||||||
let ast = sql::parse(txt)?;
|
let ast = sql::parse(txt)?;
|
||||||
// Create a new execution context
|
// Process all statements
|
||||||
let ctx = Context::background().freeze();
|
exe.ns = session.ns;
|
||||||
// Create a new query executor
|
exe.db = session.db;
|
||||||
let exe = Executor::new();
|
exe.execute(ctx, ast)
|
||||||
// Process all of the queries
|
|
||||||
exe.execute(&ctx, ast)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(ast: Query, vars: Vars) -> Result<Responses, Error> {
|
pub async fn process(ast: Query, session: Session, vars: Variables) -> Result<Responses, Error> {
|
||||||
// Create a new execution context
|
|
||||||
let ctx = Context::background().freeze();
|
|
||||||
// Create a new query executor
|
// Create a new query executor
|
||||||
let exe = Executor::new();
|
let mut exe = Executor::new();
|
||||||
// Process all of the queries
|
// Store session info on context
|
||||||
exe.execute(&ctx, ast)
|
let ctx = session.context();
|
||||||
|
// Process all statements
|
||||||
|
exe.ns = session.ns;
|
||||||
|
exe.db = session.db;
|
||||||
|
exe.execute(ctx, ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export(session: Session) -> Result<String, Error> {
|
||||||
|
// Create a new query executor
|
||||||
|
let mut exe = Executor::new();
|
||||||
|
// Create a new execution context
|
||||||
|
let ctx = session.context();
|
||||||
|
// Process database export
|
||||||
|
exe.ns = session.ns;
|
||||||
|
exe.db = session.db;
|
||||||
|
exe.export(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,163 @@
|
||||||
|
use crate::ctx::Context;
|
||||||
use crate::dbs::response::{Response, Responses};
|
use crate::dbs::response::{Response, Responses};
|
||||||
|
use crate::dbs::Auth;
|
||||||
|
use crate::dbs::Level;
|
||||||
|
use crate::dbs::Options;
|
||||||
use crate::dbs::Process;
|
use crate::dbs::Process;
|
||||||
use crate::dbs::Runtime;
|
use crate::dbs::Runtime;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::query::Query;
|
use crate::sql::query::Query;
|
||||||
|
use crate::sql::statement::Statement;
|
||||||
|
use crate::sql::value::Value;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Executor {
|
pub struct Executor {
|
||||||
pub id: String,
|
pub id: Option<String>,
|
||||||
pub ns: String,
|
pub ns: Option<String>,
|
||||||
pub db: String,
|
pub db: Option<String>,
|
||||||
|
pub txn: Option<()>,
|
||||||
|
pub err: Option<Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Executor {
|
impl Executor {
|
||||||
pub fn new() -> Executor {
|
pub fn new() -> Executor {
|
||||||
Executor {
|
Executor {
|
||||||
id: String::from("id"),
|
id: None,
|
||||||
ns: String::from("ns"),
|
ns: None,
|
||||||
db: String::from("db"),
|
db: None,
|
||||||
|
..Executor::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&self, ctx: &Runtime, qry: Query) -> Result<Responses, Error> {
|
pub fn check(&mut self, opt: &Options, level: Level) -> Result<(), Error> {
|
||||||
let mut r: Vec<Response> = vec![];
|
if opt.auth.check(level) == false {
|
||||||
|
return Err(Error::QueryPermissionsError);
|
||||||
|
}
|
||||||
|
if self.ns.is_none() {
|
||||||
|
return Err(Error::NsError);
|
||||||
|
}
|
||||||
|
if self.db.is_none() {
|
||||||
|
return Err(Error::DbError);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export(&mut self, ctx: Runtime) -> Result<String, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(&mut self, mut ctx: Runtime, qry: Query) -> Result<Responses, Error> {
|
||||||
|
// Initialise array of responses
|
||||||
|
let mut out: Vec<Response> = vec![];
|
||||||
|
// Create a new options
|
||||||
|
let mut opt = Options::new(&Auth::No);
|
||||||
|
// Process all statements in query
|
||||||
for stm in qry.statements().iter() {
|
for stm in qry.statements().iter() {
|
||||||
|
// Reset errors
|
||||||
|
if self.txn.is_none() {
|
||||||
|
self.err = None;
|
||||||
|
}
|
||||||
// Get the statement start time
|
// Get the statement start time
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
// Process a single statement
|
// Process a single statement
|
||||||
let res = stm.process(&ctx, self, None);
|
let res = match stm {
|
||||||
|
// Specify runtime options
|
||||||
|
Statement::Option(stm) => {
|
||||||
|
match &stm.name.name[..] {
|
||||||
|
"FIELD_QUERIES" => opt = opt.fields(stm.what),
|
||||||
|
"EVENT_QUERIES" => opt = opt.events(stm.what),
|
||||||
|
"TABLE_QUERIES" => opt = opt.tables(stm.what),
|
||||||
|
"IMPORT" => opt = opt.import(stm.what),
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Begin a new transaction
|
||||||
|
Statement::Begin(stm) => {
|
||||||
|
let res = stm.process(&ctx, &opt, self, None);
|
||||||
|
self.err = res.err();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Cancel a running transaction
|
||||||
|
Statement::Cancel(stm) => {
|
||||||
|
let res = stm.process(&ctx, &opt, self, None);
|
||||||
|
self.err = res.err();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Commit a running transaction
|
||||||
|
Statement::Commit(stm) => {
|
||||||
|
let res = stm.process(&ctx, &opt, self, None);
|
||||||
|
self.err = res.err();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Process param definition statements
|
||||||
|
Statement::Set(stm) => {
|
||||||
|
match stm.process(&ctx, &opt, self, None) {
|
||||||
|
Ok(val) => {
|
||||||
|
let mut new = Context::new(&ctx);
|
||||||
|
let key = stm.name.to_owned();
|
||||||
|
new.add_value(key, val);
|
||||||
|
ctx = new.freeze();
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
|
// Process all other normal statements
|
||||||
|
_ => {
|
||||||
|
// Enable context override
|
||||||
|
let mut ctx = Context::new(&ctx).freeze();
|
||||||
|
// Specify statement timeout
|
||||||
|
if let Some(timeout) = stm.timeout() {
|
||||||
|
let mut new = Context::new(&ctx);
|
||||||
|
new.add_timeout(timeout);
|
||||||
|
ctx = new.freeze();
|
||||||
|
}
|
||||||
|
// Process statement
|
||||||
|
let res = stm.process(&ctx, &opt, self, None);
|
||||||
|
// Catch statement timeout
|
||||||
|
if let Some(timeout) = stm.timeout() {
|
||||||
|
if ctx.is_timedout() {
|
||||||
|
self.err = Some(Error::QueryTimeoutError {
|
||||||
|
timer: timeout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Continue with result
|
||||||
|
res
|
||||||
|
}
|
||||||
|
};
|
||||||
// Get the statement end time
|
// Get the statement end time
|
||||||
let dur = now.elapsed();
|
let dur = now.elapsed();
|
||||||
|
// Check transaction errors
|
||||||
r.push(Response {
|
match &self.err {
|
||||||
sql: format!("{}", stm),
|
Some(e) => out.push(Response {
|
||||||
time: format!("{:?}", dur),
|
time: format!("{:?}", dur),
|
||||||
status: match res {
|
status: String::from("ERR"),
|
||||||
Ok(_) => String::from("OK"),
|
detail: Some(format!("{}", e)),
|
||||||
Err(_) => String::from("ERR"),
|
result: None,
|
||||||
},
|
}),
|
||||||
result: match res {
|
None => {
|
||||||
Ok(v) => Some(v),
|
// Format responses
|
||||||
Err(_) => None,
|
match res {
|
||||||
},
|
Ok(v) => out.push(Response {
|
||||||
})
|
time: format!("{:?}", dur),
|
||||||
|
status: String::from("OK"),
|
||||||
|
detail: None,
|
||||||
|
result: v.output(),
|
||||||
|
}),
|
||||||
|
Err(e) => out.push(Response {
|
||||||
|
time: format!("{:?}", dur),
|
||||||
|
status: String::from("ERR"),
|
||||||
|
detail: Some(format!("{}", e)),
|
||||||
|
result: None,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
Ok(Responses(r))
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return responses
|
||||||
|
Ok(Responses(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
|
mod auth;
|
||||||
mod dbs;
|
mod dbs;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod iterator;
|
mod iterator;
|
||||||
|
mod options;
|
||||||
mod process;
|
mod process;
|
||||||
mod response;
|
mod response;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
mod session;
|
||||||
|
|
||||||
|
pub use self::auth::*;
|
||||||
pub use self::dbs::*;
|
pub use self::dbs::*;
|
||||||
pub use self::executor::*;
|
pub use self::executor::*;
|
||||||
pub use self::iterator::*;
|
pub use self::iterator::*;
|
||||||
|
pub use self::options::*;
|
||||||
pub use self::process::*;
|
pub use self::process::*;
|
||||||
pub use self::response::*;
|
pub use self::response::*;
|
||||||
pub use self::runtime::*;
|
pub use self::runtime::*;
|
||||||
|
pub use self::session::*;
|
||||||
|
|
117
src/dbs/options.rs
Normal file
117
src/dbs/options.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use crate::cnf;
|
||||||
|
use crate::dbs::Auth;
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::version::Version;
|
||||||
|
|
||||||
|
// An Options is passed around when processing a set of query
|
||||||
|
// statements. An Options contains specific information for how
|
||||||
|
// to process each particular statement, including the record
|
||||||
|
// version to retrieve, whether futures should be processed, and
|
||||||
|
// whether field/event/table queries should be processed (useful
|
||||||
|
// when importing data, where these queries might fail).
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Options<'a> {
|
||||||
|
pub auth: &'a Auth,
|
||||||
|
pub dive: usize, // How many subqueries have we gone into?
|
||||||
|
pub force: bool, // Should we force tables/events to re-run?
|
||||||
|
pub fields: bool, // Should we process field queries?
|
||||||
|
pub events: bool, // Should we process event queries?
|
||||||
|
pub tables: bool, // Should we process table queries?
|
||||||
|
pub futures: bool, // Should we process function futures?
|
||||||
|
pub version: Option<&'a Version>, // Current
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for Options<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Options::new(&Auth::No)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Options<'a> {
|
||||||
|
// Create a new Options object
|
||||||
|
pub fn new(auth: &'a Auth) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
auth,
|
||||||
|
dive: 0,
|
||||||
|
force: false,
|
||||||
|
fields: true,
|
||||||
|
events: true,
|
||||||
|
tables: true,
|
||||||
|
futures: false,
|
||||||
|
version: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn dive(&self) -> Result<Options<'a>, Error> {
|
||||||
|
if self.dive < cnf::MAX_RECURSIVE_QUERIES {
|
||||||
|
Ok(Options {
|
||||||
|
dive: self.dive + 1,
|
||||||
|
..*self
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::RecursiveSubqueryError {
|
||||||
|
limit: self.dive,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn force(&self, v: bool) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
force: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn fields(&self, v: bool) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
fields: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn events(&self, v: bool) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
events: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn tables(&self, v: bool) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
tables: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn import(&self, v: bool) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
fields: v,
|
||||||
|
events: v,
|
||||||
|
tables: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn futures(&self, v: bool) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
futures: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Options object for a subquery
|
||||||
|
pub fn version(&self, v: Option<&'a Version>) -> Options<'a> {
|
||||||
|
Options {
|
||||||
|
version: v,
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
src/dbs/session.rs
Normal file
49
src/dbs/session.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use crate::ctx::Context;
|
||||||
|
use crate::dbs::Auth;
|
||||||
|
use crate::dbs::Runtime;
|
||||||
|
use crate::sql::value::Value;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct Session {
|
||||||
|
pub au: Auth, // Authentication info
|
||||||
|
pub ip: Option<String>, // Session ip address
|
||||||
|
pub or: Option<String>, // Session origin
|
||||||
|
pub id: Option<String>, // Session id
|
||||||
|
pub ns: Option<String>, // Namespace
|
||||||
|
pub db: Option<String>, // Database
|
||||||
|
pub sc: Option<String>, // Scope
|
||||||
|
pub sd: Option<Value>, // Scope auth data
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub fn context(&self) -> Runtime {
|
||||||
|
let mut ctx = Context::background();
|
||||||
|
// Add session value
|
||||||
|
let key = String::from("session");
|
||||||
|
let val: Value = self.into();
|
||||||
|
ctx.add_value(key, val);
|
||||||
|
// Add scope value
|
||||||
|
let key = String::from("scope");
|
||||||
|
let val: Value = self.sc.to_owned().into();
|
||||||
|
ctx.add_value(key, val);
|
||||||
|
// Add auth data
|
||||||
|
let key = String::from("auth");
|
||||||
|
let val: Value = self.sd.to_owned().into();
|
||||||
|
ctx.add_value(key, val);
|
||||||
|
// Output context
|
||||||
|
ctx.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Value> for &Session {
|
||||||
|
fn into(self) -> Value {
|
||||||
|
Value::from(map! {
|
||||||
|
"ip".to_string() => self.ip.to_owned().into(),
|
||||||
|
"or".to_string() => self.or.to_owned().into(),
|
||||||
|
"id".to_string() => self.id.to_owned().into(),
|
||||||
|
"ns".to_string() => self.ns.to_owned().into(),
|
||||||
|
"db".to_string() => self.db.to_owned().into(),
|
||||||
|
"sc".to_string() => self.sc.to_owned().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue