parent
aa30822f0e
commit
e7f40c575a
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Created on Mon Jun 20 2022
|
||||
*
|
||||
* 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) 2022, 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 {
|
||||
super::{ast::Entity, error},
|
||||
crate::{
|
||||
actions::{ActionError, ActionResult},
|
||||
protocol::interface::ProtocolSpec,
|
||||
util::Life,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn from_slice_action_result<P: ProtocolSpec>(slice: &[u8]) -> ActionResult<Life<'_, Entity>> {
|
||||
match Entity::from_slice(slice) {
|
||||
Ok(slc) => Ok(Life::new(slc)),
|
||||
Err(e) => Err(ActionError::ActionError(error::cold_err::<P>(e))),
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Jul 27 2021
|
||||
*
|
||||
* 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) 2021, 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 super::parser;
|
||||
use super::parser::VALID_CONTAINER_NAME;
|
||||
use crate::corestore::memstore::ObjectID;
|
||||
use crate::dbnet::connection::prelude::*;
|
||||
use crate::kvengine::encoding;
|
||||
use crate::registry;
|
||||
use core::str;
|
||||
|
||||
pub const TABLE: &[u8] = "TABLE".as_bytes();
|
||||
pub const KEYSPACE: &[u8] = "KEYSPACE".as_bytes();
|
||||
const VOLATILE: &[u8] = "volatile".as_bytes();
|
||||
const FORCE_REMOVE: &[u8] = "force".as_bytes();
|
||||
|
||||
action! {
|
||||
/// Handle `create table <tableid> <model>(args)` and `create keyspace <ksid>`
|
||||
/// like queries
|
||||
fn create(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
// minlength is 2 (create has already been checked)
|
||||
ensure_length::<P>(act.len(), |size| size > 1)?;
|
||||
let mut create_what = unsafe { act.next().unsafe_unwrap() }.to_vec();
|
||||
create_what.make_ascii_uppercase();
|
||||
match create_what.as_ref() {
|
||||
TABLE => create_table(handle, con, act).await?,
|
||||
KEYSPACE => create_keyspace(handle, con, act).await?,
|
||||
_ => {
|
||||
con._write_raw(P::RSTRING_UNKNOWN_DDL_QUERY).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle `drop table <tableid>` and `drop keyspace <ksid>`
|
||||
/// like queries
|
||||
fn ddl_drop(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
// minlength is 2 (create has already been checked)
|
||||
ensure_length::<P>(act.len(), |size| size > 1)?;
|
||||
let mut create_what = unsafe { act.next().unsafe_unwrap() }.to_vec();
|
||||
create_what.make_ascii_uppercase();
|
||||
match create_what.as_ref() {
|
||||
TABLE => drop_table(handle, con, act).await?,
|
||||
KEYSPACE => drop_keyspace(handle, con, act).await?,
|
||||
_ => {
|
||||
con._write_raw(P::RSTRING_UNKNOWN_DDL_QUERY).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// We should have `<tableid> <model>(args) properties`
|
||||
fn create_table(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |size| size > 1 && size < 4)?;
|
||||
let table_name = unsafe { act.next().unsafe_unwrap() };
|
||||
let model_name = unsafe { act.next().unsafe_unwrap() };
|
||||
let (table_entity, model_code) = parser::parse_table_args::<P>(table_name, model_name)?;
|
||||
let is_volatile = match act.next() {
|
||||
Some(maybe_volatile) => {
|
||||
ensure_cond_or_err(maybe_volatile.eq(VOLATILE), P::RSTRING_UNKNOWN_PROPERTY)?;
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
};
|
||||
if registry::state_okay() {
|
||||
translate_ddl_error::<P, ()>(handle.create_table(table_entity, model_code, is_volatile))?;
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// We should have `<ksid>`
|
||||
fn create_keyspace(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len == 1)?;
|
||||
match act.next() {
|
||||
Some(ksid) => {
|
||||
ensure_cond_or_err(encoding::is_utf8(&ksid), P::RCODE_ENCODING_ERROR)?;
|
||||
let ksid_str = unsafe { str::from_utf8_unchecked(ksid) };
|
||||
ensure_cond_or_err(VALID_CONTAINER_NAME.is_match(ksid_str), P::RSTRING_BAD_EXPRESSION)?;
|
||||
ensure_cond_or_err(ksid.len() < 64, P::RSTRING_CONTAINER_NAME_TOO_LONG)?;
|
||||
let ksid = unsafe { ObjectID::from_slice(ksid_str) };
|
||||
if registry::state_okay() {
|
||||
translate_ddl_error::<P, ()>(handle.create_keyspace(ksid))?;
|
||||
con._write_raw(P::RCODE_OKAY).await?
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
}
|
||||
None => return util::err(P::RCODE_ACTION_ERR),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drop a table (`<tblid>` only)
|
||||
fn drop_table(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |size| size == 1)?;
|
||||
match act.next() {
|
||||
Some(eg) => {
|
||||
let entity_group = parser::Entity::from_slice::<P>(eg)?;
|
||||
if registry::state_okay() {
|
||||
translate_ddl_error::<P, ()>(handle.drop_table(entity_group, false))?;
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
},
|
||||
None => return util::err(P::RCODE_ACTION_ERR),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drop a keyspace (`<ksid>` only)
|
||||
fn drop_keyspace(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |size| size == 1)?;
|
||||
match act.next() {
|
||||
Some(ksid) => {
|
||||
ensure_cond_or_err(ksid.len() < 64, P::RSTRING_CONTAINER_NAME_TOO_LONG)?;
|
||||
let force_remove = match act.next() {
|
||||
Some(bts) if bts.eq(FORCE_REMOVE) => true,
|
||||
None => false,
|
||||
_ => {
|
||||
return util::err(P::RCODE_UNKNOWN_ACTION);
|
||||
}
|
||||
};
|
||||
if registry::state_okay() {
|
||||
let objid = unsafe {ObjectID::from_slice(ksid)};
|
||||
let result = if force_remove {
|
||||
handle.force_drop_keyspace(objid)
|
||||
} else {
|
||||
handle.drop_keyspace(objid)
|
||||
};
|
||||
translate_ddl_error::<P, ()>(result)?;
|
||||
con._write_raw(P::RCODE_OKAY).await?;
|
||||
} else {
|
||||
return util::err(P::RCODE_SERVER_ERR);
|
||||
}
|
||||
},
|
||||
None => return util::err(P::RCODE_ACTION_ERR),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Jul 27 2021
|
||||
*
|
||||
* 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) 2021, 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 {
|
||||
super::{
|
||||
ddl::{KEYSPACE, TABLE},
|
||||
parser::Entity,
|
||||
},
|
||||
crate::dbnet::connection::prelude::*,
|
||||
};
|
||||
|
||||
const KEYSPACES: &[u8] = "KEYSPACES".as_bytes();
|
||||
|
||||
action! {
|
||||
/// Runs an inspect query:
|
||||
/// - `INSPECT KEYSPACES` is run by this function itself
|
||||
/// - `INSPECT TABLE <tblid>` is delegated to self::inspect_table
|
||||
/// - `INSPECT KEYSPACE <ksid>` is delegated to self::inspect_keyspace
|
||||
fn inspect(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
match act.next() {
|
||||
Some(inspect_what) => {
|
||||
let mut inspect_what = inspect_what.to_vec();
|
||||
inspect_what.make_ascii_uppercase();
|
||||
match inspect_what.as_ref() {
|
||||
KEYSPACE => inspect_keyspace(handle, con, act).await?,
|
||||
TABLE => inspect_table(handle, con, act).await?,
|
||||
KEYSPACES => {
|
||||
ensure_length::<P>(act.len(), |len| len == 0)?;
|
||||
// let's return what all keyspaces exist
|
||||
con.write_typed_non_null_array(
|
||||
handle.get_store().list_keyspaces(),
|
||||
b'+'
|
||||
).await?
|
||||
}
|
||||
_ => return util::err(P::RSTRING_UNKNOWN_INSPECT_QUERY),
|
||||
}
|
||||
}
|
||||
None => return util::err(P::RCODE_ACTION_ERR),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// INSPECT a keyspace. This should only have the keyspace ID
|
||||
fn inspect_keyspace(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len < 2)?;
|
||||
con.write_typed_non_null_array(handle.list_tables::<P>(act.next())?, b'+').await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// INSPECT a table. This should only have the table ID
|
||||
fn inspect_table(handle: &Corestore, con: &'a mut T, mut act: ActionIter<'a>) {
|
||||
ensure_length::<P>(act.len(), |len| len < 2)?;
|
||||
let maybe_entity = match act.next() {
|
||||
Some(e) => Some(Entity::from_slice::<P>(e)?),
|
||||
None => None,
|
||||
};
|
||||
con.write_string(&handle.describe_table::<P>(maybe_entity)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Jul 27 2021
|
||||
*
|
||||
* 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) 2021, 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::corestore::{lazy::Lazy, memstore::ObjectID};
|
||||
use crate::kvengine::encoding;
|
||||
use crate::queryengine::ProtocolSpec;
|
||||
use crate::util::{
|
||||
self,
|
||||
compiler::{self, cold_err},
|
||||
};
|
||||
use core::{fmt, str};
|
||||
use regex::Regex;
|
||||
|
||||
type LazyRegexFn = Lazy<Regex, fn() -> Regex>;
|
||||
|
||||
const KEYMAP: &[u8] = "keymap".as_bytes();
|
||||
const BINSTR: &[u8] = "binstr".as_bytes();
|
||||
const STR: &[u8] = "str".as_bytes();
|
||||
const LIST_STR: &[u8] = "list<str>".as_bytes();
|
||||
const LIST_BINSTR: &[u8] = "list<binstr>".as_bytes();
|
||||
|
||||
pub(super) static VALID_CONTAINER_NAME: LazyRegexFn =
|
||||
LazyRegexFn::new(|| Regex::new("^[a-zA-Z_][a-zA-Z_0-9]*$").unwrap());
|
||||
pub(super) static VALID_TYPENAME: LazyRegexFn =
|
||||
LazyRegexFn::new(|| Regex::new("^<[a-zA-Z][a-zA-Z0-9]+[^>\\s]?>{1}$").unwrap());
|
||||
|
||||
pub(super) fn parse_table_args<'a, P: ProtocolSpec>(
|
||||
table_name: &'a [u8],
|
||||
model_name: &'a [u8],
|
||||
) -> Result<(Entity<'a>, u8), &'static [u8]> {
|
||||
if compiler::unlikely(!encoding::is_utf8(&table_name) || !encoding::is_utf8(&model_name)) {
|
||||
return Err(P::RCODE_ENCODING_ERROR);
|
||||
}
|
||||
let model_name_str = unsafe { str::from_utf8_unchecked(model_name) };
|
||||
|
||||
// get the entity group
|
||||
let entity_group = Entity::from_slice::<P>(table_name)?;
|
||||
let splits: Vec<&str> = model_name_str.split('(').collect();
|
||||
if compiler::unlikely(splits.len() != 2) {
|
||||
return Err(P::RSTRING_BAD_EXPRESSION);
|
||||
}
|
||||
|
||||
let model_name_split = unsafe { ucidx!(splits, 0) };
|
||||
let model_args_split = unsafe { ucidx!(splits, 1) };
|
||||
|
||||
// model name has to have at least one char while model args should have
|
||||
// atleast `)` 1 chars (for example if the model takes no arguments: `smh()`)
|
||||
if compiler::unlikely(model_name_split.is_empty() || model_args_split.is_empty()) {
|
||||
return Err(P::RSTRING_BAD_EXPRESSION);
|
||||
}
|
||||
|
||||
// THIS IS WHERE WE HANDLE THE NEWER MODELS
|
||||
if model_name_split.as_bytes() != KEYMAP {
|
||||
return Err(P::RSTRING_UNKNOWN_MODEL);
|
||||
}
|
||||
|
||||
let non_bracketed_end =
|
||||
unsafe { ucidx!(*model_args_split.as_bytes(), model_args_split.len() - 1) != b')' };
|
||||
|
||||
if compiler::unlikely(non_bracketed_end) {
|
||||
return Err(P::RSTRING_BAD_EXPRESSION);
|
||||
}
|
||||
|
||||
// should be (ty1, ty2)
|
||||
let model_args: Vec<&str> = model_args_split[..model_args_split.len() - 1]
|
||||
.split(',')
|
||||
.map(|v| v.trim())
|
||||
.collect();
|
||||
if compiler::unlikely(model_args.len() != 2) {
|
||||
// nope, someone had fun with commas or they added more args
|
||||
// let's check if it was comma fun or if it was arg fun
|
||||
return cold_err({
|
||||
let all_nonzero = model_args.into_iter().all(|v| !v.is_empty());
|
||||
if all_nonzero {
|
||||
// arg fun
|
||||
Err(P::RSTRING_TOO_MANY_ARGUMENTS)
|
||||
} else {
|
||||
// comma fun
|
||||
Err(P::RSTRING_BAD_EXPRESSION)
|
||||
}
|
||||
});
|
||||
}
|
||||
let key_ty = unsafe { ucidx!(model_args, 0) };
|
||||
let val_ty = unsafe { ucidx!(model_args, 1) };
|
||||
let valid_key_ty = if let Some(idx) = key_ty.chars().position(|v| v.eq(&'<')) {
|
||||
VALID_CONTAINER_NAME.is_match(&key_ty[..idx]) && VALID_TYPENAME.is_match(&key_ty[idx..])
|
||||
} else {
|
||||
VALID_CONTAINER_NAME.is_match(key_ty)
|
||||
};
|
||||
let valid_val_ty = if let Some(idx) = val_ty.chars().position(|v| v.eq(&'<')) {
|
||||
VALID_CONTAINER_NAME.is_match(&val_ty[..idx]) && VALID_TYPENAME.is_match(&val_ty[idx..])
|
||||
} else {
|
||||
VALID_CONTAINER_NAME.is_match(val_ty)
|
||||
};
|
||||
if compiler::unlikely(!(valid_key_ty || valid_val_ty)) {
|
||||
return Err(P::RSTRING_BAD_EXPRESSION);
|
||||
}
|
||||
let key_ty = key_ty.as_bytes();
|
||||
let val_ty = val_ty.as_bytes();
|
||||
let model_code: u8 = match (key_ty, val_ty) {
|
||||
// pure KVEBlob
|
||||
(BINSTR, BINSTR) => 0,
|
||||
(BINSTR, STR) => 1,
|
||||
(STR, STR) => 2,
|
||||
(STR, BINSTR) => 3,
|
||||
// KVExt: listmap
|
||||
(BINSTR, LIST_BINSTR) => 4,
|
||||
(BINSTR, LIST_STR) => 5,
|
||||
(STR, LIST_BINSTR) => 6,
|
||||
(STR, LIST_STR) => 7,
|
||||
// KVExt bad keytypes (we can't use lists as keys for obvious reasons)
|
||||
(LIST_STR, _) | (LIST_BINSTR, _) => return Err(P::RSTRING_BAD_TYPE_FOR_KEY),
|
||||
_ => return Err(P::RCODE_UNKNOWN_DATA_TYPE),
|
||||
};
|
||||
Ok((entity_group, model_code))
|
||||
}
|
||||
|
||||
type ByteSlice<'a> = &'a [u8];
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum Entity<'a> {
|
||||
/// Fully qualified syntax (ks:table)
|
||||
Full(ByteSlice<'a>, ByteSlice<'a>),
|
||||
/// Half entity syntax (only ks/table)
|
||||
Single(ByteSlice<'a>),
|
||||
/// Partial entity syntax (`:table`)
|
||||
Partial(ByteSlice<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a crate::blueql::Entity> for Entity<'a> {
|
||||
fn from(e: &'a crate::blueql::Entity) -> Entity<'a> {
|
||||
unsafe {
|
||||
// UNSAFE(@ohsayan): Lifetime guarantees safety
|
||||
match e {
|
||||
crate::blueql::Entity::Current(c) => Entity::Single(c.as_slice()),
|
||||
crate::blueql::Entity::Full(k, t) => Entity::Full(k.as_slice(), t.as_slice()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum OwnedEntity {
|
||||
Full(ObjectID, ObjectID),
|
||||
Single(ObjectID),
|
||||
Partial(ObjectID),
|
||||
}
|
||||
|
||||
impl fmt::Debug for OwnedEntity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OwnedEntity::Full(a, b) => write!(
|
||||
f,
|
||||
"Full('{}:{}')",
|
||||
String::from_utf8_lossy(a),
|
||||
String::from_utf8_lossy(b)
|
||||
),
|
||||
OwnedEntity::Single(a) => write!(f, "Single('{}')", String::from_utf8_lossy(a)),
|
||||
OwnedEntity::Partial(a) => write!(f, "Partial(':{}')", String::from_utf8_lossy(a)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for Entity<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.as_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Entity<'a> {
|
||||
pub fn from_slice<P: ProtocolSpec>(input: ByteSlice<'a>) -> Result<Entity<'a>, &'static [u8]> {
|
||||
let parts: Vec<&[u8]> = input.split(|b| *b == b':').collect();
|
||||
if compiler::unlikely(parts.is_empty() || parts.len() > 2) {
|
||||
return util::err(P::RSTRING_BAD_EXPRESSION);
|
||||
}
|
||||
// just the table
|
||||
let first_entity = unsafe { ucidx!(parts, 0) };
|
||||
if parts.len() == 1 {
|
||||
Ok(Entity::Single(Self::verify_entity_name::<P>(first_entity)?))
|
||||
} else {
|
||||
let second_entity = Self::verify_entity_name::<P>(unsafe { ucidx!(parts, 1) })?;
|
||||
if first_entity.is_empty() {
|
||||
// partial syntax; so the table is in the second position
|
||||
Ok(Entity::Partial(second_entity))
|
||||
} else {
|
||||
let keyspace = Self::verify_entity_name::<P>(first_entity)?;
|
||||
let table = Self::verify_entity_name::<P>(second_entity)?;
|
||||
Ok(Entity::Full(keyspace, table))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn verify_entity_name<P: ProtocolSpec>(input: &[u8]) -> Result<&[u8], &'static [u8]> {
|
||||
let mut valid_name = input.len() < 65
|
||||
&& encoding::is_utf8(input)
|
||||
&& unsafe { VALID_CONTAINER_NAME.is_match(str::from_utf8_unchecked(input)) };
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// paths on Windows are case insensitive that's why this is necessary
|
||||
valid_name &=
|
||||
!(input.eq_ignore_ascii_case(b"PRELOAD") || input.eq_ignore_ascii_case(b"PARTMAP"));
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
valid_name &= (input != b"PRELOAD") && (input != b"PARTMAP");
|
||||
}
|
||||
if compiler::likely(valid_name && !input.is_empty()) {
|
||||
// valid name
|
||||
Ok(input)
|
||||
} else if compiler::unlikely(input.is_empty()) {
|
||||
// bad expression (something like `:`)
|
||||
util::err(P::RSTRING_BAD_EXPRESSION)
|
||||
} else if compiler::unlikely(input.eq(b"system")) {
|
||||
// system cannot be switched to
|
||||
util::err(P::RSTRING_PROTECTED_OBJECT)
|
||||
} else {
|
||||
// the container has a bad name
|
||||
util::err(P::RSTRING_BAD_CONTAINER_NAME)
|
||||
}
|
||||
}
|
||||
pub fn as_owned(&self) -> OwnedEntity {
|
||||
unsafe {
|
||||
match self {
|
||||
Self::Full(a, b) => {
|
||||
OwnedEntity::Full(ObjectID::from_slice(a), ObjectID::from_slice(b))
|
||||
}
|
||||
Self::Single(a) => OwnedEntity::Single(ObjectID::from_slice(a)),
|
||||
Self::Partial(a) => OwnedEntity::Partial(ObjectID::from_slice(a)),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn into_owned(self) -> OwnedEntity {
|
||||
self.as_owned()
|
||||
}
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
/*
|
||||
* Created on Tue Jul 27 2021
|
||||
*
|
||||
* 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) 2021, 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 super::parser;
|
||||
|
||||
mod parser_ddl_tests {
|
||||
use super::parser::Entity;
|
||||
use crate::protocol::interface::ProtocolSpec;
|
||||
use crate::protocol::Skyhash2;
|
||||
macro_rules! byvec {
|
||||
($($element:expr),*) => {
|
||||
vec![
|
||||
$(
|
||||
$element.as_bytes()
|
||||
),*
|
||||
]
|
||||
};
|
||||
}
|
||||
fn parse_table_args_test(input: Vec<&'static [u8]>) -> Result<(Entity<'_>, u8), &'static [u8]> {
|
||||
super::parser::parse_table_args::<Skyhash2>(input[0], input[1])
|
||||
}
|
||||
#[test]
|
||||
fn test_table_args_valid() {
|
||||
// binstr, binstr
|
||||
let it = byvec!("mytbl", "keymap(binstr,binstr)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 0);
|
||||
|
||||
// binstr, str
|
||||
let it = byvec!("mytbl", "keymap(binstr,str)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 1);
|
||||
|
||||
// str, str
|
||||
let it = byvec!("mytbl", "keymap(str,str)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 2);
|
||||
|
||||
// str, binstr
|
||||
let it = byvec!("mytbl", "keymap(str,binstr)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 3);
|
||||
|
||||
// now test kvext: listmap
|
||||
// binstr, list<binstr>
|
||||
let it = byvec!("mytbl", "keymap(binstr,list<binstr>)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 4);
|
||||
|
||||
// binstr, list<str>
|
||||
let it = byvec!("mytbl", "keymap(binstr,list<str>)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 5);
|
||||
|
||||
// str, list<binstr>
|
||||
let it = byvec!("mytbl", "keymap(str,list<binstr>)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 6);
|
||||
|
||||
// str, list<str>
|
||||
let it = byvec!("mytbl", "keymap(str,list<str>)");
|
||||
let (tbl_name, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tbl_name, Entity::Single(b"mytbl"));
|
||||
assert_eq!(mcode, 7);
|
||||
}
|
||||
#[test]
|
||||
fn test_table_bad_ident() {
|
||||
let it = byvec!("1one", "keymap(binstr,binstr)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_CONTAINER_NAME
|
||||
);
|
||||
let it = byvec!("%whywouldsomeone", "keymap(binstr,binstr)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_CONTAINER_NAME
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_table_whitespaced_datatypes() {
|
||||
let it = byvec!("mycooltbl", "keymap(binstr, binstr)");
|
||||
let (tblid, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tblid, Entity::Single(b"mycooltbl"));
|
||||
assert_eq!(mcode, 0);
|
||||
|
||||
let it = byvec!("mycooltbl", "keymap(binstr, str)");
|
||||
let (tblid, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tblid, Entity::Single(b"mycooltbl"));
|
||||
assert_eq!(mcode, 1);
|
||||
|
||||
let it = byvec!("mycooltbl", "keymap(str, str)");
|
||||
let (tblid, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tblid, Entity::Single(b"mycooltbl"));
|
||||
assert_eq!(mcode, 2);
|
||||
|
||||
let it = byvec!("mycooltbl", "keymap(str, binstr)");
|
||||
let (tblid, mcode) = parse_table_args_test(it).unwrap();
|
||||
assert_eq!(tblid, Entity::Single(b"mycooltbl"));
|
||||
assert_eq!(mcode, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_badty() {
|
||||
let it = byvec!("mycooltbl", "keymap(wth, str)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RCODE_UNKNOWN_DATA_TYPE
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(wth, wth)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RCODE_UNKNOWN_DATA_TYPE
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(str, wth)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RCODE_UNKNOWN_DATA_TYPE
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(wth1, wth2)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RCODE_UNKNOWN_DATA_TYPE
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_table_bad_model() {
|
||||
let it = byvec!("mycooltbl", "wthmap(wth, wth)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_UNKNOWN_MODEL
|
||||
);
|
||||
let it = byvec!("mycooltbl", "wthmap(str, str)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_UNKNOWN_MODEL
|
||||
);
|
||||
let it = byvec!("mycooltbl", "wthmap()");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_UNKNOWN_MODEL
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_table_malformed_expr() {
|
||||
let it = byvec!("mycooltbl", "keymap(");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(,,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap),");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap),,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap),,)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(,)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(,,)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap,,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap,,)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(str,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(str,str");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(str,str,");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(str,str,)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let it = byvec!("mycooltbl", "keymap(str,str,),");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_too_many_args() {
|
||||
let it = byvec!("mycooltbl", "keymap(str, str, str)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_TOO_MANY_ARGUMENTS
|
||||
);
|
||||
|
||||
// this should be valid for not-yet-known data types too
|
||||
let it = byvec!("mycooltbl", "keymap(wth, wth, wth)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_TOO_MANY_ARGUMENTS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_key_type() {
|
||||
let it = byvec!("myverycooltbl", "keymap(list<str>, str)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_TYPE_FOR_KEY
|
||||
);
|
||||
let it = byvec!("myverycooltbl", "keymap(list<binstr>, binstr)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_TYPE_FOR_KEY
|
||||
);
|
||||
// for consistency checks
|
||||
let it = byvec!("myverycooltbl", "keymap(list<str>, binstr)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_TYPE_FOR_KEY
|
||||
);
|
||||
let it = byvec!("myverycooltbl", "keymap(list<binstr>, str)");
|
||||
assert_eq!(
|
||||
parse_table_args_test(it).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_TYPE_FOR_KEY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod entity_parser_tests {
|
||||
use super::parser::Entity;
|
||||
use crate::protocol::interface::ProtocolSpec;
|
||||
use crate::protocol::Skyhash2;
|
||||
#[test]
|
||||
fn test_query_full_entity_okay() {
|
||||
let x = byt!("ks:tbl");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap(),
|
||||
Entity::Full(b"ks", b"tbl")
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_query_half_entity() {
|
||||
let x = byt!("tbl");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap(),
|
||||
Entity::Single(b"tbl")
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn test_query_partial_entity() {
|
||||
let x = byt!(":tbl");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap(),
|
||||
Entity::Partial(b"tbl")
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn test_query_entity_badexpr() {
|
||||
let x = byt!("ks:");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!(":");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!("::");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!("::ks");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!("ks::tbl");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!("ks::");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!("ks::tbl::");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
let x = byt!("::ks::tbl::");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&x).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_EXPRESSION
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_entity_name() {
|
||||
let ename = byt!("$var");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&ename).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_CONTAINER_NAME
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn ks_or_table_with_preload_or_partmap() {
|
||||
let badname = byt!("PARTMAP");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&badname).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_CONTAINER_NAME
|
||||
);
|
||||
let badname = byt!("PRELOAD");
|
||||
assert_eq!(
|
||||
Entity::from_slice::<Skyhash2>(&badname).unwrap_err(),
|
||||
Skyhash2::RSTRING_BAD_CONTAINER_NAME
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue