Simplify entity handling

next
Sayan Nandan 1 year ago
parent f98c5d3aa4
commit 87adc1046d
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -26,11 +26,18 @@
mod model;
mod space;
mod util;
// test
#[cfg(test)]
mod tests;
// imports
use {
crate::engine::{core::space::Space, idx::IndexST},
self::{model::ModelView, util::EntityLocator},
crate::engine::{
core::space::Space,
error::{DatabaseError, DatabaseResult},
idx::{IndexST, STIndex},
},
parking_lot::RwLock,
};
// re-exports
@ -57,9 +64,28 @@ impl GlobalNS {
}
#[cfg(test)]
pub(self) fn test_new_empty_space(&self, space_id: &str) -> bool {
use super::idx::STIndex;
self.index_space
.write()
.st_insert(space_id.into(), Space::empty())
}
pub fn with_space<T>(
&self,
space: &str,
f: impl FnOnce(&Space) -> DatabaseResult<T>,
) -> DatabaseResult<T> {
let sread = self.index_space.read();
let Some(space) = sread.st_get(space) else {
return Err(DatabaseError::DdlSpaceNotFound);
};
f(space)
}
pub fn with_model<'a, T, E, F>(&self, entity: E, f: F) -> DatabaseResult<T>
where
F: FnOnce(&ModelView) -> DatabaseResult<T>,
E: 'a + EntityLocator<'a>,
{
entity
.parse_entity()
.and_then(|(space, model)| self.with_space(space, |space| space.with_model(model, f)))
}
}

@ -248,50 +248,41 @@ impl<'a> AlterPlan<'a> {
impl ModelView {
pub fn exec_alter(gns: &GlobalNS, alter: AlterModel) -> DatabaseResult<()> {
let Some((space, model)) = alter.model.into_full() else {
return Err(DatabaseError::ExpectedEntity);
};
let gns = gns.spaces().read();
let Some(space) = gns.st_get(space.as_str()) else {
return Err(DatabaseError::DdlSpaceNotFound)
};
let space = space.models().read();
let Some(model) = space.st_get(model.as_str()) else {
return Err(DatabaseError::DdlModelNotFound);
};
// make intent
let iwm = model.intent_write_model();
// prepare plan
let plan = AlterPlan::fdeltas(model, &iwm, alter)?;
// we have a legal plan; acquire exclusive if we need it
if !plan.no_lock {
// TODO(@ohsayan): allow this later on, once we define the syntax
return Err(DatabaseError::NeedLock);
}
// fine, we're good
let mut iwm = iwm;
match plan.action {
AlterAction::Ignore => drop(iwm),
AlterAction::Add(new_fields) => {
// TODO(@ohsayan): this impacts lockdown duration; fix it
new_fields
.st_iter_kv()
.map(|(x, y)| (x.clone(), y.clone()))
.for_each(|(field_id, field)| {
iwm.fields_mut().st_insert(field_id, field);
});
}
AlterAction::Remove(remove) => {
remove.iter().for_each(|field_id| {
iwm.fields_mut().st_delete(field_id.as_str());
});
gns.with_model(alter.model, |model| {
// make intent
let iwm = model.intent_write_model();
// prepare plan
let plan = AlterPlan::fdeltas(model, &iwm, alter)?;
// we have a legal plan; acquire exclusive if we need it
if !plan.no_lock {
// TODO(@ohsayan): allow this later on, once we define the syntax
return Err(DatabaseError::NeedLock);
}
AlterAction::Update(u) => {
u.into_iter().for_each(|(field_id, field)| {
iwm.fields_mut().st_update(&field_id, field);
});
// fine, we're good
let mut iwm = iwm;
match plan.action {
AlterAction::Ignore => drop(iwm),
AlterAction::Add(new_fields) => {
// TODO(@ohsayan): this impacts lockdown duration; fix it
new_fields
.st_iter_kv()
.map(|(x, y)| (x.clone(), y.clone()))
.for_each(|(field_id, field)| {
iwm.fields_mut().st_insert(field_id, field);
});
}
AlterAction::Remove(remove) => {
remove.iter().for_each(|field_id| {
iwm.fields_mut().st_delete(field_id.as_str());
});
}
AlterAction::Update(u) => {
u.into_iter().for_each(|(field_id, field)| {
iwm.fields_mut().st_update(&field_id, field);
});
}
}
}
Ok(())
Ok(())
})
}
}

@ -30,6 +30,8 @@ pub mod cell;
#[cfg(test)]
use std::cell::RefCell;
use super::util::EntityLocator;
use {
crate::engine::{
core::model::cell::Datacell,
@ -47,7 +49,7 @@ use {
parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard},
};
type Fields = IndexSTSeqCns<Box<str>, Field>;
pub(in crate::engine::core) type Fields = IndexSTSeqCns<Box<str>, Field>;
// FIXME(@ohsayan): update this!
@ -157,31 +159,20 @@ impl ModelView {
impl ModelView {
pub fn exec_create(gns: &super::GlobalNS, stmt: CreateModel) -> DatabaseResult<()> {
let name = stmt.model_name;
let (space_name, model_name) = stmt.model_name.parse_entity()?;
let model = Self::process_create(stmt)?;
let space_rl = gns.spaces().read();
let Some((space_name, model_name)) = name.into_full() else {
return Err(DatabaseError::ExpectedEntity);
};
let Some(space) = space_rl.get(space_name.as_str()) else {
return Err(DatabaseError::DdlSpaceNotFound)
};
space._create_model(model_name.as_str(), model)
gns.with_space(space_name, |space| space._create_model(model_name, model))
}
pub fn exec_drop(gns: &super::GlobalNS, stmt: DropModel) -> DatabaseResult<()> {
let Some((space, model)) = stmt.entity.into_full() else {
return Err(DatabaseError::ExpectedEntity);
};
let spaces = gns.spaces().read();
let Some(space) = spaces.st_get(space.as_str()) else {
return Err(DatabaseError::DdlSpaceNotFound);
};
let mut w_space = space.models().write();
match w_space.st_delete_if(model.as_str(), |mdl| !mdl.is_empty_atomic()) {
Some(true) => Ok(()),
Some(false) => Err(DatabaseError::DdlModelViewNotEmpty),
None => Err(DatabaseError::DdlModelNotFound),
}
let (space, model) = stmt.entity.parse_entity()?;
gns.with_space(space, |space| {
let mut w_space = space.models().write();
match w_space.st_delete_if(model, |mdl| !mdl.is_empty_atomic()) {
Some(true) => Ok(()),
Some(false) => Err(DatabaseError::DdlModelViewNotEmpty),
None => Err(DatabaseError::DdlModelNotFound),
}
})
}
}

@ -88,6 +88,17 @@ impl Space {
pub(super) fn models(&self) -> &RWLIdx<Box<str>, ModelView> {
&self.mns
}
pub fn with_model<T>(
&self,
model: &str,
f: impl FnOnce(&ModelView) -> DatabaseResult<T>,
) -> DatabaseResult<T> {
let mread = self.mns.read();
let Some(model) = mread.st_get(model) else {
return Err(DatabaseError::DdlModelNotFound);
};
f(model)
}
}
impl Space {
@ -150,23 +161,20 @@ impl Space {
mut updated_props,
}: AlterSpace,
) -> DatabaseResult<()> {
match gns.spaces().read().st_get(space_name.as_str()) {
Some(space) => {
let mut space_env = space.meta.env.write();
match updated_props.remove(SpaceMeta::KEY_ENV) {
Some(Some(DictEntryGeneric::Map(env))) if updated_props.is_empty() => {
if !md_dict::rmerge_metadata(&mut space_env, env) {
return Err(DatabaseError::DdlSpaceBadProperty);
}
gns.with_space(&space_name, |space| {
let mut space_env = space.meta.env.write();
match updated_props.remove(SpaceMeta::KEY_ENV) {
Some(Some(DictEntryGeneric::Map(env))) if updated_props.is_empty() => {
if !md_dict::rmerge_metadata(&mut space_env, env) {
return Err(DatabaseError::DdlSpaceBadProperty);
}
Some(None) if updated_props.is_empty() => space_env.clear(),
None => {}
_ => return Err(DatabaseError::DdlSpaceBadProperty),
}
Ok(())
Some(None) if updated_props.is_empty() => space_env.clear(),
None => {}
_ => return Err(DatabaseError::DdlSpaceBadProperty),
}
None => Err(DatabaseError::DdlSpaceNotFound),
}
Ok(())
})
}
pub fn exec_drop(
gns: &super::GlobalNS,

@ -0,0 +1,51 @@
/*
* Created on Thu Apr 06 2023
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2023, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use crate::engine::{
error::{DatabaseError, DatabaseResult},
ql::ast::Entity,
};
pub trait EntityLocator<'a> {
fn parse_entity(self) -> DatabaseResult<(&'a str, &'a str)>
where
Self: 'a;
}
impl<'a> EntityLocator<'a> for (&'a str, &'a str) {
fn parse_entity(self) -> DatabaseResult<(&'a str, &'a str)> {
Ok(self)
}
}
impl<'a> EntityLocator<'a> for Entity<'a> {
fn parse_entity(self) -> DatabaseResult<(&'a str, &'a str)>
where
Self: 'a,
{
self.into_full_str().ok_or(DatabaseError::ExpectedEntity)
}
}

@ -389,6 +389,13 @@ 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
}
}
pub fn into_single(self) -> Option<Ident<'a>> {
if let Self::Single(a) = self {
Some(a)
@ -396,6 +403,13 @@ impl<'a> Entity<'a> {
None
}
}
pub fn into_single_str(self) -> Option<&'a str> {
if let Self::Single(a) = self {
Some(a.as_str())
} else {
None
}
}
#[inline(always)]
/// Parse a full entity from the given slice
///

Loading…
Cancel
Save