Implement v1 executor for update
parent
f2a0fda29d
commit
4580b76d55
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Created on Thu May 11 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::{query_meta::AssignmentOperator, GlobalNS},
|
||||
data::{
|
||||
cell::Datacell,
|
||||
lit::LitIR,
|
||||
spec::{Dataspec1D, DataspecMeta1D},
|
||||
tag::{DataTag, TagClass},
|
||||
},
|
||||
error::{DatabaseError, DatabaseResult},
|
||||
idx::STIndex,
|
||||
ql::dml::upd::{AssignmentExpression, UpdateStatement},
|
||||
sync,
|
||||
},
|
||||
util::compiler,
|
||||
},
|
||||
std::mem,
|
||||
};
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn dc_op_fail(_: &Datacell, _: LitIR) -> (bool, Datacell) {
|
||||
(false, Datacell::null())
|
||||
}
|
||||
// bool
|
||||
unsafe fn dc_op_bool_ass(_: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
(true, Datacell::new_bool(rhs.read_bool_uck()))
|
||||
}
|
||||
// uint
|
||||
unsafe fn dc_op_uint_ass(_: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
(true, Datacell::new_uint(rhs.read_uint_uck()))
|
||||
}
|
||||
unsafe fn dc_op_uint_add(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (sum, of) = dc.read_uint().overflowing_add(rhs.read_uint_uck());
|
||||
(of, Datacell::new_uint(sum))
|
||||
}
|
||||
unsafe fn dc_op_uint_sub(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (diff, of) = dc.read_uint().overflowing_sub(rhs.read_uint_uck());
|
||||
(of, Datacell::new_uint(diff))
|
||||
}
|
||||
unsafe fn dc_op_uint_mul(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (prod, of) = dc.read_uint().overflowing_mul(rhs.read_uint_uck());
|
||||
(of, Datacell::new_uint(prod))
|
||||
}
|
||||
unsafe fn dc_op_uint_div(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (quo, of) = dc.read_uint().overflowing_div(rhs.read_uint_uck());
|
||||
(of, Datacell::new_uint(quo))
|
||||
}
|
||||
// sint
|
||||
unsafe fn dc_op_sint_ass(_: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
(true, Datacell::new_sint(rhs.read_sint_uck()))
|
||||
}
|
||||
unsafe fn dc_op_sint_add(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (sum, of) = dc.read_sint().overflowing_add(rhs.read_sint_uck());
|
||||
(of, Datacell::new_sint(sum))
|
||||
}
|
||||
unsafe fn dc_op_sint_sub(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (diff, of) = dc.read_sint().overflowing_sub(rhs.read_sint_uck());
|
||||
(of, Datacell::new_sint(diff))
|
||||
}
|
||||
unsafe fn dc_op_sint_mul(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (prod, of) = dc.read_sint().overflowing_mul(rhs.read_sint_uck());
|
||||
(of, Datacell::new_sint(prod))
|
||||
}
|
||||
unsafe fn dc_op_sint_div(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let (quo, of) = dc.read_sint().overflowing_div(rhs.read_sint_uck());
|
||||
(of, Datacell::new_sint(quo))
|
||||
}
|
||||
/*
|
||||
float
|
||||
---
|
||||
FIXME(@ohsayan): floating point always upsets me now and then, this time its
|
||||
the silent overflow boom and I think I should implement a strict mode (no MySQL,
|
||||
not `STRICT_ALL_TABLES` unless we do actually end up going down that route. In
|
||||
that case, oops)
|
||||
--
|
||||
TODO(@ohsayan): account for float32 overflow
|
||||
*/
|
||||
unsafe fn dc_op_float_ass(_: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
(true, Datacell::new_float(rhs.read_float_uck()))
|
||||
}
|
||||
unsafe fn dc_op_float_add(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let sum = dc.read_float() + rhs.read_float_uck();
|
||||
(true, Datacell::new_float(sum))
|
||||
}
|
||||
unsafe fn dc_op_float_sub(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let diff = dc.read_float() - rhs.read_float_uck();
|
||||
(true, Datacell::new_float(diff))
|
||||
}
|
||||
unsafe fn dc_op_float_mul(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let prod = dc.read_float() - rhs.read_float_uck();
|
||||
(true, Datacell::new_float(prod))
|
||||
}
|
||||
unsafe fn dc_op_float_div(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let quo = dc.read_float() * rhs.read_float_uck();
|
||||
(true, Datacell::new_float(quo))
|
||||
}
|
||||
// binary
|
||||
unsafe fn dc_op_bin_ass(_dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let new_bin = rhs.read_bin_uck();
|
||||
let mut v = Vec::new();
|
||||
if v.try_reserve_exact(new_bin.len()).is_err() {
|
||||
return dc_op_fail(_dc, rhs);
|
||||
}
|
||||
v.extend_from_slice(new_bin);
|
||||
(true, Datacell::new_bin(v.into_boxed_slice()))
|
||||
}
|
||||
unsafe fn dc_op_bin_add(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let push_into_bin = rhs.read_bin_uck();
|
||||
let mut bin = Vec::new();
|
||||
if compiler::unlikely(bin.try_reserve_exact(push_into_bin.len()).is_err()) {
|
||||
return dc_op_fail(dc, rhs);
|
||||
}
|
||||
bin.extend_from_slice(dc.read_bin());
|
||||
bin.extend_from_slice(push_into_bin);
|
||||
(true, Datacell::new_bin(bin.into_boxed_slice()))
|
||||
}
|
||||
// string
|
||||
unsafe fn dc_op_str_ass(_dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let new_str = rhs.read_str_uck();
|
||||
let mut v = String::new();
|
||||
if v.try_reserve_exact(new_str.len()).is_err() {
|
||||
return dc_op_fail(_dc, rhs);
|
||||
}
|
||||
v.push_str(new_str);
|
||||
(true, Datacell::new_str(v.into_boxed_str()))
|
||||
}
|
||||
unsafe fn dc_op_str_add(dc: &Datacell, rhs: LitIR) -> (bool, Datacell) {
|
||||
let push_into_str = rhs.read_str_uck();
|
||||
let mut str = String::new();
|
||||
if compiler::unlikely(str.try_reserve_exact(push_into_str.len()).is_err()) {
|
||||
return dc_op_fail(dc, rhs);
|
||||
}
|
||||
str.push_str(dc.read_str());
|
||||
str.push_str(push_into_str);
|
||||
(true, Datacell::new_str(str.into_boxed_str()))
|
||||
}
|
||||
|
||||
static OPERATOR: [unsafe fn(&Datacell, LitIR) -> (bool, Datacell); {
|
||||
TagClass::max() * (AssignmentOperator::max() + 1)
|
||||
}] = [
|
||||
// bool
|
||||
dc_op_bool_ass,
|
||||
// -- pad: 4
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
// uint
|
||||
dc_op_uint_ass,
|
||||
dc_op_uint_add,
|
||||
dc_op_uint_sub,
|
||||
dc_op_uint_mul,
|
||||
dc_op_uint_div,
|
||||
// sint
|
||||
dc_op_sint_ass,
|
||||
dc_op_sint_add,
|
||||
dc_op_sint_sub,
|
||||
dc_op_sint_mul,
|
||||
dc_op_sint_div,
|
||||
// float
|
||||
dc_op_float_ass,
|
||||
dc_op_float_add,
|
||||
dc_op_float_sub,
|
||||
dc_op_float_mul,
|
||||
dc_op_float_div,
|
||||
// bin
|
||||
dc_op_bin_ass,
|
||||
dc_op_bin_add,
|
||||
// -- pad: 3
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
// str
|
||||
dc_op_str_ass,
|
||||
dc_op_str_add,
|
||||
// -- pad: 3
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
dc_op_fail,
|
||||
];
|
||||
|
||||
#[inline(always)]
|
||||
const fn opc(opr: TagClass, ope: AssignmentOperator) -> usize {
|
||||
(AssignmentOperator::count() * opr.word()) + ope.word()
|
||||
}
|
||||
|
||||
pub fn update(gns: &GlobalNS, mut update: UpdateStatement) -> DatabaseResult<()> {
|
||||
gns.with_model(update.entity(), |mdl| {
|
||||
let mut ret = Ok(());
|
||||
let key = mdl.resolve_where(update.clauses_mut())?;
|
||||
let irm = mdl.intent_read_model();
|
||||
let g = sync::atm::cpin();
|
||||
let Some(row) = mdl.primary_index().select(key, &g) else {
|
||||
return Err(DatabaseError::DmlEntryNotFound)
|
||||
};
|
||||
let mut row_data_wl = row.d_data().write();
|
||||
let mut rollback_now = false;
|
||||
let mut rollback_data = Vec::with_capacity(update.expressions().len());
|
||||
let mut assn_expressions = update.into_expressions().into_iter();
|
||||
/*
|
||||
FIXME(@ohsayan): where's my usual magic? I'll do it once we have the SE stabilized
|
||||
*/
|
||||
while (assn_expressions.len() != 0) & (!rollback_now) {
|
||||
let AssignmentExpression {
|
||||
lhs,
|
||||
rhs,
|
||||
operator_fn,
|
||||
} = unsafe {
|
||||
// UNSAFE(@ohsayan): pre-loop cond
|
||||
assn_expressions.next().unwrap_unchecked()
|
||||
};
|
||||
let field_definition;
|
||||
let field_data;
|
||||
match (
|
||||
irm.fields().st_get(lhs.as_str()),
|
||||
row_data_wl.fields_mut().st_get_mut(lhs.as_str()),
|
||||
) {
|
||||
(Some(fdef), Some(fdata)) => {
|
||||
field_definition = fdef;
|
||||
field_data = fdata;
|
||||
}
|
||||
_ => {
|
||||
rollback_now = true;
|
||||
ret = Err(DatabaseError::FieldNotFound);
|
||||
break;
|
||||
}
|
||||
}
|
||||
match (
|
||||
field_definition.layers()[0].tag().tag_class(),
|
||||
rhs.kind().tag_class(),
|
||||
) {
|
||||
(tag_a, tag_b)
|
||||
if (tag_a == tag_b) & (tag_a < TagClass::List) & field_data.is_init() =>
|
||||
{
|
||||
let (okay, new) = unsafe { OPERATOR[opc(tag_a, operator_fn)](field_data, rhs) };
|
||||
rollback_now &= !okay;
|
||||
rollback_data.push((lhs.as_str(), mem::replace(field_data, new)));
|
||||
}
|
||||
(tag_a, tag_b)
|
||||
if (tag_a == tag_b)
|
||||
& field_data.is_null()
|
||||
& (operator_fn == AssignmentOperator::Assign) =>
|
||||
{
|
||||
rollback_data.push((lhs.as_str(), mem::replace(field_data, rhs.into())));
|
||||
}
|
||||
(TagClass::List, tag_b) => {
|
||||
if field_definition.layers()[1].tag().tag_class() == tag_b {
|
||||
unsafe {
|
||||
// UNSAFE(@ohsayan): matched tags
|
||||
let mut list = field_data.read_list().write();
|
||||
if list.try_reserve(1).is_ok() {
|
||||
list.push(rhs.into());
|
||||
} else {
|
||||
rollback_now = true;
|
||||
ret = Err(DatabaseError::ServerError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rollback_now = true;
|
||||
ret = Err(DatabaseError::DmlConstraintViolationFieldTypedef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
ret = Err(DatabaseError::DmlConstraintViolationFieldTypedef);
|
||||
rollback_now = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if compiler::unlikely(rollback_now) {
|
||||
rollback_data
|
||||
.into_iter()
|
||||
.for_each(|(field_id, restored_data)| {
|
||||
row_data_wl.fields_mut().st_update(field_id, restored_data);
|
||||
});
|
||||
}
|
||||
ret
|
||||
})
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Created on Fri May 12 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 std::mem;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum AssignmentOperator {
|
||||
Assign = 0,
|
||||
AddAssign = 1,
|
||||
SubAssign = 2,
|
||||
MulAssign = 3,
|
||||
DivAssign = 4,
|
||||
}
|
||||
|
||||
impl AssignmentOperator {
|
||||
pub const fn disc(&self) -> u8 {
|
||||
unsafe {
|
||||
// UNSAFE(@ohsayan): just go back to school already; dscr
|
||||
mem::transmute(*self)
|
||||
}
|
||||
}
|
||||
pub const fn max() -> usize {
|
||||
Self::DivAssign.disc() as _
|
||||
}
|
||||
pub const fn word(&self) -> usize {
|
||||
self.disc() as _
|
||||
}
|
||||
pub const fn count() -> usize {
|
||||
5
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Created on Sun May 14 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::GlobalNS, data::cell::Datacell, error::DatabaseError};
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let gns = GlobalNS::empty();
|
||||
assert_eq!(
|
||||
super::exec_update(
|
||||
&gns,
|
||||
"create model myspace.mymodel(username: string, email: string, followers: uint64, following: uint64)",
|
||||
"insert into myspace.mymodel('sayan', 'sayan@example.com', 0, 100)",
|
||||
"update myspace.mymodel set followers += 200000, following -= 15, email = 'sn@example.com' where username = 'sayan'",
|
||||
"select * from myspace.mymodel where username = 'sayan'"
|
||||
).unwrap(),
|
||||
intovec!["sayan", "sn@example.com", 200_000_u64, 85_u64],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_null() {
|
||||
let gns = GlobalNS::empty();
|
||||
assert_eq!(
|
||||
super::exec_update(
|
||||
&gns,
|
||||
"create model myspace.mymodel(username: string, password: string, null email: string)",
|
||||
"insert into myspace.mymodel('sayan', 'pass123', null)",
|
||||
"update myspace.mymodel set email = 'sayan@example.com' where username = 'sayan'",
|
||||
"select * from myspace.mymodel where username='sayan'"
|
||||
)
|
||||
.unwrap(),
|
||||
intovec!["sayan", "pass123", "sayan@example.com"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_unknown_fields() {
|
||||
let gns = GlobalNS::empty();
|
||||
assert_eq!(
|
||||
super::exec_update(
|
||||
&gns,
|
||||
"create model myspace.mymodel(username: string, password: string, null email: string)",
|
||||
"insert into myspace.mymodel('sayan', 'pass123', null)",
|
||||
"update myspace.mymodel set email2 = 'sayan@example.com', password += '4' where username = 'sayan'",
|
||||
"select * from myspace.mymodel where username='sayan'"
|
||||
)
|
||||
.unwrap_err(),
|
||||
DatabaseError::FieldNotFound
|
||||
);
|
||||
// verify integrity
|
||||
assert_eq!(
|
||||
super::exec_select_only(&gns, "select * from myspace.mymodel where username='sayan'")
|
||||
.unwrap(),
|
||||
intovec!["sayan", "pass123", Datacell::null()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_typedef_violation() {
|
||||
let gns = GlobalNS::empty();
|
||||
assert_eq!(
|
||||
super::exec_update(
|
||||
&gns,
|
||||
"create model myspace.mymodel(username: string, password: string, rank: uint8)",
|
||||
"insert into myspace.mymodel('sayan', 'pass123', 1)",
|
||||
"update myspace.mymodel set password = 'pass1234', rank = 'one' where username = 'sayan'",
|
||||
"select * from myspace.mymodel where username = 'sayan'"
|
||||
)
|
||||
.unwrap_err(),
|
||||
DatabaseError::DmlConstraintViolationFieldTypedef
|
||||
);
|
||||
// verify integrity
|
||||
assert_eq!(
|
||||
super::exec_select_only(
|
||||
&gns,
|
||||
"select * from myspace.mymodel where username = 'sayan'"
|
||||
)
|
||||
.unwrap(),
|
||||
intovec!["sayan", "pass123", 1u64]
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue