From 45522660bb27eedb6fd95b3ea9f5e7bfa2a10055 Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Thu, 1 Feb 2024 20:40:29 -0500 Subject: [PATCH] Where I left off. --- src/morethantext/fieldtype/mod.rs | 89 +++++ src/morethantext/fieldtype/static_string.rs | 50 +++ src/morethantext/graphql.rs | 179 +++++++++ src/morethantext/mttsql.pest | 6 + src/morethantext/old-mod.rs | 398 ++++++++++++++++++++ src/morethantext/old-mod2.rs | 120 ++++++ src/morethantext/old-mod3.rs | 153 ++++++++ 7 files changed, 995 insertions(+) create mode 100644 src/morethantext/fieldtype/mod.rs create mode 100644 src/morethantext/fieldtype/static_string.rs create mode 100644 src/morethantext/graphql.rs create mode 100644 src/morethantext/mttsql.pest create mode 100644 src/morethantext/old-mod.rs create mode 100644 src/morethantext/old-mod2.rs create mode 100644 src/morethantext/old-mod3.rs diff --git a/src/morethantext/fieldtype/mod.rs b/src/morethantext/fieldtype/mod.rs new file mode 100644 index 0000000..4a6f959 --- /dev/null +++ b/src/morethantext/fieldtype/mod.rs @@ -0,0 +1,89 @@ +mod static_string; + +use crate::morethantext::error::MTTError; +use static_string::StaticString; +use std::fmt; + +pub enum FieldType { + StaticString(StaticString), +} + +impl FieldType { + fn new(ftype: &str, data: &str) -> Result { + let field = match ftype { + "StaticString" => StaticString::new(data), + _ => Err(MTTError::new(format!( + "field type {} does not exist", + ftype + ))), + }; + match field { + Ok(fld) => Ok(fld.into()), + Err(e) => Err(e), + } + } +} + +impl fmt::Display for FieldType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FieldType::StaticString(data) => write!(f, "{}", data), + } + } +} + +impl From for FieldType { + fn from(data: StaticString) -> Self { + FieldType::StaticString(data) + } +} + +#[cfg(test)] +mod converstion { + use super::*; + + #[test] + fn from_static_string() { + let data = "a static string"; + let field = StaticString::new(data).unwrap(); + let ftype: FieldType = field.into(); + assert!( + ftype.to_string() == data, + "\n\nGot: {}\nWant: {}", + ftype.to_string(), + data + ); + } + + #[test] + fn bad_field_type() -> Result<(), String> { + let field_type = "dragon"; + let err_msg = format!("field type {} does not exist", field_type); + match FieldType::new(field_type, "marmalade") { + Ok(_) => Err("Should have returned an error.".to_string()), + Err(err) => { + if err.to_string() == err_msg { + Ok(()) + } else { + Err(format!( + "Error message is incorrect: Got: '{}' Want: '{}'", + err.to_string(), + err_msg + )) + } + } + } + } + + #[test] + fn new_static_string() { + let data = "This is a test."; + let field = FieldType::new("StaticString", data).unwrap(); + assert!( + field.to_string() == data, + "\n\nGot: {}\nWant: {}\n\n", + field.to_string(), + data + ); + } +} diff --git a/src/morethantext/fieldtype/static_string.rs b/src/morethantext/fieldtype/static_string.rs new file mode 100644 index 0000000..8f054c6 --- /dev/null +++ b/src/morethantext/fieldtype/static_string.rs @@ -0,0 +1,50 @@ +use crate::morethantext::error::MTTError; +use std::fmt; + +pub struct StaticString { + data: String, +} + +impl StaticString { + pub fn new(name: S) -> Result + where + S: Into, + { + Ok(Self { data: name.into() }) + } +} + +impl fmt::Display for StaticString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.data) + } +} + +#[cfg(test)] +mod creation { + use super::*; + + #[test] + fn new_accepts_str() { + let data = "some data"; + let field = StaticString::new(data).unwrap(); + assert!( + field.to_string() == data, + "\n\nGot: {}\nWant: {}", + field.to_string(), + data + ); + } + + #[test] + fn new_accepts_string() { + let data = "actual string"; + let field = StaticString::new(data.to_string()).unwrap(); + assert!( + field.to_string() == data, + "\n\nGot: {}\nWant: {}", + field.to_string(), + data + ); + } +} diff --git a/src/morethantext/graphql.rs b/src/morethantext/graphql.rs new file mode 100644 index 0000000..67ea004 --- /dev/null +++ b/src/morethantext/graphql.rs @@ -0,0 +1,179 @@ +use async_graphql::{Context, EmptySubscription, Error, Object, Result, Schema}; +use async_std::sync::RwLock; +use serde_json; + +mod database; + +#[derive(Clone)] +struct Table { + name: String, +} + +impl Table { + async fn new(name: String) -> Self { + Self { name: name } + } +} + +#[Object] +impl Table { + async fn name(&self) -> String { + self.name.to_string() + } + + async fn describe(&self) -> Vec { + Vec::new() + } +} + +struct Query; + +#[Object] +impl Query { + async fn table(&self, ctx: &Context<'_>, name: String) -> Result> { + let tbls = ctx + .data::>>() + .unwrap() + .read() + .await + .to_vec(); + match tbls.binary_search_by(|t| t.name.cmp(&name)) { + Ok(idx) => Ok(Some(tbls[idx].clone())), + Err(_) => Ok(None), + } + } + + async fn tables(&self, ctx: &Context<'_>) -> Vec { + ctx.data::>>() + .unwrap() + .read() + .await + .to_vec() + } +} + +struct Mutation; + +#[Object] +impl Mutation { + async fn create_table(&self, ctx: &Context<'_>, name: String) -> Result> { + let mut tables = ctx.data::>>().unwrap().write().await; + match tables.binary_search_by(|t| t.name.cmp(&name)) { + Ok(_) => Err(Error::new(format!("Table {} already exists.", &name))), + Err(_) => { + let output = Table::new(name).await; + tables.push(output.clone()); + tables.sort_by_key(|k| k.name.clone()); + Ok(Some(output)) + } + } + } +} + +#[derive(Clone)] +pub struct MoreThanText { + schema: Schema, +} + +impl MoreThanText { + pub fn new() -> Self { + let tables: Vec
= Vec::new(); + Self { + schema: Schema::build(Query, Mutation, EmptySubscription) + .data(RwLock::new(tables)) + .finish(), + } + } + + pub async fn execute(&self, qry: &str) -> String { + let res = self.schema.execute(qry).await; + serde_json::to_string(&res).unwrap() + } +} + +#[cfg(test)] +mod support { + use super::*; + + pub fn compare(db: &MoreThanText, output: &str, expected: &str) { + assert!( + output == expected, + "\n\n{}\nGot: {}\nWant: {}\n\n", + db.schema.sdl(), + output, + expected + ); + } +} + +#[cfg(test)] +mod queries { + use super::*; + + #[async_std::test] + async fn list_table() { + let db = MoreThanText::new(); + db.execute(r#"mutation {createTable(name: "wilma"){name}}"#) + .await; + db.execute(r#"mutation {createTable(name: "betty"){name}}"#) + .await; + let output = db.execute(r#"{table(name: "wilma"){name}}"#).await; + let expected = r#"{"data":{"table":{"name":"wilma"}}}"#; + support::compare(&db, &output, &expected); + } + + #[async_std::test] + async fn list_no_table() { + let db = MoreThanText::new(); + let output = db.execute(r#"{table(name: "slade"){name}}"#).await; + let expected = r#"{"data":{"table":null}}"#; + support::compare(&db, &output, &expected); + } + + #[async_std::test] + async fn list_tables() { + let db = MoreThanText::new(); + db.execute(r#"mutation {createTable(name: "fred"){name}}"#) + .await; + db.execute(r#"mutation {createTable(name: "barney"){name}}"#) + .await; + let output = db.execute(r#"{tables{name}}"#).await; + let expected = r#"{"data":{"tables":[{"name":"barney"},{"name":"fred"}]}}"#; + support::compare(&db, &output, &expected); + } + + #[async_std::test] + async fn empty_table_description() { + let db = MoreThanText::new(); + let output = db + .execute(r#"mutation {createTable(name: "pebbles"){name describe}}"#) + .await; + let expected = r#"{"data":{"createTable":{"name":"pebbles","describe":[]}}}"#; + support::compare(&db, &output, &expected); + } +} + +#[cfg(test)] +mod mutations { + use super::*; + + #[async_std::test] + async fn add_table() { + let db = MoreThanText::new(); + let output = db + .execute(r#"mutation {createTable(name: "william"){name}}"#) + .await; + let expected = r#"{"data":{"createTable":{"name":"william"}}}"#; + support::compare(&db, &output, &expected); + } + + #[async_std::test] + async fn cannot_add_duplicate_table() { + let db = MoreThanText::new(); + let qry = r#"mutation {createTable(name: "gadzoo"){name}}"#; + db.execute(&qry).await; + let output = db.execute(qry).await; + let expected = r#"{"data":null,"errors":[{"message":"Table gadzoo already exists.","locations":[{"line":1,"column":11}],"path":["createTable"]}]}"#; + support::compare(&db, &output, &expected); + } +} diff --git a/src/morethantext/mttsql.pest b/src/morethantext/mttsql.pest new file mode 100644 index 0000000..f16b4b7 --- /dev/null +++ b/src/morethantext/mttsql.pest @@ -0,0 +1,6 @@ +char = _{ ASCII_ALPHANUMERIC | "_" } +whitespace = _{" " | "\t" | "\r" | "\n"} + +name = {char+} +command = {"create database" ~ whitespace+ ~ name ~ ";"} +script = {command+} diff --git a/src/morethantext/old-mod.rs b/src/morethantext/old-mod.rs new file mode 100644 index 0000000..67e4ae2 --- /dev/null +++ b/src/morethantext/old-mod.rs @@ -0,0 +1,398 @@ +/* +use async_std::sync::{Arc, RwLock}; +use std::{collections::HashMap, error::Error, fmt, str::FromStr}; + +#[derive(Debug)] +pub struct DBError { + detail: String, + source: Option>, +} + +impl DBError { + fn new(detail: String) -> Self { + Self { + detail: detail.to_string(), + source: None, + } + } +} + +impl fmt::Display for DBError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.detail) + } +} + +impl Error for DBError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.source { + Some(err) => Some(err), + None => None, + } + } +} + +#[derive(Clone, PartialEq)] +pub enum FieldType { + Table, +} + +impl fmt::Display for FieldType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FieldType::Table => write!(f, "table"), + } + } +} + +impl FromStr for FieldType { + type Err = DBError; + + fn from_str(input: &str) -> Result { + match input { + "table" => Ok(FieldType::Table), + _ => Err(DBError::new(format!("field type {} does not exist", input))), + } + } +} + +pub struct Table { + fields: Arc>>, +} + +impl Table { + pub async fn new() -> Self { + Self { + fields: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub async fn update_field(&self, name: &str, ftype: &str) -> Result<(), Box> { + let ftype = match FieldType::from_str(ftype) { + Ok(field) => field, + Err(err) => { + let mut error = DBError::new(format!("failed to add field {}", name)); + error.source = Some(Box::new(err)); + return Err(Box::new(error)); + } + }; + let mut fmap = self.fields.write().await; + match fmap.get(name) { + Some(_) => Err(Box::new(DBError::new(format!( + "field {} already exists", + name + )))), + None => { + fmap.insert(name.to_string(), ftype); + Ok(()) + } + } + } + + pub async fn fields(&self) -> HashMap { + let fmap = self.fields.read().await; + fmap.clone() + } +} +*/ +use async_std::sync::{Arc, RwLock}; +use std::collections::HashMap; + +pub mod error; +mod fieldtype; + +use error::MTTError; +use fieldtype::FieldType; + +#[derive(Clone)] +pub struct MoreThanText; + +impl MoreThanText { + pub async fn new() -> Self { + Self {} + } + + pub async fn add_table(&self, name: &str) -> Table { + Table::new() + } + + pub async fn get_table(&self, name: &str) -> Table { + Table::new() + } +} + +#[derive(Clone, PartialEq)] +struct FieldDef; + +pub struct Table { + fields: Arc>>, +} + +impl Table { + fn new() -> Self { + Self { + fields: Arc::new(RwLock::new(HashMap::new())), + } + } + + async fn add_field(&self, name: &str) { + let mut field_defs = self.fields.write().await; + field_defs.insert(name.to_string(), FieldDef {}); + } + + async fn get_field(&self, name: &str) -> Option { + let field_defs = self.fields.read().await; + match field_defs.get(name) { + Some(def) => Some(def.clone()), + None => None, + } + } + + async fn new_record(&self) -> Record { + Record::new() + } +} + +struct Record { + data: Arc>>, +} + +impl Record { + fn new() -> Self { + Self { + data: Arc::new(RwLock::new(HashMap::new())), + } + } + + /* + async fn update_field(&self, name: String, data: FieldType) { + let mut map = self.data.write().await; + map.insert(name, data); + } + + async fn get_field(&self, name: &str) -> Option { + let map = self.data.read().await; + match map.get(name) { + Some(field) => Some(field.clone()), + None => None, + } + } + */ +} + +#[cfg(test)] +mod databases { + use super::*; + + #[async_std::test] + async fn new_database() { + MoreThanText::new().await; + } + + async fn add_table() { + let db = MoreThanText::new().await; + let name = "table"; + db.add_table(name).await; + db.get_table(name).await; + } +} + +#[cfg(test)] +mod tables { + use super::*; + + #[test] + fn new_table() { + Table::new(); + } + + #[async_std::test] + async fn add_field_definition() { + let tbl = Table::new(); + let name = "field"; + let expected = FieldDef {}; + tbl.add_field(name).await; + let output = tbl.get_field(name).await.unwrap(); + assert!(output == expected, "Did not return a field definition."); + } + + #[async_std::test] + async fn missing_field_definition() { + let tbl = Table::new(); + let output = tbl.get_field("missing").await; + assert!( + output == None, + "Should return None if field does not exist." + ); + } + + #[async_std::test] + async fn get_empty_record() { + let tbl = Table::new(); + tbl.new_record().await; + } +} + +#[cfg(test)] +mod records { + use super::*; + + /* + #[async_std::test] + async fn update_fields() { + let rec = Record::new(); + let name = "elephant"; + let data = ""; + let sstr = StaticString::new(); + rec.update_field(name.to_string(), sstr).await; + let output = rec.get_field(name).await.unwrap(); + assert!( + output.to_string() == data, + "\n\nGot: {}\nWant: {}\n\n", + output.to_string(), + data + ) + } + + #[async_std::test] + async fn empty_field() { + let rec = Record::new(); + let name = "mull"; + let output = rec.get_field(name).await; + assert!(output == None, "Should return an option."); + } + */ +} + +/* +#[cfg(test)] +mod tables { + use super::*; + + #[async_std::test] + async fn new_table() { + Table::new().await; + } + + #[async_std::test] + async fn update_field() { + let table = Table::new().await; + let mut expected: HashMap = HashMap::new(); + expected.insert("stan".to_string(), FieldType::Table); + expected.insert("lee".to_string(), FieldType::Table); + table.update_field("stan", "table").await.unwrap(); + table.update_field("lee", "table").await.unwrap(); + let output = table.fields().await; + assert!(output == expected, "Table did not get the fields added."); + } + + #[async_std::test] + async fn add_bad_field() -> Result<(), String> { + let table = Table::new().await; + let name = "failure"; + let bad_type = "ljksdbtt"; + let expected = format!("failed to add field {}", name); + let source = format!("field type {} does not exist", bad_type); + match table.update_field(name, bad_type).await { + Ok(_) => Err("A bad field type should not return successfully".to_string()), + Err(err) => { + if format!("{}", err) != expected { + Err(format!("Got: '{}' - Want: '{}'", err, expected)) + } else if format!("{}", err.source().unwrap()) != source { + Err(format!( + "Got: '{}' - Want: '{}'", + err.source().unwrap(), + source + )) + } else { + Ok(()) + } + } + } + } + + #[async_std::test] + async fn add_duplicate_field() -> Result<(), String> { + let table = Table::new().await; + let name = "twice"; + let expected = format!("field {} already exists", name); + table.update_field(name, "table").await.unwrap(); + match table.update_field(name, "table").await { + Ok(_) => Err(format!("Cannot have two fields with named '{}'", name)), + Err(err) => { + if format!("{}", err) == expected { + Ok(()) + } else { + Err(format!("Got: '{}' - Want: '{}'", err, expected)) + } + } + } + } +} + +#[cfg(test)] +mod databases { + use super::*; + + #[async_std::test] + async fn new_database() { + MoreThanText::new().await; + } + + #[async_std::test] + async fn add_table() { + let db = MoreThanText::new().await; + db.add_table("fred".to_string()).await; + } +} + +#[cfg(test)] +mod fieldtypes { + use super::*; + + fn get_field_map() -> HashMap { + let mut fields: HashMap = HashMap::new(); + fields.insert("table".to_string(), FieldType::Table); + return fields; + } + + #[test] + fn convert_to_string() { + for (key, value) in get_field_map().iter() { + assert!( + key == &value.to_string(), + "\n\nGot: {}\nWant: {}\n\n", + value.to_string(), + key + ); + } + } + + #[test] + fn convert_from_string() { + for (key, value) in get_field_map().iter() { + assert!( + &FieldType::from_str(key).unwrap() == value, + "\n\nDid not return a FieldType::{}", + key + ); + } + } + + #[test] + fn convert_from_string_error() -> Result<(), String> { + let ftype = "lkjsdfh"; + let expected = format!("field type {} does not exist", ftype); + match FieldType::from_str(ftype) { + Ok(_) => Err(format!("Found field type {}", ftype)), + Err(err) => { + if format!("{}", err) == expected { + Ok(()) + } else { + Err(format!("Got: '{}' - Want: '{}'", err, expected)) + } + } + } + } +} +*/ diff --git a/src/morethantext/old-mod2.rs b/src/morethantext/old-mod2.rs new file mode 100644 index 0000000..9012066 --- /dev/null +++ b/src/morethantext/old-mod2.rs @@ -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>>, +} + +impl MoreThanText { + pub async fn new() -> Self { + Self { + tables: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub async fn new_table(&self, tname: S) -> Result + where + S: Into, + { + 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
{ + 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; + } +} diff --git a/src/morethantext/old-mod3.rs b/src/morethantext/old-mod3.rs new file mode 100644 index 0000000..0e8f33a --- /dev/null +++ b/src/morethantext/old-mod3.rs @@ -0,0 +1,153 @@ +pub mod error; + +use async_std::sync::{Arc, RwLock}; +use error::DBError; +use std::collections::HashMap; + +#[derive(Clone)] +pub struct MoreThanText { + databases: Arc>>, +} + +impl MoreThanText { + pub async fn new() -> Self { + Self { + databases: Arc::new(RwLock::new(HashMap::new())), + } + } + + 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 { + let databases = self.databases.read().await; + match databases.get(name) { + Some(db) => Ok(db.clone()), + None => Err(DBError::new("database name not found")), + } + } +} + +#[derive(Clone)] +struct Database; + +impl Database { + async fn new() -> Self { + Self {} + } + + async fn add_table(&self, _name: &str) {} +} + +struct Table; + +impl Table { + async fn new() -> Self { + Self {} + } +} + +#[cfg(test)] +mod engine_functions { + use super::*; + + #[async_std::test] + async fn create_database() { + let mtt = MoreThanText::new().await; + mtt.create_database("smith").await.unwrap(); + } + + #[async_std::test] + 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(DBError::new(format!( + "incorrect err message: got: '{}' want: '{}'", + err.to_string(), + msg + ))) + } + } + } + } + + #[async_std::test] + 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 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 + ))) + } + } + } + } + + #[async_std::test] + async fn create_get_table() { + let db = "thedatabase"; + let mtt = MoreThanText::new().await; + mtt.create_database(db).await.unwrap(); + let dbase = mtt.use_database(db).await.unwrap(); + dbase.add_table("melvin").await; + } +} + +#[cfg(test)] +mod database_functions { + use super::*; + + #[async_std::test] + async fn new_database() { + Database::new().await; + } + + #[async_std::test] + async fn new_table() { + let db = Database::new().await; + db.add_table("fred").await; + } +} + +#[cfg(test)] +mod table_functions { + use super::*; + + #[async_std::test] + async fn new_table() { + Table::new().await; + } +}