Simplify DML path [skip ci]

next
Sayan Nandan 11 months ago
parent 09bd217998
commit cac33bf7c2
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -24,6 +24,9 @@
*
*/
use self::dml::QueryExecMeta;
pub use self::util::{EntityID, EntityIDRef};
use super::{fractal::GlobalInstanceLike, ql::ast::Entity};
pub(in crate::engine) mod dml;
pub mod exec;
pub(in crate::engine) mod index;
@ -36,86 +39,105 @@ mod util;
pub(super) mod tests;
// imports
use {
self::{model::Model, util::EntityLocator},
self::model::Model,
crate::engine::{
core::space::Space,
error::{QueryError, QueryResult},
fractal::GlobalInstanceLike,
idx::{IndexST, STIndex},
idx::IndexST,
},
parking_lot::RwLock,
std::collections::HashMap,
};
/// Use this for now since it substitutes for a file lock (and those syscalls are expensive),
/// but something better is in the offing
type RWLIdx<K, V> = RwLock<IndexST<K, V>>;
// FIXME(@ohsayan): Make sure we update what all structures we're making use of here
#[cfg_attr(test, derive(Debug))]
pub struct GlobalNS {
index_space: RWLIdx<Box<str>, Space>,
}
pub(self) fn with_model_for_data_update<'a, E, F>(
global: &impl GlobalInstanceLike,
entity: E,
f: F,
) -> QueryResult<()>
where
F: FnOnce(&Model) -> QueryResult<dml::QueryExecMeta>,
E: 'a + EntityLocator<'a>,
{
let (space_name, model_name) = entity.parse_entity()?;
global
.namespace()
.with_model((space_name, model_name), |mdl| {
let r = f(mdl);
match r {
Ok(dhint) => {
model::DeltaState::guard_delta_overflow(
global, space_name, model_name, mdl, dhint,
);
Ok(())
}
Err(e) => Err(e),
}
})
idx_mdl: RWLIdx<EntityID, Model>,
idx: RWLIdx<Box<str>, RwLock<Space>>,
}
impl GlobalNS {
pub fn spaces(&self) -> &RWLIdx<Box<str>, Space> {
&self.index_space
}
pub fn empty() -> Self {
Self {
index_space: RWLIdx::default(),
idx_mdl: RWLIdx::default(),
idx: RWLIdx::default(),
}
}
#[cfg(test)]
pub(self) fn test_new_empty_space(&self, space_id: &str) -> bool {
self.index_space
.write()
.st_insert(space_id.into(), Space::empty())
pub fn ddl_with_spaces_write<T>(
&self,
f: impl FnOnce(&mut HashMap<Box<str>, RwLock<Space>>) -> T,
) -> T {
let mut spaces = self.idx.write();
f(&mut spaces)
}
pub fn with_space<T>(
pub fn ddl_with_space_mut<T>(
&self,
space: &str,
f: impl FnOnce(&Space) -> QueryResult<T>,
f: impl FnOnce(&mut Space) -> QueryResult<T>,
) -> QueryResult<T> {
let sread = self.index_space.read();
let Some(space) = sread.st_get(space) else {
let spaces = self.idx.read();
let Some(space) = spaces.get(space) else {
return Err(QueryError::QExecObjectNotFound);
};
f(space)
let mut space = space.write();
f(&mut space)
}
pub fn with_model<'a, T, E, F>(&self, entity: E, f: F) -> QueryResult<T>
pub fn with_model_space<'a, T, F>(&self, entity: Entity<'a>, f: F) -> QueryResult<T>
where
F: FnOnce(&Space, &Model) -> QueryResult<T>,
{
let (space, model_name) = entity.into_full_result()?;
let mdl_idx = self.idx_mdl.read();
let Some(model) = mdl_idx.get(&EntityIDRef::new(&space, &model_name)) else {
return Err(QueryError::QExecObjectNotFound);
};
let space_read = self.idx.read();
let space = space_read.get(space.as_str()).unwrap().read();
f(&space, model)
}
pub fn with_model<'a, T, F>(&self, entity: Entity<'a>, f: F) -> QueryResult<T>
where
F: FnOnce(&Model) -> QueryResult<T>,
E: 'a + EntityLocator<'a>,
{
entity
.parse_entity()
.and_then(|(space, model)| self.with_space(space, |space| space.with_model(model, f)))
let (space, model_name) = entity.into_full_result()?;
let mdl_idx = self.idx_mdl.read();
let Some(model) = mdl_idx.get(&EntityIDRef::new(&space, &model_name)) else {
return Err(QueryError::QExecObjectNotFound);
};
f(model)
}
pub fn idx_models(&self) -> &RWLIdx<EntityID, Model> {
&self.idx_mdl
}
pub fn idx(&self) -> &RWLIdx<Box<str>, RwLock<Space>> {
&self.idx
}
#[cfg(test)]
pub fn create_empty_test_space(&self, space_name: &str) {
let _ = self
.idx()
.write()
.insert(space_name.into(), Space::new_auto_all().into());
}
}
pub(self) fn with_model_for_data_update<'a, F>(
global: &impl GlobalInstanceLike,
entity: Entity<'a>,
f: F,
) -> QueryResult<()>
where
F: FnOnce(&Model) -> QueryResult<QueryExecMeta>,
{
let (space, model_name) = entity.into_full_result()?;
let mdl_idx = global.namespace().idx_mdl.read();
let Some(model) = mdl_idx.get(&EntityIDRef::new(&space, &model_name)) else {
return Err(QueryError::QExecObjectNotFound);
};
let r = f(model)?;
model::DeltaState::guard_delta_overflow(global, &space, &model_name, model, r);
Ok(())
}

@ -28,7 +28,6 @@ use {
super::{Field, IWModel, Layer, Model},
crate::{
engine::{
core::util::EntityLocator,
data::{
tag::{DataTag, TagClass},
DictEntryGeneric,
@ -253,9 +252,10 @@ impl Model {
global: &G,
alter: AlterModel,
) -> QueryResult<()> {
let (space_name, model_name) = EntityLocator::parse_entity(alter.model)?;
global.namespace().with_space(space_name, |space| {
space.with_model(model_name, |model| {
let (space_name, model_name) = alter.model.into_full_result()?;
global
.namespace()
.with_model_space(alter.model, |space, model| {
// make intent
let iwm = model.intent_write_model();
// prepare plan
@ -275,7 +275,12 @@ impl Model {
if G::FS_IS_NON_NULL {
// prepare txn
let txn = gnstxn::AlterModelAddTxn::new(
gnstxn::ModelIDRef::new_ref(space_name, space, model_name, model),
gnstxn::ModelIDRef::new_ref(
&space_name,
&space,
&model_name,
model,
),
&new_fields,
);
// commit txn
@ -296,7 +301,7 @@ impl Model {
if G::FS_IS_NON_NULL {
// prepare txn
let txn = gnstxn::AlterModelRemoveTxn::new(
gnstxn::ModelIDRef::new_ref(space_name, space, model_name, model),
gnstxn::ModelIDRef::new_ref(&space_name, space, &model_name, model),
&removed,
);
// commit txn
@ -314,7 +319,7 @@ impl Model {
if G::FS_IS_NON_NULL {
// prepare txn
let txn = gnstxn::AlterModelUpdateTxn::new(
gnstxn::ModelIDRef::new_ref(space_name, space, model_name, model),
gnstxn::ModelIDRef::new_ref(&space_name, space, &model_name, model),
&updated,
);
// commit txn
@ -327,6 +332,5 @@ impl Model {
}
Ok(())
})
})
}
}

@ -32,7 +32,7 @@ use std::cell::RefCell;
use {
self::delta::{IRModel, IRModelSMData, ISyncMatrix, IWModel},
super::{index::PrimaryIndex, util::EntityLocator},
super::index::PrimaryIndex,
crate::engine::{
data::{
cell::Datacell,
@ -48,12 +48,14 @@ use {
drop::DropModel,
syn::{FieldSpec, LayerSpec},
},
txn::gns as gnstxn,
txn::gns::{self as gnstxn, SpaceIDRef},
},
std::cell::UnsafeCell,
};
pub(in crate::engine::core) use self::delta::{DeltaState, DeltaVersion, SchemaDeltaKind};
use super::util::{EntityID, EntityIDRef};
pub(in crate::engine) type Fields = IndexSTSeqCns<Box<str>, Field>;
#[derive(Debug)]
@ -204,40 +206,41 @@ impl Model {
global: &G,
stmt: CreateModel,
) -> QueryResult<()> {
let (space_name, model_name) = stmt.model_name.parse_entity()?;
let (space_name, model_name) = stmt.model_name.into_full_result()?;
let model = Self::process_create(stmt)?;
global.namespace().with_space(space_name, |space| {
let mut w_space = space.models().write();
if w_space.st_contains(model_name) {
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.as_str()) {
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 {
let irm = model.intent_read_model();
let mut txn_driver = global.namespace_txn_driver().lock();
// prepare txn
let txn = gnstxn::CreateModelTxn::new(
gnstxn::SpaceIDRef::new(space_name, space),
model_name,
SpaceIDRef::new(&space_name, &space),
&model_name,
&model,
&irm,
);
// attempt to initialize driver
global.initialize_model_driver(
space_name,
&space_name,
space.get_uuid(),
model_name,
&model_name,
model.get_uuid(),
)?;
// commit txn
match txn_driver.try_commit(txn) {
Ok(()) => {}
Err(e) => {
// failed to commit, delete this
// failed to commit, request cleanup
global.taskmgr_post_standard_priority(Task::new(
GenericTask::delete_model_dir(
space_name,
&space_name,
space.get_uuid(),
model_name,
&model_name,
model.get_uuid(),
),
));
@ -246,7 +249,12 @@ impl Model {
}
}
// update global state
let _ = w_space.st_insert(model_name.into(), model);
let _ = space.models_mut().insert(model_name.as_str().into());
let _ = global
.namespace()
.idx_models()
.write()
.insert(EntityID::new(&space_name, &model_name), model);
Ok(())
})
}
@ -254,32 +262,44 @@ impl Model {
global: &G,
stmt: DropModel,
) -> QueryResult<()> {
let (space_name, model_name) = stmt.entity.parse_entity()?;
global.namespace().with_space(space_name, |space| {
let mut w_space = space.models().write();
let Some(model) = w_space.get(model_name) else {
let (space_name, model_name) = stmt.entity.into_full_result()?;
global.namespace().ddl_with_space_mut(&space_name, |space| {
if !space.models().contains(model_name.as_str()) {
// the model isn't even present
return Err(QueryError::QExecObjectNotFound);
};
}
// get exclusive lock on models
let mut models_idx = global.namespace().idx_models().write();
let model = models_idx
.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 {
// nope, we can't drop this
return Err(QueryError::QExecDdlNotEmpty);
}
// okay this is looking good for us
if G::FS_IS_NON_NULL {
// prepare txn
let txn = gnstxn::DropModelTxn::new(gnstxn::ModelIDRef::new(
gnstxn::SpaceIDRef::new(space_name, space),
model_name,
SpaceIDRef::new(&space_name, &space),
&model_name,
model.get_uuid(),
model.delta_state().schema_current_version().value_u64(),
));
// commit txn
global.namespace_txn_driver().lock().try_commit(txn)?;
// ask for cleanup
// request cleanup
global.taskmgr_post_standard_priority(Task::new(GenericTask::delete_model_dir(
space_name,
&space_name,
space.get_uuid(),
model_name,
&model_name,
model.get_uuid(),
)));
}
// update global state
let _ = w_space.st_delete(model_name);
let _ = models_idx.remove(&EntityIDRef::new(&space_name, &model_name));
let _ = space.models_mut().remove(model_name.as_str());
Ok(())
})
}

@ -26,58 +26,26 @@
use {
crate::engine::{
core::{model::Model, RWLIdx},
data::{dict, uuid::Uuid, DictEntryGeneric, DictGeneric},
error::{QueryError, QueryResult},
fractal::{GenericTask, GlobalInstanceLike, Task},
idx::{IndexST, STIndex},
idx::STIndex,
ql::ddl::{alt::AlterSpace, crt::CreateSpace, drop::DropSpace},
storage::v1::{loader::SEInitState, RawFSInterface},
txn::gns as gnstxn,
},
parking_lot::RwLock,
std::collections::HashSet,
};
#[derive(Debug)]
/// A space with the model namespace
#[derive(Debug, PartialEq)]
pub struct Space {
uuid: Uuid,
mns: RWLIdx<Box<str>, Model>,
pub(super) meta: SpaceMeta,
models: HashSet<Box<str>>,
props: DictGeneric,
}
#[derive(Debug, Default)]
/// Space metadata
pub struct SpaceMeta {
pub(super) props: RwLock<DictGeneric>,
}
impl SpaceMeta {
pub const KEY_ENV: &'static str = "env";
pub fn new_with_meta(props: DictGeneric) -> Self {
Self {
props: RwLock::new(props),
}
}
pub fn with_env(env: DictGeneric) -> Self {
Self {
props: RwLock::new(into_dict!("env" => DictEntryGeneric::Map(env))),
}
}
pub fn dict(&self) -> &RwLock<DictGeneric> {
&self.props
}
#[cfg(test)]
pub fn get_env<'a>(rwl: &'a parking_lot::RwLockReadGuard<'a, DictGeneric>) -> &'a DictGeneric {
match rwl.get(Self::KEY_ENV).unwrap() {
DictEntryGeneric::Data(_) => unreachable!(),
DictEntryGeneric::Map(m) => m,
}
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, PartialEq)]
/// Procedure for `create space`
struct ProcedureCreate {
space_name: Box<str>,
@ -85,66 +53,52 @@ struct ProcedureCreate {
}
impl Space {
pub fn _create_model(&self, name: &str, model: Model) -> QueryResult<()> {
if self
.mns
.write()
.st_insert(name.to_string().into_boxed_str(), model)
{
Ok(())
} else {
Err(QueryError::QExecDdlObjectAlreadyExists)
pub fn new(uuid: Uuid, models: HashSet<Box<str>>, props: DictGeneric) -> Self {
Self {
uuid,
models,
props,
}
}
#[cfg(test)]
pub fn new_auto_all() -> Self {
Self::new_auto(Default::default(), Default::default())
}
pub fn get_uuid(&self) -> Uuid {
self.uuid
}
pub fn models(&self) -> &RWLIdx<Box<str>, Model> {
&self.mns
pub fn new_restore_empty(uuid: Uuid, props: DictGeneric) -> Self {
Self::new(uuid, Default::default(), props)
}
pub fn metadata(&self) -> &SpaceMeta {
&self.meta
pub fn new_empty_auto(props: DictGeneric) -> Self {
Self::new_auto(Default::default(), props)
}
pub fn with_model<T>(
&self,
model: &str,
f: impl FnOnce(&Model) -> QueryResult<T>,
) -> QueryResult<T> {
let mread = self.mns.read();
let Some(model) = mread.st_get(model) else {
return Err(QueryError::QExecObjectNotFound);
};
f(model)
pub fn new_auto(models: HashSet<Box<str>>, props: DictGeneric) -> Self {
Self::new(Uuid::new(), models, props)
}
pub(crate) fn new_restore_empty(meta: SpaceMeta, uuid: Uuid) -> Space {
Self::new_with_uuid(Default::default(), meta, uuid)
pub fn models(&self) -> &HashSet<Box<str>> {
&self.models
}
}
impl Space {
#[cfg(test)]
pub fn empty() -> Self {
Space::new_auto(Default::default(), SpaceMeta::with_env(into_dict! {}))
pub fn models_mut(&mut self) -> &mut HashSet<Box<str>> {
&mut self.models
}
#[cfg(test)]
pub fn empty_with_uuid(uuid: Uuid) -> Self {
Space::new_with_uuid(Default::default(), SpaceMeta::with_env(into_dict!()), uuid)
pub fn props(&self) -> &DictGeneric {
&self.props
}
#[inline(always)]
pub fn new_auto(mns: IndexST<Box<str>, Model>, meta: SpaceMeta) -> Self {
Self {
uuid: Uuid::new(),
mns: RWLIdx::new(mns),
meta,
}
pub fn props_mut(&mut self) -> &mut DictGeneric {
&mut self.props
}
pub fn new_with_uuid(mns: IndexST<Box<str>, Model>, meta: SpaceMeta, uuid: Uuid) -> Self {
Self {
uuid,
meta,
mns: RwLock::new(mns),
#[cfg(test)]
pub fn env(&self) -> &DictGeneric {
match self.props().get(Self::KEY_ENV).unwrap() {
DictEntryGeneric::Map(m) => m,
_ => panic!(),
}
}
}
impl Space {
const KEY_ENV: &'static str = "env";
#[inline]
/// Validate a `create` stmt
fn process_create(
@ -154,25 +108,34 @@ impl Space {
}: CreateSpace,
) -> QueryResult<ProcedureCreate> {
let space_name = space_name.to_string().into_boxed_str();
// check env
let env = match props.remove(SpaceMeta::KEY_ENV) {
Some(DictEntryGeneric::Map(m)) if props.is_empty() => m,
Some(DictEntryGeneric::Data(l)) if l.is_null() => IndexST::default(),
None if props.is_empty() => IndexST::default(),
// now let's check our props
match props.get(Self::KEY_ENV) {
Some(d) if props.len() == 1 => {
match d {
DictEntryGeneric::Data(d) if d.is_init() => {
// not the right type for a dict
return Err(QueryError::QExecDdlInvalidProperties);
}
DictEntryGeneric::Data(_) => {
// a null? make it empty
let _ =
props.insert(Self::KEY_ENV.into(), DictEntryGeneric::Map(into_dict!()));
}
DictEntryGeneric::Map(_) => {}
}
}
None if props.is_empty() => {
let _ = props.st_insert(Self::KEY_ENV.into(), DictEntryGeneric::Map(into_dict!()));
}
_ => {
// unknown properties
// in all the other cases, we have illegal properties
// not the right type for a dict
return Err(QueryError::QExecDdlInvalidProperties);
}
};
}
Ok(ProcedureCreate {
space_name,
space: Self::new_auto(
IndexST::default(),
SpaceMeta::with_env(
// FIXME(@ohsayan): see this is bad. attempt to do it at AST build time
dict::rflatten_metadata(env),
),
),
space: Space::new_empty_auto(dict::rflatten_metadata(props)),
})
}
}
@ -184,36 +147,36 @@ impl Space {
) -> QueryResult<()> {
// process create
let ProcedureCreate { space_name, space } = Self::process_create(space)?;
// acquire access
let mut wl = global.namespace().spaces().write();
if wl.st_contains(&space_name) {
return Err(QueryError::QExecDdlObjectAlreadyExists);
}
// commit txn
if G::FS_IS_NON_NULL {
// prepare txn
let s_read = space.metadata().dict().read();
let txn = gnstxn::CreateSpaceTxn::new(&s_read, &space_name, &space);
// try to create space for...the space
G::FileSystem::fs_create_dir_all(&SEInitState::space_dir(
&space_name,
space.get_uuid(),
))?;
// lock the global namespace
global.namespace().ddl_with_spaces_write(|spaces| {
if spaces.st_contains(&space_name) {
return Err(QueryError::QExecDdlObjectAlreadyExists);
}
// commit txn
match global.namespace_txn_driver().lock().try_commit(txn) {
Ok(()) => {}
Err(e) => {
// tell fractal to clean it up sometime
global.taskmgr_post_standard_priority(Task::new(
GenericTask::delete_space_dir(&space_name, space.get_uuid()),
));
return Err(e.into());
if G::FS_IS_NON_NULL {
// prepare txn
let txn = gnstxn::CreateSpaceTxn::new(space.props(), &space_name, &space);
// try to create space for...the space
G::FileSystem::fs_create_dir_all(&SEInitState::space_dir(
&space_name,
space.get_uuid(),
))?;
// commit txn
match global.namespace_txn_driver().lock().try_commit(txn) {
Ok(()) => {}
Err(e) => {
// tell fractal to clean it up sometime
global.taskmgr_post_standard_priority(Task::new(
GenericTask::delete_space_dir(&space_name, space.get_uuid()),
));
return Err(e.into());
}
}
}
}
// update global state
let _ = wl.st_insert(space_name, space);
Ok(())
// update global state
let _ = spaces.st_insert(space_name, RwLock::new(space));
Ok(())
})
}
#[allow(unused)]
pub fn transactional_exec_alter<G: GlobalInstanceLike>(
@ -223,16 +186,15 @@ impl Space {
updated_props,
}: AlterSpace,
) -> QueryResult<()> {
global.namespace().with_space(&space_name, |space| {
match updated_props.get(SpaceMeta::KEY_ENV) {
global.namespace().ddl_with_space_mut(&space_name, |space| {
match updated_props.get(Self::KEY_ENV) {
Some(DictEntryGeneric::Map(_)) if updated_props.len() == 1 => {}
Some(DictEntryGeneric::Data(l)) if updated_props.len() == 1 && l.is_null() => {}
None if updated_props.is_empty() => return Ok(()),
_ => return Err(QueryError::QExecDdlInvalidProperties),
}
let mut space_props = space.meta.dict().write();
// create patch
let patch = match dict::rprepare_metadata_patch(&space_props, updated_props) {
let patch = match dict::rprepare_metadata_patch(space.props(), updated_props) {
Some(patch) => patch,
None => return Err(QueryError::QExecDdlInvalidProperties),
};
@ -244,64 +206,49 @@ impl Space {
global.namespace_txn_driver().lock().try_commit(txn)?;
}
// merge
dict::rmerge_data_with_patch(&mut space_props, patch);
dict::rmerge_data_with_patch(space.props_mut(), patch);
// the `env` key may have been popped, so put it back (setting `env: null` removes the env key and we don't want to waste time enforcing this in the
// merge algorithm)
let _ = space_props.st_insert(
SpaceMeta::KEY_ENV.into(),
DictEntryGeneric::Map(into_dict!()),
);
let _ = space
.props_mut()
.st_insert(Self::KEY_ENV.into(), DictEntryGeneric::Map(into_dict!()));
Ok(())
})
}
pub fn transactional_exec_drop<G: GlobalInstanceLike>(
global: &G,
DropSpace { space, force: _ }: DropSpace,
DropSpace {
space: space_name,
force: _,
}: DropSpace,
) -> QueryResult<()> {
// TODO(@ohsayan): force remove option
// TODO(@ohsayan): should a drop space block the entire global table?
let space_name = space;
let mut wgns = global.namespace().spaces().write();
let space = match wgns.get(space_name.as_str()) {
Some(space) => space,
None => return Err(QueryError::QExecObjectNotFound),
};
let space_w = space.mns.write();
if space_w.st_len() != 0 {
return Err(QueryError::QExecDdlNotEmpty);
}
// we can remove this
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)?;
// ask for cleanup
global.taskmgr_post_standard_priority(Task::new(GenericTask::delete_space_dir(
&space_name,
space.get_uuid(),
)));
}
drop(space_w);
let _ = wgns.st_delete(space_name.as_str());
Ok(())
}
}
#[cfg(test)]
impl PartialEq for SpaceMeta {
fn eq(&self, other: &Self) -> bool {
let x = self.props.read();
let y = other.props.read();
*x == *y
}
}
#[cfg(test)]
impl PartialEq for Space {
fn eq(&self, other: &Self) -> bool {
let self_mns = self.mns.read();
let other_mns = other.mns.read();
self.meta == other.meta && *self_mns == *other_mns && self.uuid == other.uuid
global.namespace().ddl_with_spaces_write(|spaces| {
let Some(space) = spaces.get(space_name.as_str()) else {
return Err(QueryError::QExecObjectNotFound);
};
let space = space.read();
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(),
)));
}
// good, we can get rid of this thing
drop(space);
let _ = spaces.st_delete(space_name.as_str());
Ok(())
})
}
}

@ -28,10 +28,10 @@ use crate::engine::{
core::{
model::{alt::AlterPlan, Model},
tests::ddl_model::{create, exec_create},
EntityIDRef,
},
error::QueryResult,
fractal::GlobalInstanceLike,
idx::STIndex,
ql::{ast::parse_ast_node_full, ddl::alt::AlterModel, tests::lex_insecure},
};
@ -55,19 +55,19 @@ fn exec_plan(
) -> QueryResult<()> {
let mdl_name = exec_create(global, model, new_space)?;
let prev_uuid = {
let gns = global.namespace().spaces().read();
let space = gns.get("myspace").unwrap();
let space_read = space.models().read();
space_read.get(mdl_name.as_str()).unwrap().get_uuid()
global
.namespace()
.idx_models()
.read()
.get(&EntityIDRef::new("myspace", &mdl_name))
.map(|mdl| mdl.get_uuid())
.unwrap()
};
let tok = lex_insecure(plan.as_bytes()).unwrap();
let alter = parse_ast_node_full::<AlterModel>(&tok[2..]).unwrap();
let (_space, model_name) = alter.model.into_full().unwrap();
Model::transactional_exec_alter(global, alter)?;
let gns_read = global.namespace().spaces().read();
let space = gns_read.st_get("myspace").unwrap();
let model = space.models().read();
let model = model.st_get(model_name.as_str()).unwrap();
let models = global.namespace().idx_models().read();
let model = models.get(&EntityIDRef::new("myspace", &mdl_name)).unwrap();
assert_eq!(prev_uuid, model.get_uuid());
f(model);
Ok(())

@ -29,10 +29,9 @@ mod crt;
mod layer;
use crate::engine::{
core::{model::Model, space::Space},
core::{model::Model, EntityIDRef},
error::QueryResult,
fractal::GlobalInstanceLike,
idx::STIndex,
ql::{
ast::{parse_ast_node_full, Entity},
ddl::crt::CreateModel,
@ -59,7 +58,7 @@ pub fn exec_create(
if create_new_space {
global
.namespace()
.test_new_empty_space(&create_model.model_name.into_full().unwrap().0);
.create_empty_test_space(&create_model.model_name.into_full().unwrap().0)
}
Model::transactional_exec_create(global, create_model).map(|_| name)
}
@ -71,21 +70,13 @@ pub fn exec_create_new_space(
exec_create(global, create_stmt, true).map(|_| ())
}
fn with_space(global: &impl GlobalInstanceLike, space_name: &str, f: impl Fn(&Space)) {
let rl = global.namespace().spaces().read();
let space = rl.st_get(space_name).unwrap();
f(space);
}
fn with_model(
global: &impl GlobalInstanceLike,
space_id: &str,
model_name: &str,
f: impl Fn(&Model),
) {
with_space(global, space_id, |space| {
let space_rl = space.models().read();
let model = space_rl.st_get(model_name).unwrap();
f(model)
})
let models = global.namespace().idx_models().read();
let model = models.get(&EntityIDRef::new(space_id, model_name)).unwrap();
f(model)
}

@ -25,8 +25,8 @@
*/
use crate::engine::{
core::space::{Space, SpaceMeta},
data::cell::Datacell,
core::space::Space,
data::{cell::Datacell, DictEntryGeneric},
error::QueryError,
fractal::test_utils::TestGlobal,
};
@ -41,10 +41,9 @@ fn alter_add_prop_env_var() {
|space| {
assert_eq!(
space,
&Space::new_with_uuid(
into_dict!(),
SpaceMeta::with_env(into_dict! ("MY_NEW_PROP" => Datacell::new_uint_default(100))),
space.get_uuid()
&Space::new_restore_empty(
space.get_uuid(),
into_dict!("env" => DictEntryGeneric::Map(into_dict!("MY_NEW_PROP" => Datacell::new_uint_default(100)))),
)
);
},
@ -59,9 +58,8 @@ fn alter_update_prop_env_var() {
&global,
"create space myspace with { env: { MY_NEW_PROP: 100 } }",
|space| {
let rl = space.meta.dict().read();
assert_eq!(
SpaceMeta::get_env(&rl).get("MY_NEW_PROP").unwrap(),
space.env().get("MY_NEW_PROP").unwrap(),
&(Datacell::new_uint_default(100).into())
)
},
@ -73,10 +71,9 @@ fn alter_update_prop_env_var() {
|space| {
assert_eq!(
space,
&Space::new_with_uuid(
into_dict!(),
SpaceMeta::with_env(into_dict! ("MY_NEW_PROP" => Datacell::new_uint_default(200))),
&Space::new_restore_empty(
uuid,
into_dict! ("env" => DictEntryGeneric::Map(into_dict!("MY_NEW_PROP" => Datacell::new_uint_default(200)))),
)
)
},
@ -91,9 +88,8 @@ fn alter_remove_prop_env_var() {
&global,
"create space myspace with { env: { MY_NEW_PROP: 100 } }",
|space| {
let rl = space.meta.dict().read();
assert_eq!(
SpaceMeta::get_env(&rl).get("MY_NEW_PROP").unwrap(),
space.env().get("MY_NEW_PROP").unwrap(),
&(Datacell::new_uint_default(100).into())
)
},
@ -105,7 +101,10 @@ fn alter_remove_prop_env_var() {
|space| {
assert_eq!(
space,
&Space::new_with_uuid(into_dict!(), SpaceMeta::with_env(into_dict!()), uuid)
&Space::new_restore_empty(
uuid,
into_dict!("env" => DictEntryGeneric::Map(into_dict!()))
)
)
},
)
@ -133,16 +132,21 @@ fn alter_remove_all_env() {
&global,
"create space myspace with { env: { MY_NEW_PROP: 100 } }",
|space| {
let rl = space.meta.dict().read();
assert_eq!(
SpaceMeta::get_env(&rl).get("MY_NEW_PROP").unwrap(),
space.env().get("MY_NEW_PROP").unwrap(),
&(Datacell::new_uint_default(100).into())
)
},
)
.unwrap();
super::exec_alter(&global, "alter space myspace with { env: null }", |space| {
assert_eq!(space, &Space::empty_with_uuid(uuid))
assert_eq!(
space,
&Space::new_restore_empty(
uuid,
into_dict!("env" => DictEntryGeneric::Map(into_dict!()))
)
)
})
.unwrap();
}

@ -25,8 +25,8 @@
*/
use crate::engine::{
core::space::{Space, SpaceMeta},
data::cell::Datacell,
core::space::Space,
data::{cell::Datacell, DictEntryGeneric},
error::QueryError,
fractal::test_utils::TestGlobal,
};
@ -35,7 +35,7 @@ use crate::engine::{
fn exec_create_space_simple() {
let global = TestGlobal::new_with_tmp_nullfs_driver();
super::exec_create(&global, "create space myspace", |spc| {
assert!(spc.models().read().is_empty())
assert!(spc.models().is_empty())
})
.unwrap();
}
@ -55,12 +55,11 @@ fn exec_create_space_with_env() {
|space| {
assert_eq!(
space,
&Space::new_with_uuid(
into_dict! {},
SpaceMeta::with_env(into_dict! {
"MAX_MODELS" => Datacell::new_uint_default(100)
}),
space.get_uuid()
&Space::new_restore_empty(
space.get_uuid(),
into_dict! {
"env" => DictEntryGeneric::Map(into_dict!("MAX_MODELS" => Datacell::new_uint_default(100)))
},
)
);
},

@ -48,7 +48,7 @@ fn exec_create(
ast::parse_ast_node_full::<crate::engine::ql::ddl::crt::CreateSpace>(&tok[2..]).unwrap();
let name = ast_node.space_name;
Space::transactional_exec_create(gns, ast_node)?;
gns.namespace().with_space(&name, |space| {
gns.namespace().ddl_with_space_mut(&name, |space| {
verify(space);
Ok(space.get_uuid())
})
@ -64,7 +64,7 @@ fn exec_alter(
ast::parse_ast_node_full::<crate::engine::ql::ddl::alt::AlterSpace>(&tok[2..]).unwrap();
let name = ast_node.space_name;
Space::transactional_exec_alter(gns, ast_node)?;
gns.namespace().with_space(&name, |space| {
gns.namespace().ddl_with_space_mut(&name, |space| {
verify(space);
Ok(space.get_uuid())
})

@ -30,7 +30,7 @@ mod select;
mod update;
use crate::engine::{
core::{dml, index::Row, model::Model},
core::{dml, index::Row, model::Model, space::Space},
data::{cell::Datacell, lit::Lit},
error::QueryResult,
fractal::GlobalInstanceLike,
@ -43,9 +43,11 @@ use crate::engine::{
};
fn _exec_only_create_space_model(global: &impl GlobalInstanceLike, model: &str) -> QueryResult<()> {
if !global.namespace().spaces().read().contains_key("myspace") {
global.namespace().test_new_empty_space("myspace");
}
let _ = global
.namespace()
.idx()
.write()
.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)

@ -24,28 +24,127 @@
*
*/
use crate::engine::{
error::{QueryError, QueryResult},
ql::ast::Entity,
use std::{
alloc::{dealloc, Layout},
borrow::Borrow,
fmt,
hash::Hash,
marker::PhantomData,
mem::ManuallyDrop,
slice, str,
};
pub trait EntityLocator<'a> {
fn parse_entity(self) -> QueryResult<(&'a str, &'a str)>
where
Self: 'a;
pub struct EntityID {
sp: *mut u8,
sl: usize,
ep: *mut u8,
el: usize,
}
impl<'a> EntityLocator<'a> for (&'a str, &'a str) {
fn parse_entity(self) -> QueryResult<(&'a str, &'a str)> {
Ok(self)
impl EntityID {
pub fn new(space: &str, entity: &str) -> Self {
let mut space = ManuallyDrop::new(space.to_owned().into_boxed_str().into_boxed_bytes());
let mut entity = ManuallyDrop::new(entity.to_owned().into_boxed_str().into_boxed_bytes());
Self {
sp: space.as_mut_ptr(),
sl: space.len(),
ep: entity.as_mut_ptr(),
el: entity.len(),
}
}
pub fn space(&self) -> &str {
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.sp, self.sl)) }
}
pub fn entity(&self) -> &str {
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ep, self.el)) }
}
}
impl Drop for EntityID {
fn drop(&mut self) {
unsafe {
dealloc(self.sp, Layout::array::<u8>(self.sl).unwrap_unchecked());
dealloc(self.ep, Layout::array::<u8>(self.el).unwrap_unchecked());
}
}
}
impl PartialEq for EntityID {
fn eq(&self, other: &Self) -> bool {
self.space() == other.space() && self.entity() == other.entity()
}
}
impl Eq for EntityID {}
impl Hash for EntityID {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.space().hash(state);
self.entity().hash(state);
}
}
impl fmt::Debug for EntityID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EntityID")
.field("space", &self.space())
.field("entity", &self.entity())
.finish()
}
}
pub struct EntityIDRef<'a> {
sp: *const u8,
sl: usize,
ep: *const u8,
el: usize,
_lt: PhantomData<(&'a str, &'a str)>,
}
impl<'a> EntityIDRef<'a> {
pub fn new(space: &'a str, entity: &'a str) -> Self {
Self {
sp: space.as_ptr(),
sl: space.len(),
ep: entity.as_ptr(),
el: entity.len(),
_lt: PhantomData,
}
}
pub fn space(&self) -> &'a str {
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.sp, self.sl)) }
}
pub fn entity(&self) -> &'a str {
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ep, self.el)) }
}
}
impl<'a> PartialEq for EntityIDRef<'a> {
fn eq(&self, other: &Self) -> bool {
self.space() == other.space() && self.entity() == other.entity()
}
}
impl<'a> Eq for EntityIDRef<'a> {}
impl<'a> Hash for EntityIDRef<'a> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.space().hash(state);
self.entity().hash(state);
}
}
impl<'a> fmt::Debug for EntityIDRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EntityIDRef")
.field("space", &self.space())
.field("entity", &self.entity())
.finish()
}
}
impl<'a> EntityLocator<'a> for Entity<'a> {
fn parse_entity(self) -> QueryResult<(&'a str, &'a str)>
where
Self: 'a,
{
self.into_full_str().ok_or(QueryError::QLExpectedEntity)
impl<'a> Borrow<EntityIDRef<'a>> for EntityID {
fn borrow(&self) -> &EntityIDRef<'a> {
unsafe { core::mem::transmute(self) }
}
}

@ -30,6 +30,7 @@ use {
engine::{
core::model::{delta::DataDelta, Model},
data::uuid::Uuid,
ql::ast::Entity,
storage::v1::LocalFS,
},
util::os,
@ -279,17 +280,17 @@ impl FractalMgr {
// was way behind in the queue
return;
};
let res =
global
._namespace()
.with_model((model_id.space(), model_id.model()), |model| {
if model.get_uuid() != model_id.uuid() {
// once again, throughput maximization will lead to, in extremely rare cases, this
// branch returning. but it is okay
return Ok(());
}
Self::try_write_model_data_batch(model, observed_size, mdl_driver)
});
let res = global._namespace().with_model(
Entity::Full(model_id.space().into(), model_id.model().into()),
|model| {
if model.get_uuid() != model_id.uuid() {
// once again, throughput maximization will lead to, in extremely rare cases, this
// branch returning. but it is okay
return Ok(());
}
Self::try_write_model_data_batch(model, observed_size, mdl_driver)
},
);
match res {
Ok(()) => {
if observed_size != 0 {
@ -298,7 +299,7 @@ impl FractalMgr {
}
Err(_) => {
log::error!(
"fhp: error writing data batch for model {}. Retrying...",
"fhp: error writing data batch for model {}. retrying ...",
model_id.uuid()
);
// enqueue again for retrying
@ -369,21 +370,21 @@ impl FractalMgr {
let mdl_drivers = global.get_state().get_mdl_drivers().read();
for (model_id, driver) in mdl_drivers.iter() {
let mut observed_len = 0;
let res =
global
._namespace()
.with_model((model_id.space(), model_id.model()), |model| {
if model.get_uuid() != model_id.uuid() {
// once again, throughput maximization will lead to, in extremely rare cases, this
// branch returning. but it is okay
return Ok(());
}
// mark that we're taking these deltas
observed_len = model
.delta_state()
.__fractal_take_full_from_data_delta(super::FractalToken::new());
Self::try_write_model_data_batch(model, observed_len, driver)
});
let res = global._namespace().with_model(
Entity::Full(model_id.space().into(), model_id.model().into()),
|model| {
if model.get_uuid() != model_id.uuid() {
// once again, throughput maximization will lead to, in extremely rare cases, this
// branch returning. but it is okay
return Ok(());
}
// mark that we're taking these deltas
observed_len = model
.delta_state()
.__fractal_take_full_from_data_delta(super::FractalToken::new());
Self::try_write_model_data_batch(model, observed_len, driver)
},
);
match res {
Ok(()) => {
if observed_len != 0 {

@ -324,6 +324,21 @@ pub enum Entity<'a> {
Full(Ident<'a>, Ident<'a>),
}
impl<'a> Entity<'a> {
pub fn into_full_result(self) -> QueryResult<(Ident<'a>, Ident<'a>)> {
match self {
Self::Full(a, b) => Ok((a, b)),
_ => Err(QueryError::QLExpectedEntity),
}
}
}
impl<'a> From<(&'a str, &'a str)> for Entity<'a> {
fn from((s, e): (&'a str, &'a str)) -> Self {
Self::Full(s.into(), e.into())
}
}
impl<'a> Entity<'a> {
#[cfg(test)]
pub fn into_full(self) -> Option<(Ident<'a>, Ident<'a>)> {
@ -333,13 +348,6 @@ impl<'a> Entity<'a> {
None
}
}
pub fn into_full_str(self) -> Option<(&'a str, &'a str)> {
if let Self::Full(a, b) = self {
Some((a.as_str(), b.as_str()))
} else {
None
}
}
#[inline(always)]
/// Parse a full entity from the given slice
///
@ -453,6 +461,7 @@ impl<'a> Entity<'a> {
}
#[derive(Debug, PartialEq)]
#[allow(dead_code)] // TODO(@ohsayan): get rid of this
/// A [`Statement`] is a fully BlueQL statement that can be executed by the query engine
// TODO(@ohsayan): Determine whether we actually need this
pub enum Statement<'a> {
@ -496,6 +505,7 @@ pub enum Statement<'a> {
#[inline(always)]
#[cfg(debug_assertions)]
#[allow(dead_code)] // TODO(@ohsayan): get rid of this
pub fn compile<'a, Qd: QueryData<'a>>(tok: &'a [Token<'a>], d: Qd) -> QueryResult<Statement<'a>> {
if compiler::unlikely(tok.len() < 2) {
return Err(QueryError::QLUnexpectedEndOfStatement);

@ -30,7 +30,7 @@ use {
engine::{
core::{
model::{delta::IRModel, Field, Layer, Model},
space::{Space, SpaceMeta},
space::Space,
},
data::{
tag::{DataTag, TagClass, TagSelector},
@ -515,9 +515,6 @@ impl<'a> PersistObject for SpaceLayoutRef<'a> {
scanner,
super::map::MapIndexSizeMD(md.prop_c),
)?;
Ok(Space::new_restore_empty(
SpaceMeta::new_with_meta(space_meta),
md.uuid,
))
Ok(Space::new_restore_empty(md.uuid, space_meta))
}
}

@ -29,7 +29,7 @@ use {
crate::engine::{
core::{
model::{Field, Layer, Model},
space::{Space, SpaceMeta},
space::Space,
},
data::{
cell::Datacell,
@ -111,11 +111,10 @@ fn model() {
#[test]
fn space() {
let uuid = Uuid::new();
let space = Space::new_with_uuid(Default::default(), SpaceMeta::default(), uuid);
let space_meta_read = space.metadata().dict().read();
let space = Space::new_restore_empty(uuid, Default::default());
let enc = super::enc::enc_full::<obj::SpaceLayoutRef>(obj::SpaceLayoutRef::from((
&space,
&*space_meta_read,
space.props(),
)));
let dec = super::dec::dec_full::<obj::SpaceLayoutRef>(&enc).unwrap();
assert_eq!(space, dec);

@ -30,7 +30,7 @@ use crate::engine::storage::v1::{
JournalWriter,
};
use crate::engine::{
core::GlobalNS,
core::{EntityIDRef, GlobalNS},
data::uuid::Uuid,
error::RuntimeResult,
fractal::error::ErrorContext,
@ -76,10 +76,15 @@ impl SEInitState {
std::fs::create_dir(DATA_DIR).inherit_set_dmsg("creating data directory")?;
}
if !is_new {
let models = gns.idx_models().read();
// this is an existing instance, so read in all data
for (space_name, space) in gns.spaces().read().iter() {
for (space_name, space) in gns.idx().read().iter() {
let space = space.read();
let space_uuid = space.get_uuid();
for (model_name, model) in space.models().read().iter() {
for model_name in space.models().iter() {
let model = models
.get(&EntityIDRef::new(&space_name, &model_name))
.unwrap();
let path =
Self::model_path(space_name, space_uuid, model_name, model.get_uuid());
let persist_driver = batch_jrnl::reinit(&path, model).inherit_set_dmsg(

@ -31,7 +31,7 @@ use {
core::{
model::{delta::IRModel, Field, Model},
space::Space,
GlobalNS,
GlobalNS, {EntityID, EntityIDRef},
},
data::uuid::Uuid,
error::TransactionError,
@ -160,14 +160,31 @@ fn with_space<T>(
space_id: &super::SpaceIDRes,
mut f: impl FnMut(&Space) -> RuntimeResult<T>,
) -> RuntimeResult<T> {
let spaces = gns.spaces().read();
let spaces = gns.idx().read();
let Some(space) = spaces.st_get(&space_id.name) else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
let space = space.read();
if space.get_uuid() != space_id.uuid {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
f(space)
f(&space)
}
fn with_space_mut<T>(
gns: &GlobalNS,
space_id: &super::SpaceIDRes,
mut f: impl FnMut(&mut Space) -> RuntimeResult<T>,
) -> RuntimeResult<T> {
let spaces = gns.idx().read();
let Some(space) = spaces.st_get(&space_id.name) else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
let mut space = space.write();
if space.get_uuid() != space_id.uuid {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
f(&mut space)
}
fn with_model<T>(
@ -176,9 +193,10 @@ fn with_model<T>(
model_id: &ModelIDRes,
mut f: impl FnMut(&Model) -> RuntimeResult<T>,
) -> RuntimeResult<T> {
with_space(gns, space_id, |space| {
let models = space.models().read();
let Some(model) = models.st_get(&model_id.model_name) else {
with_space(gns, space_id, |_| {
let models = gns.idx_models().read();
let Some(model) = models.get(&EntityIDRef::new(&space_id.name, &model_id.model_name))
else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
if model.get_uuid() != model_id.model_uuid {
@ -302,26 +320,30 @@ impl<'a> GNSEvent for CreateModelTxn<'a> {
}: Self::RestoreType,
gns: &GlobalNS,
) -> RuntimeResult<()> {
let rgns = gns.spaces().read();
/*
NOTE(@ohsayan):
do note that this is a little interesting situation especially because we need to be able to handle
changes in the schema *and* be able to "sync" that (for consistency) with the model's primary index.
There is no evident way about how this is going to be handled, but the ideal way would be to keep
versioned index of schemas.
A jump to the second branch is practically impossible and should be caught long before we actually end up
here (due to mismatched checksums), but might be theoretically possible because the cosmic rays can be wild
(or well magnetic stuff arounding spinning disks). But we just want to be extra sure. Don't let the aliens (or
rather, radiation) from the cosmos deter us!
*/
match rgns.st_get(&space_id.name) {
Some(space) if space.get_uuid() == space_id.uuid => {
if space._create_model(&model_name, model).is_ok() {
Ok(())
} else {
Err(TransactionError::OnRestoreDataConflictAlreadyExists.into())
}
}
Some(_) => return Err(TransactionError::OnRestoreDataConflictMismatch.into()),
None => return Err(TransactionError::OnRestoreDataMissing.into()),
let spaces = gns.idx().write();
let mut models = gns.idx_models().write();
let Some(space) = spaces.get(&space_id.name) else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
let mut space = space.write();
if space.models().contains(&model_name) {
return Err(TransactionError::OnRestoreDataConflictAlreadyExists.into());
}
if models
.insert(EntityID::new(&space_id.name, &model_name), model)
.is_some()
{
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
space.models_mut().insert(model_name);
Ok(())
}
}
@ -724,13 +746,19 @@ impl<'a> GNSEvent for DropModelTxn<'a> {
}: Self::RestoreType,
gns: &GlobalNS,
) -> RuntimeResult<()> {
with_space(gns, &space_id, |space| {
let mut wgns = space.models().write();
match wgns.st_delete_if(&model_name, |mdl| mdl.get_uuid() == model_uuid) {
Some(true) => Ok(()),
Some(false) => return Err(TransactionError::OnRestoreDataConflictMismatch.into()),
None => Err(TransactionError::OnRestoreDataMissing.into()),
with_space_mut(gns, &space_id, |space| {
let mut models = gns.idx_models().write();
if !space.models_mut().remove(&model_name) {
return Err(TransactionError::OnRestoreDataMissing.into());
}
let Some(removed_model) = models.remove(&EntityIDRef::new(&space_id.name, &model_name))
else {
return Err(TransactionError::OnRestoreDataMissing.into());
};
if removed_model.get_uuid() != model_uuid {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
Ok(())
})
}
}

@ -120,8 +120,8 @@ impl<'a> GNSEvent for CreateSpaceTxn<'a> {
CreateSpaceTxnRestorePL { space_name, space }: CreateSpaceTxnRestorePL,
gns: &crate::engine::core::GlobalNS,
) -> RuntimeResult<()> {
let mut wgns = gns.spaces().write();
if wgns.st_insert(space_name, space) {
let mut spaces = gns.idx().write();
if spaces.st_insert(space_name, space.into()) {
Ok(())
} else {
Err(TransactionError::OnRestoreDataConflictAlreadyExists.into())
@ -215,11 +215,11 @@ impl<'a> GNSEvent for AlterSpaceTxn<'a> {
}: Self::RestoreType,
gns: &crate::engine::core::GlobalNS,
) -> RuntimeResult<()> {
let gns = gns.spaces().read();
let gns = gns.idx().read();
match gns.st_get(&space_id.name) {
Some(space) => {
let mut wmeta = space.metadata().dict().write();
if !crate::engine::data::dict::rmerge_metadata(&mut wmeta, space_meta) {
let mut space = space.write();
if !crate::engine::data::dict::rmerge_metadata(space.props_mut(), space_meta) {
return Err(TransactionError::OnRestoreDataConflictMismatch.into());
}
}
@ -278,10 +278,13 @@ impl<'a> GNSEvent for DropSpaceTxn<'a> {
super::SpaceIDRes { uuid, name }: Self::RestoreType,
gns: &GlobalNS,
) -> RuntimeResult<()> {
let mut wgns = gns.spaces().write();
let mut wgns = gns.idx().write();
match wgns.entry(name) {
std::collections::hash_map::Entry::Occupied(oe) => {
if oe.get().get_uuid() == uuid {
let space = oe.get().read();
if space.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
drop(space);
oe.remove_entry();
Ok(())
} else {

@ -27,7 +27,7 @@
use crate::engine::{
core::{
model::{Field, Layer, Model},
space::{Space, SpaceMeta},
space::Space,
},
data::{cell::Datacell, tag::TagSelector, uuid::Uuid, DictEntryGeneric},
error::QueryError,
@ -58,10 +58,11 @@ fn init_space(global: &impl GlobalInstanceLike, space_name: &str, env: &str) ->
Space::transactional_exec_create(global, stmt).unwrap();
global
.namespace()
.spaces()
.idx()
.read()
.get(name.as_str())
.unwrap()
.read()
.get_uuid()
}
@ -76,13 +77,13 @@ fn create_space() {
}
multirun(|| {
let global = TestGlobal::new_with_vfs_driver(log_name);
let spaces = global.namespace().idx().read();
let space = spaces.get("myspace").unwrap().read();
assert_eq!(
global.namespace().spaces().read().get("myspace").unwrap(),
&*space,
&Space::new_restore_empty(
SpaceMeta::with_env(
into_dict!("SAYAN_MAX" => DictEntryGeneric::Data(Datacell::new_uint_default(65536)))
),
uuid
uuid,
into_dict!("SAYAN_MAX" => DictEntryGeneric::Data(Datacell::new_uint_default(65536)))
)
);
})
@ -104,13 +105,13 @@ fn alter_space() {
}
multirun(|| {
let global = TestGlobal::new_with_vfs_driver(log_name);
let spaces = global.namespace().idx().read();
let space = spaces.get("myspace").unwrap().read();
assert_eq!(
global.namespace().spaces().read().get("myspace").unwrap(),
&*space,
&Space::new_restore_empty(
SpaceMeta::with_env(
into_dict!("SAYAN_MAX" => DictEntryGeneric::Data(Datacell::new_uint_default(65536)))
),
uuid
uuid,
into_dict!("SAYAN_MAX" => DictEntryGeneric::Data(Datacell::new_uint_default(65536)))
)
);
})
@ -129,7 +130,7 @@ fn drop_space() {
}
multirun(|| {
let global = TestGlobal::new_with_vfs_driver(log_name);
assert_eq!(global.namespace().spaces().read().get("myspace"), None);
assert!(global.namespace().idx().read().get("myspace").is_none());
})
})
}
@ -174,7 +175,7 @@ fn create_model() {
let global = TestGlobal::new_with_vfs_driver(log_name);
global
.namespace()
.with_model(("myspace", "mymodel"), |model| {
.with_model(("myspace", "mymodel").into(), |model| {
assert_eq!(
model,
&Model::new_restore(
@ -212,7 +213,7 @@ fn alter_model_add() {
let global = TestGlobal::new_with_vfs_driver(log_name);
global
.namespace()
.with_model(("myspace", "mymodel"), |model| {
.with_model(("myspace", "mymodel").into(), |model| {
assert_eq!(
model
.intent_read_model()
@ -251,7 +252,7 @@ fn alter_model_remove() {
let global = TestGlobal::new_with_vfs_driver(log_name);
global
.namespace()
.with_model(("myspace", "mymodel"), |model| {
.with_model(("myspace", "mymodel").into(), |model| {
let irm = model.intent_read_model();
assert!(irm.fields().st_get("has_secure_key").is_none());
assert!(irm.fields().st_get("is_dumb").is_none());
@ -284,7 +285,7 @@ fn alter_model_update() {
let global = TestGlobal::new_with_vfs_driver(log_name);
global
.namespace()
.with_model(("myspace", "mymodel"), |model| {
.with_model(("myspace", "mymodel").into(), |model| {
assert_eq!(
model
.intent_read_model()
@ -316,7 +317,7 @@ fn drop_model() {
assert_eq!(
global
.namespace()
.with_model(("myspace", "mymodel"), |_| { Ok(()) })
.with_model(("myspace", "mymodel").into(), |_| { Ok(()) })
.unwrap_err(),
QueryError::QExecObjectNotFound
);

@ -46,23 +46,23 @@ mod space_tests {
};
#[test]
fn create() {
let orig_space = Space::empty();
let space_r = orig_space.metadata().dict().read();
let orig_space = Space::new_auto_all();
let space_r = orig_space.props();
let txn = CreateSpaceTxn::new(&space_r, "myspace", &orig_space);
let encoded = enc::enc_full_self(txn);
let decoded = dec::dec_full::<CreateSpaceTxn>(&encoded).unwrap();
assert_eq!(
CreateSpaceTxnRestorePL {
space_name: "myspace".into(),
space: Space::empty_with_uuid(orig_space.get_uuid())
space: Space::new_restore_empty(orig_space.get_uuid(), Default::default())
},
decoded
);
}
#[test]
fn alter() {
let space = Space::empty();
let space_r = space.metadata().dict().read();
let space = Space::new_auto_all();
let space_r = space.props();
let txn = AlterSpaceTxn::new(SpaceIDRef::new("myspace", &space), &space_r);
let encoded = enc::enc_full_self(txn);
let decoded = dec::dec_full::<AlterSpaceTxn>(&encoded).unwrap();
@ -76,7 +76,7 @@ mod space_tests {
}
#[test]
fn drop() {
let space = Space::empty();
let space = Space::new_auto_all();
let txn = DropSpaceTxn::new(super::SpaceIDRef::new("myspace", &space));
let encoded = enc::enc_full_self(txn);
let decoded = dec::dec_full::<DropSpaceTxn>(&encoded).unwrap();
@ -103,7 +103,7 @@ mod model_tests {
},
};
fn default_space_model() -> (Space, Model) {
let space = Space::empty();
let space = Space::new_auto_all();
let model = Model::new_restore(
Uuid::new(),
"username".into(),

Loading…
Cancel
Save