Allow non empty DDL drops

Drop even if non empty during benchmark
next
Sayan Nandan 10 months ago
parent 8d6a047f02
commit 01c684bd38
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -105,12 +105,13 @@ async fn run_blocking_stmt(
let alter = stmt == KeywordStmt::Alter; let alter = stmt == KeywordStmt::Alter;
let drop = stmt == KeywordStmt::Drop; let drop = stmt == KeywordStmt::Drop;
let last_id = b.is_ident(); 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_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 c_m = (create & Token![model].eq(a) & last_id) as u8 * 3;
let a_s = (alter & Token![space].eq(a) & last_id) as u8 * 4; 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 a_m = (alter & Token![model].eq(a) & last_id) as u8 * 5;
let d_s = (drop & Token![space].eq(a) & last_id) as u8 * 6; 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) as u8 * 7; let d_m = (drop & Token![model].eq(a) & (last_id | last_allow)) as u8 * 7;
let fc = sysctl as u8 | c_s | c_m | a_s | a_m | d_s | d_m; let fc = sysctl as u8 | c_s | c_m | a_s | a_m | d_s | d_m;
state.cursor_ahead_if(!sysctl); state.cursor_ahead_if(!sysctl);
static BLK_EXEC: [fn( static BLK_EXEC: [fn(

@ -69,6 +69,14 @@ impl GlobalNS {
idx: RWLIdx::default(), idx: RWLIdx::default(),
} }
} }
pub fn ddl_with_all_mut<T>(
&self,
f: impl FnOnce(&mut HashMap<Box<str>, Space>, &mut HashMap<EntityID, Model>) -> T,
) -> T {
let mut spaces = self.idx.write();
let mut models = self.idx_mdl.write();
f(&mut spaces, &mut models)
}
pub fn ddl_with_spaces_write<T>( pub fn ddl_with_spaces_write<T>(
&self, &self,
f: impl FnOnce(&mut HashMap<Box<str>, Space>) -> T, f: impl FnOnce(&mut HashMap<Box<str>, Space>) -> T,

@ -335,7 +335,7 @@ impl Model {
.get(&EntityIDRef::new(&space_name, &model_name)) .get(&EntityIDRef::new(&space_name, &model_name))
.unwrap(); .unwrap();
// the model must be empty for us to clean it up! (NB: consistent view + EX) // the model must be empty for us to clean it up! (NB: consistent view + EX)
if model.primary_index().count() != 0 { if (model.primary_index().count() != 0) & !(stmt.force) {
// nope, we can't drop this // nope, we can't drop this
return Err(QueryError::QExecDdlNotEmpty); return Err(QueryError::QExecDdlNotEmpty);
} }

@ -24,6 +24,8 @@
* *
*/ */
use super::EntityIDRef;
use { use {
crate::engine::{ crate::engine::{
data::{dict, uuid::Uuid, DictEntryGeneric, DictGeneric}, data::{dict, uuid::Uuid, DictEntryGeneric, DictGeneric},
@ -218,33 +220,60 @@ impl Space {
global: &G, global: &G,
DropSpace { DropSpace {
space: space_name, space: space_name,
force: _, force,
}: DropSpace, }: DropSpace,
) -> QueryResult<()> { ) -> QueryResult<()> {
// TODO(@ohsayan): force remove option if force {
// TODO(@ohsayan): should a drop space block the entire global table? global.namespace().ddl_with_all_mut(|spaces, models| {
global.namespace().ddl_with_spaces_write(|spaces| { let Some(space) = spaces.remove(space_name.as_str()) else {
let Some(space) = spaces.get(space_name.as_str()) else { return Err(QueryError::QExecObjectNotFound);
return Err(QueryError::QExecObjectNotFound); };
}; // commit drop
if !space.models.is_empty() { if G::FS_IS_NON_NULL {
// nonempty, we can't do anything // prepare txn
return Err(QueryError::QExecDdlNotEmpty); let txn =
} gnstxn::DropSpaceTxn::new(gnstxn::SpaceIDRef::new(&space_name, &space));
// okay, it's empty; good riddance // commit txn
if G::FS_IS_NON_NULL { global.namespace_txn_driver().lock().try_commit(txn)?;
// prepare txn // request cleanup
let txn = gnstxn::DropSpaceTxn::new(gnstxn::SpaceIDRef::new(&space_name, &space)); global.taskmgr_post_standard_priority(Task::new(
// commit txn GenericTask::delete_space_dir(&space_name, space.get_uuid()),
global.namespace_txn_driver().lock().try_commit(txn)?; ));
// request cleanup }
global.taskmgr_post_standard_priority(Task::new(GenericTask::delete_space_dir( for model in space.models.into_iter() {
&space_name, let e: EntityIDRef<'static> = unsafe {
space.get_uuid(), // UNSAFE(@ohsayan): I want to try what the borrow checker has been trying
))); core::mem::transmute(EntityIDRef::new(space_name.as_str(), &model))
} };
let _ = spaces.st_delete(space_name.as_str()); let _ = models.st_delete(&e);
Ok(()) }
}) let _ = spaces.st_delete(space_name.as_str());
Ok(())
})
} else {
global.namespace().ddl_with_spaces_write(|spaces| {
let Some(space) = spaces.get(space_name.as_str()) else {
return Err(QueryError::QExecObjectNotFound);
};
if !space.models.is_empty() {
// nonempty, we can't do anything
return Err(QueryError::QExecDdlNotEmpty);
}
// okay, it's empty; good riddance
if G::FS_IS_NON_NULL {
// prepare txn
let txn =
gnstxn::DropSpaceTxn::new(gnstxn::SpaceIDRef::new(&space_name, &space));
// commit txn
global.namespace_txn_driver().lock().try_commit(txn)?;
// request cleanup
global.taskmgr_post_standard_priority(Task::new(
GenericTask::delete_space_dir(&space_name, space.get_uuid()),
));
}
let _ = spaces.st_delete(space_name.as_str());
Ok(())
})
}
} }
} }

@ -150,8 +150,8 @@ impl<'a> Borrow<EntityIDRef<'a>> for EntityID {
} }
} }
impl From<(&'static str, &'static str)> for EntityIDRef<'static> { impl<'a> From<(&'a str, &'a str)> for EntityIDRef<'a> {
fn from((s, e): (&'static str, &'static str)) -> Self { fn from((s, e): (&'a str, &'a str)) -> Self {
Self::new(s, e) Self::new(s, e)
} }
} }

@ -317,6 +317,15 @@ macro_rules! Token {
(null) => { (null) => {
__kw_misc!(Null) __kw_misc!(Null)
}; };
(not) => {
__kw_misc!(Not)
};
(return) => {
__kw_misc!(Return)
};
(allow) => {
__kw_misc!(Allow)
};
} }
macro_rules! union { macro_rules! union {

@ -29,7 +29,7 @@ use crate::engine::{
error::{QueryError, QueryResult}, error::{QueryError, QueryResult},
ql::{ ql::{
ast::{QueryData, State}, ast::{QueryData, State},
lex::{Ident, Token}, lex::Ident,
}, },
}; };
@ -47,24 +47,42 @@ impl<'a> DropSpace<'a> {
Self { space, force } Self { space, force }
} }
fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<DropSpace<'a>> { fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<DropSpace<'a>> {
/*
either drop space <myspace> OR drop space allow not empty <myspace>
*/
if state.cursor_is_ident() { if state.cursor_is_ident() {
let ident = state.fw_read(); let ident = state.fw_read();
// should we force drop?
let force = state.cursor_rounded_eq(Token::Ident(Ident::from("force")));
state.cursor_ahead_if(force);
// either `force` or nothing // either `force` or nothing
return Ok(DropSpace::new( return Ok(DropSpace::new(
unsafe { unsafe {
// UNSAFE(@ohsayan): Safe because the if predicate ensures that tok[0] (relative) is indeed an ident // UNSAFE(@ohsayan): Safe because the if predicate ensures that tok[0] (relative) is indeed an ident
ident.uck_read_ident() ident.uck_read_ident()
}, },
force, false,
)); ));
} else {
if ddl_allow_non_empty(state) {
state.cursor_ahead_by(3);
let space_name = unsafe {
// UNSAFE(@ohsayan): verified in branch
state.fw_read().uck_read_ident()
};
return Ok(DropSpace::new(space_name, true));
}
} }
Err(QueryError::QLInvalidSyntax) Err(QueryError::QLInvalidSyntax)
} }
} }
#[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));
let tok_not = Token![not].eq(state.offset_current_r(1));
let tok_empty = state.offset_current_r(2).ident_eq("empty");
let name = state.offset_current_r(3).is_ident();
(tok_allow & tok_not & tok_empty & name) & (state.remaining() >= 4)
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct DropModel<'a> { pub struct DropModel<'a> {
pub(in crate::engine) entity: EntityIDRef<'a>, pub(in crate::engine) entity: EntityIDRef<'a>,
@ -77,10 +95,17 @@ impl<'a> DropModel<'a> {
Self { entity, force } Self { entity, force }
} }
fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> { fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> {
let e = state.try_entity_ref_result()?; if state.cursor_is_ident() {
let force = state.cursor_rounded_eq(Token::Ident(Ident::from("force"))); let e = state.try_entity_ref_result()?;
state.cursor_ahead_if(force); return Ok(DropModel::new(e, false));
Ok(DropModel::new(e, force)) } 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));
}
}
Err(QueryError::QLInvalidSyntax)
} }
} }

@ -370,6 +370,7 @@ flattened_lut! {
Remove, Remove,
Transform, Transform,
Set, Set,
Return,
// sort related // sort related
Order, Order,
Sort, Sort,
@ -397,6 +398,7 @@ flattened_lut! {
Else, Else,
Where, Where,
When, When,
Allow,
// value // value
Auto, Auto,
Default, Default,
@ -421,6 +423,7 @@ flattened_lut! {
} }
impl Keyword { impl Keyword {
#[inline(always)]
pub fn get(k: &[u8]) -> Option<Self> { pub fn get(k: &[u8]) -> Option<Self> {
if (k.len() > Self::SIZE_MAX) | (k.len() < Self::SIZE_MIN) { if (k.len() > Self::SIZE_MAX) | (k.len() < Self::SIZE_MIN) {
None None
@ -429,13 +432,13 @@ impl Keyword {
} }
} }
fn compute(key: &[u8]) -> Option<Self> { fn compute(key: &[u8]) -> Option<Self> {
static G: [u8; 64] = [ static G: [u8; 69] = [
0, 27, 13, 56, 18, 0, 26, 30, 33, 56, 20, 41, 56, 39, 23, 34, 36, 23, 17, 40, 38, 45, 0, 0, 9, 64, 16, 43, 7, 49, 24, 8, 41, 37, 19, 66, 18, 0, 17, 0, 12, 63, 34, 56, 3, 24,
8, 25, 26, 24, 53, 59, 30, 14, 9, 60, 12, 29, 6, 47, 3, 38, 19, 5, 13, 51, 41, 34, 0, 55, 14, 0, 67, 7, 0, 39, 60, 56, 0, 51, 23, 31, 19, 30, 12, 10, 58, 20, 39, 32, 0, 6,
22, 43, 13, 46, 33, 11, 12, 36, 58, 40, 0, 36, 2, 19, 49, 53, 23, 55, 0, 30, 26, 58, 52, 62, 39, 27, 24, 9, 4, 21, 24, 68, 10, 38, 40, 21, 62, 27, 53, 27, 44,
]; ];
static M1: [u8; 11] = *b"RtEMxHylmiZ"; static M1: [u8; 11] = *b"D8N5FwqrxdA";
static M2: [u8; 11] = *b"F1buDOZ2nzz"; static M2: [u8; 11] = *b"FsIPJv9hsXx";
let h1 = Self::_sum(key, M1) % G.len(); let h1 = Self::_sum(key, M1) % G.len();
let h2 = Self::_sum(key, M2) % G.len(); let h2 = Self::_sum(key, M2) % G.len();
let h = (G[h1] + G[h2]) as usize % G.len(); let h = (G[h1] + G[h2]) as usize % G.len();

@ -1124,7 +1124,7 @@ mod ddl_other_query_tests {
} }
#[test] #[test]
fn drop_space_force() { fn drop_space_force() {
let src = lex_insecure(br"drop space myspace force").unwrap(); let src = lex_insecure(br"drop space allow not empty myspace").unwrap();
assert_eq!( assert_eq!(
parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(), parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(),
DropSpace::new(Ident::from("myspace"), true) DropSpace::new(Ident::from("myspace"), true)
@ -1140,7 +1140,7 @@ mod ddl_other_query_tests {
} }
#[test] #[test]
fn drop_model_force() { fn drop_model_force() {
let src = lex_insecure(br"drop model mymodel force").unwrap(); let src = lex_insecure(br"drop model allow not empty mymodel").unwrap();
assert_eq!( assert_eq!(
parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(), parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(),
DropModel::new(("apps", "mymodel").into(), true) DropModel::new(("apps", "mymodel").into(), true)

@ -33,7 +33,7 @@ fn inspect_global_as_root_returns_user_info() {
assert!(inspect.contains("\"users\":")); assert!(inspect.contains("\"users\":"));
} }
#[dbtest] #[dbtest(switch_user(username = "sneaking_user_info"))]
fn inspect_global_as_std_user_does_not_return_user_info() { fn inspect_global_as_std_user_does_not_return_user_info() {
let mut db = db!(); let mut db = db!();
let inspect: String = db.query_parse(&query!("inspect global")).unwrap(); let inspect: String = db.query_parse(&query!("inspect global")).unwrap();

@ -28,7 +28,7 @@ use {
super::GNSEvent, super::GNSEvent,
crate::{ crate::{
engine::{ engine::{
core::{space::Space, GlobalNS}, core::{space::Space, EntityIDRef, GlobalNS},
data::DictGeneric, data::DictGeneric,
error::{RuntimeResult, TransactionError}, error::{RuntimeResult, TransactionError},
idx::STIndex, idx::STIndex,
@ -278,10 +278,17 @@ impl<'a> GNSEvent for DropSpaceTxn<'a> {
gns: &GlobalNS, gns: &GlobalNS,
) -> RuntimeResult<()> { ) -> RuntimeResult<()> {
let mut wgns = gns.idx().write(); let mut wgns = gns.idx().write();
let mut wmodel = gns.idx_models().write();
match wgns.entry(name) { match wgns.entry(name) {
std::collections::hash_map::Entry::Occupied(oe) => { std::collections::hash_map::Entry::Occupied(oe) => {
if oe.get().get_uuid() == uuid { if oe.get().get_uuid() == uuid {
// NB(@ohsayan): we do not need to remove models here since they must have been already removed for this query to have actually executed for model in oe.get().models() {
let id: EntityIDRef<'static> = unsafe {
// UNSAFE(@ohsayan): I really need a pack of what the borrow checker has been reveling on
core::mem::transmute(EntityIDRef::new(oe.key(), &model))
};
let _ = wmodel.st_delete(&id);
}
oe.remove_entry(); oe.remove_entry();
Ok(()) Ok(())
} else { } else {

@ -160,8 +160,7 @@ pub fn run(bench: BenchConfig) -> error::BenchResult<()> {
fn cleanup(mut main_thread_db: Connection) -> Result<(), error::BenchError> { fn cleanup(mut main_thread_db: Connection) -> Result<(), error::BenchError> {
trace!("dropping space and table"); trace!("dropping space and table");
main_thread_db.query_parse::<()>(&query!("drop model bench.bench"))?; main_thread_db.query_parse::<()>(&query!("drop space allow not empty bench"))?;
main_thread_db.query_parse::<()>(&query!("drop space bench"))?;
Ok(()) Ok(())
} }

Loading…
Cancel
Save