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 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 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) as u8 * 6;
let d_m = (drop & Token![model].eq(a) & last_id) as u8 * 7;
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 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(

@ -69,6 +69,14 @@ impl GlobalNS {
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>(
&self,
f: impl FnOnce(&mut HashMap<Box<str>, Space>) -> T,

@ -335,7 +335,7 @@ impl Model {
.get(&EntityIDRef::new(&space_name, &model_name))
.unwrap();
// 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
return Err(QueryError::QExecDdlNotEmpty);
}

@ -24,6 +24,8 @@
*
*/
use super::EntityIDRef;
use {
crate::engine::{
data::{dict, uuid::Uuid, DictEntryGeneric, DictGeneric},
@ -218,33 +220,60 @@ impl Space {
global: &G,
DropSpace {
space: space_name,
force: _,
force,
}: DropSpace,
) -> QueryResult<()> {
// TODO(@ohsayan): force remove option
// TODO(@ohsayan): should a drop space block the entire global table?
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(())
})
if force {
global.namespace().ddl_with_all_mut(|spaces, models| {
let Some(space) = spaces.remove(space_name.as_str()) else {
return Err(QueryError::QExecObjectNotFound);
};
// commit drop
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()),
));
}
for model in space.models.into_iter() {
let e: EntityIDRef<'static> = unsafe {
// UNSAFE(@ohsayan): I want to try what the borrow checker has been trying
core::mem::transmute(EntityIDRef::new(space_name.as_str(), &model))
};
let _ = models.st_delete(&e);
}
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> {
fn from((s, e): (&'static str, &'static str)) -> Self {
impl<'a> From<(&'a str, &'a str)> for EntityIDRef<'a> {
fn from((s, e): (&'a str, &'a str)) -> Self {
Self::new(s, e)
}
}

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

@ -29,7 +29,7 @@ use crate::engine::{
error::{QueryError, QueryResult},
ql::{
ast::{QueryData, State},
lex::{Ident, Token},
lex::Ident,
},
};
@ -47,24 +47,42 @@ impl<'a> DropSpace<'a> {
Self { space, force }
}
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() {
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
return Ok(DropSpace::new(
unsafe {
// UNSAFE(@ohsayan): Safe because the if predicate ensures that tok[0] (relative) is indeed an 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)
}
}
#[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)]
pub struct DropModel<'a> {
pub(in crate::engine) entity: EntityIDRef<'a>,
@ -77,10 +95,17 @@ impl<'a> DropModel<'a> {
Self { entity, force }
}
fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> {
let e = state.try_entity_ref_result()?;
let force = state.cursor_rounded_eq(Token::Ident(Ident::from("force")));
state.cursor_ahead_if(force);
Ok(DropModel::new(e, force))
if state.cursor_is_ident() {
let e = state.try_entity_ref_result()?;
return Ok(DropModel::new(e, false));
} 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,
Transform,
Set,
Return,
// sort related
Order,
Sort,
@ -397,6 +398,7 @@ flattened_lut! {
Else,
Where,
When,
Allow,
// value
Auto,
Default,
@ -421,6 +423,7 @@ flattened_lut! {
}
impl Keyword {
#[inline(always)]
pub fn get(k: &[u8]) -> Option<Self> {
if (k.len() > Self::SIZE_MAX) | (k.len() < Self::SIZE_MIN) {
None
@ -429,13 +432,13 @@ impl Keyword {
}
}
fn compute(key: &[u8]) -> Option<Self> {
static G: [u8; 64] = [
0, 27, 13, 56, 18, 0, 26, 30, 33, 56, 20, 41, 56, 39, 23, 34, 36, 23, 17, 40, 38, 45,
8, 25, 26, 24, 53, 59, 30, 14, 9, 60, 12, 29, 6, 47, 3, 38, 19, 5, 13, 51, 41, 34, 0,
22, 43, 13, 46, 33, 11, 12, 36, 58, 40, 0, 36, 2, 19, 49, 53, 23, 55, 0,
static G: [u8; 69] = [
0, 0, 9, 64, 16, 43, 7, 49, 24, 8, 41, 37, 19, 66, 18, 0, 17, 0, 12, 63, 34, 56, 3, 24,
55, 14, 0, 67, 7, 0, 39, 60, 56, 0, 51, 23, 31, 19, 30, 12, 10, 58, 20, 39, 32, 0, 6,
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 M2: [u8; 11] = *b"F1buDOZ2nzz";
static M1: [u8; 11] = *b"D8N5FwqrxdA";
static M2: [u8; 11] = *b"FsIPJv9hsXx";
let h1 = Self::_sum(key, M1) % G.len();
let h2 = Self::_sum(key, M2) % G.len();
let h = (G[h1] + G[h2]) as usize % G.len();

@ -1124,7 +1124,7 @@ mod ddl_other_query_tests {
}
#[test]
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!(
parse_ast_node_full::<DropSpace>(&src[2..]).unwrap(),
DropSpace::new(Ident::from("myspace"), true)
@ -1140,7 +1140,7 @@ mod ddl_other_query_tests {
}
#[test]
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!(
parse_ast_node_full_with_space::<DropModel>(&src[2..], "apps").unwrap(),
DropModel::new(("apps", "mymodel").into(), true)

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

@ -28,7 +28,7 @@ use {
super::GNSEvent,
crate::{
engine::{
core::{space::Space, GlobalNS},
core::{space::Space, EntityIDRef, GlobalNS},
data::DictGeneric,
error::{RuntimeResult, TransactionError},
idx::STIndex,
@ -278,10 +278,17 @@ impl<'a> GNSEvent for DropSpaceTxn<'a> {
gns: &GlobalNS,
) -> RuntimeResult<()> {
let mut wgns = gns.idx().write();
let mut wmodel = gns.idx_models().write();
match wgns.entry(name) {
std::collections::hash_map::Entry::Occupied(oe) => {
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();
Ok(())
} else {

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

Loading…
Cancel
Save