Add ability to define global parameters on database

Closes #133
This commit is contained in:
Tobie Morgan Hitchcock 2023-01-09 12:43:47 +00:00
parent 2949ea7f4d
commit 7fabf54593
13 changed files with 353 additions and 33 deletions

View file

@ -202,6 +202,10 @@ pub enum Error {
#[error("The scope token does not exist")]
StNotFound,
/// The requested param does not exist
#[error("The param does not exist")]
PaNotFound,
/// The requested table does not exist
#[error("The table does not exist")]
TbNotFound,

View file

@ -10,6 +10,7 @@
/// DL /*{ns}*{db}!dl{us}
/// DT /*{ns}*{db}!dt{tk}
/// SC /*{ns}*{db}!sc{sc}
/// PA /*{ns}*{db}!pa{pa}
/// TB /*{ns}*{db}!tb{tb}
/// LQ /*{ns}*{db}!lq{lq}
///
@ -46,6 +47,7 @@ pub mod namespace;
pub mod nl;
pub mod ns;
pub mod nt;
pub mod pa;
pub mod sc;
pub mod scope;
pub mod st;

64
lib/src/key/pa.rs Normal file
View file

@ -0,0 +1,64 @@
use derive::Key;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
pub struct Pa {
__: u8,
_a: u8,
pub ns: String,
_b: u8,
pub db: String,
_c: u8,
_d: u8,
_e: u8,
pub pa: String,
}
pub fn new(ns: &str, db: &str, pa: &str) -> Pa {
Pa::new(ns.to_string(), db.to_string(), pa.to_string())
}
pub fn prefix(ns: &str, db: &str) -> Vec<u8> {
let mut k = super::database::new(ns, db).encode().unwrap();
k.extend_from_slice(&[0x21, 0x70, 0x61, 0x00]);
k
}
pub fn suffix(ns: &str, db: &str) -> Vec<u8> {
let mut k = super::database::new(ns, db).encode().unwrap();
k.extend_from_slice(&[0x21, 0x70, 0x61, 0xff]);
k
}
impl Pa {
pub fn new(ns: String, db: String, pa: String) -> Pa {
Pa {
__: 0x2f, // /
_a: 0x2a, // *
ns,
_b: 0x2a, // *
db,
_c: 0x21, // !
_d: 0x70, // p
_e: 0x61, // a
pa,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn key() {
use super::*;
#[rustfmt::skip]
let val = Pa::new(
"test".to_string(),
"test".to_string(),
"test".to_string(),
);
let enc = Pa::encode(&val).unwrap();
let dec = Pa::decode(&enc).unwrap();
assert_eq!(val, dec);
}
}

View file

@ -5,6 +5,7 @@ use crate::sql::statements::DefineFieldStatement;
use crate::sql::statements::DefineIndexStatement;
use crate::sql::statements::DefineLoginStatement;
use crate::sql::statements::DefineNamespaceStatement;
use crate::sql::statements::DefineParamStatement;
use crate::sql::statements::DefineScopeStatement;
use crate::sql::statements::DefineTableStatement;
use crate::sql::statements::DefineTokenStatement;
@ -25,6 +26,7 @@ pub enum Entry {
Dts(Arc<[DefineTokenStatement]>),
Scs(Arc<[DefineScopeStatement]>),
Sts(Arc<[DefineTokenStatement]>),
Pas(Arc<[DefineParamStatement]>),
Tbs(Arc<[DefineTableStatement]>),
Evs(Arc<[DefineEventStatement]>),
Fds(Arc<[DefineFieldStatement]>),

View file

@ -16,6 +16,7 @@ use sql::statements::DefineFieldStatement;
use sql::statements::DefineIndexStatement;
use sql::statements::DefineLoginStatement;
use sql::statements::DefineNamespaceStatement;
use sql::statements::DefineParamStatement;
use sql::statements::DefineScopeStatement;
use sql::statements::DefineTableStatement;
use sql::statements::DefineTokenStatement;
@ -812,6 +813,28 @@ impl Transaction {
}
}
}
/// Retrieve all scope definitions for a specific database.
pub async fn all_pa(
&mut self,
ns: &str,
db: &str,
) -> Result<Arc<[DefineParamStatement]>, Error> {
let key = crate::key::pa::prefix(ns, db);
match self.cache.exi(&key) {
true => match self.cache.get(&key) {
Some(Entry::Pas(v)) => Ok(v),
_ => unreachable!(),
},
_ => {
let beg = crate::key::pa::prefix(ns, db);
let end = crate::key::pa::suffix(ns, db);
let val = self.getr(beg..end, u32::MAX).await?;
let val = val.convert().into();
self.cache.set(key, Entry::Pas(Arc::clone(&val)));
Ok(val)
}
}
}
/// Retrieve all table definitions for a specific database.
pub async fn all_tb(
&mut self,
@ -1018,6 +1041,17 @@ impl Transaction {
let val = self.get(key).await?.ok_or(Error::StNotFound)?;
Ok(val.into())
}
/// Retrieve a specific param definition.
pub async fn get_pa(
&mut self,
ns: &str,
db: &str,
pa: &str,
) -> Result<DefineParamStatement, Error> {
let key = crate::key::pa::new(ns, db, pa);
let val = self.get(key).await?.ok_or(Error::PaNotFound)?;
Ok(val.into())
}
/// Retrieve a specific table definition.
pub async fn get_tb(
&mut self,
@ -1329,6 +1363,20 @@ impl Transaction {
chn.send(bytes!("")).await?;
}
}
// Output PARAMS
{
let pas = self.all_pa(ns, db).await?;
if !pas.is_empty() {
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!("-- PARAMS")).await?;
chn.send(bytes!("-- ------------------------------")).await?;
chn.send(bytes!("")).await?;
for pa in pas.iter() {
chn.send(bytes!(format!("{};", pa))).await?;
}
chn.send(bytes!("")).await?;
}
}
// Output TABLES
{
let tbs = self.all_tb(ns, db).await?;

View file

@ -42,6 +42,7 @@ impl Param {
match self.first() {
// The first part will be a field
Some(Part::Field(v)) => match v.as_str() {
// This is a special param
"this" | "self" => match doc {
// The base document exists
Some(v) => {
@ -55,8 +56,9 @@ impl Param {
// The base document does not exist
None => Ok(Value::None),
},
// This is a normal param
_ => match ctx.value(v) {
// The base variable exists
// The param has been set locally
Some(v) => {
// Get the path parts
let pth: &[Part] = self;
@ -65,8 +67,22 @@ impl Param {
// Return the desired field
res.get(ctx, opt, txn, pth.next()).await
}
// The base variable does not exist
None => Ok(Value::None),
// The param has not been set locally
None => {
// Clone transaction
let run = txn.clone();
// Claim transaction
let mut run = run.lock().await;
// Get the param definition
let val = run.get_pa(opt.ns(), opt.db(), v).await;
// Check if the param has been set globally
match val {
// The param has been set globally
Ok(v) => Ok(v.value),
// The param has not been set globally
Err(_) => Ok(Value::None),
}
}
},
},
_ => unreachable!(),

View file

@ -23,6 +23,7 @@ use argon2::Argon2;
use derive::Store;
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::char;
use nom::combinator::{map, opt};
use nom::multi::many0;
use nom::sequence::tuple;
@ -40,6 +41,7 @@ pub enum DefineStatement {
Login(DefineLoginStatement),
Token(DefineTokenStatement),
Scope(DefineScopeStatement),
Param(DefineParamStatement),
Table(DefineTableStatement),
Event(DefineEventStatement),
Field(DefineFieldStatement),
@ -60,6 +62,7 @@ impl DefineStatement {
Self::Login(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Token(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Scope(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Param(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Table(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Event(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Field(ref v) => v.compute(ctx, opt, txn, doc).await,
@ -76,6 +79,7 @@ impl fmt::Display for DefineStatement {
Self::Login(v) => Display::fmt(v, f),
Self::Token(v) => Display::fmt(v, f),
Self::Scope(v) => Display::fmt(v, f),
Self::Param(v) => Display::fmt(v, f),
Self::Table(v) => Display::fmt(v, f),
Self::Event(v) => Display::fmt(v, f),
Self::Field(v) => Display::fmt(v, f),
@ -91,6 +95,7 @@ pub fn define(i: &str) -> IResult<&str, DefineStatement> {
map(login, DefineStatement::Login),
map(token, DefineStatement::Token),
map(scope, DefineStatement::Scope),
map(param, DefineStatement::Param),
map(table, DefineStatement::Table),
map(event, DefineStatement::Event),
map(field, DefineStatement::Field),
@ -569,6 +574,68 @@ fn scope_signin(i: &str) -> IResult<&str, DefineScopeOption> {
// --------------------------------------------------
// --------------------------------------------------
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)]
pub struct DefineParamStatement {
pub name: Ident,
pub value: Value,
}
impl DefineParamStatement {
pub(crate) async fn compute(
&self,
_ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
_doc: Option<&Value>,
) -> Result<Value, Error> {
// Selected DB?
opt.needs(Level::Db)?;
// Allowed to run?
opt.check(Level::Db)?;
// Clone transaction
let run = txn.clone();
// Claim transaction
let mut run = run.lock().await;
// Process the statement
let key = crate::key::pa::new(opt.ns(), opt.db(), &self.name);
run.add_ns(opt.ns(), opt.strict).await?;
run.add_db(opt.ns(), opt.db(), opt.strict).await?;
run.set(key, self).await?;
// Ok all good
Ok(Value::None)
}
}
impl fmt::Display for DefineParamStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DEFINE PARAM ${} VALUE {}", self.name, self.value)
}
}
fn param(i: &str) -> IResult<&str, DefineParamStatement> {
let (i, _) = tag_no_case("DEFINE")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("PARAM")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = char('$')(i)?;
let (i, name) = ident(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("VALUE")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, value) = value(i)?;
Ok((
i,
DefineParamStatement {
name,
value,
},
))
}
// --------------------------------------------------
// --------------------------------------------------
// --------------------------------------------------
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)]
pub struct DefineTableStatement {
pub name: Ident,

View file

@ -70,18 +70,18 @@ impl InfoStatement {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("db".to_owned(), tmp.into());
// Process the tokens
let mut tmp = Object::default();
for v in run.all_nt(opt.ns()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("nt".to_owned(), tmp.into());
// Process the logins
let mut tmp = Object::default();
for v in run.all_nl(opt.ns()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("nl".to_owned(), tmp.into());
// Process the tokens
let mut tmp = Object::default();
for v in run.all_nt(opt.ns()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("nt".to_owned(), tmp.into());
// Ok all good
Value::from(res).ok()
}
@ -96,30 +96,36 @@ impl InfoStatement {
let mut run = run.lock().await;
// Create the result set
let mut res = Object::default();
// Process the tables
let mut tmp = Object::default();
for v in run.all_tb(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("tb".to_owned(), tmp.into());
// Process the scopes
let mut tmp = Object::default();
for v in run.all_sc(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("sc".to_owned(), tmp.into());
// Process the tokens
let mut tmp = Object::default();
for v in run.all_dt(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("dt".to_owned(), tmp.into());
// Process the logins
let mut tmp = Object::default();
for v in run.all_dl(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("dl".to_owned(), tmp.into());
// Process the tokens
let mut tmp = Object::default();
for v in run.all_dt(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("dt".to_owned(), tmp.into());
// Process the params
let mut tmp = Object::default();
for v in run.all_pa(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("pa".to_owned(), tmp.into());
// Process the scopes
let mut tmp = Object::default();
for v in run.all_sc(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("sc".to_owned(), tmp.into());
// Process the tables
let mut tmp = Object::default();
for v in run.all_tb(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("tb".to_owned(), tmp.into());
// Ok all good
Value::from(res).ok()
}
@ -166,18 +172,18 @@ impl InfoStatement {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("fd".to_owned(), tmp.into());
// Process the indexes
let mut tmp = Object::default();
for v in run.all_ix(opt.ns(), opt.db(), tb).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("ix".to_owned(), tmp.into());
// Process the tables
let mut tmp = Object::default();
for v in run.all_ft(opt.ns(), opt.db(), tb).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("ft".to_owned(), tmp.into());
// Process the indexes
let mut tmp = Object::default();
for v in run.all_ix(opt.ns(), opt.db(), tb).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("ix".to_owned(), tmp.into());
// Ok all good
Value::from(res).ok()
}

View file

@ -44,6 +44,7 @@ pub use self::define::DefineIndexStatement;
pub use self::define::DefineLoginOption;
pub use self::define::DefineLoginStatement;
pub use self::define::DefineNamespaceStatement;
pub use self::define::DefineParamStatement;
pub use self::define::DefineScopeOption;
pub use self::define::DefineScopeStatement;
pub use self::define::DefineStatement;
@ -57,6 +58,7 @@ pub use self::remove::RemoveFieldStatement;
pub use self::remove::RemoveIndexStatement;
pub use self::remove::RemoveLoginStatement;
pub use self::remove::RemoveNamespaceStatement;
pub use self::remove::RemoveParamStatement;
pub use self::remove::RemoveScopeStatement;
pub use self::remove::RemoveStatement;
pub use self::remove::RemoveTableStatement;

View file

@ -13,6 +13,7 @@ use crate::sql::value::Value;
use derive::Store;
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::char;
use nom::combinator::{map, opt};
use nom::sequence::tuple;
use serde::{Deserialize, Serialize};
@ -25,6 +26,7 @@ pub enum RemoveStatement {
Login(RemoveLoginStatement),
Token(RemoveTokenStatement),
Scope(RemoveScopeStatement),
Param(RemoveParamStatement),
Table(RemoveTableStatement),
Event(RemoveEventStatement),
Field(RemoveFieldStatement),
@ -45,6 +47,7 @@ impl RemoveStatement {
Self::Login(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Token(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Scope(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Param(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Table(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Event(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Field(ref v) => v.compute(ctx, opt, txn, doc).await,
@ -61,6 +64,7 @@ impl Display for RemoveStatement {
Self::Login(v) => Display::fmt(v, f),
Self::Token(v) => Display::fmt(v, f),
Self::Scope(v) => Display::fmt(v, f),
Self::Param(v) => Display::fmt(v, f),
Self::Table(v) => Display::fmt(v, f),
Self::Event(v) => Display::fmt(v, f),
Self::Field(v) => Display::fmt(v, f),
@ -76,6 +80,7 @@ pub fn remove(i: &str) -> IResult<&str, RemoveStatement> {
map(login, RemoveStatement::Login),
map(token, RemoveStatement::Token),
map(scope, RemoveStatement::Scope),
map(param, RemoveStatement::Param),
map(table, RemoveStatement::Table),
map(event, RemoveStatement::Event),
map(field, RemoveStatement::Field),
@ -428,6 +433,60 @@ fn scope(i: &str) -> IResult<&str, RemoveScopeStatement> {
// --------------------------------------------------
// --------------------------------------------------
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)]
pub struct RemoveParamStatement {
pub name: Ident,
}
impl RemoveParamStatement {
pub(crate) async fn compute(
&self,
_ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
_doc: Option<&Value>,
) -> Result<Value, Error> {
// Selected DB?
opt.needs(Level::Db)?;
// Allowed to run?
opt.check(Level::Db)?;
// Clone transaction
let run = txn.clone();
// Claim transaction
let mut run = run.lock().await;
// Delete the definition
let key = crate::key::pa::new(opt.ns(), opt.db(), &self.name);
run.del(key).await?;
// Ok all good
Ok(Value::None)
}
}
impl fmt::Display for RemoveParamStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "REMOVE PARAM {}", self.name)
}
}
fn param(i: &str) -> IResult<&str, RemoveParamStatement> {
let (i, _) = tag_no_case("REMOVE")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("PARAM")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = char('$')(i)?;
let (i, name) = ident(i)?;
Ok((
i,
RemoveParamStatement {
name,
},
))
}
// --------------------------------------------------
// --------------------------------------------------
// --------------------------------------------------
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)]
pub struct RemoveTableStatement {
pub name: Ident,

View file

@ -76,6 +76,9 @@ async fn define_statement_table_drop() -> Result<(), Error> {
"{
dl: {},
dt: {},
pa: {},
sc: {},
pa: {},
sc: {},
tb: { test: 'DEFINE TABLE test DROP SCHEMALESS' },
}",
@ -104,6 +107,7 @@ async fn define_statement_table_schemaless() -> Result<(), Error> {
"{
dl: {},
dt: {},
pa: {},
sc: {},
tb: { test: 'DEFINE TABLE test SCHEMALESS' },
}",
@ -136,6 +140,7 @@ async fn define_statement_table_schemafull() -> Result<(), Error> {
"{
dl: {},
dt: {},
pa: {},
sc: {},
tb: { test: 'DEFINE TABLE test SCHEMAFULL' },
}",
@ -164,6 +169,7 @@ async fn define_statement_table_schemaful() -> Result<(), Error> {
"{
dl: {},
dt: {},
pa: {},
sc: {},
tb: { test: 'DEFINE TABLE test SCHEMAFULL' },
}",

View file

@ -5,6 +5,49 @@ use surrealdb::err::Error;
use surrealdb::kvs::Datastore;
use surrealdb::sql::Value;
#[tokio::test]
async fn define_global_param() -> Result<(), Error> {
let sql = "
DEFINE PARAM $test VALUE 12345;
INFO FOR DB;
SELECT * FROM $test;
LET $test = 56789;
SELECT * FROM $test;
";
let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(&sql, &ses, None, false).await?;
assert_eq!(res.len(), 5);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
dl: {},
dt: {},
pa: { test: 'DEFINE PARAM $test VALUE 12345' },
sc: {},
tb: {},
}",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[12345]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result?;
let val = Value::parse("[56789]");
assert_eq!(tmp, val);
//
Ok(())
}
#[tokio::test]
async fn define_protected_param() -> Result<(), Error> {
let sql = "

View file

@ -192,6 +192,7 @@ async fn loose_mode_all_ok() -> Result<(), Error> {
"{
dl: {},
dt: {},
pa: {},
sc: {},
tb: { test: 'DEFINE TABLE test SCHEMALESS PERMISSIONS NONE' },
}",