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