Add space DDL txns and full chain tests

Also ensure that the `env` key is not when merged
next
Sayan Nandan 1 year ago
parent 39edfc64c9
commit 29d4137a2c
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -50,6 +50,7 @@ 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>,
}

@ -217,51 +217,84 @@ impl Space {
Self::transactional_exec_create(gns, driver, space)
})
}
/// Execute a `alter` stmt
pub fn exec_alter(
pub fn transactional_exec_alter<TI: gnstxn::GNSTransactionDriverLLInterface>(
gns: &super::GlobalNS,
txn_driver: &mut gnstxn::GNSTransactionDriverAnyFS<TI>,
AlterSpace {
space_name,
mut updated_props,
updated_props,
}: AlterSpace,
) -> DatabaseResult<()> {
gns.with_space(&space_name, |space| {
let mut space_props = space.meta.props.write();
let DictEntryGeneric::Map(space_env_mut) =
space_props.get_mut(SpaceMeta::KEY_ENV).unwrap()
else {
unreachable!()
};
match updated_props.remove(SpaceMeta::KEY_ENV) {
Some(DictEntryGeneric::Map(env)) if updated_props.is_empty() => {
if !dict::rmerge_metadata(space_env_mut, env) {
return Err(DatabaseError::DdlSpaceBadProperty);
}
}
Some(DictEntryGeneric::Data(l)) if updated_props.is_empty() & l.is_null() => {
space_env_mut.clear()
}
None => {}
match updated_props.get(SpaceMeta::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(DatabaseError::DdlSpaceBadProperty),
}
let mut space_props = space.meta.dict().write();
// create patch
let patch = match dict::rprepare_metadata_patch(&space_props, updated_props) {
Some(patch) => patch,
None => return Err(DatabaseError::DdlSpaceBadProperty),
};
if TI::NONNULL {
// prepare txn
let txn =
gnstxn::AlterSpaceTxn::new(gnstxn::SpaceIDRef::new(&space_name, space), &patch);
// commit
txn_driver.try_commit(txn)?;
}
// merge
dict::rmerge_data_with_patch(&mut space_props, 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!()),
);
Ok(())
})
}
pub fn exec_drop(
#[cfg(test)]
/// Execute a `alter` stmt
pub fn exec_alter(gns: &super::GlobalNS, alter: AlterSpace) -> DatabaseResult<()> {
gnstxn::GNSTransactionDriverNullZero::nullzero_create_exec(gns, move |driver| {
Self::transactional_exec_alter(gns, driver, alter)
})
}
pub fn transactional_exec_drop<TI: gnstxn::GNSTransactionDriverLLInterface>(
gns: &super::GlobalNS,
txn_driver: &mut gnstxn::GNSTransactionDriverAnyFS<TI>,
DropSpace { space, force: _ }: DropSpace,
) -> DatabaseResult<()> {
// TODO(@ohsayan): force remove option
// TODO(@ohsayan): should a drop space block the entire global table?
match gns
.spaces()
.write()
.st_delete_if(space.as_str(), |space| space.mns.read().len() == 0)
{
Some(true) => Ok(()),
Some(false) => Err(DatabaseError::DdlSpaceRemoveNonEmpty),
None => Err(DatabaseError::DdlSpaceNotFound),
let space_name = space;
let mut wgns = gns.spaces().write();
let space = match wgns.get(space_name.as_str()) {
Some(space) => space,
None => return Err(DatabaseError::DdlSpaceNotFound),
};
let space_w = space.mns.write();
if space_w.st_len() != 0 {
return Err(DatabaseError::DdlSpaceRemoveNonEmpty);
}
// we can remove this
if TI::NONNULL {
// prepare txn
let txn = gnstxn::DropSpaceTxn::new(gnstxn::SpaceIDRef::new(&space_name, space));
txn_driver.try_commit(txn)?;
}
drop(space_w);
let _ = wgns.st_delete(space_name.as_str());
Ok(())
}
#[cfg(test)]
pub fn exec_drop(gns: &super::GlobalNS, drop_space: DropSpace) -> DatabaseResult<()> {
gnstxn::GNSTransactionDriverNullZero::nullzero_create_exec(gns, move |driver| {
Self::transactional_exec_drop(gns, driver, drop_space)
})
}
}

@ -89,7 +89,7 @@ pub fn rprepare_metadata_patch(current: &DictGeneric, new: DictGeneric) -> Optio
}
}
fn rmerge_data_with_patch(current: &mut DictGeneric, patch: DictGeneric) {
pub fn rmerge_data_with_patch(current: &mut DictGeneric, patch: DictGeneric) {
for (key, patch) in patch {
match patch {
DictEntryGeneric::Data(d) if d.is_init() => {

@ -66,6 +66,7 @@ pub type GNSTransactionDriverVFS = GNSTransactionDriverAnyFS<VirtualFS>;
const CURRENT_LOG_VERSION: u32 = 0;
pub trait GNSTransactionDriverLLInterface: RawFileIOInterface {
/// If true, this is an actual txn driver with a non-null (not `/dev/null` like) journal
const NONNULL: bool = <Self as RawFileIOInterface>::NOTNULL;
}
impl<T: RawFileIOInterface> GNSTransactionDriverLLInterface for T {}
@ -110,8 +111,23 @@ impl<F: GNSTransactionDriverLLInterface> GNSTransactionDriverAnyFS<F> {
host_run_mode: header_meta::HostRunMode,
host_startup_counter: u64,
) -> TransactionResult<Self> {
let journal = v1::open_journal(
Self::open_or_reinit_with_name(
gns,
"gns.db-tlog",
host_setting_version,
host_run_mode,
host_startup_counter,
)
}
pub fn open_or_reinit_with_name(
gns: &GlobalNS,
log_file_name: &str,
host_setting_version: u32,
host_run_mode: header_meta::HostRunMode,
host_startup_counter: u64,
) -> TransactionResult<Self> {
let journal = v1::open_journal(
log_file_name,
header_meta::FileSpecifier::GNSTxnLog,
header_meta::FileSpecifierVersion::__new(CURRENT_LOG_VERSION),
host_setting_version,

@ -0,0 +1,145 @@
/*
* Created on Fri Aug 25 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::{
core::{
space::{Space, SpaceMeta},
GlobalNS,
},
data::{cell::Datacell, uuid::Uuid, DictEntryGeneric},
ql::{ast::parse_ast_node_full, ddl::crt::CreateSpace, tests::lex_insecure},
storage::v1::header_meta::HostRunMode,
txn::gns::GNSTransactionDriverVFS,
};
fn double_run(f: impl FnOnce() + Copy) {
f();
f();
}
fn with_variable<T>(var: T, f: impl FnOnce(T)) {
f(var);
}
fn init_txn_driver(gns: &GlobalNS, log_name: &str) -> GNSTransactionDriverVFS {
GNSTransactionDriverVFS::open_or_reinit_with_name(&gns, log_name, 0, HostRunMode::Prod, 0)
.unwrap()
}
fn init_space(
gns: &GlobalNS,
driver: &mut GNSTransactionDriverVFS,
space_name: &str,
env: &str,
) -> Uuid {
let query = format!("create space {space_name} with {{ env: {env} }}");
let stmt = lex_insecure(query.as_bytes()).unwrap();
let stmt = parse_ast_node_full::<CreateSpace>(&stmt[2..]).unwrap();
let name = stmt.space_name;
Space::transactional_exec_create(&gns, driver, stmt).unwrap();
gns.spaces().read().get(name.as_str()).unwrap().get_uuid()
}
#[test]
fn create_space() {
with_variable("create_space_test.gns.db-tlog", |log_name| {
let uuid;
// start 1
{
let gns = GlobalNS::empty();
let mut driver = init_txn_driver(&gns, log_name);
uuid = init_space(&gns, &mut driver, "myspace", "{ SAYAN_MAX: 65536 }"); // good lord that doesn't sound like a good variable
driver.close().unwrap();
}
double_run(|| {
let gns = GlobalNS::empty();
let driver = init_txn_driver(&gns, log_name);
assert_eq!(
gns.spaces().read().get("myspace").unwrap(),
&Space::new_restore_empty(
SpaceMeta::with_env(
into_dict!("SAYAN_MAX" => DictEntryGeneric::Data(Datacell::new_uint(65536)))
),
uuid
)
);
driver.close().unwrap();
})
})
}
#[test]
fn alter_space() {
with_variable("alter_space_test.gns.db-tlog", |log_name| {
let uuid;
{
let gns = GlobalNS::empty();
let mut driver = init_txn_driver(&gns, log_name);
uuid = init_space(&gns, &mut driver, "myspace", "{}");
let stmt =
lex_insecure("alter space myspace with { env: { SAYAN_MAX: 65536 } }".as_bytes())
.unwrap();
let stmt = parse_ast_node_full(&stmt[2..]).unwrap();
Space::transactional_exec_alter(&gns, &mut driver, stmt).unwrap();
driver.close().unwrap();
}
double_run(|| {
let gns = GlobalNS::empty();
let driver = init_txn_driver(&gns, log_name);
assert_eq!(
gns.spaces().read().get("myspace").unwrap(),
&Space::new_restore_empty(
SpaceMeta::with_env(
into_dict!("SAYAN_MAX" => DictEntryGeneric::Data(Datacell::new_uint(65536)))
),
uuid
)
);
driver.close().unwrap();
})
})
}
#[test]
fn drop_space() {
with_variable("drop_space_test.gns.db-tlog", |log_name| {
{
let gns = GlobalNS::empty();
let mut driver = init_txn_driver(&gns, log_name);
let _ = init_space(&gns, &mut driver, "myspace", "{}");
let stmt = lex_insecure("drop space myspace".as_bytes()).unwrap();
let stmt = parse_ast_node_full(&stmt[2..]).unwrap();
Space::transactional_exec_drop(&gns, &mut driver, stmt).unwrap();
driver.close().unwrap();
}
double_run(|| {
let gns = GlobalNS::empty();
let driver = init_txn_driver(&gns, log_name);
assert_eq!(gns.spaces().read().get("myspace"), None);
driver.close().unwrap();
})
})
}

@ -24,4 +24,5 @@
*
*/
mod full_chain;
mod io;

Loading…
Cancel
Save