Support `if exists` and `if not exists`

next
Sayan Nandan 10 months ago
parent 0ade763b78
commit 9e1ce8c3e2
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -62,6 +62,16 @@ pub async fn dispatch_to_executor<'a>(
}
}
fn _callgs_map<A: ASTNode<'static> + core::fmt::Debug, T>(
g: &Global,
state: &mut State<'static, InplaceData>,
f: impl FnOnce(&Global, A) -> Result<T, QueryError>,
map: impl FnOnce(T) -> Response,
) -> QueryResult<Response> {
let cs = ASTNode::parse_from_state_hardened(state)?;
Ok(map(f(&g, cs)?))
}
#[inline(always)]
fn _callgs<A: ASTNode<'static> + core::fmt::Debug, T>(
g: &Global,
@ -106,27 +116,28 @@ async fn run_blocking_stmt(
let drop = stmt == KeywordStmt::Drop;
let last_id = b.is_ident();
let last_allow = Token![allow].eq(b);
let c_s = (create & Token![space].eq(a) & last_id) as u8 * 2;
let c_m = (create & Token![model].eq(a) & last_id) as u8 * 3;
let last_if = Token![if].eq(b);
let c_s = (create & Token![space].eq(a) & (last_id | last_if)) as u8 * 2;
let c_m = (create & Token![model].eq(a) & (last_id | last_if)) as u8 * 3;
let a_s = (alter & Token![space].eq(a) & last_id) as u8 * 4;
let a_m = (alter & Token![model].eq(a) & last_id) as u8 * 5;
let d_s = (drop & Token![space].eq(a) & (last_id | last_allow)) as u8 * 6;
let d_m = (drop & Token![model].eq(a) & (last_id | last_allow)) as u8 * 7;
let d_s = (drop & Token![space].eq(a) & (last_id | last_allow | last_if)) as u8 * 6;
let d_m = (drop & Token![model].eq(a) & (last_id | last_allow | last_if)) as u8 * 7;
let fc = sysctl as u8 | c_s | c_m | a_s | a_m | d_s | d_m;
state.cursor_ahead_if(!sysctl);
static BLK_EXEC: [fn(
Global,
&ClientLocalState,
&mut State<'static, InplaceData>,
) -> QueryResult<()>; 8] = [
) -> QueryResult<Response>; 8] = [
|_, _, _| Err(QueryError::QLUnknownStatement),
blocking_exec_sysctl,
|g, _, t| _callgs(&g, t, Space::transactional_exec_create),
|g, _, t| _callgs(&g, t, Model::transactional_exec_create),
|g, _, t| _callgs(&g, t, Space::transactional_exec_alter),
|g, _, t| _callgs(&g, t, Model::transactional_exec_alter),
|g, _, t| _callgs(&g, t, Space::transactional_exec_drop),
|g, _, t| _callgs(&g, t, Model::transactional_exec_drop),
|g, _, t| _callgs_map(&g, t, Space::transactional_exec_create, Response::Bool),
|g, _, t| _callgs_map(&g, t, Model::transactional_exec_create, Response::Bool),
|g, _, t| _callgs_map(&g, t, Space::transactional_exec_alter, |_| Response::Empty),
|g, _, t| _callgs_map(&g, t, Model::transactional_exec_alter, |_| Response::Empty),
|g, _, t| _callgs_map(&g, t, Space::transactional_exec_drop, Response::Bool),
|g, _, t| _callgs_map(&g, t, Model::transactional_exec_drop, Response::Bool),
];
let r = unsafe {
// UNSAFE(@ohsayan): the only await is within this block
@ -135,8 +146,7 @@ async fn run_blocking_stmt(
let static_state: &'static mut State<'static, InplaceData> =
core::mem::transmute(&mut state);
tokio::task::spawn_blocking(move || {
BLK_EXEC[fc as usize](c_glob, static_cstate, static_state)?;
Ok(Response::Empty)
BLK_EXEC[fc as usize](c_glob, static_cstate, static_state)
})
.await
};
@ -147,9 +157,9 @@ fn blocking_exec_sysctl(
g: Global,
cstate: &ClientLocalState,
state: &mut State<'static, InplaceData>,
) -> QueryResult<()> {
) -> QueryResult<Response> {
let r = ASTNode::parse_from_state_hardened(state)?;
super::dcl::exec(g, cstate, r)
super::dcl::exec(g, cstate, r).map(|_| Response::Empty)
}
/*

@ -210,6 +210,7 @@ impl Model {
model_name: _,
fields,
props,
..
}: CreateModel,
) -> QueryResult<Self> {
let mut private = ModelPrivate::empty();
@ -266,13 +267,18 @@ impl Model {
pub fn transactional_exec_create<G: GlobalInstanceLike>(
global: &G,
stmt: CreateModel,
) -> QueryResult<()> {
) -> QueryResult<bool> {
let (space_name, model_name) = (stmt.model_name.space(), stmt.model_name.entity());
let if_nx = stmt.if_not_exists;
let model = Self::process_create(stmt)?;
global.namespace().ddl_with_space_mut(&space_name, |space| {
// TODO(@ohsayan): be extra cautious with post-transactional tasks (memck)
if space.models().contains(model_name) {
return Err(QueryError::QExecDdlObjectAlreadyExists);
if if_nx {
return Ok(false);
} else {
return Err(QueryError::QExecDdlObjectAlreadyExists);
}
}
// since we've locked this down, no one else can parallely create another model in the same space (or remove)
if G::FS_IS_NON_NULL {
@ -314,18 +320,22 @@ impl Model {
.idx_models()
.write()
.insert(EntityID::new(&space_name, &model_name), model);
Ok(())
Ok(true)
})
}
pub fn transactional_exec_drop<G: GlobalInstanceLike>(
global: &G,
stmt: DropModel,
) -> QueryResult<()> {
) -> QueryResult<bool> {
let (space_name, model_name) = (stmt.entity.space(), stmt.entity.entity());
global.namespace().ddl_with_space_mut(&space_name, |space| {
if !space.models().contains(model_name) {
// the model isn't even present
return Err(QueryError::QExecObjectNotFound);
if stmt.if_exists {
return Ok(false);
} else {
// the model isn't even present
return Err(QueryError::QExecObjectNotFound);
}
}
// get exclusive lock on models
let mut models_idx = global.namespace().idx_models().write();
@ -360,7 +370,7 @@ impl Model {
// update global state
let _ = models_idx.remove(&EntityIDRef::new(&space_name, &model_name));
let _ = space.models_mut().remove(model_name);
Ok(())
Ok(true)
})
}
}

@ -51,6 +51,7 @@ pub struct Space {
struct ProcedureCreate {
space_name: Box<str>,
space: Space,
if_not_exists: bool,
}
impl Space {
@ -106,6 +107,7 @@ impl Space {
CreateSpace {
space_name,
mut props,
if_not_exists,
}: CreateSpace,
) -> QueryResult<ProcedureCreate> {
let space_name = space_name.to_string().into_boxed_str();
@ -137,6 +139,7 @@ impl Space {
Ok(ProcedureCreate {
space_name,
space: Space::new_empty_auto(dict::rflatten_metadata(props)),
if_not_exists,
})
}
}
@ -145,13 +148,21 @@ impl Space {
pub fn transactional_exec_create<G: GlobalInstanceLike>(
global: &G,
space: CreateSpace,
) -> QueryResult<()> {
) -> QueryResult<bool> {
// process create
let ProcedureCreate { space_name, space } = Self::process_create(space)?;
let ProcedureCreate {
space_name,
space,
if_not_exists,
} = Self::process_create(space)?;
// lock the global namespace
global.namespace().ddl_with_spaces_write(|spaces| {
if spaces.st_contains(&space_name) {
return Err(QueryError::QExecDdlObjectAlreadyExists);
if if_not_exists {
return Ok(false);
} else {
return Err(QueryError::QExecDdlObjectAlreadyExists);
}
}
// commit txn
if G::FS_IS_NON_NULL {
@ -176,7 +187,7 @@ impl Space {
}
// update global state
let _ = spaces.st_insert(space_name, space);
Ok(())
Ok(true)
})
}
#[allow(unused)]
@ -221,12 +232,17 @@ impl Space {
DropSpace {
space: space_name,
force,
if_exists,
}: DropSpace,
) -> QueryResult<()> {
) -> QueryResult<bool> {
if force {
global.namespace().ddl_with_all_mut(|spaces, models| {
let Some(space) = spaces.remove(space_name.as_str()) else {
return Err(QueryError::QExecObjectNotFound);
if if_exists {
return Ok(false);
} else {
return Err(QueryError::QExecObjectNotFound);
}
};
// commit drop
if G::FS_IS_NON_NULL {
@ -256,12 +272,16 @@ impl Space {
);
}
let _ = spaces.st_delete(space_name.as_str());
Ok(())
Ok(true)
})
} else {
global.namespace().ddl_with_spaces_write(|spaces| {
let Some(space) = spaces.get(space_name.as_str()) else {
return Err(QueryError::QExecObjectNotFound);
if if_exists {
return Ok(false);
} else {
return Err(QueryError::QExecObjectNotFound);
}
};
if !space.models.is_empty() {
// nonempty, we can't do anything
@ -280,7 +300,7 @@ impl Space {
));
}
let _ = spaces.st_delete(space_name.as_str());
Ok(())
Ok(true)
})
}
}

@ -50,7 +50,7 @@ fn _exec_only_create_space_model(global: &impl GlobalInstanceLike, model: &str)
.insert("myspace".into(), Space::new_auto_all().into());
let lex_create_model = lex_insecure(model.as_bytes()).unwrap();
let stmt_create_model = parse_ast_node_full(&lex_create_model[2..]).unwrap();
Model::transactional_exec_create(global, stmt_create_model)
Model::transactional_exec_create(global, stmt_create_model).map(|_| ())
}
fn _exec_only_insert<T>(

@ -328,7 +328,10 @@ macro_rules! Token {
};
(all) => {
__kw_misc!(All)
}
};
(exists) => {
__kw_stmt!(Exists)
};
}
macro_rules! union {

@ -120,6 +120,7 @@ pub enum Response {
size: usize,
data: Vec<u8>,
},
Bool(bool),
}
pub(super) async fn query_loop<S: Socket>(
@ -168,8 +169,8 @@ pub(super) async fn query_loop<S: Socket>(
}
(_, QExchangeResult::Error) => {
// respond with error
let [a, b] =
(QueryError::SysNetworkSystemIllegalClientPacket.value_u8() as u16).to_le_bytes();
let [a, b] = (QueryError::SysNetworkSystemIllegalClientPacket.value_u8() as u16)
.to_le_bytes();
con.write_all(&[ResponseType::Error.value_u8(), a, b])
.await?;
con.flush().await?;
@ -192,6 +193,10 @@ pub(super) async fn query_loop<S: Socket>(
con.write_u8(b'\n').await?;
con.write_all(&data).await?;
}
Ok(Response::Bool(b)) => {
con.write_all(&[ResponseType::Bool.value_u8(), b as u8])
.await?
}
Ok(Response::Null) => con.write_u8(ResponseType::Null.value_u8()).await?,
Err(e) => {
let [a, b] = (e.value_u8() as u16).to_le_bytes();

@ -40,6 +40,13 @@ use {
},
};
fn sig_if_not_exists<'a, Qd: QueryData<'a>>(state: &State<'a, Qd>) -> bool {
Token![if].eq(state.offset_current_r(0))
& Token![not].eq(state.offset_current_r(1))
& Token![exists].eq(state.offset_current_r(2))
& (state.remaining() >= 3)
}
#[derive(Debug, PartialEq)]
/// A space
pub struct CreateSpace<'a> {
@ -47,6 +54,7 @@ pub struct CreateSpace<'a> {
pub space_name: Ident<'a>,
/// properties
pub props: DictGeneric,
pub if_not_exists: bool,
}
impl<'a> CreateSpace<'a> {
@ -57,6 +65,13 @@ impl<'a> CreateSpace<'a> {
if compiler::unlikely(state.remaining() < 1) {
return compiler::cold_rerr(QueryError::QLUnexpectedEndOfStatement);
}
// check for `if not exists`
let if_not_exists = sig_if_not_exists(state);
state.cursor_ahead_by(if_not_exists as usize * 3);
// get space name
if state.exhausted() {
return compiler::cold_rerr(QueryError::QLUnexpectedEndOfStatement);
}
let space_name = state.fw_read();
state.poison_if_not(space_name.is_ident());
// either we have `with` or nothing. don't be stupid
@ -65,7 +80,7 @@ impl<'a> CreateSpace<'a> {
state.cursor_ahead_if(has_more_properties); // +WITH
let mut d = DictGeneric::new();
// properties
if has_more_properties && state.okay() {
if has_more_properties & state.okay() {
syn::rfold_dict(DictFoldState::OB, state, &mut d);
}
if state.okay() {
@ -75,6 +90,7 @@ impl<'a> CreateSpace<'a> {
space_name.uck_read_ident()
},
props: d,
if_not_exists,
})
} else {
Err(QueryError::QLInvalidSyntax)
@ -91,6 +107,8 @@ pub struct CreateModel<'a> {
pub(in crate::engine) fields: Vec<FieldSpec<'a>>,
/// properties
pub(in crate::engine) props: DictGeneric,
/// if not exists
pub(in crate::engine) if_not_exists: bool,
}
/*
@ -106,18 +124,22 @@ impl<'a> CreateModel<'a> {
model_name: EntityIDRef<'a>,
fields: Vec<FieldSpec<'a>>,
props: DictGeneric,
if_not_exists: bool,
) -> Self {
Self {
model_name,
fields,
props,
if_not_exists,
}
}
fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> {
if compiler::unlikely(state.remaining() < 10) {
return compiler::cold_rerr(QueryError::QLUnexpectedEndOfStatement);
}
// if not exists?
let if_not_exists = sig_if_not_exists(state);
state.cursor_ahead_by(if_not_exists as usize * 3);
// model name; ignore errors
let model_uninit = state.try_entity_buffered_into_state_uninit();
state.poison_if_not(state.cursor_eq(Token![() open]));
@ -150,6 +172,7 @@ impl<'a> CreateModel<'a> {
},
fields,
props,
if_not_exists,
})
} else {
Err(QueryError::QLInvalidSyntax)

@ -33,23 +33,33 @@ use crate::engine::{
},
};
fn sig_if_exists<'a, Qd: QueryData<'a>>(state: &State<'a, Qd>) -> bool {
Token![if].eq(state.offset_current_r(0)) & Token![exists].eq(state.offset_current_r(1))
}
#[derive(Debug, PartialEq)]
/// A generic representation of `drop` query
pub struct DropSpace<'a> {
pub(in crate::engine) space: Ident<'a>,
pub(in crate::engine) force: bool,
pub(in crate::engine) if_exists: bool,
}
impl<'a> DropSpace<'a> {
#[inline(always)]
/// Instantiate
pub const fn new(space: Ident<'a>, force: bool) -> Self {
Self { space, force }
pub const fn new(space: Ident<'a>, force: bool, if_exists: bool) -> Self {
Self {
space,
force,
if_exists,
}
}
fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<DropSpace<'a>> {
/*
either drop space <myspace> OR drop space allow not empty <myspace>
*/
let if_exists = check_if_exists(state)?;
if state.cursor_is_ident() {
let ident = state.fw_read();
// either `force` or nothing
@ -59,6 +69,7 @@ impl<'a> DropSpace<'a> {
ident.uck_read_ident()
},
false,
if_exists,
));
} else {
if ddl_allow_non_empty(state) {
@ -67,13 +78,25 @@ impl<'a> DropSpace<'a> {
// UNSAFE(@ohsayan): verified in branch
state.fw_read().uck_read_ident()
};
return Ok(DropSpace::new(space_name, true));
return Ok(DropSpace::new(space_name, true, if_exists));
}
}
Err(QueryError::QLInvalidSyntax)
}
}
fn check_if_exists<'a, Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> Result<bool, QueryError> {
if state.exhausted() {
return Err(QueryError::QLUnexpectedEndOfStatement);
}
let if_exists = sig_if_exists(state);
state.cursor_ahead_by((if_exists as usize) << 1);
if state.exhausted() {
return Err(QueryError::QLUnexpectedEndOfStatement);
}
Ok(if_exists)
}
#[inline(always)]
fn ddl_allow_non_empty<'a, Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> bool {
let tok_allow = Token![allow].eq(state.offset_current_r(0));
@ -87,22 +110,28 @@ fn ddl_allow_non_empty<'a, Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> bool
pub struct DropModel<'a> {
pub(in crate::engine) entity: EntityIDRef<'a>,
pub(in crate::engine) force: bool,
pub(in crate::engine) if_exists: bool,
}
impl<'a> DropModel<'a> {
#[inline(always)]
pub fn new(entity: EntityIDRef<'a>, force: bool) -> Self {
Self { entity, force }
pub fn new(entity: EntityIDRef<'a>, force: bool, if_exists: bool) -> Self {
Self {
entity,
force,
if_exists,
}
}
fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> {
let if_exists = check_if_exists(state)?;
if state.cursor_is_ident() {
let e = state.try_entity_ref_result()?;
return Ok(DropModel::new(e, false));
return Ok(DropModel::new(e, false, if_exists));
} else {
if ddl_allow_non_empty(state) {
state.cursor_ahead_by(3); // allow not empty
let e = state.try_entity_ref_result()?;
return Ok(DropModel::new(e, true));
return Ok(DropModel::new(e, true, if_exists));
}
}
Err(QueryError::QLInvalidSyntax)

@ -25,7 +25,10 @@
*/
use {
super::lex::{InsecureLexer, SecureLexer, Symbol, Token},
super::{
ast::{self, traits::ASTNode},
lex::{InsecureLexer, SecureLexer, Symbol, Token},
},
crate::{
engine::{data::cell::Datacell, error::QueryResult},
util::test_utils,
@ -138,3 +141,38 @@ fn fuzz_tokens(src: &[u8], fuzzverify: impl Fn(bool, &[Token]) -> bool) {
}
}
}
pub(self) fn fullparse_verify<'a, A: ASTNode<'a> + 'a>(q: &'a str, offset: usize, v: impl Fn(A)) {
let tok = lex_insecure(q.as_bytes()).unwrap();
unsafe {
let q: &'a [_] = core::mem::transmute(tok.as_slice());
let a: A = ASTNode::from_insecure_tokens_full(&q[offset..]).unwrap();
v(a);
}
}
pub(self) fn fullparse_verify_substmt<'a, A: ASTNode<'a> + 'a>(q: &'a str, v: impl Fn(A)) {
fullparse_verify(q, 2, v)
}
pub(self) fn fullparse_verify_with_space<'a, A: ASTNode<'a> + 'a>(
q: &'a str,
space_name: &'static str,
offset: usize,
v: impl Fn(A),
) {
let tok = lex_insecure(q.as_bytes()).unwrap();
unsafe {
let q: &'static [_] = core::mem::transmute(&tok.as_slice()[offset..]);
let a: A = ast::parse_ast_node_full_with_space(q, space_name).unwrap();
v(a);
}
}
pub(self) fn fullparse_verify_substmt_with_space<'a, A: ASTNode<'a> + 'a>(
q: &'a str,
space_name: &'static str,
v: impl Fn(A),
) {
fullparse_verify_with_space(q, space_name, 2, v)
}

@ -32,38 +32,34 @@ use {
mod alter_space {
use {
super::*,
crate::engine::{
data::lit::Lit,
ql::{ast::parse_ast_node_full, ddl::alt::AlterSpace},
},
crate::engine::{data::lit::Lit, ql::ddl::alt::AlterSpace},
};
#[test]
fn alter_space_mini() {
let tok = lex_insecure(b"alter model mymodel with {}").unwrap();
let r = parse_ast_node_full::<AlterSpace>(&tok[2..]).unwrap();
assert_eq!(r, AlterSpace::new(Ident::from("mymodel"), null_dict! {}));
fullparse_verify_substmt("alter model mymodel with {}", |r: AlterSpace| {
assert_eq!(r, AlterSpace::new(Ident::from("mymodel"), null_dict! {}));
})
}
#[test]
fn alter_space() {
let tok = lex_insecure(
br#"
alter model mymodel with {
max_entry: 1000,
driver: "ts-0.8"
}
"#,
)
.unwrap();
let r = parse_ast_node_full::<AlterSpace>(&tok[2..]).unwrap();
assert_eq!(
r,
AlterSpace::new(
Ident::from("mymodel"),
null_dict! {
"max_entry" => Lit::new_uint(1000),
"driver" => Lit::new_string("ts-0.8".into())
}
)
fullparse_verify_substmt(
r#"
alter model mymodel with {
max_entry: 1000,
driver: "ts-0.8"
}"#,
|r: AlterSpace| {
assert_eq!(
r,
AlterSpace::new(
Ident::from("mymodel"),
null_dict! {
"max_entry" => Lit::new_uint(1000),
"driver" => Lit::new_string("ts-0.8".into())
}
)
);
},
);
}
}
@ -378,233 +374,221 @@ mod fields {
}
mod schemas {
use super::*;
use crate::engine::ql::{
ast::parse_ast_node_full_with_space,
ddl::{
crt::CreateModel,
syn::{FieldSpec, LayerSpec},
},
use crate::engine::ql::ddl::{
crt::CreateModel,
syn::{FieldSpec, LayerSpec},
};
#[test]
fn schema_mini() {
let tok = lex_insecure(
b"
create model mymodel(
primary username: string,
password: binary
)
",
)
.unwrap();
let tok = &tok[2..];
// parse model
let model = parse_ast_node_full_with_space::<CreateModel>(tok, "apps").unwrap();
assert_eq!(
model,
CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true,
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false,
)
],
null_dict! {}
)
)
let mut ret = CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true,
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false,
),
],
null_dict! {},
false,
);
fullparse_verify_substmt_with_space(
"create model mymodel(
primary username: string,
password: binary
)",
"apps",
|r: CreateModel| assert_eq!(r, ret),
);
ret.if_not_exists = true;
fullparse_verify_substmt_with_space(
"create model if not exists mymodel(
primary username: string,
password: binary
)",
"apps",
|r: CreateModel| assert_eq!(r, ret),
);
}
#[test]
fn schema() {
let tok = lex_insecure(
b"
create model mymodel(
primary username: string,
password: binary,
null profile_pic: binary
)
",
)
.unwrap();
let tok = &tok[2..];
// parse model
let model = parse_ast_node_full_with_space::<CreateModel>(tok, "apps").unwrap();
assert_eq!(
model,
CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true,
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false,
),
FieldSpec::new(
Ident::from("profile_pic"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
true,
false,
)
],
null_dict! {}
)
)
let mut ret = CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true,
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false,
),
FieldSpec::new(
Ident::from("profile_pic"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
true,
false,
),
],
null_dict! {},
false,
);
fullparse_verify_substmt_with_space(
"create model mymodel(
primary username: string,
password: binary,
null profile_pic: binary
)",
"apps",
|r: CreateModel| assert_eq!(r, ret),
);
ret.if_not_exists = true;
fullparse_verify_substmt_with_space(
"create model if not exists mymodel(
primary username: string,
password: binary,
null profile_pic: binary
)",
"apps",
|r: CreateModel| assert_eq!(r, ret),
);
}
#[test]
fn schema_pro() {
let tok = lex_insecure(
b"
create model mymodel(
primary username: string,
password: binary,
null profile_pic: binary,
null notes: list {
type: string,
unique: true,
}
)
",
)
.unwrap();
let tok = &tok[2..];
// parse model
let model = parse_ast_node_full_with_space::<CreateModel>(tok, "apps").unwrap();
assert_eq!(
model,
CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false
),
FieldSpec::new(
Ident::from("profile_pic"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
true,
false
),
FieldSpec::new(
Ident::from("notes"),
vec![
LayerSpec::new(Ident::from("string"), null_dict! {}),
LayerSpec::new(
Ident::from("list"),
null_dict! {
"unique" => Lit::new_bool(true)
}
)
],
true,
false
)
],
null_dict! {}
)
let mut ret = CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true,
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false,
),
FieldSpec::new(
Ident::from("profile_pic"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
true,
false,
),
FieldSpec::new(
Ident::from("notes"),
vec![
LayerSpec::new(Ident::from("string"), null_dict! {}),
LayerSpec::new(
Ident::from("list"),
null_dict! {
"unique" => Lit::new_bool(true)
},
),
],
true,
false,
),
],
null_dict! {},
false,
);
ret.if_not_exists = true;
fullparse_verify_substmt_with_space(
"
create model if not exists mymodel(
primary username: string,
password: binary,
null profile_pic: binary,
null notes: list {
type: string,
unique: true,
}
)
",
"apps",
|r: CreateModel| assert_eq!(ret, r),
);
}
#[test]
fn schema_pro_max() {
let tok = lex_insecure(
b"
create model mymodel(
primary username: string,
password: binary,
null profile_pic: binary,
null notes: list {
type: string,
unique: true,
}
) with {
env: {
free_user_limit: 100,
},
storage_driver: \"skyheap\"
}
",
)
.unwrap();
let tok = &tok[2..];
// parse model
let model = parse_ast_node_full_with_space::<CreateModel>(tok, "apps").unwrap();
assert_eq!(
model,
CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false
),
FieldSpec::new(
Ident::from("profile_pic"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
true,
false
),
FieldSpec::new(
Ident::from("notes"),
vec![
LayerSpec::new(Ident::from("string"), null_dict! {}),
LayerSpec::new(
Ident::from("list"),
null_dict! {
"unique" => Lit::new_bool(true)
}
)
],
true,
false
)
],
null_dict! {
"env" => null_dict! {
"free_user_limit" => Lit::new_uint(100),
},
"storage_driver" => Lit::new_string("skyheap".into()),
let mut ret = CreateModel::new(
("apps", "mymodel").into(),
vec![
FieldSpec::new(
Ident::from("username"),
vec![LayerSpec::new(Ident::from("string"), null_dict! {})],
false,
true,
),
FieldSpec::new(
Ident::from("password"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
false,
false,
),
FieldSpec::new(
Ident::from("profile_pic"),
vec![LayerSpec::new(Ident::from("binary"), null_dict! {})],
true,
false,
),
FieldSpec::new(
Ident::from("notes"),
vec![
LayerSpec::new(Ident::from("string"), null_dict! {}),
LayerSpec::new(
Ident::from("list"),
null_dict! {
"unique" => Lit::new_bool(true)
},
),
],
true,
false,
),
],
null_dict! {
"env" => null_dict! {
"free_user_limit" => Lit::new_uint(100),
},
"storage_driver" => Lit::new_string("skyheap".into()),
},
false,
);
ret.if_not_exists = true;
fullparse_verify_substmt_with_space(
"
create model if not exists mymodel(
primary username: string,
password: binary,
null profile_pic: binary,
null notes: list {
type: string,
unique: true,
}
)
)
) with {
env: {
free_user_limit: 100,
},
storage_driver: \"skyheap\"
}",
"apps",
|r: CreateModel| assert_eq!(r, ret),
);
}
}
mod dict_field_syntax {
@ -1119,7 +1103,12 @@ mod ddl_other_query_tests {
let src = lex_insecure(br"drop space myspace").unwrap();
assert_eq!(
parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(),
DropSpace::new(Ident::from("myspace"), false)
DropSpace::new(Ident::from("myspace"), false, false)
);
let src = lex_insecure(br"drop space if exists myspace").unwrap();
assert_eq!(
parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(),
DropSpace::new(Ident::from("myspace"), false, true)
);
}
#[test]
@ -1127,7 +1116,12 @@ mod ddl_other_query_tests {
let src = lex_insecure(br"drop space allow not empty myspace").unwrap();
assert_eq!(
parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(),
DropSpace::new(Ident::from("myspace"), true)
DropSpace::new(Ident::from("myspace"), true, false)
);
let src = lex_insecure(br"drop space if exists allow not empty myspace").unwrap();
assert_eq!(
parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(),
DropSpace::new(Ident::from("myspace"), true, true)
);
}
#[test]
@ -1135,7 +1129,12 @@ mod ddl_other_query_tests {
let src = lex_insecure(br"drop model mymodel").unwrap();
assert_eq!(
parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(),
DropModel::new(("apps", "mymodel").into(), false)
DropModel::new(("apps", "mymodel").into(), false, false)
);
let src = lex_insecure(br"drop model if exists mymodel").unwrap();
assert_eq!(
parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(),
DropModel::new(("apps", "mymodel").into(), false, true)
);
}
#[test]
@ -1143,7 +1142,12 @@ mod ddl_other_query_tests {
let src = lex_insecure(br"drop model allow not empty mymodel").unwrap();
assert_eq!(
parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(),
DropModel::new(("apps", "mymodel").into(), true)
DropModel::new(("apps", "mymodel").into(), true, false)
);
let src = lex_insecure(br"drop model if exists allow not empty mymodel").unwrap();
assert_eq!(
parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(),
DropModel::new(("apps", "mymodel").into(), true, true)
);
}
}

Loading…
Cancel
Save