From c9e55451f38e39a143d7d212255a0f8c5c78669f Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Wed, 28 Jul 2021 07:03:34 +0530 Subject: [PATCH] Add inspection queries This lets the user explore a keyspace/table. --- server/src/corestore/array.rs | 2 +- server/src/corestore/mod.rs | 3 + server/src/corestore/table.rs | 10 +++ server/src/main.rs | 1 + server/src/protocol/responses.rs | 1 + server/src/queryengine/ddl.rs | 4 +- server/src/queryengine/inspect.rs | 101 ++++++++++++++++++++++++++++++ server/src/queryengine/mod.rs | 4 +- server/src/resp/mod.rs | 55 ++++++++++++++++ 9 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 server/src/queryengine/inspect.rs diff --git a/server/src/corestore/array.rs b/server/src/corestore/array.rs index d0c7a8a5..ddd48440 100644 --- a/server/src/corestore/array.rs +++ b/server/src/corestore/array.rs @@ -288,7 +288,7 @@ impl Array { // of the array /// Get self as a slice. Super safe because we guarantee that all the other invarians /// are upheld - fn as_slice(&self) -> &[T] { + pub fn as_slice(&self) -> &[T] { unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) } } /// Get self as a mutable slice. Super safe (see comment above) diff --git a/server/src/corestore/mod.rs b/server/src/corestore/mod.rs index 5d1ebcd6..18b139d4 100644 --- a/server/src/corestore/mod.rs +++ b/server/src/corestore/mod.rs @@ -164,6 +164,9 @@ impl Corestore { } Ok(()) } + pub fn get_keyspace(&self, ksid: ObjectID) -> Option> { + self.store.get_keyspace_atomic_ref(&ksid) + } /// Get an atomic reference to a table pub fn get_table(&self, entity: EntityGroup) -> KeyspaceResult> { match entity { diff --git a/server/src/corestore/table.rs b/server/src/corestore/table.rs index 1c1b0a43..6c0937c5 100644 --- a/server/src/corestore/table.rs +++ b/server/src/corestore/table.rs @@ -62,6 +62,16 @@ impl Table { DataModel::KV(kv) => kv.len(), } } + /// Returns this table's _description_ + pub fn describe_self(&self) -> &'static str { + match self.get_model_code() { + 0 => r#"KeyValue(binstr,binstr)"#, + 1 => r#"KeyValue(binstr,str)"#, + 2 => r#"KeyValue(str,str)"#, + 3 => r#"KeyValue(str,binstr)"#, + _ => unsafe { impossible!() }, + } + } pub fn truncate_table(&self) { match self.model_store { DataModel::KV(ref kv) => kv.truncate_table(), diff --git a/server/src/main.rs b/server/src/main.rs index a08ac6e9..a4f59cd2 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -158,6 +158,7 @@ pub fn pre_shutdown_cleanup(pid_file: fs::File, mr: Option<&Memstore>) { process::exit(0x01); } if let Some(mr) = mr { + log::info!("Compacting tree"); if let Err(e) = storage::interface::cleanup_tree(mr) { log::error!("Failed to compact tree: {}", e); process::exit(0x01); diff --git a/server/src/protocol/responses.rs b/server/src/protocol/responses.rs index e0e0cc93..488e9756 100644 --- a/server/src/protocol/responses.rs +++ b/server/src/protocol/responses.rs @@ -79,6 +79,7 @@ pub mod groups { pub const TOO_MANY_ARGUMENTS: &[u8] = "!13\ntoo-many-args\n".as_bytes(); pub const CONTAINER_NAME_TOO_LONG: &[u8] = "!23\ncontainer-name-too-long\n".as_bytes(); pub const BAD_CONTAINER_NAME: &[u8] = "!18\nbad-container-name\n".as_bytes(); + pub const UNKNOWN_INSPECT_QUERY: &[u8] = "!21\nunknown-inspect-query\n".as_bytes(); } pub mod full_responses { diff --git a/server/src/queryengine/ddl.rs b/server/src/queryengine/ddl.rs index 34f9351b..d28ff1ed 100644 --- a/server/src/queryengine/ddl.rs +++ b/server/src/queryengine/ddl.rs @@ -32,8 +32,8 @@ use crate::dbnet::connection::prelude::*; use crate::kvengine::encoding; use core::str; -const TABLE: &[u8] = "TABLE".as_bytes(); -const KEYSPACE: &[u8] = "KEYSPACE".as_bytes(); +pub const TABLE: &[u8] = "TABLE".as_bytes(); +pub const KEYSPACE: &[u8] = "KEYSPACE".as_bytes(); action!( /// Handle `create table (args)` and `create keyspace ` diff --git a/server/src/queryengine/inspect.rs b/server/src/queryengine/inspect.rs new file mode 100644 index 00000000..5028f64e --- /dev/null +++ b/server/src/queryengine/inspect.rs @@ -0,0 +1,101 @@ +/* + * 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 + * + * 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 . + * +*/ + +use super::ddl::{KEYSPACE, TABLE}; +use crate::corestore::memstore::ObjectID; +use crate::dbnet::connection::prelude::*; + +const KEYSPACES: &[u8] = "KEYSPACES".as_bytes(); +action! { + fn inspect(handle: &Corestore, con: &mut T, mut act: ActionIter) { + 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 => { + // let's return what all keyspaces exist + let ks_list: Vec = handle + .get_store() + .keyspaces + .iter() + .map(|kv| kv.key().clone()) + .collect(); + con.write_flat_array_length(ks_list.len()).await?; + for tbl in ks_list { + con.write_response(tbl).await?; + } + } + _ => conwrite!(con, responses::groups::UNKNOWN_INSPECT_QUERY)?, + } + } + None => aerr!(con, aerr), + } + Ok(()) + } +} + +action! { + fn inspect_keyspace(handle: &Corestore, con: &mut T, mut act: ActionIter) { + match act.next() { + Some(keyspace_name) => { + let ksid = if keyspace_name.len() > 64 { + return conwrite!(con, responses::groups::BAD_CONTAINER_NAME); + } else { + unsafe { + ObjectID::from_slice(keyspace_name) + } + }; + let ks = match handle.get_keyspace(ksid) { + Some(kspace) => kspace, + None => return conwrite!(con, responses::groups::CONTAINER_NOT_FOUND), + }; + let tbl_list: Vec = ks.tables.iter().map(|kv| kv.key().clone()).collect(); + con.write_flat_array_length(tbl_list.len()).await?; + for tbl in tbl_list { + con.write_response(tbl).await?; + } + }, + None => aerr!(con, aerr), + } + Ok(()) + } +} + +action! { + fn inspect_table(handle: &Corestore, con: &mut T, mut act: ActionIter) { + match act.next() { + Some(entity) => { + let entity = handle_entity!(con, entity); + conwrite!(con, get_tbl!(entity, handle, con).describe_self())?; + }, + None => aerr!(con, aerr), + } + Ok(()) + } +} diff --git a/server/src/queryengine/mod.rs b/server/src/queryengine/mod.rs index cf588bc7..8a3981da 100644 --- a/server/src/queryengine/mod.rs +++ b/server/src/queryengine/mod.rs @@ -34,6 +34,7 @@ use crate::protocol::Element; use crate::{actions, admin}; use bytes::Bytes; mod ddl; +mod inspect; pub mod parser; #[cfg(test)] mod tests; @@ -105,7 +106,8 @@ where POP => actions::pop::pop, CREATE => ddl::create, DROP => ddl::ddl_drop, - USE => self::entity_swap + USE => self::entity_swap, + INSPECT => inspect::inspect ); Ok(()) } diff --git a/server/src/resp/mod.rs b/server/src/resp/mod.rs index ab0a2051..8e1904e9 100644 --- a/server/src/resp/mod.rs +++ b/server/src/resp/mod.rs @@ -27,6 +27,7 @@ //! Utilities for generating responses, which are only used by the `server` //! use crate::corestore::buffers::Integer64; +use crate::corestore::memstore::ObjectID; use bytes::Bytes; use skytable::RespCode; use std::future::Future; @@ -124,6 +125,33 @@ impl Writable for &'static [u8] { } } +impl Writable for &'static str { + fn write<'s>( + self, + con: &'s mut impl IsConnection, + ) -> Pin> + Send + Sync + 's)>> { + async fn write_bytes(con: &mut impl IsConnection, bytes: &str) -> Result<(), IoError> { + // First write a `+` character to the stream since this is a + // string (we represent `String`s as `Byte` objects internally) + // and since `Bytes` are effectively `String`s we will append the + // type operator `+` to the stream + con.write_lowlevel(&[b'+']).await?; + // Now get the size of the Bytes object as bytes + let size = Integer64::from(bytes.len()); + // Write this to the stream + con.write_lowlevel(&size).await?; + // Now write a LF character + con.write_lowlevel(&[b'\n']).await?; + // Now write the REAL bytes (of the object) + con.write_lowlevel(bytes.as_bytes()).await?; + // Now write another LF + con.write_lowlevel(&[b'\n']).await?; + Ok(()) + } + Box::pin(write_bytes(con, self)) + } +} + impl Writable for BytesWrapper { fn write<'s>( self, @@ -231,3 +259,30 @@ impl Writable for u64 { Box::pin(write_bytes(con, self)) } } + +impl Writable for ObjectID { + fn write<'s>( + self, + con: &'s mut impl IsConnection, + ) -> Pin> + Send + Sync + 's)>> { + async fn write_bytes(con: &mut impl IsConnection, bytes: ObjectID) -> Result<(), IoError> { + // First write a `+` character to the stream since this is a + // string (we represent `String`s as `Byte` objects internally) + // and since `Bytes` are effectively `String`s we will append the + // type operator `+` to the stream + con.write_lowlevel(&[b'+']).await?; + // Now get the size of the Bytes object as bytes + let size = Integer64::from(bytes.len()); + // Write this to the stream + con.write_lowlevel(&size).await?; + // Now write a LF character + con.write_lowlevel(&[b'\n']).await?; + // Now write the REAL bytes (of the object) + con.write_lowlevel(&bytes).await?; + // Now write another LF + con.write_lowlevel(&[b'\n']).await?; + Ok(()) + } + Box::pin(write_bytes(con, self)) + } +}