Upgrade to new SE
parent
102a4b4b40
commit
d6f9b54868
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Created on Wed Feb 21 2024
|
||||
*
|
||||
* 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) 2024, Sayan Nandan <nandansayan@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 {
|
||||
super::RWLIdx,
|
||||
crate::engine::{
|
||||
error::{QueryError, QueryResult},
|
||||
fractal::GlobalInstanceLike,
|
||||
txn::gns::sysctl::{AlterUserTxn, CreateUserTxn, DropUserTxn},
|
||||
},
|
||||
std::collections::hash_map::Entry,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemDatabase {
|
||||
users: RWLIdx<Box<str>, User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct User {
|
||||
password: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(password: Box<[u8]>) -> Self {
|
||||
Self { password }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum VerifyUser {
|
||||
NotFound,
|
||||
IncorrectPassword,
|
||||
Okay,
|
||||
OkayRoot,
|
||||
}
|
||||
|
||||
impl VerifyUser {
|
||||
pub fn is_root(&self) -> bool {
|
||||
matches!(self, Self::OkayRoot)
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemDatabase {
|
||||
pub const ROOT_ACCOUNT: &'static str = "root";
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
users: RWLIdx::default(),
|
||||
}
|
||||
}
|
||||
pub fn users(&self) -> &RWLIdx<Box<str>, User> {
|
||||
&self.users
|
||||
}
|
||||
pub fn __verify_user(&self, username: &str, password: &[u8]) -> VerifyUser {
|
||||
self.users
|
||||
.read()
|
||||
.get(username)
|
||||
.map(|user| {
|
||||
if rcrypt::verify(password, &user.password).unwrap() {
|
||||
if username == Self::ROOT_ACCOUNT {
|
||||
VerifyUser::OkayRoot
|
||||
} else {
|
||||
VerifyUser::Okay
|
||||
}
|
||||
} else {
|
||||
VerifyUser::IncorrectPassword
|
||||
}
|
||||
})
|
||||
.unwrap_or(VerifyUser::NotFound)
|
||||
}
|
||||
pub fn __insert_user(&self, username: Box<str>, password: Box<[u8]>) -> bool {
|
||||
match self.users.write().entry(username) {
|
||||
Entry::Vacant(ve) => {
|
||||
ve.insert(User::new(password));
|
||||
true
|
||||
}
|
||||
Entry::Occupied(_) => false,
|
||||
}
|
||||
}
|
||||
pub fn __delete_user(&self, username: &str) -> bool {
|
||||
self.users.write().remove(username).is_some()
|
||||
}
|
||||
pub fn __change_user_password(&self, username: &str, new_password: Box<[u8]>) -> bool {
|
||||
match self.users.write().get_mut(username) {
|
||||
Some(user) => {
|
||||
user.password = new_password;
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemDatabase {
|
||||
pub fn create_user(
|
||||
&self,
|
||||
global: &impl GlobalInstanceLike,
|
||||
username: Box<str>,
|
||||
password: &str,
|
||||
) -> QueryResult<()> {
|
||||
let mut users = self.users.write();
|
||||
if users.contains_key(&username) {
|
||||
return Err(QueryError::SysAuthError);
|
||||
}
|
||||
let password_hash = rcrypt::hash(password, rcrypt::DEFAULT_COST).unwrap();
|
||||
match global
|
||||
.gns_driver()
|
||||
.lock()
|
||||
.gns_driver()
|
||||
.commit_event(CreateUserTxn::new(&username, &password_hash))
|
||||
{
|
||||
Ok(()) => {
|
||||
users.insert(username, User::new(password_hash.into_boxed_slice()));
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("failed to create user: {e}");
|
||||
return Err(QueryError::SysTransactionalError);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn alter_user(
|
||||
&self,
|
||||
global: &impl GlobalInstanceLike,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> QueryResult<()> {
|
||||
match self.users.write().get_mut(username) {
|
||||
Some(user) => {
|
||||
let password_hash = rcrypt::hash(password, rcrypt::DEFAULT_COST).unwrap();
|
||||
match global
|
||||
.gns_driver()
|
||||
.lock()
|
||||
.gns_driver()
|
||||
.commit_event(AlterUserTxn::new(username, &password_hash))
|
||||
{
|
||||
Ok(()) => {
|
||||
user.password = password_hash.into_boxed_slice();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("failed to alter user: {e}");
|
||||
Err(QueryError::SysTransactionalError)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Err(QueryError::SysAuthError),
|
||||
}
|
||||
}
|
||||
pub fn drop_user(&self, global: &impl GlobalInstanceLike, username: &str) -> QueryResult<()> {
|
||||
let mut users = self.users.write();
|
||||
if !users.contains_key(username) {
|
||||
return Err(QueryError::SysAuthError);
|
||||
}
|
||||
match global
|
||||
.gns_driver()
|
||||
.lock()
|
||||
.gns_driver()
|
||||
.commit_event(DropUserTxn::new(username))
|
||||
{
|
||||
Ok(()) => {
|
||||
let _ = users.remove(username);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("failed to remove user: {e}");
|
||||
Err(QueryError::SysTransactionalError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,274 +0,0 @@
|
||||
/*
|
||||
* Created on Sun Sep 10 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::{
|
||||
config::{ConfigAuth, ConfigMode},
|
||||
error::{QueryError, QueryResult},
|
||||
storage::safe_interfaces::FSInterface,
|
||||
},
|
||||
parking_lot::RwLock,
|
||||
std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
marker::PhantomData,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemStore<Fs> {
|
||||
syscfg: SysConfig,
|
||||
_fs: PhantomData<Fs>,
|
||||
}
|
||||
|
||||
impl<Fs> SystemStore<Fs> {
|
||||
pub fn system_store(&self) -> &SysConfig {
|
||||
&self.syscfg
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The global system configuration
|
||||
pub struct SysConfig {
|
||||
auth_data: RwLock<SysAuth>,
|
||||
host_data: SysHostData,
|
||||
run_mode: ConfigMode,
|
||||
}
|
||||
|
||||
impl PartialEq for SysConfig {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.run_mode == other.run_mode
|
||||
&& self.host_data == other.host_data
|
||||
&& self.auth_data.read().eq(&other.auth_data.read())
|
||||
}
|
||||
}
|
||||
|
||||
impl SysConfig {
|
||||
/// Initialize a new system config
|
||||
pub fn new(auth_data: RwLock<SysAuth>, host_data: SysHostData, run_mode: ConfigMode) -> Self {
|
||||
Self {
|
||||
auth_data,
|
||||
host_data,
|
||||
run_mode,
|
||||
}
|
||||
}
|
||||
pub fn new_full(new_auth: ConfigAuth, host_data: SysHostData, run_mode: ConfigMode) -> Self {
|
||||
Self::new(
|
||||
RwLock::new(SysAuth::new(
|
||||
into_dict!(SysAuthUser::USER_ROOT => SysAuthUser::new(
|
||||
rcrypt::hash(new_auth.root_key.as_str(), rcrypt::DEFAULT_COST)
|
||||
.unwrap()
|
||||
.into_boxed_slice())),
|
||||
)),
|
||||
host_data,
|
||||
run_mode,
|
||||
)
|
||||
}
|
||||
pub fn new_auth(new_auth: ConfigAuth, run_mode: ConfigMode) -> Self {
|
||||
Self::new_full(new_auth, SysHostData::new(0, 0), run_mode)
|
||||
}
|
||||
#[cfg(test)]
|
||||
/// A test-mode default setting with the root password set to `password12345678`
|
||||
pub(super) fn test_default() -> Self {
|
||||
Self {
|
||||
auth_data: RwLock::new(SysAuth::new(
|
||||
into_dict!(SysAuthUser::USER_ROOT => SysAuthUser::new(
|
||||
rcrypt::hash("password12345678", rcrypt::DEFAULT_COST)
|
||||
.unwrap()
|
||||
.into_boxed_slice())),
|
||||
)),
|
||||
host_data: SysHostData::new(0, 0),
|
||||
run_mode: ConfigMode::Dev,
|
||||
}
|
||||
}
|
||||
/// Returns a handle to the authentication data
|
||||
pub fn auth_data(&self) -> &RwLock<SysAuth> {
|
||||
&self.auth_data
|
||||
}
|
||||
/// Returns a reference to host data
|
||||
pub fn host_data(&self) -> &SysHostData {
|
||||
&self.host_data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// The host data section (system.host)
|
||||
pub struct SysHostData {
|
||||
startup_counter: u64,
|
||||
settings_version: u32,
|
||||
}
|
||||
|
||||
impl SysHostData {
|
||||
/// New [`SysHostData`]
|
||||
pub fn new(startup_counter: u64, settings_version: u32) -> Self {
|
||||
Self {
|
||||
startup_counter,
|
||||
settings_version,
|
||||
}
|
||||
}
|
||||
/// Returns the startup counter
|
||||
///
|
||||
/// Note:
|
||||
/// - If this is `0` -> this is the first boot
|
||||
/// - If this is `1` -> this is the second boot (... and so on)
|
||||
pub fn startup_counter(&self) -> u64 {
|
||||
self.startup_counter
|
||||
}
|
||||
/// Returns the settings version
|
||||
///
|
||||
/// Note:
|
||||
/// - If this is `0` -> this is the initial setting (first boot)
|
||||
///
|
||||
/// If it stays at 0, this means that the settings were never changed
|
||||
pub fn settings_version(&self) -> u32 {
|
||||
self.settings_version
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fs: FSInterface> SystemStore<Fs> {
|
||||
pub fn _new(syscfg: SysConfig) -> Self {
|
||||
Self {
|
||||
syscfg,
|
||||
_fs: PhantomData,
|
||||
}
|
||||
}
|
||||
fn _try_sync_or(&self, auth: &mut SysAuth, rb: impl FnOnce(&mut SysAuth)) -> QueryResult<()> {
|
||||
match self.sync_db(auth) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("failed to sync system store: {e}");
|
||||
rb(auth);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Create a new user with the given details
|
||||
pub fn create_new_user(&self, username: String, password: String) -> QueryResult<()> {
|
||||
// TODO(@ohsayan): we want to be very careful with this
|
||||
let _username = username.clone();
|
||||
let mut auth = self.system_store().auth_data().write();
|
||||
match auth.users.entry(username.into()) {
|
||||
Entry::Vacant(ve) => {
|
||||
ve.insert(SysAuthUser::new(
|
||||
rcrypt::hash(password, rcrypt::DEFAULT_COST)
|
||||
.unwrap()
|
||||
.into_boxed_slice(),
|
||||
));
|
||||
self._try_sync_or(&mut auth, |auth| {
|
||||
auth.users.remove(_username.as_str());
|
||||
})
|
||||
}
|
||||
Entry::Occupied(_) => Err(QueryError::SysAuthError),
|
||||
}
|
||||
}
|
||||
pub fn alter_user(&self, username: String, password: String) -> QueryResult<()> {
|
||||
let mut auth = self.system_store().auth_data().write();
|
||||
match auth.users.get_mut(username.as_str()) {
|
||||
Some(user) => {
|
||||
let last_pass_hash = core::mem::replace(
|
||||
&mut user.key,
|
||||
rcrypt::hash(password, rcrypt::DEFAULT_COST)
|
||||
.unwrap()
|
||||
.into_boxed_slice(),
|
||||
);
|
||||
self._try_sync_or(&mut auth, |auth| {
|
||||
auth.users.get_mut(username.as_str()).unwrap().key = last_pass_hash;
|
||||
})
|
||||
}
|
||||
None => Err(QueryError::SysAuthError),
|
||||
}
|
||||
}
|
||||
pub fn drop_user(&self, username: &str) -> QueryResult<()> {
|
||||
let mut auth = self.system_store().auth_data().write();
|
||||
if username == SysAuthUser::USER_ROOT {
|
||||
// you can't remove root!
|
||||
return Err(QueryError::SysAuthError);
|
||||
}
|
||||
match auth.users.remove_entry(username) {
|
||||
Some((username, user)) => self._try_sync_or(&mut auth, |auth| {
|
||||
let _ = auth.users.insert(username, user);
|
||||
}),
|
||||
None => Err(QueryError::SysAuthError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
auth
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// The auth data section (system.auth)
|
||||
pub struct SysAuth {
|
||||
users: HashMap<Box<str>, SysAuthUser>,
|
||||
}
|
||||
|
||||
impl SysAuth {
|
||||
/// New [`SysAuth`] with the given settings
|
||||
pub fn new(users: HashMap<Box<str>, SysAuthUser>) -> Self {
|
||||
Self { users }
|
||||
}
|
||||
pub fn verify_user_check_root<T: AsRef<[u8]> + ?Sized>(
|
||||
&self,
|
||||
username: &str,
|
||||
password: &T,
|
||||
) -> QueryResult<bool> {
|
||||
match self.users.get(username) {
|
||||
Some(user) if rcrypt::verify(password, user.key()).unwrap() => {
|
||||
Ok(username == SysAuthUser::USER_ROOT)
|
||||
}
|
||||
Some(_) | None => Err(QueryError::SysAuthError),
|
||||
}
|
||||
}
|
||||
/// Verify the user with the given details
|
||||
pub fn verify_user<T: AsRef<[u8]> + ?Sized>(
|
||||
&self,
|
||||
username: &str,
|
||||
password: &T,
|
||||
) -> QueryResult<()> {
|
||||
self.verify_user_check_root(username, password).map(|_| ())
|
||||
}
|
||||
pub fn users(&self) -> &HashMap<Box<str>, SysAuthUser> {
|
||||
&self.users
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// The auth user
|
||||
pub struct SysAuthUser {
|
||||
key: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl SysAuthUser {
|
||||
pub const USER_ROOT: &'static str = "root";
|
||||
/// Create a new [`SysAuthUser`]
|
||||
pub fn new(key: Box<[u8]>) -> Self {
|
||||
Self { key }
|
||||
}
|
||||
/// Get the key
|
||||
pub fn key(&self) -> &[u8] {
|
||||
self.key.as_ref()
|
||||
}
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Created on Wed Feb 21 2024
|
||||
*
|
||||
* 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) 2024, Sayan Nandan <nandansayan@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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
gns txn impls
|
||||
*/
|
||||
|
||||
use {
|
||||
super::r1::{dec, impls::gns::GNSEvent, PersistObject},
|
||||
crate::{
|
||||
engine::{
|
||||
core::GlobalNS,
|
||||
error::{StorageError, TransactionError},
|
||||
mem::BufferedScanner,
|
||||
txn::gns::sysctl::{AlterUserTxn, CreateUserTxn, DropUserTxn},
|
||||
RuntimeResult,
|
||||
},
|
||||
util::EndianQW,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
create user txn
|
||||
*/
|
||||
|
||||
impl<'a> GNSEvent for CreateUserTxn<'a> {
|
||||
type CommitType = Self;
|
||||
type RestoreType = FullUserDefinition;
|
||||
fn update_global_state(
|
||||
FullUserDefinition { username, password }: Self::RestoreType,
|
||||
gns: &GlobalNS,
|
||||
) -> RuntimeResult<()> {
|
||||
if gns.sys_db().__insert_user(username, password) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::OnRestoreDataConflictAlreadyExists.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FullUserDefinition {
|
||||
username: Box<str>,
|
||||
password: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl FullUserDefinition {
|
||||
fn new(username: Box<str>, password: Box<[u8]>) -> Self {
|
||||
Self { username, password }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreateUserMetadata {
|
||||
uname_l: u64,
|
||||
pwd_l: u64,
|
||||
props_l: u64,
|
||||
}
|
||||
|
||||
impl CreateUserMetadata {
|
||||
pub fn new(uname_l: u64, pwd_l: u64, props_l: u64) -> Self {
|
||||
Self {
|
||||
uname_l,
|
||||
pwd_l,
|
||||
props_l,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PersistObject for CreateUserTxn<'a> {
|
||||
const METADATA_SIZE: usize = sizeof!(u64, 3);
|
||||
type InputType = Self;
|
||||
type OutputType = FullUserDefinition;
|
||||
type Metadata = CreateUserMetadata;
|
||||
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
|
||||
scanner.has_left((md.uname_l + md.pwd_l) as usize)
|
||||
}
|
||||
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
|
||||
// [username length: 8B][password length: 8B][properties length: 8B]
|
||||
buf.extend(data.username().len().u64_bytes_le());
|
||||
buf.extend(data.password_hash().len().u64_bytes_le());
|
||||
buf.extend(0u64.u64_bytes_le());
|
||||
}
|
||||
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> RuntimeResult<Self::Metadata> {
|
||||
let uname_l = scanner.next_u64_le();
|
||||
let pwd_l = scanner.next_u64_le();
|
||||
let props_l = scanner.next_u64_le();
|
||||
Ok(CreateUserMetadata::new(uname_l, pwd_l, props_l))
|
||||
}
|
||||
fn obj_enc(buf: &mut Vec<u8>, data: Self::InputType) {
|
||||
buf.extend(data.username().as_bytes());
|
||||
buf.extend(data.password_hash());
|
||||
}
|
||||
unsafe fn obj_dec(
|
||||
s: &mut BufferedScanner,
|
||||
md: Self::Metadata,
|
||||
) -> RuntimeResult<Self::OutputType> {
|
||||
let username = dec::utils::decode_string(s, md.uname_l as _)?;
|
||||
let password = s.next_chunk_variable(md.pwd_l as _);
|
||||
if md.props_l == 0 {
|
||||
Ok(FullUserDefinition::new(
|
||||
username.into_boxed_str(),
|
||||
password.to_vec().into_boxed_slice(),
|
||||
))
|
||||
} else {
|
||||
Err(StorageError::InternalDecodeStructureIllegalData.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
alter user txn
|
||||
*/
|
||||
|
||||
impl<'a> GNSEvent for AlterUserTxn<'a> {
|
||||
type CommitType = Self;
|
||||
type RestoreType = FullUserDefinition;
|
||||
fn update_global_state(
|
||||
FullUserDefinition { username, password }: Self::RestoreType,
|
||||
gns: &GlobalNS,
|
||||
) -> RuntimeResult<()> {
|
||||
if gns.sys_db().__change_user_password(&username, password) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::OnRestoreDataConflictMismatch.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PersistObject for AlterUserTxn<'a> {
|
||||
const METADATA_SIZE: usize = sizeof!(u64, 3);
|
||||
type InputType = Self;
|
||||
type OutputType = FullUserDefinition;
|
||||
type Metadata = CreateUserMetadata;
|
||||
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
|
||||
scanner.has_left((md.uname_l + md.pwd_l) as usize)
|
||||
}
|
||||
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
|
||||
// [username length: 8B][password length: 8B][properties length: 8B]
|
||||
buf.extend(data.username().len().u64_bytes_le());
|
||||
buf.extend(data.password_hash().len().u64_bytes_le());
|
||||
buf.extend(0u64.u64_bytes_le());
|
||||
}
|
||||
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> RuntimeResult<Self::Metadata> {
|
||||
let uname_l = scanner.next_u64_le();
|
||||
let pwd_l = scanner.next_u64_le();
|
||||
let props_l = scanner.next_u64_le();
|
||||
Ok(CreateUserMetadata::new(uname_l, pwd_l, props_l))
|
||||
}
|
||||
fn obj_enc(buf: &mut Vec<u8>, data: Self::InputType) {
|
||||
buf.extend(data.username().as_bytes());
|
||||
buf.extend(data.password_hash());
|
||||
}
|
||||
unsafe fn obj_dec(
|
||||
s: &mut BufferedScanner,
|
||||
md: Self::Metadata,
|
||||
) -> RuntimeResult<Self::OutputType> {
|
||||
let username = dec::utils::decode_string(s, md.uname_l as _)?;
|
||||
let password = s.next_chunk_variable(md.pwd_l as _);
|
||||
if md.props_l == 0 {
|
||||
Ok(FullUserDefinition::new(
|
||||
username.into_boxed_str(),
|
||||
password.to_vec().into_boxed_slice(),
|
||||
))
|
||||
} else {
|
||||
Err(StorageError::InternalDecodeStructureIllegalData.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
drop user txn
|
||||
*/
|
||||
|
||||
pub struct DropUserPayload(Box<str>);
|
||||
|
||||
impl<'a> GNSEvent for DropUserTxn<'a> {
|
||||
type CommitType = Self;
|
||||
type RestoreType = DropUserPayload;
|
||||
fn update_global_state(
|
||||
DropUserPayload(username): Self::RestoreType,
|
||||
gns: &GlobalNS,
|
||||
) -> RuntimeResult<()> {
|
||||
if gns.sys_db().__delete_user(&username) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::OnRestoreDataConflictMismatch.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PersistObject for DropUserTxn<'a> {
|
||||
const METADATA_SIZE: usize = sizeof!(u64);
|
||||
type InputType = Self;
|
||||
type OutputType = DropUserPayload;
|
||||
type Metadata = u64;
|
||||
fn pretest_can_dec_object(scanner: &BufferedScanner, md: &Self::Metadata) -> bool {
|
||||
scanner.has_left(*md as usize)
|
||||
}
|
||||
fn meta_enc(buf: &mut Vec<u8>, data: Self::InputType) {
|
||||
buf.extend(data.username().len().u64_bytes_le())
|
||||
}
|
||||
unsafe fn meta_dec(scanner: &mut BufferedScanner) -> RuntimeResult<Self::Metadata> {
|
||||
Ok(scanner.next_u64_le())
|
||||
}
|
||||
fn obj_enc(buf: &mut Vec<u8>, data: Self::InputType) {
|
||||
buf.extend(data.username().as_bytes());
|
||||
}
|
||||
unsafe fn obj_dec(
|
||||
s: &mut BufferedScanner,
|
||||
md: Self::Metadata,
|
||||
) -> RuntimeResult<Self::OutputType> {
|
||||
let username = dec::utils::decode_string(s, md as usize)?;
|
||||
Ok(DropUserPayload(username.into_boxed_str()))
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Created on Sat Jul 29 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
type VirtualFS = crate::engine::storage::common::interface::fs_test::VirtualFS;
|
||||
|
||||
mod batch;
|
||||
mod rw;
|
||||
mod tx;
|
||||
|
||||
mod sysdb {
|
||||
use {
|
||||
super::{super::sysdb::SystemStoreInitState, VirtualFS as VFS},
|
||||
crate::engine::{
|
||||
config::{AuthDriver, ConfigAuth, ConfigMode},
|
||||
fractal::sys_store::SystemStore,
|
||||
},
|
||||
};
|
||||
fn open_sysdb(
|
||||
auth_config: ConfigAuth,
|
||||
sysdb_path: &str,
|
||||
sysdb_cow_path: &str,
|
||||
) -> (SystemStore<VFS>, SystemStoreInitState) {
|
||||
SystemStore::<VFS>::open_with_name(sysdb_path, sysdb_cow_path, auth_config, ConfigMode::Dev)
|
||||
.unwrap()
|
||||
}
|
||||
#[test]
|
||||
fn open_close() {
|
||||
let open = |auth_config| {
|
||||
open_sysdb(
|
||||
auth_config,
|
||||
"open_close_test.sys.db",
|
||||
"open_close_test.sys.cow.db",
|
||||
)
|
||||
};
|
||||
let auth_config = ConfigAuth::new(AuthDriver::Pwd, "password12345678".into());
|
||||
{
|
||||
let (config, state) = open(auth_config.clone());
|
||||
assert_eq!(state, SystemStoreInitState::Created);
|
||||
assert!(config
|
||||
.system_store()
|
||||
.auth_data()
|
||||
.read()
|
||||
.verify_user("root", "password12345678")
|
||||
.is_ok());
|
||||
assert_eq!(config.system_store().host_data().settings_version(), 0);
|
||||
assert_eq!(config.system_store().host_data().startup_counter(), 0);
|
||||
}
|
||||
// reboot
|
||||
let (config, state) = open(auth_config);
|
||||
assert_eq!(state, SystemStoreInitState::Unchanged);
|
||||
assert!(config
|
||||
.system_store()
|
||||
.auth_data()
|
||||
.read()
|
||||
.verify_user("root", "password12345678")
|
||||
.is_ok());
|
||||
assert_eq!(config.system_store().host_data().settings_version(), 0);
|
||||
assert_eq!(config.system_store().host_data().startup_counter(), 1);
|
||||
}
|
||||
#[test]
|
||||
fn open_change_root_password() {
|
||||
let open = |auth_config| {
|
||||
open_sysdb(
|
||||
auth_config,
|
||||
"open_change_root_password.sys.db",
|
||||
"open_change_root_password.sys.cow.db",
|
||||
)
|
||||
};
|
||||
{
|
||||
let (config, state) = open(ConfigAuth::new(AuthDriver::Pwd, "password12345678".into()));
|
||||
assert_eq!(state, SystemStoreInitState::Created);
|
||||
assert!(config
|
||||
.system_store()
|
||||
.auth_data()
|
||||
.read()
|
||||
.verify_user("root", "password12345678")
|
||||
.is_ok());
|
||||
assert_eq!(config.system_store().host_data().settings_version(), 0);
|
||||
assert_eq!(config.system_store().host_data().startup_counter(), 0);
|
||||
}
|
||||
let (config, state) = open(ConfigAuth::new(AuthDriver::Pwd, "password23456789".into()));
|
||||
assert_eq!(state, SystemStoreInitState::UpdatedRoot);
|
||||
assert!(config
|
||||
.system_store()
|
||||
.auth_data()
|
||||
.read()
|
||||
.verify_user("root", "password23456789")
|
||||
.is_ok());
|
||||
assert_eq!(config.system_store().host_data().settings_version(), 1);
|
||||
assert_eq!(config.system_store().host_data().startup_counter(), 1);
|
||||
}
|
||||
}
|
@ -1,384 +0,0 @@
|
||||
/*
|
||||
* Created on Wed Sep 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::{
|
||||
core::{
|
||||
index::{DcFieldIndex, PrimaryIndexKey, Row},
|
||||
model::{
|
||||
delta::{DataDelta, DataDeltaKind, DeltaVersion},
|
||||
Field, Layer, Model,
|
||||
},
|
||||
},
|
||||
data::{cell::Datacell, tag::TagSelector, uuid::Uuid},
|
||||
idx::MTIndex,
|
||||
storage::{
|
||||
common::interface::{fs_test::VirtualFS, fs_traits::FileOpen},
|
||||
v1::raw::{
|
||||
batch_jrnl::{
|
||||
DataBatchPersistDriver, DataBatchRestoreDriver, DecodedBatchEvent,
|
||||
DecodedBatchEventKind, NormalBatch,
|
||||
},
|
||||
rw::SDSSFileIO,
|
||||
spec::{self, Header},
|
||||
},
|
||||
},
|
||||
},
|
||||
util::test_utils,
|
||||
},
|
||||
crossbeam_epoch::pin,
|
||||
};
|
||||
|
||||
fn pkey(v: impl Into<Datacell>) -> PrimaryIndexKey {
|
||||
PrimaryIndexKey::try_from_dc(v.into()).unwrap()
|
||||
}
|
||||
|
||||
fn open_file(fpath: &str) -> FileOpen<SDSSFileIO<VirtualFS>, (SDSSFileIO<VirtualFS>, Header)> {
|
||||
SDSSFileIO::open_or_create_perm_rw::<spec::DataBatchJournalV1>(fpath).unwrap()
|
||||
}
|
||||
|
||||
fn open_batch_data(fpath: &str, mdl: &Model) -> DataBatchPersistDriver<VirtualFS> {
|
||||
match open_file(fpath) {
|
||||
FileOpen::Created(f) => DataBatchPersistDriver::new(f, true),
|
||||
FileOpen::Existing((f, _header)) => {
|
||||
let mut dbr = DataBatchRestoreDriver::new(f).unwrap();
|
||||
dbr.read_data_batch_into_model(mdl).unwrap();
|
||||
DataBatchPersistDriver::new(dbr.into_file().unwrap(), false)
|
||||
}
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn new_delta(
|
||||
schema: u64,
|
||||
txnid: u64,
|
||||
pk: impl Into<Datacell>,
|
||||
data: DcFieldIndex,
|
||||
change: DataDeltaKind,
|
||||
) -> DataDelta {
|
||||
new_delta_with_row(
|
||||
txnid,
|
||||
Row::new(
|
||||
pkey(pk),
|
||||
data,
|
||||
DeltaVersion::__new(schema),
|
||||
DeltaVersion::__new(txnid),
|
||||
),
|
||||
change,
|
||||
)
|
||||
}
|
||||
|
||||
fn new_delta_with_row(txnid: u64, row: Row, change: DataDeltaKind) -> DataDelta {
|
||||
DataDelta::new(DeltaVersion::__new(txnid), row, change)
|
||||
}
|
||||
|
||||
fn flush_deltas_and_re_read<const N: usize>(
|
||||
mdl: &Model,
|
||||
dt: [DataDelta; N],
|
||||
fname: &str,
|
||||
) -> Vec<NormalBatch> {
|
||||
let mut restore_driver = flush_batches_and_return_restore_driver(dt, mdl, fname);
|
||||
let batch = restore_driver.read_all_batches().unwrap();
|
||||
batch
|
||||
}
|
||||
|
||||
fn flush_batches_and_return_restore_driver<const N: usize>(
|
||||
dt: [DataDelta; N],
|
||||
mdl: &Model,
|
||||
fname: &str,
|
||||
) -> DataBatchRestoreDriver<VirtualFS> {
|
||||
// delta queue
|
||||
let g = pin();
|
||||
for delta in dt {
|
||||
mdl.delta_state().append_new_data_delta(delta, &g);
|
||||
}
|
||||
let file = open_file(fname).into_created().unwrap();
|
||||
{
|
||||
let mut persist_driver = DataBatchPersistDriver::new(file, true).unwrap();
|
||||
persist_driver.write_new_batch(&mdl, N).unwrap();
|
||||
persist_driver.close().unwrap();
|
||||
}
|
||||
DataBatchRestoreDriver::new(open_file(fname).into_existing().unwrap().0).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_multi_open_reopen() {
|
||||
let uuid = Uuid::new();
|
||||
let mdl = Model::new_restore(
|
||||
uuid,
|
||||
"username".into(),
|
||||
TagSelector::String.into_full(),
|
||||
into_dict!(
|
||||
"username" => Field::new([Layer::str()].into(), false),
|
||||
"password" => Field::new([Layer::bin()].into(), false)
|
||||
),
|
||||
);
|
||||
for _ in 0..100 {
|
||||
let writer = open_batch_data("empty_multi_open_reopen.db-btlog", &mdl);
|
||||
writer.close().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unskewed_delta() {
|
||||
let uuid = Uuid::new();
|
||||
let mdl = Model::new_restore(
|
||||
uuid,
|
||||
"username".into(),
|
||||
TagSelector::String.into_full(),
|
||||
into_dict!(
|
||||
"username" => Field::new([Layer::str()].into(), false),
|
||||
"password" => Field::new([Layer::bin()].into(), false)
|
||||
),
|
||||
);
|
||||
let deltas = [
|
||||
new_delta(
|
||||
0,
|
||||
0,
|
||||
"sayan",
|
||||
into_dict!("password" => Datacell::new_bin("37ae4b773a9fc7a20164eb16".as_bytes().into())),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta(
|
||||
0,
|
||||
1,
|
||||
"badguy",
|
||||
into_dict!("password" => Datacell::new_bin("5fe3cbdc470b667cb1ba288a".as_bytes().into())),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta(
|
||||
0,
|
||||
2,
|
||||
"doggo",
|
||||
into_dict!("password" => Datacell::new_bin("c80403f9d0ae4d5d0e829dd0".as_bytes().into())),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta(0, 3, "badguy", into_dict!(), DataDeltaKind::Delete),
|
||||
];
|
||||
let batches = flush_deltas_and_re_read(&mdl, deltas, "unskewed_delta.db-btlog");
|
||||
assert_eq!(
|
||||
batches,
|
||||
vec![NormalBatch::new(
|
||||
vec![
|
||||
DecodedBatchEvent::new(
|
||||
0,
|
||||
pkey("sayan"),
|
||||
DecodedBatchEventKind::Insert(vec![Datacell::new_bin(
|
||||
b"37ae4b773a9fc7a20164eb16".to_vec().into_boxed_slice()
|
||||
)])
|
||||
),
|
||||
DecodedBatchEvent::new(
|
||||
1,
|
||||
pkey("badguy"),
|
||||
DecodedBatchEventKind::Insert(vec![Datacell::new_bin(
|
||||
b"5fe3cbdc470b667cb1ba288a".to_vec().into_boxed_slice()
|
||||
)])
|
||||
),
|
||||
DecodedBatchEvent::new(
|
||||
2,
|
||||
pkey("doggo"),
|
||||
DecodedBatchEventKind::Insert(vec![Datacell::new_bin(
|
||||
b"c80403f9d0ae4d5d0e829dd0".to_vec().into_boxed_slice()
|
||||
)])
|
||||
),
|
||||
DecodedBatchEvent::new(3, pkey("badguy"), DecodedBatchEventKind::Delete)
|
||||
],
|
||||
0
|
||||
)]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skewed_delta() {
|
||||
// prepare model definition
|
||||
let uuid = Uuid::new();
|
||||
let mdl = Model::new_restore(
|
||||
uuid,
|
||||
"catname".into(),
|
||||
TagSelector::String.into_full(),
|
||||
into_dict!(
|
||||
"catname" => Field::new([Layer::str()].into(), false),
|
||||
"is_good" => Field::new([Layer::bool()].into(), false),
|
||||
"magical" => Field::new([Layer::bool()].into(), false),
|
||||
),
|
||||
);
|
||||
let row = Row::new(
|
||||
pkey("Schrödinger's cat"),
|
||||
into_dict!("is_good" => Datacell::new_bool(true), "magical" => Datacell::new_bool(false)),
|
||||
DeltaVersion::__new(0),
|
||||
DeltaVersion::__new(2),
|
||||
);
|
||||
{
|
||||
// update the row
|
||||
let mut wl = row.d_data().write();
|
||||
wl.set_txn_revised(DeltaVersion::__new(3));
|
||||
*wl.fields_mut().get_mut("magical").unwrap() = Datacell::new_bool(true);
|
||||
}
|
||||
// prepare deltas
|
||||
let deltas = [
|
||||
// insert catname: Schrödinger's cat, is_good: true
|
||||
new_delta_with_row(0, row.clone(), DataDeltaKind::Insert),
|
||||
// insert catname: good cat, is_good: true, magical: false
|
||||
new_delta(
|
||||
0,
|
||||
1,
|
||||
"good cat",
|
||||
into_dict!("is_good" => Datacell::new_bool(true), "magical" => Datacell::new_bool(false)),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
// insert catname: bad cat, is_good: false, magical: false
|
||||
new_delta(
|
||||
0,
|
||||
2,
|
||||
"bad cat",
|
||||
into_dict!("is_good" => Datacell::new_bool(false), "magical" => Datacell::new_bool(false)),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
// update catname: Schrödinger's cat, is_good: true, magical: true
|
||||
new_delta_with_row(3, row.clone(), DataDeltaKind::Update),
|
||||
];
|
||||
let batch = flush_deltas_and_re_read(&mdl, deltas, "skewed_delta.db-btlog");
|
||||
assert_eq!(
|
||||
batch,
|
||||
vec![NormalBatch::new(
|
||||
vec![
|
||||
DecodedBatchEvent::new(
|
||||
1,
|
||||
pkey("good cat"),
|
||||
DecodedBatchEventKind::Insert(vec![
|
||||
Datacell::new_bool(true),
|
||||
Datacell::new_bool(false)
|
||||
])
|
||||
),
|
||||
DecodedBatchEvent::new(
|
||||
2,
|
||||
pkey("bad cat"),
|
||||
DecodedBatchEventKind::Insert(vec![
|
||||
Datacell::new_bool(false),
|
||||
Datacell::new_bool(false)
|
||||
])
|
||||
),
|
||||
DecodedBatchEvent::new(
|
||||
3,
|
||||
pkey("Schrödinger's cat"),
|
||||
DecodedBatchEventKind::Update(vec![
|
||||
Datacell::new_bool(true),
|
||||
Datacell::new_bool(true)
|
||||
])
|
||||
)
|
||||
],
|
||||
0
|
||||
)]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skewed_shuffled_persist_restore() {
|
||||
let uuid = Uuid::new();
|
||||
let model = Model::new_restore(
|
||||
uuid,
|
||||
"username".into(),
|
||||
TagSelector::String.into_full(),
|
||||
into_dict!("username" => Field::new([Layer::str()].into(), false), "password" => Field::new([Layer::str()].into(), false)),
|
||||
);
|
||||
let mongobongo = Row::new(
|
||||
pkey("mongobongo"),
|
||||
into_dict!("password" => "dumbo"),
|
||||
DeltaVersion::__new(0),
|
||||
DeltaVersion::__new(4),
|
||||
);
|
||||
let rds = Row::new(
|
||||
pkey("rds"),
|
||||
into_dict!("password" => "snail"),
|
||||
DeltaVersion::__new(0),
|
||||
DeltaVersion::__new(5),
|
||||
);
|
||||
let deltas = [
|
||||
new_delta(
|
||||
0,
|
||||
0,
|
||||
"sayan",
|
||||
into_dict!("password" => "pwd123456"),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta(
|
||||
0,
|
||||
1,
|
||||
"joseph",
|
||||
into_dict!("password" => "pwd234567"),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta(
|
||||
0,
|
||||
2,
|
||||
"haley",
|
||||
into_dict!("password" => "pwd345678"),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta(
|
||||
0,
|
||||
3,
|
||||
"charlotte",
|
||||
into_dict!("password" => "pwd456789"),
|
||||
DataDeltaKind::Insert,
|
||||
),
|
||||
new_delta_with_row(4, mongobongo.clone(), DataDeltaKind::Insert),
|
||||
new_delta_with_row(5, rds.clone(), DataDeltaKind::Insert),
|
||||
new_delta_with_row(6, mongobongo.clone(), DataDeltaKind::Delete),
|
||||
new_delta_with_row(7, rds.clone(), DataDeltaKind::Delete),
|
||||
];
|
||||
for i in 0..deltas.len() {
|
||||
// prepare pretest
|
||||
let fname = format!("skewed_shuffled_persist_restore_round{i}.db-btlog");
|
||||
let mut deltas = deltas.clone();
|
||||
let mut randomizer = test_utils::randomizer();
|
||||
test_utils::shuffle_slice(&mut deltas, &mut randomizer);
|
||||
// restore
|
||||
let mut restore_driver = flush_batches_and_return_restore_driver(deltas, &model, &fname);
|
||||
restore_driver.read_data_batch_into_model(&model).unwrap();
|
||||
}
|
||||
let g = pin();
|
||||
for delta in &deltas[..4] {
|
||||
let row = model
|
||||
.primary_index()
|
||||
.__raw_index()
|
||||
.mt_get(delta.row().d_key(), &g)
|
||||
.unwrap();
|
||||
let row_data = row.read();
|
||||
assert_eq!(row_data.fields().len(), 1);
|
||||
assert_eq!(
|
||||
row_data.fields().get("password").unwrap(),
|
||||
delta
|
||||
.row()
|
||||
.d_data()
|
||||
.read()
|
||||
.fields()
|
||||
.get("password")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Sep 05 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::storage::{
|
||||
common::interface::fs_traits::FileOpen,
|
||||
v1::raw::{rw::SDSSFileIO, spec},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn create_delete() {
|
||||
{
|
||||
let f = SDSSFileIO::<super::VirtualFS>::open_or_create_perm_rw::<spec::TestFile>(
|
||||
"hello_world.db-tlog",
|
||||
)
|
||||
.unwrap();
|
||||
match f {
|
||||
FileOpen::Existing(_) => panic!(),
|
||||
FileOpen::Created(_) => {}
|
||||
};
|
||||
}
|
||||
let open = SDSSFileIO::<super::VirtualFS>::open_or_create_perm_rw::<spec::TestFile>(
|
||||
"hello_world.db-tlog",
|
||||
)
|
||||
.unwrap();
|
||||
let _ = match open {
|
||||
FileOpen::Existing(_) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Sep 05 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::{RuntimeResult, StorageError},
|
||||
storage::v1::raw::{
|
||||
journal::raw::{self, JournalAdapter, JournalWriter},
|
||||
spec,
|
||||
},
|
||||
},
|
||||
util,
|
||||
},
|
||||
std::cell::RefCell,
|
||||
};
|
||||
|
||||
pub struct Database {
|
||||
data: RefCell<[u8; 10]>,
|
||||
}
|
||||
impl Database {
|
||||
fn copy_data(&self) -> [u8; 10] {
|
||||
*self.data.borrow()
|
||||
}
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: RefCell::new([0; 10]),
|
||||
}
|
||||
}
|
||||
fn reset(&self) {
|
||||
*self.data.borrow_mut() = [0; 10];
|
||||
}
|
||||
fn set(&self, pos: usize, val: u8) {
|
||||
self.data.borrow_mut()[pos] = val;
|
||||
}
|
||||
fn txn_set(
|
||||
&self,
|
||||
pos: usize,
|
||||
val: u8,
|
||||
txn_writer: &mut JournalWriter<super::VirtualFS, DatabaseTxnAdapter>,
|
||||
) -> RuntimeResult<()> {
|
||||
self.set(pos, val);
|
||||
txn_writer.append_event(TxEvent::Set(pos, val))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TxEvent {
|
||||
#[allow(unused)]
|
||||
Reset,
|
||||
Set(usize, u8),
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum TxError {
|
||||
SDSS(StorageError),
|
||||
}
|
||||
direct_from! {
|
||||
TxError => {
|
||||
StorageError as SDSS
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseTxnAdapter;
|
||||
impl JournalAdapter for DatabaseTxnAdapter {
|
||||
const RECOVERY_PLUGIN: bool = false;
|
||||
type Error = TxError;
|
||||
type JournalEvent = TxEvent;
|
||||
type GlobalState = Database;
|
||||
|
||||
fn encode(event: Self::JournalEvent) -> Box<[u8]> {
|
||||
/*
|
||||
[1B: opcode][8B:Index][1B: New value]
|
||||
*/
|
||||
let opcode = match event {
|
||||
TxEvent::Reset => 0u8,
|
||||
TxEvent::Set(_, _) => 1u8,
|
||||
};
|
||||
let index = match event {
|
||||
TxEvent::Reset => 0u64,
|
||||
TxEvent::Set(index, _) => index as u64,
|
||||
};
|
||||
let new_value = match event {
|
||||
TxEvent::Reset => 0,
|
||||
TxEvent::Set(_, val) => val,
|
||||
};
|
||||
let mut ret = Vec::with_capacity(10);
|
||||
ret.push(opcode);
|
||||
ret.extend(index.to_le_bytes());
|
||||
ret.push(new_value);
|
||||
ret.into_boxed_slice()
|
||||
}
|
||||
|
||||
fn decode_and_update_state(payload: &[u8], gs: &Self::GlobalState) -> Result<(), TxError> {
|
||||
assert!(payload.len() >= 10, "corrupt file");
|
||||
let opcode = payload[0];
|
||||
let index = u64::from_le_bytes(util::copy_slice_to_array(&payload[1..9]));
|
||||
let new_value = payload[9];
|
||||
match opcode {
|
||||
0 if index == 0 && new_value == 0 => gs.reset(),
|
||||
1 if index < 10 && index < isize::MAX as u64 => gs.set(index as usize, new_value),
|
||||
_ => return Err(TxError::SDSS(StorageError::JournalLogEntryCorrupted.into())),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn open_log(
|
||||
log_name: &str,
|
||||
db: &Database,
|
||||
) -> RuntimeResult<JournalWriter<super::VirtualFS, DatabaseTxnAdapter>> {
|
||||
raw::open_or_create_journal::<DatabaseTxnAdapter, super::VirtualFS, spec::TestFile>(
|
||||
log_name, db,
|
||||
)
|
||||
.map(|v| v.into_inner())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_boot_second_readonly() {
|
||||
// create log
|
||||
let db1 = Database::new();
|
||||
let x = || -> RuntimeResult<()> {
|
||||
let mut log = open_log("testtxn.log", &db1)?;
|
||||
db1.txn_set(0, 20, &mut log)?;
|
||||
db1.txn_set(9, 21, &mut log)?;
|
||||
log.close()
|
||||
};
|
||||
x().unwrap();
|
||||
// backup original data
|
||||
let original_data = db1.copy_data();
|
||||
// restore log
|
||||
let empty_db2 = Database::new();
|
||||
open_log("testtxn.log", &empty_db2)
|
||||
.unwrap()
|
||||
.close()
|
||||
.unwrap();
|
||||
assert_eq!(original_data, empty_db2.copy_data());
|
||||
}
|
||||
#[test]
|
||||
fn oneboot_mod_twoboot_mod_thirdboot_read() {
|
||||
// first boot: set all to 1
|
||||
let db1 = Database::new();
|
||||
let x = || -> RuntimeResult<()> {
|
||||
let mut log = open_log("duatxn.db-tlog", &db1)?;
|
||||
for i in 0..10 {
|
||||
db1.txn_set(i, 1, &mut log)?;
|
||||
}
|
||||
log.close()
|
||||
};
|
||||
x().unwrap();
|
||||
let bkp_db1 = db1.copy_data();
|
||||
drop(db1);
|
||||
// second boot
|
||||
let db2 = Database::new();
|
||||
let x = || -> RuntimeResult<()> {
|
||||
let mut log = open_log("duatxn.db-tlog", &db2)?;
|
||||
assert_eq!(bkp_db1, db2.copy_data());
|
||||
for i in 0..10 {
|
||||
let current_val = db2.data.borrow()[i];
|
||||
db2.txn_set(i, current_val + i as u8, &mut log)?;
|
||||
}
|
||||
log.close()
|
||||
};
|
||||
x().unwrap();
|
||||
let bkp_db2 = db2.copy_data();
|
||||
drop(db2);
|
||||
// third boot
|
||||
let db3 = Database::new();
|
||||
let log = open_log("duatxn.db-tlog", &db3).unwrap();
|
||||
log.close().unwrap();
|
||||
assert_eq!(bkp_db2, db3.copy_data());
|
||||
assert_eq!(
|
||||
db3.copy_data(),
|
||||
(1..=10)
|
||||
.into_iter()
|
||||
.map(u8::from)
|
||||
.collect::<Box<[u8]>>()
|
||||
.as_ref()
|
||||
);
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Created on Wed Feb 21 2024
|
||||
*
|
||||
* 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) 2024, Sayan Nandan <nandansayan@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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct CreateUserTxn<'a> {
|
||||
username: &'a str,
|
||||
password_hash: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> CreateUserTxn<'a> {
|
||||
pub fn new(username: &'a str, password_hash: &'a [u8]) -> Self {
|
||||
Self {
|
||||
username,
|
||||
password_hash,
|
||||
}
|
||||
}
|
||||
pub fn username(&self) -> &str {
|
||||
self.username
|
||||
}
|
||||
pub fn password_hash(&self) -> &[u8] {
|
||||
self.password_hash
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct AlterUserTxn<'a> {
|
||||
username: &'a str,
|
||||
password_hash: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> AlterUserTxn<'a> {
|
||||
pub fn new(username: &'a str, password_hash: &'a [u8]) -> Self {
|
||||
Self {
|
||||
username,
|
||||
password_hash,
|
||||
}
|
||||
}
|
||||
pub fn username(&self) -> &str {
|
||||
self.username
|
||||
}
|
||||
pub fn password_hash(&self) -> &[u8] {
|
||||
self.password_hash
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct DropUserTxn<'a> {
|
||||
username: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> DropUserTxn<'a> {
|
||||
pub fn new(username: &'a str) -> Self {
|
||||
Self { username }
|
||||
}
|
||||
pub fn username(&self) -> &str {
|
||||
self.username
|
||||
}
|
||||
}
|
||||
|
||||
impl_gns_event!(CreateUserTxn<'_> = CreateUser, AlterUserTxn<'_> = AlterUser, DropUserTxn<'_> = DropUser);
|
Loading…
Reference in New Issue