Added initial elements for SQL.

This commit is contained in:
2022-09-27 07:31:59 -04:00
parent dda166ecbe
commit 786c69d840
7 changed files with 363 additions and 533 deletions

View File

@ -1,3 +1,7 @@
extern crate pest;
#[macro_use]
extern crate pest_derive;
use tide::{
http::StatusCode,
sessions::{MemoryStore, SessionMiddleware},

View File

@ -1,195 +1,90 @@
use std::{error::Error, fmt, sync::Arc};
use std::{error::Error, fmt};
#[derive(Debug)]
pub enum MTTError {
Generic(Generic),
pub struct DBError {
msg: String,
src: Option<Box<dyn Error + 'static>>,
}
impl MTTError {
pub fn new<S>(detail: S) -> Self
where
S: Into<String>,
{
Generic::new(detail).into()
}
pub fn add_source<E>(&mut self, source: E)
where
E: Into<MTTError>,
{
match self {
MTTError::Generic(err) => err.add_source(source),
}
}
}
impl Error for MTTError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
MTTError::Generic(err) => err.source(),
}
}
}
impl fmt::Display for MTTError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MTTError::Generic(err) => write!(f, "{}", err),
}
}
}
impl From<Generic> for MTTError {
fn from(err: Generic) -> Self {
MTTError::Generic(err)
}
}
#[derive(Debug)]
pub struct Generic {
detail: String,
source: Option<Arc<MTTError>>,
}
impl Generic {
fn new<S>(detail: S) -> Self
impl DBError {
pub fn new<S>(msg: S) -> Self
where
S: Into<String>,
{
Self {
detail: detail.into(),
source: None,
msg: msg.into(),
src: None,
}
}
fn add_source<E>(&mut self, source: E)
pub fn add_source<E>(&mut self, src: E)
where
E: Into<MTTError>,
E: Error + 'static,
{
self.source = Some(Arc::new(source.into()));
self.src = Some(Box::new(src));
}
}
impl Error for Generic {
impl Error for DBError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.source {
match &self.src {
Some(err) => Some(err.as_ref()),
None => None,
}
}
}
impl fmt::Display for Generic {
impl fmt::Display for DBError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.detail)
write!(f, "{}", self.msg)
}
}
#[cfg(test)]
mod mtterror {
mod create {
use super::*;
#[test]
fn create_with_str() {
let detail = "Something";
let err = MTTError::new(detail);
fn with_str() {
let msg = "something happened";
let err = DBError::new(msg);
assert!(
err.to_string() == detail,
"\n\nGot: {}\nWant: {}\n\n",
err.to_string() == msg,
"Got: {} -- Want: {}",
err.to_string(),
detail
msg
);
assert!(
err.source().is_none(),
"Error source should initialoze to None."
"Error should initialize with no source."
);
}
#[test]
fn create_with_string() {
let detail = "massive".to_string();
let err = MTTError::new(detail.clone());
fn with_string() {
let msg = "it went boom".to_string();
let err = DBError::new(msg.clone());
assert!(
err.to_string() == detail,
"\n\nGot: {}\nWant: {}\n\n",
err.to_string() == msg,
"Got: {} -- Want: {}",
err.to_string(),
detail
msg
);
assert!(
err.source().is_none(),
"Error should initialize with no source."
);
}
#[test]
fn with_source() {
let mut err = MTTError::new("the error");
let detail = "This is the cause";
let src = MTTError::new(detail);
err.add_source(src);
assert!(
err.source().unwrap().to_string() == detail,
"/n/nGot: {}\nWant: {}\n\n",
err.source().unwrap().to_string(),
detail
);
}
}
#[cfg(test)]
mod generic {
use super::*;
#[test]
fn create_with_str() {
let detail = "new error";
let err = Generic::new(detail);
assert!(
err.to_string() == detail,
"\n\nGot: {}\nWant: {}\n\n",
err.to_string(),
detail
);
assert!(
err.source().is_none(),
"Error source should initialoze to None."
);
let error: MTTError = err.into();
assert!(
error.to_string() == detail,
"\n\nGot: {}\nWant: {}\n\n",
error.to_string(),
detail
);
}
#[test]
fn create_with_string() {
let detail = "some error".to_string();
let err = Generic::new(detail.clone());
assert!(
err.to_string() == detail,
"\n\nGot: {}\nWant: {}\n\n",
err.to_string(),
detail
);
}
#[test]
fn with_source() {
let par_detail = "parent error";
let cld_detail = "child error";
let par_err = Generic::new(par_detail);
let mut cld_err = Generic::new(cld_detail);
cld_err.add_source(par_err);
assert!(
cld_err.source().unwrap().to_string() == par_detail,
"/n/nGot: {}\nWant: {}\n\n",
cld_err.source().unwrap().to_string(),
par_detail
);
let error: MTTError = cld_err.into();
assert!(
error.source().unwrap().to_string() == par_detail,
"/n/nGot: {}\nWant: {}\n\n",
error.source().unwrap().to_string(),
par_detail
);
let msg = "but this caused the problem";
let mut par = DBError::new("parent error");
let src = DBError::new(msg);
par.add_source(src);
let output = par.source();
assert!(output.is_some(), "Should return source.");
let source = output.unwrap();
assert!(source.to_string() == msg);
}
}

View File

@ -1,120 +1,183 @@
pub mod error;
pub mod fieldtype;
use async_std::sync::{Arc, RwLock};
use error::MTTError;
use error::DBError;
use pest::Parser;
use std::collections::HashMap;
enum Ast {
Script,
Command,
Action,
Object,
Name,
}
#[derive(Parser)]
#[grammar = "morethantext/mttsql.pest"]
struct MTTSQL;
#[derive(Clone)]
pub struct MoreThanText {
tables: Arc<RwLock<HashMap<String, Table>>>,
databases: Arc<RwLock<HashMap<String, Database>>>,
}
impl MoreThanText {
pub async fn new() -> Self {
Self {
tables: Arc::new(RwLock::new(HashMap::new())),
databases: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn new_table<S>(&self, tname: S) -> Result<Table, MTTError>
where
S: Into<String>,
{
let mut tables = self.tables.write().await;
let name = tname.into();
match tables.get(&name) {
Some(_) => Err(MTTError::new(format!("table {} already exists", name))),
None => {
let table = Table::new().await;
tables.insert(name, table.clone());
Ok(table)
pub async fn execute(&self, script: &str) -> Result<(), DBError> {
match MTTSQL::parse(Rule::file, script) {
Ok(mut commands) => {
let pair = commands.next().unwrap();
match pair.as_rule() {
Rule::script => Ast::Script,
Rule::command => Ast::Command,
Rule::action => Ast::Action,
Rule::object => Ast::Object,
Rule::name => Ast::Name,
Rule::char | Rule::whitespace | Rule::file | Rule::EOI => unreachable!(),
};
Ok(())
}
Err(err) => {
let mut error = DBError::new("script parsing error");
error.add_source(err);
Err(error)
}
}
}
pub async fn get_table(&self, name: &str) -> Option<Table> {
let tables = self.tables.read().await;
match tables.get(name) {
Some(tbl) => Some(tbl.clone()),
None => None,
async fn create_database(&self, name: &str) -> Result<(), DBError> {
let mut databases = self.databases.write().await;
match databases.get(name) {
Some(_) => Err(DBError::new("duplicate database name")),
None => {
let db = Database::new().await;
databases.insert(name.to_string(), db);
Ok(())
}
}
}
async fn use_database(&self, name: &str) -> Result<(), DBError> {
let databases = self.databases.read().await;
match databases.get(name) {
Some(_) => Ok(()),
None => Err(DBError::new("database name not found")),
}
}
}
#[derive(Clone)]
pub struct Table;
struct Database;
impl Table {
pub async fn new() -> Self {
impl Database {
async fn new() -> Self {
Self {}
}
async fn new_column(&self, _name: &str, _type: &str) {}
}
#[cfg(test)]
mod database {
mod engine_functions {
use super::*;
#[async_std::test]
async fn create_table_with_str() {
let db = MoreThanText::new().await;
db.new_table("william").await.unwrap();
async fn create_database() {
let mtt = MoreThanText::new().await;
mtt.create_database("smith").await.unwrap();
}
#[async_std::test]
async fn create_table_with_string() {
let db = MoreThanText::new().await;
db.new_table("marvin".to_string()).await.unwrap();
}
#[async_std::test]
async fn table_names_are_unique() -> Result<(), String> {
let db = MoreThanText::new().await;
let name = "alexandar";
let msg = format!("table {} already exists", name);
db.new_table(name).await.unwrap();
match db.new_table(name).await {
Ok(_) => Err("Duplicate table names are not allowed.".to_string()),
async fn database_names_must_be_unique() -> Result<(), DBError> {
let mtt = MoreThanText::new().await;
let msg = "duplicate database name";
mtt.create_database("john").await.unwrap();
match mtt.create_database("john").await {
Ok(_) => Err(DBError::new("Duplicate names should cause error")),
Err(err) => {
if err.to_string() == msg {
Ok(())
} else {
Err(format!(
"Error message is incorrect: Got: '{}' Want: '{}'",
Err(DBError::new(format!(
"incorrect err message: got: '{}' want: '{}'",
err.to_string(),
msg
))
)))
}
}
}
}
#[async_std::test]
async fn get_non_existant_table() {
let db = MoreThanText::new().await;
let table = db.get_table("missing").await;
assert!(table.is_none(), "There should be no table.");
async fn use_database() -> Result<(), DBError> {
let mtt = MoreThanText::new().await;
let dbname = "Johnson";
mtt.create_database(dbname).await.unwrap();
mtt.use_database(dbname).await.unwrap();
Ok(())
}
#[async_std::test]
async fn get_a_table() {
let db = MoreThanText::new().await;
let name = "here";
db.new_table(name).await.unwrap();
let table = db.get_table(name).await;
assert!(table.is_some(), "Table should be found.");
async fn use_missing_database() -> Result<(), DBError> {
let error = "database name not found";
let mtt = MoreThanText::new().await;
match mtt.use_database("ssmith").await {
Ok(_) => Err(DBError::new("Should raise database missing error")),
Err(err) => {
if err.to_string() == error {
Ok(())
} else {
Err(DBError::new(format!(
"Incorrect error message: Got '{}' Want '{}'",
err.to_string(),
error
)))
}
}
}
}
}
#[cfg(test)]
mod table {
mod database_functions {
use super::*;
#[async_std::test]
async fn add_column() {
let tbl = Table::new().await;
tbl.new_column("fred", "StaticString").await;
async fn new_database() {
Database::new().await;
}
}
#[cfg(test)]
mod mtt_commands {
use super::*;
#[async_std::test]
async fn create_database() {
let mtt = MoreThanText::new().await;
mtt.execute("create database fred;").await.unwrap();
}
#[async_std::test]
async fn unsuccessful_parse() -> Result<(), DBError> {
let msg = "script parsing error";
let mtt = MoreThanText::new().await;
match mtt.execute("#$%^&").await {
Ok(_) => Err(DBError::new("Should show a parse failure.")),
Err(err) => {
if err.to_string() == msg {
Ok(())
} else {
Err(DBError::new(format!(
"Error message is incorrect: Got: '{}' Want: '{}'",
err.to_string(),
msg
)))
}
}
}
}
}

View File

@ -0,0 +1,8 @@
whitespace = _{" " | "\t" | "\r" | "\n"}
action = {"create"}
object = {"database"}
char = _{ ASCII_ALPHANUMERIC | "_" }
name = {char+}
command = {whitespace* ~ action ~ whitespace+ ~ object ~ whitespace+ ~ name ~ whitespace* ~ ";" ~ whitespace*}
script = {command+}
file = _{SOI ~ script ~ EOI}

View File

@ -0,0 +1,120 @@
pub mod error;
pub mod fieldtype;
use async_std::sync::{Arc, RwLock};
use error::MTTError;
use std::collections::HashMap;
#[derive(Clone)]
pub struct MoreThanText {
tables: Arc<RwLock<HashMap<String, Table>>>,
}
impl MoreThanText {
pub async fn new() -> Self {
Self {
tables: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn new_table<S>(&self, tname: S) -> Result<Table, MTTError>
where
S: Into<String>,
{
let mut tables = self.tables.write().await;
let name = tname.into();
match tables.get(&name) {
Some(_) => Err(MTTError::new(format!("table {} already exists", name))),
None => {
let table = Table::new().await;
tables.insert(name, table.clone());
Ok(table)
}
}
}
pub async fn get_table(&self, name: &str) -> Option<Table> {
let tables = self.tables.read().await;
match tables.get(name) {
Some(tbl) => Some(tbl.clone()),
None => None,
}
}
}
#[derive(Clone)]
pub struct Table;
impl Table {
pub async fn new() -> Self {
Self {}
}
async fn new_column(&self, _name: &str, _type: &str) {}
}
#[cfg(test)]
mod database {
use super::*;
#[async_std::test]
async fn create_table_with_str() {
let db = MoreThanText::new().await;
db.new_table("william").await.unwrap();
}
#[async_std::test]
async fn create_table_with_string() {
let db = MoreThanText::new().await;
db.new_table("marvin".to_string()).await.unwrap();
}
#[async_std::test]
async fn table_names_are_unique() -> Result<(), String> {
let db = MoreThanText::new().await;
let name = "alexandar";
let msg = format!("table {} already exists", name);
db.new_table(name).await.unwrap();
match db.new_table(name).await {
Ok(_) => Err("Duplicate table names are not allowed.".to_string()),
Err(err) => {
if err.to_string() == msg {
Ok(())
} else {
Err(format!(
"Error message is incorrect: Got: '{}' Want: '{}'",
err.to_string(),
msg
))
}
}
}
}
#[async_std::test]
async fn get_non_existant_table() {
let db = MoreThanText::new().await;
let table = db.get_table("missing").await;
assert!(table.is_none(), "There should be no table.");
}
#[async_std::test]
async fn get_a_table() {
let db = MoreThanText::new().await;
let name = "here";
db.new_table(name).await.unwrap();
let table = db.get_table(name).await;
assert!(table.is_some(), "Table should be found.");
}
}
#[cfg(test)]
mod table {
use super::*;
#[async_std::test]
async fn add_column() {
let tbl = Table::new().await;
tbl.new_column("fred", "StaticString").await;
}
}