From f9fb8f0670407cad8913fe40cff9efaaad03126d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Mon, 22 Jan 2024 11:18:43 +0100 Subject: [PATCH 01/21] feat: add server cli configuration --- Cargo.lock | 204 ++++++++++++++++++++++++++++++++++++++++--- server/Cargo.toml | 8 +- server/src/config.rs | 28 ++++++ server/src/main.rs | 9 +- 4 files changed, 234 insertions(+), 15 deletions(-) create mode 100644 server/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 8b89a08..31287bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,54 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.76" @@ -107,6 +155,46 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "client" version = "0.1.0" @@ -116,12 +204,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -174,7 +274,7 @@ checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -216,7 +316,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -299,6 +399,8 @@ name = "server" version = "0.1.0" dependencies = [ "anyhow", + "clap", + "minisql", "proto", "tokio", ] @@ -325,9 +427,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.41" @@ -375,7 +483,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -395,6 +503,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "virtue" version = "0.0.13" @@ -413,7 +527,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -422,13 +545,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -437,38 +575,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/server/Cargo.toml b/server/Cargo.toml index bca61ec..0dd40ff 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,6 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.35.1", features = ["full"] } anyhow = "1.0.76" -proto = { path = "../proto" } \ No newline at end of file +clap = { version = "4.4.18", features = ["derive"] } +tokio = { version = "1.35.1", features = ["full"] } +minisql = { path = "../minisql" } +proto = { path = "../proto" } + + diff --git a/server/src/config.rs b/server/src/config.rs new file mode 100644 index 0000000..68ae54b --- /dev/null +++ b/server/src/config.rs @@ -0,0 +1,28 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::PathBuf; +use clap::Parser; + +const LOCAL_IPV4: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); + +#[derive(Debug, Parser)] +#[command(author, version, about)] +pub struct Configuration { + #[arg(short, long, default_value_t = LOCAL_IPV4, help = "IP address for the server to listen on")] + address: IpAddr, + #[arg(short, long, default_value = "5432", help = "Port for the server to listen on")] + port: u16, + #[arg(short, long, help = "Path to the data file")] + file: PathBuf, +} + +impl Configuration { + #[inline] + pub fn get_socket_address(&self) -> SocketAddr { + SocketAddr::new(self.address, self.port) + } + + #[inline] + pub fn get_file_path(&self) -> &PathBuf { + &self.file + } +} \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index bda6dfd..f0b8084 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,7 @@ +mod config; + +use std::net::SocketAddr; +use clap::Parser; use proto::handshake::response::HandshakeResponse; use proto::handshake::server::do_server_handshake; use proto::message::backend::{ @@ -11,10 +15,13 @@ use proto::writer::backend::BackendProtoWriter; use proto::writer::protowriter::{ProtoFlush, ProtoWriter}; use tokio::io::{BufReader, BufWriter}; use tokio::net::{TcpListener, TcpStream}; +use crate::config::Configuration; #[tokio::main] async fn main() -> anyhow::Result<()> { - let addr = "0.0.0.0:5432"; + let config = Configuration::parse(); + + let addr = config.get_socket_address(); let listener = TcpListener::bind(&addr).await?; println!("Server started at {addr}"); From ebabf502913b0d22737512690ba0ab9ce581108f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Mon, 22 Jan 2024 21:59:39 +0100 Subject: [PATCH 02/21] feat: support for cancel requests in handshake --- proto/src/handshake/errors.rs | 3 +++ proto/src/handshake/server.rs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/proto/src/handshake/errors.rs b/proto/src/handshake/errors.rs index 0811790..cd2a8c4 100644 --- a/proto/src/handshake/errors.rs +++ b/proto/src/handshake/errors.rs @@ -4,6 +4,7 @@ use crate::reader::errors::{ProtoConsumeError, ProtoPeekError, ProtoReadError}; use crate::writer::errors::ProtoWriteError; use thiserror::Error; use tokio::io; +use crate::message::special::CancelRequestData; #[derive(Debug, Error)] pub enum ClientHandshakeError { @@ -23,6 +24,8 @@ pub enum ClientHandshakeError { pub enum ServerHandshakeError { #[error("startup message not found")] MissingStartupMessage, + #[error("cancel request found instead of startup message")] + IsCancelRequest(CancelRequestData), #[error("socket communication failed")] Io(#[from] io::Error), #[error("deserialization of inner data failed")] diff --git a/proto/src/handshake/server.rs b/proto/src/handshake/server.rs index 6c8deb2..d1a332f 100644 --- a/proto/src/handshake/server.rs +++ b/proto/src/handshake/server.rs @@ -8,6 +8,7 @@ use crate::writer::backend::BackendProtoWriter; use crate::writer::protowriter::ProtoFlush; /// Performs server-side handshake with the client until ending it with `ReadyForQuery` message. +/// Client can send `CancelRequest` message instead of `StartupMessage` to cancel the request. /// For more info visit the [`55.2.1. Start-up`](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-START-UP) pub async fn do_server_handshake( writer: &mut (impl BackendProtoWriter + ProtoFlush), @@ -27,12 +28,16 @@ pub async fn do_server_handshake( } } - // Wait for mandatory StartupMessage + // Wait for mandatory StartupMessage or CancelRequest let startup_message = match &reader.peek_special_message().await? { Some(msg @ SpecialMessage::StartupMessage(data)) => { reader.consume_special_message(msg).await?; data.clone() } + Some(msg @ SpecialMessage::CancelRequest(data)) => { + reader.consume_special_message(msg).await?; + return Err(ServerHandshakeError::IsCancelRequest(data.clone())); + } _ => { return Err(ServerHandshakeError::MissingStartupMessage); } From 1d746430d236c032225eaaa0e45aaa88af0d5c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Tue, 23 Jan 2024 20:56:07 +0100 Subject: [PATCH 03/21] feat: return schema and column positions with select --- minisql/src/internals/row.rs | 10 +++-- minisql/src/internals/table.rs | 5 ++- minisql/src/interpreter.rs | 76 +++++++++++++++++----------------- minisql/src/lib.rs | 1 + minisql/src/restricted_row.rs | 35 ++++++++++++++++ 5 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 minisql/src/restricted_row.rs diff --git a/minisql/src/internals/row.rs b/minisql/src/internals/row.rs index ad8dc1e..e46dac9 100644 --- a/minisql/src/internals/row.rs +++ b/minisql/src/internals/row.rs @@ -1,6 +1,7 @@ use crate::type_system::Value; use std::ops::{Index, IndexMut}; use std::slice::SliceIndex; +use crate::restricted_row::RestrictedRow; pub type ColumnPosition = usize; @@ -58,14 +59,15 @@ impl Row { self.0.get(column_position) } - pub fn restrict_columns(&self, columns: &Vec) -> Row { + pub fn restrict_columns(&self, columns: &Vec) -> RestrictedRow { // If the index from `columns` is non-existant in `row`, it will just ignore it. - let mut subrow: Row = Row::new(); + let mut subrow: Vec<(ColumnPosition, Value)> = vec![]; for column_position in columns { if let Some(value) = self.get(*column_position) { - subrow.0.push(value.clone()) + subrow.push((*column_position, value.clone())); } } - subrow + + subrow.into() } } diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 2b5a5bc..c9f366c 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -3,6 +3,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use crate::error::Error; use crate::internals::column_index::ColumnIndex; use crate::internals::row::{ColumnPosition, Row}; +use crate::restricted_row::RestrictedRow; use crate::schema::{ColumnName, TableSchema, TableName}; use crate::result::DbResult; use crate::type_system::{IndexableValue, Uuid, Value}; @@ -67,7 +68,7 @@ impl Table { .collect() } - pub fn select_all_rows<'a>(&'a self, selected_column_positions: Vec) -> impl Iterator + 'a { + pub fn select_all_rows<'a>(&'a self, selected_column_positions: Vec) -> impl Iterator + 'a { self.rows .values() .map(move |row| row.restrict_columns(&selected_column_positions)) @@ -78,7 +79,7 @@ impl Table { selected_column_positions: Vec, column_position: ColumnPosition, value: Value, - ) -> DbResult + 'a> { + ) -> DbResult + 'a> { let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_column_positions); match value { Value::Indexable(value) => match self.fetch_ids_from_index(column_position, &value)? { diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 84f2494..805923e 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,11 +1,12 @@ use crate::error::Error; -use crate::internals::row::{ColumnPosition, Row}; +use crate::internals::row::ColumnPosition; use crate::schema::{TableName, TableSchema}; use crate::internals::table::Table; use crate::operation::{ColumnSelection, Condition, Operation}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Value}; use bimap::BiMap; +use crate::restricted_row::RestrictedRow; // Use `TablePosition` as index pub type Tables = Vec; @@ -20,7 +21,7 @@ pub struct State { // #[derive(Debug)] pub enum Response<'a> { - Selected(Box + 'a>), + Selected(&'a TableSchema, Box + 'a>), Inserted, Deleted(usize), // how many were deleted TableCreated, @@ -31,7 +32,7 @@ impl std::fmt::Debug for Response<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { use Response::*; match self { - Selected(_rows) => + Selected(_schema, _rows) => // TODO: How can we iterate through the rows without having to take ownership of // them? f.write_str("Some rows... trust me"), @@ -99,7 +100,7 @@ impl State { let selected_rows = match maybe_condition { None => { let x = table.select_all_rows(selected_column_positions); - Box::new(x) as Box + 'a> + Box::new(x) as Box + 'a> }, Some(Condition::Eq(eq_column_name, value)) => { @@ -112,11 +113,11 @@ impl State { eq_column_position, value, )?; - Box::new(x) as Box + 'a> + Box::new(x) as Box + 'a> } }; - Ok(Response::Selected(selected_rows)) + Ok(Response::Selected(table.schema(), selected_rows)) } Insert(table_name, values) => { let table: &mut Table = self.table_from_name_mut(&table_name)?; @@ -210,8 +211,8 @@ mod tests { let response: Response = state .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -263,8 +264,8 @@ mod tests { .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -272,9 +273,9 @@ mod tests { let row = &rows[0]; assert!(row.len() == 3); - assert!(row[0] == id); - assert!(row[1] == name); - assert!(row[2] == age); + assert!(row[0].1 == id); + assert!(row[1].1 == name); + assert!(row[2].1 == age); } #[test] @@ -328,24 +329,25 @@ mod tests { { let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_, rows) = response else { panic!() }; - let rows: Vec<_> = rows.collect(); + + let rows: Vec<_> = rows.collect(); assert!(rows.len() == 2); let row0 = &rows[0]; let row1 = &rows[1]; assert!(row0.len() == 3); - assert!(row0[0] == id0); - assert!(row0[1] == name0); - assert!(row0[2] == age0); + assert!(row0[0].1 == id0); + assert!(row0[1].1 == name0); + assert!(row0[2].1 == age0); assert!(row1.len() == 3); - assert!(row1[0] == id1); - assert!(row1[1] == name1); - assert!(row1[2] == age1); + assert!(row1[0].1 == id1); + assert!(row1[1].1 == name1); + assert!(row1[2].1 == age1); } { @@ -356,8 +358,8 @@ mod tests { Some(Eq("id".to_string(), id0.clone())), )) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -365,9 +367,9 @@ mod tests { let row0 = &rows[0]; assert!(row0.len() == 3); - assert!(row0[0] == id0); - assert!(row0[1] == name0); - assert!(row0[2] == age0); + assert!(row0[0].1 == id0); + assert!(row0[1].1 == name0); + assert!(row0[2].1 == age0); } { @@ -378,8 +380,8 @@ mod tests { Some(Eq("id".to_string(), id0.clone())), )) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -387,8 +389,8 @@ mod tests { let row0 = &rows[0]; assert!(row0.len() == 2); - assert!(row0[0] == name0); - assert!(row0[1] == id0); + assert!(row0[0].1 == name0); + assert!(row0[1].1 == id0); } } @@ -452,8 +454,8 @@ mod tests { let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -461,9 +463,9 @@ mod tests { let row = &rows[0]; assert!(row.len() == 3); - assert!(row[0] == id1); - assert!(row[1] == name1); - assert!(row[2] == age1); + assert!(row[0].1 == id1); + assert!(row[1].1 == name1); + assert!(row[2].1 == age1); } #[test] @@ -644,4 +646,4 @@ pub fn example() { println!("{:?}", response); println!(); } -} +} \ No newline at end of file diff --git a/minisql/src/lib.rs b/minisql/src/lib.rs index b8e95c3..f9a0b09 100644 --- a/minisql/src/lib.rs +++ b/minisql/src/lib.rs @@ -5,3 +5,4 @@ pub mod type_system; mod error; mod internals; mod result; +pub mod restricted_row; diff --git a/minisql/src/restricted_row.rs b/minisql/src/restricted_row.rs new file mode 100644 index 0000000..26be188 --- /dev/null +++ b/minisql/src/restricted_row.rs @@ -0,0 +1,35 @@ +use std::ops::Index; +use std::slice::SliceIndex; +use crate::internals::row::ColumnPosition; +use crate::type_system::Value; + +#[derive(Debug, Clone)] +pub struct RestrictedRow(Vec<(ColumnPosition, Value)>); + +impl Index for RestrictedRow +where + Idx: SliceIndex<[(ColumnPosition, Value)]>, +{ + type Output = Idx::Output; + + fn index(&self, index: Idx) -> &Self::Output { + &self.0[index] + } +} + +impl From> for RestrictedRow { + fn from(v: Vec<(ColumnPosition, Value)>) -> Self { + RestrictedRow(v) + } +} + +impl RestrictedRow { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } +} + From f47fd24232edfecd398c3aa1dd6253acb3710093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Tue, 23 Jan 2024 21:02:13 +0100 Subject: [PATCH 04/21] feat: add thiserror annotations to error --- Cargo.lock | 1 + minisql/Cargo.toml | 1 + minisql/src/error.rs | 12 +++++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 31287bb..0471c9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,7 @@ name = "minisql" version = "0.1.0" dependencies = [ "bimap", + "thiserror", ] [[package]] diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 1a108f1..6164b6b 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] bimap = "0.6.3" +thiserror = "1.0.50" diff --git a/minisql/src/error.rs b/minisql/src/error.rs index 6000c0e..fe11cd2 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,17 +1,27 @@ +use thiserror::Error; use crate::internals::row::ColumnPosition; use crate::schema::{ColumnName, TableName}; use crate::operation::InsertionValues; use crate::type_system::{DbType, Uuid, Value}; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { + #[error("table {0} does not exist")] TableDoesNotExist(TableName), + #[error("column {1} of table {0} does not exist")] ColumnDoesNotExist(TableName, ColumnName), + #[error("column position {1} of table {0} does not exist")] ColumnPositionDoesNotExist(TableName, ColumnPosition), + #[error("column {1} of table {0} has unexpected type {2:?} and value {3:?}")] ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value), + #[error("table {0} already contains row with id {1}")] AttemptingToInsertAlreadyPresentId(TableName, Uuid), + #[error("table {0} is missing annotation for column {1}")] MissingTypeAnnotationOfColumn(TableName, ColumnPosition), + #[error("table {0} is missing column {1} in insert values {2:?}")] MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), + #[error("table {0} has mismatch between insert values {1:?} and columns")] MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), + #[error("table {0} cannot be indexed on column {1}")] AttemptToIndexNonIndexableColumn(TableName, ColumnName), } From 7773a2585e67820c90b226186e1df6d5fe9723eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Tue, 23 Jan 2024 21:55:43 +0100 Subject: [PATCH 05/21] feat: add type system encoding to text bytes --- minisql/src/error.rs | 17 +++++ minisql/src/type_system.rs | 153 +++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/minisql/src/error.rs b/minisql/src/error.rs index fe11cd2..0574aff 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,3 +1,5 @@ +use std::num::{ParseFloatError, ParseIntError}; +use std::str::Utf8Error; use thiserror::Error; use crate::internals::row::ColumnPosition; use crate::schema::{ColumnName, TableName}; @@ -25,3 +27,18 @@ pub enum Error { #[error("table {0} cannot be indexed on column {1}")] AttemptToIndexNonIndexableColumn(TableName, ColumnName), } + +#[derive(Debug, Error)] +pub enum TypeConversionError { + #[error("failed to decode bytes to string")] + TextDecodeFailed(#[from] Utf8Error), + #[error("failed to parse float from text")] + NumberDecodeFailed(#[from] ParseFloatError), + #[error("failed to parse int from text")] + IntDecodeFailed(#[from] ParseIntError), + #[error("uknown type with oid {oid} and size {size}")] + UnknownType { + oid: i32, + size: i32 + } +} diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 4edc3ec..caa85ab 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -1,3 +1,5 @@ +use crate::error::TypeConversionError; + // ==============Types================ #[derive(Debug, Clone, Copy)] pub enum DbType { @@ -39,4 +41,155 @@ impl Value { }, } } + + pub fn type_oid(&self) -> i32 { + match self { + Self::Number(_) => 701, + Self::Indexable(val) => match val { + IndexableValue::String(_) => 25, + IndexableValue::Int(_) => 23, + IndexableValue::Uuid(_) => 2950, + }, + } + } + + pub fn type_size(&self) -> i32 { + match self { + Self::Number(_) => 8, + Self::Indexable(val) => match val { + IndexableValue::String(_) => -2, // null terminated string + IndexableValue::Int(_) => 8, + IndexableValue::Uuid(_) => 16, + }, + } + } + + pub fn as_text_bytes(&self) -> Vec { + match self { + Self::Number(n) => format!("{n}").into_bytes(), + Self::Indexable(i) => match i { + IndexableValue::String(s) => format!("{s}\0").into_bytes(), + IndexableValue::Int(i) => format!("{i}").into_bytes(), + IndexableValue::Uuid(u) => format!("{u}").into_bytes(), + }, + } + } + + pub fn from_text_bytes(bytes: &[u8], type_oid: i32, type_size: i32) -> Result { + match (type_oid, type_size) { + (701, 8) => { + let s = std::str::from_utf8(bytes)?; + let n = s.parse::()?; + Ok(Value::Number(n)) + } + (25, -2) => { + let s = std::str::from_utf8(bytes)?; + let s = &s[..s.len() - 1]; // remove null terminator + Ok(Value::Indexable(IndexableValue::String(s.to_string()))) + } + (23, 8) => { + let s = std::str::from_utf8(bytes)?; + let n = s.parse::()?; + Ok(Value::Indexable(IndexableValue::Int(n))) + } + (2950, 16) => { + let s = std::str::from_utf8(bytes)?; + let n = s.parse::()?; + Ok(Value::Indexable(IndexableValue::Uuid(n))) + } + (oid, size) => Err(TypeConversionError::UnknownType { oid, size }), + } + } +} + +mod tests { + use crate::error::TypeConversionError::UnknownType; + use super::{Value, IndexableValue}; + + #[test] + fn test_encode_number() { + let value = Value::Number(123.456); + let oid = value.type_oid(); + let size = value.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + assert_eq!(oid, 701); + assert_eq!(size, 8); + } + + #[test] + fn test_encode_string() { + let value = Value::Indexable(IndexableValue::String("hello".to_string())); + let oid = value.type_oid(); + let size = value.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + assert_eq!(oid, 25); + assert_eq!(size, -2); + } + + #[test] + fn test_encode_string_utf8() { + let value = Value::Indexable(IndexableValue::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string())); + let oid = value.type_oid(); + let size = value.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + assert_eq!(oid, 25); + assert_eq!(size, -2); + } + + #[test] + fn test_encode_int() { + let value = Value::Indexable(IndexableValue::Int(123)); + let oid = value.type_oid(); + let size = value.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + assert_eq!(oid, 23); + assert_eq!(size, 8); + } + + #[test] + fn test_encode_uuid() { + let value = Value::Indexable(IndexableValue::Uuid(123)); + let oid = value.type_oid(); + let size = value.type_size(); + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); + + assert_eq!(value, from_bytes); + + assert_eq!(oid, 2950); + assert_eq!(size, 16); + } + + #[test] + fn test_mismatched_size() { + let value = Value::Indexable(IndexableValue::Uuid(123)); + let oid = value.type_oid(); + let size = 8; + + let bytes = value.as_text_bytes(); + let from_bytes = Value::from_text_bytes(&bytes, oid, size); + + assert!(matches!(from_bytes, Err(UnknownType { oid: 2950, size: 8 }))) + } } From 4c826923a51c0a015c0d2b59f556ee99319eaaeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Tue, 23 Jan 2024 21:58:00 +0100 Subject: [PATCH 06/21] fix: type size bit length --- minisql/src/error.rs | 4 ++-- minisql/src/type_system.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/minisql/src/error.rs b/minisql/src/error.rs index 0574aff..2552f74 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -36,9 +36,9 @@ pub enum TypeConversionError { NumberDecodeFailed(#[from] ParseFloatError), #[error("failed to parse int from text")] IntDecodeFailed(#[from] ParseIntError), - #[error("uknown type with oid {oid} and size {size}")] + #[error("unknown type with oid {oid} and size {size}")] UnknownType { oid: i32, - size: i32 + size: i16 } } diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index caa85ab..03cfb13 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -53,7 +53,7 @@ impl Value { } } - pub fn type_size(&self) -> i32 { + pub fn type_size(&self) -> i16 { match self { Self::Number(_) => 8, Self::Indexable(val) => match val { @@ -75,7 +75,7 @@ impl Value { } } - pub fn from_text_bytes(bytes: &[u8], type_oid: i32, type_size: i32) -> Result { + pub fn from_text_bytes(bytes: &[u8], type_oid: i32, type_size: i16) -> Result { match (type_oid, type_size) { (701, 8) => { let s = std::str::from_utf8(bytes)?; From 7b79dd69b47dac7cbea93373ade0dd2b2b1fc9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Wed, 24 Jan 2024 23:25:02 +0100 Subject: [PATCH 07/21] feat: add resettable cancellation token --- server/src/cancellation.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 server/src/cancellation.rs diff --git a/server/src/cancellation.rs b/server/src/cancellation.rs new file mode 100644 index 0000000..59f2cb1 --- /dev/null +++ b/server/src/cancellation.rs @@ -0,0 +1,34 @@ +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +pub struct ResetCancelToken { + is_canceled: Arc, +} + +impl ResetCancelToken { + pub fn new() -> Self { + Self { + is_canceled: Arc::new(AtomicBool::new(false)), + } + } + + pub fn is_canceled(&self) -> bool { + self.is_canceled.load(Ordering::SeqCst) + } + + pub fn cancel(&self) { + self.is_canceled.store(true, Ordering::SeqCst); + } + + pub fn reset(&self) { + self.is_canceled.store(false, Ordering::SeqCst); + } +} + +impl Clone for ResetCancelToken { + fn clone(&self) -> Self { + Self { + is_canceled: self.is_canceled.clone(), + } + } +} \ No newline at end of file From 51ed3bbc5ce03ba6ead5eba6d617d44e42a8a4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 25 Jan 2024 23:07:27 +0100 Subject: [PATCH 08/21] feat: finish work on server --- Cargo.lock | 50 +++++++ minisql/Cargo.toml | 1 + minisql/src/interpreter.rs | 10 +- minisql/src/operation.rs | 3 + minisql/src/schema.rs | 2 +- server/Cargo.toml | 4 +- server/src/main.rs | 264 +++++++++++++++++------------------- server/src/parser_stub.rs | 63 +++++++++ server/src/proto_wrapper.rs | 104 ++++++++++++++ 9 files changed, 356 insertions(+), 145 deletions(-) create mode 100644 server/src/parser_stub.rs create mode 100644 server/src/proto_wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index 0471c9d..a284cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -256,6 +267,7 @@ version = "0.1.0" dependencies = [ "bimap", "thiserror", + "tokio", ] [[package]] @@ -326,6 +338,12 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.70" @@ -354,6 +372,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -400,9 +448,11 @@ name = "server" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "clap", "minisql", "proto", + "rand", "tokio", ] diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 6164b6b..22d0242 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] bimap = "0.6.3" thiserror = "1.0.50" +tokio = { version = "1.35.1", features = ["sync"] } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 805923e..26f11ef 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use crate::error::Error; use crate::internals::row::ColumnPosition; use crate::schema::{TableName, TableSchema}; @@ -6,6 +7,7 @@ use crate::operation::{ColumnSelection, Condition, Operation}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Value}; use bimap::BiMap; +use tokio::sync::Mutex; use crate::restricted_row::RestrictedRow; // Use `TablePosition` as index @@ -21,7 +23,7 @@ pub struct State { // #[derive(Debug)] pub enum Response<'a> { - Selected(&'a TableSchema, Box + 'a>), + Selected(&'a TableSchema, Arc + 'a + Send>>), Inserted, Deleted(usize), // how many were deleted TableCreated, @@ -49,7 +51,7 @@ impl std::fmt::Debug for Response<'_> { } impl State { - fn new() -> Self { + pub fn new() -> Self { Self { table_name_position_mapping: BiMap::new(), tables: vec![], @@ -100,7 +102,7 @@ impl State { let selected_rows = match maybe_condition { None => { let x = table.select_all_rows(selected_column_positions); - Box::new(x) as Box + 'a> + Arc::new(Mutex::new(x)) as Arc + 'a + Send>> }, Some(Condition::Eq(eq_column_name, value)) => { @@ -113,7 +115,7 @@ impl State { eq_column_position, value, )?; - Box::new(x) as Box + 'a> + Arc::new(Mutex::new(x)) as Arc + 'a + Send>> } }; diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 3b060c9..e30a044 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -6,6 +6,7 @@ use crate::type_system::Value; // Perhaps consider factoring the table name out // and think of the operations as operating on a unique table. // TODO: `TableName` should be replaced by `TablePosition` +#[derive(Debug)] pub enum Operation { Select(TableName, ColumnSelection, Option), Insert(TableName, InsertionValues), @@ -18,11 +19,13 @@ pub enum Operation { pub type InsertionValues = Vec<(ColumnName, Value)>; +#[derive(Debug)] pub enum ColumnSelection { All, Columns(Vec), } +#[derive(Debug)] pub enum Condition { // And(Box, Box), // Or(Box, Box), diff --git a/minisql/src/schema.rs b/minisql/src/schema.rs index c9574ac..4f2bf54 100644 --- a/minisql/src/schema.rs +++ b/minisql/src/schema.rs @@ -20,7 +20,7 @@ pub type TableName = String; pub type ColumnName = String; impl TableSchema { - pub(crate) fn new(table_name: TableName, primary_key: ColumnPosition, column_name_position_map: Vec<(ColumnName, ColumnPosition)>, types: Vec) -> Self { + pub fn new(table_name: TableName, primary_key: ColumnPosition, column_name_position_map: Vec<(ColumnName, ColumnPosition)>, types: Vec) -> Self { let mut column_name_position_mapping: BiMap = BiMap::new(); for (column_name, column_position) in column_name_position_map { column_name_position_mapping.insert(column_name, column_position); diff --git a/server/Cargo.toml b/server/Cargo.toml index 0dd40ff..256b592 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,5 +11,5 @@ clap = { version = "4.4.18", features = ["derive"] } tokio = { version = "1.35.1", features = ["full"] } minisql = { path = "../minisql" } proto = { path = "../proto" } - - +async-trait = "0.1.74" +rand = "0.8.5" diff --git a/server/src/main.rs b/server/src/main.rs index f0b8084..d4a2e89 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,49 +1,121 @@ -mod config; +use std::collections::HashMap; +use std::sync::Arc; -use std::net::SocketAddr; use clap::Parser; +use tokio::io::{BufReader, BufWriter}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::{Mutex, RwLock}; + +use minisql::interpreter::{Response, State}; +use proto::handshake::errors::ServerHandshakeError; +use proto::handshake::request::HandshakeRequest; use proto::handshake::response::HandshakeResponse; use proto::handshake::server::do_server_handshake; -use proto::message::backend::{ - BackendMessage, ColumnDescription, CommandCompleteData, DataRowData, ErrorResponseData, - ReadyForQueryData, RowDescriptionData, -}; use proto::message::frontend::FrontendMessage; -use proto::reader::oneway::OneWayProtoReader; +use proto::reader::frontend::FrontendProtoReader; use proto::reader::protoreader::ProtoReader; use proto::writer::backend::BackendProtoWriter; use proto::writer::protowriter::{ProtoFlush, ProtoWriter}; -use tokio::io::{BufReader, BufWriter}; -use tokio::net::{TcpListener, TcpStream}; + +use crate::cancellation::ResetCancelToken; use crate::config::Configuration; +use crate::parser_stub::parse_query; +use crate::proto_wrapper::{CompleteStatus, ServerProto}; + +mod config; +mod proto_wrapper; +mod cancellation; +mod parser_stub; + +type TokenStore = Arc>>; +type DbState = Arc>; #[tokio::main] async fn main() -> anyhow::Result<()> { let config = Configuration::parse(); + let state = Arc::new(RwLock::new(State::new())); + let tokens = Arc::new(Mutex::new(HashMap::<(i32, i32), ResetCancelToken>::new())); + let addr = config.get_socket_address(); let listener = TcpListener::bind(&addr).await?; println!("Server started at {addr}"); loop { + let state = state.clone(); + let tokens = tokens.clone(); + let (pid, key) = random_pid_key(); + let (socket, _) = listener.accept().await?; println!("New client connected: {}", socket.peer_addr()?); tokio::spawn(async move { - let reason = handle_stream(socket).await; + let reason = handle_stream(socket, state, tokens).await; println!("Client disconnected: {reason:?}"); }); } } -async fn handle_stream(mut stream: TcpStream) -> anyhow::Result<()> { +async fn handle_stream(mut stream: TcpStream, state: DbState, tokens: TokenStore) -> anyhow::Result<()> { let (reader, writer) = stream.split(); let mut writer = ProtoWriter::new(BufWriter::new(writer)); let mut reader = ProtoReader::new(BufReader::new(reader), 1024); - let response = HandshakeResponse::new("minisql", 123, 123); + // Create a token with random PID and key + let (pid, key, token) = create_token(&tokens).await?; - let request = do_server_handshake(&mut writer, &mut reader, response).await?; + // Handle handshake + let response = HandshakeResponse::new("minisql", pid, key); + let request = do_server_handshake(&mut writer, &mut reader, response).await; + let result = match request { + Ok(req) => handle_connection(&mut reader, &mut writer, req, state, token).await, + Err(ServerHandshakeError::IsCancelRequest(cancel)) => handle_cancellation(cancel.pid, cancel.secret, &tokens).await, + Err(e) => Err(anyhow::anyhow!("Error during handshake: {:?}", e)), + }; + + // Release cancellation token + let mut tokens = tokens.lock().await; + tokens.remove(&(pid, key)); + + result +} + +fn random_pid_key() -> (i32, i32) { + let pid = rand::random::(); + let key = rand::random::(); + (pid, key) +} + +async fn create_token(tokens: &TokenStore) -> anyhow::Result<(i32, i32, ResetCancelToken)> { + let token = ResetCancelToken::new(); + let mut tokens = tokens.lock().await; + loop { + let pid_key = random_pid_key(); + if !tokens.contains_key(&pid_key) { + tokens.insert(pid_key, token.clone()); + + let (pid, key) = pid_key; + return Ok((pid, key, token)); + } + } +} + +async fn handle_cancellation(pid: i32, key: i32, tokens: &TokenStore) -> anyhow::Result<()> { + let tokens = tokens.lock().await; + let token = tokens.get(&(pid, key)); + match token { + Some(t) => t.cancel(), + None => return Err(anyhow::anyhow!("Invalid PID and Key cancel combination")), + } + + Ok(()) +} + +async fn handle_connection(reader: &mut R, writer: &mut W, request: HandshakeRequest, state: DbState, token: ResetCancelToken) -> anyhow::Result<()> +where + R: FrontendProtoReader + Send, + W: BackendProtoWriter + ProtoFlush + Send, +{ println!("Handshake complete:\n{request:?}"); loop { @@ -57,17 +129,48 @@ async fn handle_stream(mut stream: TcpStream) -> anyhow::Result<()> { } FrontendMessage::Query(data) => { println!("Received Query: {:?}", data); - if data.query.as_str().contains("car") { - println!("Sending error message"); - send_error_response(&mut writer, "Car not found").await?; - } else if data.query.as_str().to_lowercase().contains("select") { - println!("Sending table"); - send_query_response(&mut writer).await?; - } else { - println!("Sending empty query"); - send_empty_query(&mut writer).await?; + let operation = parse_query(data.query.as_str()); + println!("Parsed query: {:?}", operation); + + let mut state = state.write().await; + let result = state.interpret(operation); + println!("Result: {:?}", result); + + match result { + Err(e) => { + writer.write_error_message(&format!("Error: {:?}", e)).await?; + } + Ok(res) => { + match res { + Response::Deleted(i) => writer.write_command_complete(CompleteStatus::Delete(i)).await?, + Response::Inserted => writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?, + Response::Selected(schema, rows) => { + let mut rows = rows.lock().await; + let first_row = rows.next(); + match first_row { + Some(row) => { + writer.write_table_header(&schema, &row).await?; + writer.write_table_row(&row).await?; + + let mut sent_rows = 1; + while let Some(row) = rows.next() { + writer.write_table_row(&row).await?; + sent_rows += 1; + } + + writer.write_command_complete(CompleteStatus::Select(sent_rows)).await?; + } + None => { + writer.write_command_complete(CompleteStatus::Select(0)).await?; + } + } + } + _ => {} + } + } } - send_ready_for_query(&mut writer).await?; + + writer.write_ready_for_query().await?; } } writer.flush().await?; @@ -75,118 +178,3 @@ async fn handle_stream(mut stream: TcpStream) -> anyhow::Result<()> { Ok(()) } - -async fn send_error_response( - writer: &mut impl BackendProtoWriter, - error_message: &str, -) -> anyhow::Result<()> { - writer - .write_proto( - ErrorResponseData { - code: b'M', - message: error_message.to_string().into(), - } - .into(), - ) - .await?; - - Ok(()) -} - -async fn send_ready_for_query(writer: &mut impl BackendProtoWriter) -> anyhow::Result<()> { - writer - .write_proto(BackendMessage::from(ReadyForQueryData { status: b'I' })) - .await?; - - Ok(()) -} - -async fn send_empty_query(writer: &mut impl BackendProtoWriter) -> anyhow::Result<()> { - writer - .write_proto(BackendMessage::EmptyQueryResponse) - .await?; - - Ok(()) -} - -async fn send_row_description(writer: &mut impl BackendProtoWriter) -> anyhow::Result<()> { - let columns = vec![ - ColumnDescription { - name: "id".to_string().into(), - table_oid: 123, - column_index: 1, - type_oid: 23, - type_size: 4, - type_modifier: -1, - format_code: 0, - }, - ColumnDescription { - name: "argument".to_string().into(), - table_oid: 123, - column_index: 2, - type_oid: 23, - type_size: 4, - type_modifier: -1, - format_code: 0, - }, - ColumnDescription { - name: "description".to_string().into(), - table_oid: 123, - column_index: 3, - type_oid: 1043, - type_size: 32, - type_modifier: -1, - format_code: 0, - }, - ]; - - writer - .write_proto( - RowDescriptionData { - columns: columns.into(), - } - .into(), - ) - .await?; - - Ok(()) -} - -async fn send_query_response(writer: &mut impl BackendProtoWriter) -> anyhow::Result<()> { - send_row_description(writer).await?; - - write_row(writer, b"0", b"1337", b"auto").await?; - write_row(writer, b"1", b"69", b"bus").await?; - write_row(writer, b"2", b"420", b"kolo").await?; - - writer - .write_proto( - CommandCompleteData { - tag: "SELECT 3".to_string().into(), - } - .into(), - ) - .await?; - - Ok(()) -} - -async fn write_row( - writer: &mut impl BackendProtoWriter, - first: &[u8], - second: &[u8], - third: &[u8], -) -> anyhow::Result<()> { - let row_data = vec![ - first.to_vec().into(), - second.to_vec().into(), - third.to_vec().into(), - ] - .into(); - - writer - .write_proto(DataRowData { columns: row_data }.into()) - .await?; - - Ok(()) -} diff --git a/server/src/parser_stub.rs b/server/src/parser_stub.rs new file mode 100644 index 0000000..c8f3e6a --- /dev/null +++ b/server/src/parser_stub.rs @@ -0,0 +1,63 @@ +use minisql::operation::{ColumnSelection, Operation}; +use minisql::schema::TableSchema; +use minisql::type_system::{DbType, IndexableValue, Value}; + +const TABLE_NAME: &'static str = "tablus"; + +static mut ID_COUNTER: u64 = 0; + +pub fn parse_query(query: &str) -> Operation { + if query.contains("select") { + if query.contains("*") { + Operation::Select(TABLE_NAME.to_string(), ColumnSelection::All, None) + } else { + Operation::Select(TABLE_NAME.to_string(), ColumnSelection::Columns(vec![ + "name".to_string(), + "price".to_string(), + ]), None) + } + } else if query.contains("insert") { + + let id = unsafe { + ID_COUNTER += 1; + ID_COUNTER + }; + + let rand_rak = rand::random::(); + let rand_price = rand::random::(); + + Operation::Insert(TABLE_NAME.to_string(), vec![ + ("id".to_string(), Value::Indexable(IndexableValue::Uuid(id))), + ("name".to_string(), Value::Indexable(IndexableValue::String(format!("Car {}", rand_rak)))), + ("price".to_string(), Value::Number(rand_price)), + ("mileage".to_string(), Value::Indexable(IndexableValue::Int(1234))), + ]) + } else if query.contains("delete") { + Operation::Delete(TABLE_NAME.to_string(), None) + } else if query.contains("create table") { + Operation::CreateTable(TABLE_NAME.to_string(), get_cars_schema()) + } else if query.contains("create index") { + Operation::CreateIndex(TABLE_NAME.to_string(), "price".to_string()) + } else { + panic!("Unknown query: {}", query); + } +} + +fn get_cars_schema() -> TableSchema { + TableSchema::new( + "cars".to_string(), + 0, + vec![ + ("id".to_string(), 0), + ("name".to_string(), 1), + ("price".to_string(), 2), + ("mileage".to_string(), 3), + ], + vec![ + DbType::Uuid, + DbType::String, + DbType::Number, + DbType::Int, + ] + ) +} \ No newline at end of file diff --git a/server/src/proto_wrapper.rs b/server/src/proto_wrapper.rs new file mode 100644 index 0000000..3415255 --- /dev/null +++ b/server/src/proto_wrapper.rs @@ -0,0 +1,104 @@ +use async_trait::async_trait; +use minisql::restricted_row::RestrictedRow; +use minisql::schema::TableSchema; +use minisql::type_system::{Value}; +use proto::message::backend::{BackendMessage, ColumnDescription, CommandCompleteData, DataRowData, ErrorResponseData, ReadyForQueryData, RowDescriptionData}; +use proto::message::primitive::pglist::PgList; +use proto::writer::backend::BackendProtoWriter; + +pub enum CompleteStatus { + Insert { + oid: i32, + rows: i32, + }, + Delete(usize), + Select(usize), +} + +impl CompleteStatus { + fn to_string(&self) -> String { + match self { + CompleteStatus::Insert { oid, rows } => format!("INSERT {} {}", oid, rows), + CompleteStatus::Delete(rows) => format!("DELETE {}", rows), + CompleteStatus::Select(rows) => format!("SELECT {}", rows), + } + } +} + +#[async_trait] +pub trait ServerProto { + async fn write_error_message(&mut self, error_message: &str) -> anyhow::Result<()>; + async fn write_ready_for_query(&mut self) -> anyhow::Result<()>; + async fn write_empty_query(&mut self) -> anyhow::Result<()>; + async fn write_table_header(&mut self, table_schema: &TableSchema, row: &RestrictedRow) -> anyhow::Result<()>; + async fn write_table_row(&mut self, row: &RestrictedRow) -> anyhow::Result<()>; + async fn write_command_complete(&mut self, status: CompleteStatus) -> anyhow::Result<()>; +} + +#[async_trait] +impl ServerProto for W where W: BackendProtoWriter + Send { + async fn write_error_message(&mut self, error_message: &str) -> anyhow::Result<()> { + self.write_proto(ErrorResponseData { + code: b'M', + message: format!("{error_message}\0").into(), + }.into()).await?; + + Ok(()) + } + + async fn write_ready_for_query(&mut self) -> anyhow::Result<()> { + self.write_proto(ReadyForQueryData { status: b'I' }.into()).await?; + Ok(()) + } + + async fn write_empty_query(&mut self) -> anyhow::Result<()> { + self.write_proto(BackendMessage::EmptyQueryResponse).await?; + Ok(()) + } + + async fn write_table_header(&mut self, table_schema: &TableSchema, row: &RestrictedRow) -> anyhow::Result<()> { + let columns = row.iter() + .map(|(index, value)| value_to_column_description(table_schema, value, index)) + .collect::>>()?; + + self.write_proto(RowDescriptionData { columns: columns.into() }.into()).await?; + Ok(()) + } + + async fn write_table_row(&mut self, row: &RestrictedRow) -> anyhow::Result<()> { + let values = row.iter() + .map(|(_, value)| value.as_text_bytes().into()) + .collect::>>(); + + self.write_proto(BackendMessage::DataRow(DataRowData { + columns: values.into(), + })).await?; + Ok(()) + } + + async fn write_command_complete(&mut self, status: CompleteStatus) -> anyhow::Result<()> { + self.write_proto(BackendMessage::CommandComplete(CommandCompleteData { + tag: status.to_string().into(), + })).await?; + Ok(()) + } +} + +fn value_to_column_description(schema: &TableSchema, value: &Value, index: &usize) -> anyhow::Result { + let name = schema.column_name_from_column_position(*index)?; + + let table_oid = schema.table_name().as_bytes().as_ptr() as i32; + let column_index = (*index).try_into()?; + let type_oid = value.type_oid(); + let type_size = value.type_size(); + + Ok(ColumnDescription { + name: name.to_string().into(), + table_oid, + column_index, + type_oid, + type_size, + type_modifier: -1, + format_code: 0, // text format + }) +} From 88fb13325a3f6d8533d86ee70f81fd3e1765d8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 25 Jan 2024 23:19:37 +0100 Subject: [PATCH 09/21] tests: asynchronize some interpreter tests --- minisql/Cargo.toml | 2 +- minisql/src/interpreter.rs | 59 +++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 22d0242..9cdfae0 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] bimap = "0.6.3" thiserror = "1.0.50" -tokio = { version = "1.35.1", features = ["sync"] } +tokio = { version = "1.35.1", features = ["sync", "rt", "macros"] } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 26f11ef..949e042 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -166,6 +166,7 @@ impl State { mod tests { use super::*; use std::collections::HashSet; + use std::ops::Deref; fn users_schema() -> TableSchema { let id: ColumnPosition = 0; @@ -201,8 +202,8 @@ mod tests { assert!(table.table_name() == &users); } - #[test] - fn test_select_empty() { + #[tokio::test] + async fn test_select_empty() { let mut state = State::new(); let users_schema = users_schema(); let users = users_schema.table_name().clone(); @@ -217,8 +218,9 @@ mod tests { let Response::Selected(schema, rows) = response else { panic!() }; - let rows: Vec<_> = rows.collect(); - assert!(rows.len() == 0); + + let mut rows = rows.lock().await; + assert!(rows.next().is_none()); } #[test] @@ -233,8 +235,8 @@ mod tests { assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); } - #[test] - fn test_insert_select_basic1() { + #[tokio::test] + async fn test_insert_select_basic1() { use IndexableValue::*; use Value::*; @@ -270,18 +272,19 @@ mod tests { let Response::Selected(schema, rows) = response else { panic!() }; - let rows: Vec<_> = rows.collect(); - assert!(rows.len() == 1); - let row = &rows[0]; + let mut rows = rows.lock().await; + let row = rows.next().expect("expected single row"); assert!(row.len() == 3); assert!(row[0].1 == id); assert!(row[1].1 == name); assert!(row[2].1 == age); + + assert!(rows.next().is_none()); } - #[test] - fn test_insert_select_basic2() { + #[tokio::test] + async fn test_insert_select_basic2() { use ColumnSelection::*; use Condition::*; use IndexableValue::*; @@ -336,20 +339,21 @@ mod tests { panic!() }; - let rows: Vec<_> = rows.collect(); - assert!(rows.len() == 2); - let row0 = &rows[0]; - let row1 = &rows[1]; + let mut rows = rows.lock().await; + let row0 = rows.next().expect("expected first row"); assert!(row0.len() == 3); assert!(row0[0].1 == id0); assert!(row0[1].1 == name0); assert!(row0[2].1 == age0); + let row1 = rows.next().expect("expected second row"); assert!(row1.len() == 3); assert!(row1[0].1 == id1); assert!(row1[1].1 == name1); assert!(row1[2].1 == age1); + + assert!(rows.next().is_none()); } { @@ -364,14 +368,15 @@ mod tests { let Response::Selected(_, rows) = response else { panic!() }; - let rows: Vec<_> = rows.collect(); - assert!(rows.len() == 1); - let row0 = &rows[0]; + + let mut rows = rows.lock().await; + let row0 = rows.next().expect("expected first row"); assert!(row0.len() == 3); assert!(row0[0].1 == id0); assert!(row0[1].1 == name0); assert!(row0[2].1 == age0); + assert!(rows.next().is_none()); } { @@ -386,18 +391,19 @@ mod tests { let Response::Selected(_, rows) = response else { panic!() }; - let rows: Vec<_> = rows.collect(); - assert!(rows.len() == 1); - let row0 = &rows[0]; + + let mut rows = rows.lock().await; + let row0 = rows.next().expect("expected first row"); assert!(row0.len() == 2); assert!(row0[0].1 == name0); assert!(row0[1].1 == id0); + assert!(rows.next().is_none()); } } - #[test] - fn test_delete() { + #[tokio::test] + async fn test_delete() { use ColumnSelection::*; use Condition::*; use IndexableValue::*; @@ -460,14 +466,15 @@ mod tests { let Response::Selected(_, rows) = response else { panic!() }; - let rows: Vec<_> = rows.collect(); - assert!(rows.len() == 1); - let row = &rows[0]; + + let mut rows = rows.lock().await; + let row = rows.next().expect("expected first row"); assert!(row.len() == 3); assert!(row[0].1 == id1); assert!(row[1].1 == name1); assert!(row[2].1 == age1); + assert!(rows.next().is_none()); } #[test] From 4fca7ce12b6e1cb852f37c15d0b74146e0ee3170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Thu, 25 Jan 2024 23:23:18 +0100 Subject: [PATCH 10/21] fix: small changes --- minisql/src/interpreter.rs | 5 ++--- minisql/src/type_system.rs | 1 + server/src/main.rs | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 949e042..a6c1235 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -166,7 +166,6 @@ impl State { mod tests { use super::*; use std::collections::HashSet; - use std::ops::Deref; fn users_schema() -> TableSchema { let id: ColumnPosition = 0; @@ -215,7 +214,7 @@ mod tests { .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) .unwrap(); assert!(matches!(response, Response::Selected(_, _))); - let Response::Selected(schema, rows) = response else { + let Response::Selected(_schema, rows) = response else { panic!() }; @@ -269,7 +268,7 @@ mod tests { .unwrap(); assert!(matches!(response, Response::Selected(_, _))); - let Response::Selected(schema, rows) = response else { + let Response::Selected(_schema, rows) = response else { panic!() }; diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index 03cfb13..5f1a0ca 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -102,6 +102,7 @@ impl Value { } } +#[cfg(test)] mod tests { use crate::error::TypeConversionError::UnknownType; use super::{Value, IndexableValue}; diff --git a/server/src/main.rs b/server/src/main.rs index d4a2e89..b35c738 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -44,7 +44,6 @@ async fn main() -> anyhow::Result<()> { loop { let state = state.clone(); let tokens = tokens.clone(); - let (pid, key) = random_pid_key(); let (socket, _) = listener.accept().await?; println!("New client connected: {}", socket.peer_addr()?); @@ -156,6 +155,11 @@ where while let Some(row) = rows.next() { writer.write_table_row(&row).await?; sent_rows += 1; + + if token.is_canceled() { + token.reset(); + break; + } } writer.write_command_complete(CompleteStatus::Select(sent_rows)).await?; From 04311ebe4847969b71ad2444bfc9508c963add91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Fri, 26 Jan 2024 22:13:33 +0100 Subject: [PATCH 11/21] feat: server improvements --- Cargo.lock | 1 + minisql/src/interpreter.rs | 6 +- server/Cargo.toml | 1 + server/src/main.rs | 119 +++++++++++++++++++++++-------------- server/src/parser_stub.rs | 63 -------------------- 5 files changed, 80 insertions(+), 110 deletions(-) delete mode 100644 server/src/parser_stub.rs diff --git a/Cargo.lock b/Cargo.lock index 5a3a6e1..ed45328 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,7 @@ dependencies = [ "async-trait", "clap", "minisql", + "parser", "proto", "rand", "tokio", diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 90e4f02..0be4310 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -23,7 +23,7 @@ pub struct State { // #[derive(Debug)] pub enum Response<'a> { - Selected(&'a TableSchema, Arc + 'a + Send>>), + Selected(&'a TableSchema, Box + 'a + Send>), Inserted, Deleted(usize), // how many were deleted TableCreated, @@ -112,7 +112,7 @@ impl State { let selected_rows = match maybe_condition { None => { let x = table.select_all_rows(selected_column_positions); - Arc::new(Mutex::new(x)) as Arc + 'a + Send>> + Box::new(x) as Box + 'a + Send> }, Some(Condition::Eq(eq_column_name, value)) => { @@ -125,7 +125,7 @@ impl State { eq_column_position, value, )?; - Arc::new(Mutex::new(x)) as Arc + 'a + Send>> + Box::new(x) as Box + 'a + Send> } }; diff --git a/server/Cargo.toml b/server/Cargo.toml index 8d8c52c..6a511f6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,3 +13,4 @@ async-trait = "0.1.74" rand = "0.8.5" minisql = { path = "../minisql" } proto = { path = "../proto" } +parser = { path = "../parser" } \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index b35c738..3588cd1 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,6 +7,8 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, RwLock}; use minisql::interpreter::{Response, State}; +use minisql::operation; +use parser::parse_and_validate; use proto::handshake::errors::ServerHandshakeError; use proto::handshake::request::HandshakeRequest; use proto::handshake::response::HandshakeResponse; @@ -19,13 +21,11 @@ use proto::writer::protowriter::{ProtoFlush, ProtoWriter}; use crate::cancellation::ResetCancelToken; use crate::config::Configuration; -use crate::parser_stub::parse_query; use crate::proto_wrapper::{CompleteStatus, ServerProto}; mod config; mod proto_wrapper; mod cancellation; -mod parser_stub; type TokenStore = Arc>>; type DbState = Arc>; @@ -127,53 +127,14 @@ where break; } FrontendMessage::Query(data) => { - println!("Received Query: {:?}", data); - let operation = parse_query(data.query.as_str()); - println!("Parsed query: {:?}", operation); - let mut state = state.write().await; - let result = state.interpret(operation); - println!("Result: {:?}", result); - + let result = handle_query(writer, &mut state, data.query.into(), &token).await; match result { + Ok(_) => {} Err(e) => { - writer.write_error_message(&format!("Error: {:?}", e)).await?; - } - Ok(res) => { - match res { - Response::Deleted(i) => writer.write_command_complete(CompleteStatus::Delete(i)).await?, - Response::Inserted => writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?, - Response::Selected(schema, rows) => { - let mut rows = rows.lock().await; - let first_row = rows.next(); - match first_row { - Some(row) => { - writer.write_table_header(&schema, &row).await?; - writer.write_table_row(&row).await?; - - let mut sent_rows = 1; - while let Some(row) = rows.next() { - writer.write_table_row(&row).await?; - sent_rows += 1; - - if token.is_canceled() { - token.reset(); - break; - } - } - - writer.write_command_complete(CompleteStatus::Select(sent_rows)).await?; - } - None => { - writer.write_command_complete(CompleteStatus::Select(0)).await?; - } - } - } - _ => {} - } + writer.write_error_message(&e.to_string()).await? } } - writer.write_ready_for_query().await?; } } @@ -182,3 +143,73 @@ where Ok(()) } + +async fn handle_query(writer: &mut W, state: &mut State, query: String, token: &ResetCancelToken) -> anyhow::Result<()> +where + W: BackendProtoWriter + ProtoFlush + Send, +{ + let metadata = state.metadata(); + let operation = parse_and_validate(query, &metadata)?; + println!("Parsed query: {:?}", operation); + + let Response = state.interpret(operation)?; + println!("Result: {:?}", Response); + + match Response { + Response::Deleted(i) => writer.write_command_complete(CompleteStatus::Delete(i)).await?, + Response::Inserted => writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?, + Response::Selected(schema, mut rows) => { + match rows.next() { + Some(row) => { + writer.write_table_header(&schema, &row).await?; + writer.write_table_row(&row).await?; + + let mut sent_rows = 1; + for row in rows { + sent_rows += 1; + writer.write_table_row(&row).await?; + if token.is_canceled() { + token.reset(); + break; + } + } + + writer.write_command_complete(CompleteStatus::Select(sent_rows)).await?; + } + _ => { + writer.write_command_complete(CompleteStatus::Select(0)).await?; + } + } + + + + // let mut rows = rows.lock().await; + // let first_row = rows.next(); + // match first_row { + // Some(row) => { + // writer.write_table_header(&schema, &row).await?; + // writer.write_table_row(&row).await?; + // + // let mut sent_rows = 1; + // while let Some(row) = rows.next() { + // writer.write_table_row(&row).await?; + // sent_rows += 1; + // + // if token.is_canceled() { + // token.reset(); + // break; + // } + // } + // + // writer.write_command_complete(CompleteStatus::Select(sent_rows)).await?; + // } + // None => { + // writer.write_command_complete(CompleteStatus::Select(0)).await?; + // } + // } + } + _ => {} + } + + Ok(()) +} diff --git a/server/src/parser_stub.rs b/server/src/parser_stub.rs deleted file mode 100644 index c8f3e6a..0000000 --- a/server/src/parser_stub.rs +++ /dev/null @@ -1,63 +0,0 @@ -use minisql::operation::{ColumnSelection, Operation}; -use minisql::schema::TableSchema; -use minisql::type_system::{DbType, IndexableValue, Value}; - -const TABLE_NAME: &'static str = "tablus"; - -static mut ID_COUNTER: u64 = 0; - -pub fn parse_query(query: &str) -> Operation { - if query.contains("select") { - if query.contains("*") { - Operation::Select(TABLE_NAME.to_string(), ColumnSelection::All, None) - } else { - Operation::Select(TABLE_NAME.to_string(), ColumnSelection::Columns(vec![ - "name".to_string(), - "price".to_string(), - ]), None) - } - } else if query.contains("insert") { - - let id = unsafe { - ID_COUNTER += 1; - ID_COUNTER - }; - - let rand_rak = rand::random::(); - let rand_price = rand::random::(); - - Operation::Insert(TABLE_NAME.to_string(), vec![ - ("id".to_string(), Value::Indexable(IndexableValue::Uuid(id))), - ("name".to_string(), Value::Indexable(IndexableValue::String(format!("Car {}", rand_rak)))), - ("price".to_string(), Value::Number(rand_price)), - ("mileage".to_string(), Value::Indexable(IndexableValue::Int(1234))), - ]) - } else if query.contains("delete") { - Operation::Delete(TABLE_NAME.to_string(), None) - } else if query.contains("create table") { - Operation::CreateTable(TABLE_NAME.to_string(), get_cars_schema()) - } else if query.contains("create index") { - Operation::CreateIndex(TABLE_NAME.to_string(), "price".to_string()) - } else { - panic!("Unknown query: {}", query); - } -} - -fn get_cars_schema() -> TableSchema { - TableSchema::new( - "cars".to_string(), - 0, - vec![ - ("id".to_string(), 0), - ("name".to_string(), 1), - ("price".to_string(), 2), - ("mileage".to_string(), 3), - ], - vec![ - DbType::Uuid, - DbType::String, - DbType::Number, - DbType::Int, - ] - ) -} \ No newline at end of file From 4dfabb8cc0497752dfeedd67147725ecdaf86a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Fri, 26 Jan 2024 22:14:09 +0100 Subject: [PATCH 12/21] Revert "tests: asynchronize some interpreter tests" This reverts commit 88fb13325a3f6d8533d86ee70f81fd3e1765d8cb. --- minisql/Cargo.toml | 2 +- minisql/src/interpreter.rs | 58 +++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 9cdfae0..22d0242 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] bimap = "0.6.3" thiserror = "1.0.50" -tokio = { version = "1.35.1", features = ["sync", "rt", "macros"] } +tokio = { version = "1.35.1", features = ["sync"] } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 0be4310..99d3e25 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -211,8 +211,8 @@ mod tests { assert!(table.table_name() == &users); } - #[tokio::test] - async fn test_select_empty() { + #[test] + fn test_select_empty() { let mut state = State::new(); let users_schema = users_schema(); let users = users_schema.table_name().clone(); @@ -227,9 +227,8 @@ mod tests { let Response::Selected(_schema, rows) = response else { panic!() }; - - let mut rows = rows.lock().await; - assert!(rows.next().is_none()); + let rows: Vec<_> = rows.collect(); + assert!(rows.len() == 0); } #[test] @@ -244,8 +243,8 @@ mod tests { assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); } - #[tokio::test] - async fn test_insert_select_basic1() { + #[test] + fn test_insert_select_basic1() { use IndexableValue::*; use Value::*; @@ -281,19 +280,18 @@ mod tests { let Response::Selected(_schema, rows) = response else { panic!() }; + let rows: Vec<_> = rows.collect(); + assert!(rows.len() == 1); + let row = &rows[0]; - let mut rows = rows.lock().await; - let row = rows.next().expect("expected single row"); assert!(row.len() == 3); assert!(row[0].1 == id); assert!(row[1].1 == name); assert!(row[2].1 == age); - - assert!(rows.next().is_none()); } - #[tokio::test] - async fn test_insert_select_basic2() { + #[test] + fn test_insert_select_basic2() { use ColumnSelection::*; use Condition::*; use IndexableValue::*; @@ -348,21 +346,20 @@ mod tests { panic!() }; - let mut rows = rows.lock().await; + let rows: Vec<_> = rows.collect(); + assert!(rows.len() == 2); + let row0 = &rows[0]; + let row1 = &rows[1]; - let row0 = rows.next().expect("expected first row"); assert!(row0.len() == 3); assert!(row0[0].1 == id0); assert!(row0[1].1 == name0); assert!(row0[2].1 == age0); - let row1 = rows.next().expect("expected second row"); assert!(row1.len() == 3); assert!(row1[0].1 == id1); assert!(row1[1].1 == name1); assert!(row1[2].1 == age1); - - assert!(rows.next().is_none()); } { @@ -377,15 +374,14 @@ mod tests { let Response::Selected(_, rows) = response else { panic!() }; - - let mut rows = rows.lock().await; - let row0 = rows.next().expect("expected first row"); + let rows: Vec<_> = rows.collect(); + assert!(rows.len() == 1); + let row0 = &rows[0]; assert!(row0.len() == 3); assert!(row0[0].1 == id0); assert!(row0[1].1 == name0); assert!(row0[2].1 == age0); - assert!(rows.next().is_none()); } { @@ -400,19 +396,18 @@ mod tests { let Response::Selected(_, rows) = response else { panic!() }; - - let mut rows = rows.lock().await; - let row0 = rows.next().expect("expected first row"); + let rows: Vec<_> = rows.collect(); + assert!(rows.len() == 1); + let row0 = &rows[0]; assert!(row0.len() == 2); assert!(row0[0].1 == name0); assert!(row0[1].1 == id0); - assert!(rows.next().is_none()); } } - #[tokio::test] - async fn test_delete() { + #[test] + fn test_delete() { use ColumnSelection::*; use Condition::*; use IndexableValue::*; @@ -475,15 +470,14 @@ mod tests { let Response::Selected(_, rows) = response else { panic!() }; - - let mut rows = rows.lock().await; - let row = rows.next().expect("expected first row"); + let rows: Vec<_> = rows.collect(); + assert!(rows.len() == 1); + let row = &rows[0]; assert!(row.len() == 3); assert!(row[0].1 == id1); assert!(row[1].1 == name1); assert!(row[2].1 == age1); - assert!(rows.next().is_none()); } #[test] From d790337423a6695a28f664831712fc9e1b8c2c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Fri, 26 Jan 2024 22:16:14 +0100 Subject: [PATCH 13/21] chore: cleanup dependencies --- Cargo.lock | 1 - minisql/Cargo.toml | 1 - minisql/src/interpreter.rs | 2 -- server/src/main.rs | 1 - 4 files changed, 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed45328..514f99c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,7 +275,6 @@ version = "0.1.0" dependencies = [ "bimap", "thiserror", - "tokio", ] [[package]] diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 22d0242..6164b6b 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -8,4 +8,3 @@ edition = "2021" [dependencies] bimap = "0.6.3" thiserror = "1.0.50" -tokio = { version = "1.35.1", features = ["sync"] } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 99d3e25..3d38c11 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use crate::error::Error; use crate::internals::row::ColumnPosition; use crate::schema::{TableName, TableSchema}; @@ -7,7 +6,6 @@ use crate::operation::{ColumnSelection, Condition, Operation}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Value}; use bimap::BiMap; -use tokio::sync::Mutex; use crate::restricted_row::RestrictedRow; // Use `TablePosition` as index diff --git a/server/src/main.rs b/server/src/main.rs index 3588cd1..bcee08d 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,7 +7,6 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, RwLock}; use minisql::interpreter::{Response, State}; -use minisql::operation; use parser::parse_and_validate; use proto::handshake::errors::ServerHandshakeError; use proto::handshake::request::HandshakeRequest; From 935d9814ae4563d113878258961d5bef3032366e Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Sat, 27 Jan 2024 18:54:54 +0100 Subject: [PATCH 14/21] Introduce new simplified Operation type for Interpreter --- minisql/src/internals/row.rs | 5 +++ minisql/src/interpreter.rs | 73 +++++++++++++++++++++++++++++++++++- minisql/src/operation.rs | 18 +++++++++ minisql/src/schema.rs | 17 ++++++++- 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/minisql/src/internals/row.rs b/minisql/src/internals/row.rs index ad8dc1e..c0c81c0 100644 --- a/minisql/src/internals/row.rs +++ b/minisql/src/internals/row.rs @@ -1,4 +1,5 @@ use crate::type_system::Value; +use crate::operation::InsertionValuesForInterpreter; use std::ops::{Index, IndexMut}; use std::slice::SliceIndex; @@ -42,6 +43,10 @@ impl Row { Row(vec![]) } + pub fn new_from_insertion_values(insertion_values: InsertionValuesForInterpreter) -> Self { + Row(insertion_values) + } + pub fn with_number_of_columns(number_of_columns: usize) -> Self { Row(Vec::with_capacity(number_of_columns)) } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index e85d085..7907ec9 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -2,7 +2,7 @@ use crate::error::Error; use crate::internals::row::{ColumnPosition, Row}; use crate::schema::{TableName, TableSchema}; use crate::internals::table::Table; -use crate::operation::{ColumnSelection, Condition, Operation}; +use crate::operation::{ColumnSelection, Condition, Operation, OperationForInterpreter, ConditionForInterpreter, ColumnSelectionForInterpreter}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Value}; use bimap::BiMap; @@ -65,6 +65,7 @@ impl State { m } + // TODO: Get rid of this fn table_from_name<'a>(&'a self, table_name: &TableName) -> DbResult<&'a Table> { match self.table_name_position_mapping.get_by_left(table_name) { Some(table_position) => { @@ -75,6 +76,10 @@ impl State { } } + fn table_at<'a>(&'a self, table_position: TablePosition) -> &'a Table { + &self.tables[table_position] + } + fn table_from_name_mut<'b: 'a, 'a>( &'b mut self, table_name: &TableName, @@ -88,6 +93,10 @@ impl State { } } + fn table_at_mut<'a>(&'a mut self, table_position: TablePosition) -> &'a mut Table { + &mut self.tables[table_position] + } + fn attach_table(&mut self, table_name: TableName, table: Table) { let new_table_position: TablePosition = self.tables.len(); self.table_name_position_mapping @@ -95,7 +104,67 @@ impl State { self.tables.push(table); } - pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult> { + pub fn interpret_for_interpreter<'a>(&'a mut self, operation: OperationForInterpreter) -> DbResult> { + // TODO: lock stuff + use OperationForInterpreter::*; + + match operation { + Select(table_position, column_selection, maybe_condition) => { + let table: &Table = self.table_at(table_position); + + let selected_rows = match maybe_condition { + None => { + let x = table.select_all_rows(column_selection); + Box::new(x) as Box + 'a + Send> + }, + + Some(ConditionForInterpreter::Eq(eq_column, value)) => { + let x = + table.select_rows_where_eq( + column_selection, + eq_column, + value, + )?; + Box::new(x) as Box + 'a + Send> + } + }; + + Ok(Response::Selected(selected_rows)) + }, + Insert(table_position, values) => { + let table: &mut Table = self.table_at_mut(table_position); + + let (id, row) = table.schema().row_from_insertion_values_for_interpreter(values)?; + table.insert_row_at(id, row)?; + Ok(Response::Inserted) + } + Delete(table_position, maybe_condition) => { + let table: &mut Table = self.table_at_mut(table_position); + + let rows_affected = match maybe_condition { + None => table.delete_all_rows(), + Some(ConditionForInterpreter::Eq(eq_column, value)) => { + table.delete_rows_where_eq(eq_column, value)? + } + }; + + Ok(Response::Deleted(rows_affected)) + } + CreateTable(table_name, table_schema) => { + let table = Table::new(table_schema); + self.attach_table(table_name, table); + + Ok(Response::TableCreated) + } + CreateIndex(table_position, column) => { + let table: &mut Table = self.table_at_mut(table_position); + table.attach_index(column)?; + Ok(Response::IndexCreated) + } + } + } + + pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult> { // TODO: lock stuff use Operation::*; diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 3b060c9..7c45fe3 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -1,5 +1,7 @@ use crate::schema::{ColumnName, TableName, TableSchema}; use crate::type_system::Value; +use crate::internals::row::ColumnPosition; +use crate::interpreter::TablePosition; // ==============SQL operations================ // TODO: Note that every operation has a table name. @@ -16,13 +18,25 @@ pub enum Operation { // DropTable(TableName), } +pub enum OperationForInterpreter { + Select(TablePosition, ColumnSelectionForInterpreter, Option), + Insert(TablePosition, InsertionValuesForInterpreter), + Delete(TablePosition, Option), + CreateTable(TableName, TableSchema), + CreateIndex(TablePosition, ColumnPosition), +} + pub type InsertionValues = Vec<(ColumnName, Value)>; +pub type InsertionValuesForInterpreter = Vec; + pub enum ColumnSelection { All, Columns(Vec), } +pub type ColumnSelectionForInterpreter = Vec; + pub enum Condition { // And(Box, Box), // Or(Box, Box), @@ -34,6 +48,10 @@ pub enum Condition { // StringCondition(StringCondition), } +pub enum ConditionForInterpreter { + Eq(ColumnPosition, Value), +} + // enum StringCondition { // Prefix(ColumnName, String), // Substring(ColumnName, String), diff --git a/minisql/src/schema.rs b/minisql/src/schema.rs index e029606..90022d2 100644 --- a/minisql/src/schema.rs +++ b/minisql/src/schema.rs @@ -1,6 +1,6 @@ use crate::error::Error; use crate::internals::row::{ColumnPosition, Row}; -use crate::operation::{ColumnSelection, InsertionValues}; +use crate::operation::{ColumnSelection, InsertionValues, InsertionValuesForInterpreter, ColumnSelectionForInterpreter}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Uuid, Value}; use bimap::BiMap; @@ -136,6 +136,21 @@ impl TableSchema { self.column_name_position_mapping.len() } + pub fn row_from_insertion_values_for_interpreter( + &self, + insertion_values: InsertionValuesForInterpreter, + ) -> DbResult<(Uuid, Row)> { + let row: Row = Row::new_from_insertion_values(insertion_values); + + let id: Uuid = match row.get(self.primary_key) { + Some(Value::Indexable(IndexableValue::Uuid(id))) => *id, + Some(_) => unreachable!(), // SAFETY: Should be guaranteed by validation + None => unreachable!(), // SAFETY: Should be guaranteed by validation + }; + + Ok((id, row)) + } + pub fn row_from_insertion_values( &self, insertion_values: InsertionValues, From 76a5be0b79f3ce557567473b0270802e8d2c4c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Sat, 27 Jan 2024 19:44:53 +0100 Subject: [PATCH 15/21] feat: server cleanup --- server/src/main.rs | 54 +++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index bcee08d..a71be68 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,6 +7,7 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, RwLock}; use minisql::interpreter::{Response, State}; +use minisql::operation::Operation; use parser::parse_and_validate; use proto::handshake::errors::ServerHandshakeError; use proto::handshake::request::HandshakeRequest; @@ -27,7 +28,7 @@ mod proto_wrapper; mod cancellation; type TokenStore = Arc>>; -type DbState = Arc>; +type SharedDbState = Arc>; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -53,7 +54,7 @@ async fn main() -> anyhow::Result<()> { } } -async fn handle_stream(mut stream: TcpStream, state: DbState, tokens: TokenStore) -> anyhow::Result<()> { +async fn handle_stream(mut stream: TcpStream, state: SharedDbState, tokens: TokenStore) -> anyhow::Result<()> { let (reader, writer) = stream.split(); let mut writer = ProtoWriter::new(BufWriter::new(writer)); let mut reader = ProtoReader::new(BufReader::new(reader), 1024); @@ -109,7 +110,7 @@ async fn handle_cancellation(pid: i32, key: i32, tokens: &TokenStore) -> anyhow: Ok(()) } -async fn handle_connection(reader: &mut R, writer: &mut W, request: HandshakeRequest, state: DbState, token: ResetCancelToken) -> anyhow::Result<()> +async fn handle_connection(reader: &mut R, writer: &mut W, request: HandshakeRequest, state: SharedDbState, token: ResetCancelToken) -> anyhow::Result<()> where R: FrontendProtoReader + Send, W: BackendProtoWriter + ProtoFlush + Send, @@ -126,8 +127,8 @@ where break; } FrontendMessage::Query(data) => { - let mut state = state.write().await; - let result = handle_query(writer, &mut state, data.query.into(), &token).await; + println!("Received Query: {:?}", data.query); + let result = handle_query(writer, &state, data.query.into(), &token).await; match result { Ok(_) => {} Err(e) => { @@ -143,18 +144,20 @@ where Ok(()) } -async fn handle_query(writer: &mut W, state: &mut State, query: String, token: &ResetCancelToken) -> anyhow::Result<()> +async fn handle_query(writer: &mut W, state: &SharedDbState, query: String, token: &ResetCancelToken) -> anyhow::Result<()> where W: BackendProtoWriter + ProtoFlush + Send, { - let metadata = state.metadata(); - let operation = parse_and_validate(query, &metadata)?; - println!("Parsed query: {:?}", operation); + let operation = { + let state = state.read().await; + let metadata = state.metadata(); + parse_and_validate(query, &metadata)? + }; - let Response = state.interpret(operation)?; - println!("Result: {:?}", Response); + let mut state = state.write().await; + let response = state.interpret(operation)?; - match Response { + match response { Response::Deleted(i) => writer.write_command_complete(CompleteStatus::Delete(i)).await?, Response::Inserted => writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?, Response::Selected(schema, mut rows) => { @@ -179,33 +182,6 @@ where writer.write_command_complete(CompleteStatus::Select(0)).await?; } } - - - - // let mut rows = rows.lock().await; - // let first_row = rows.next(); - // match first_row { - // Some(row) => { - // writer.write_table_header(&schema, &row).await?; - // writer.write_table_row(&row).await?; - // - // let mut sent_rows = 1; - // while let Some(row) = rows.next() { - // writer.write_table_row(&row).await?; - // sent_rows += 1; - // - // if token.is_canceled() { - // token.reset(); - // break; - // } - // } - // - // writer.write_command_complete(CompleteStatus::Select(sent_rows)).await?; - // } - // None => { - // writer.write_command_complete(CompleteStatus::Select(0)).await?; - // } - // } } _ => {} } From 9f3dbe3fdb1480625c535d70166d71bb0eb97fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Sat, 27 Jan 2024 19:45:21 +0100 Subject: [PATCH 16/21] chore: remove unused import --- server/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main.rs b/server/src/main.rs index a71be68..e1aa863 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,7 +7,6 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, RwLock}; use minisql::interpreter::{Response, State}; -use minisql::operation::Operation; use parser::parse_and_validate; use proto::handshake::errors::ServerHandshakeError; use proto::handshake::request::HandshakeRequest; From 08b10636c26a24053c871c86e94dbf8b7759787e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Sat, 27 Jan 2024 20:03:36 +0100 Subject: [PATCH 17/21] refactor: cleanup logging --- server/src/main.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index e1aa863..5672911 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -99,6 +99,8 @@ async fn create_token(tokens: &TokenStore) -> anyhow::Result<(i32, i32, ResetCan } async fn handle_cancellation(pid: i32, key: i32, tokens: &TokenStore) -> anyhow::Result<()> { + println!("Cancel request, PID: {}, Key: {}", pid, key); + let tokens = tokens.lock().await; let token = tokens.get(&(pid, key)); match token { @@ -114,19 +116,16 @@ where R: FrontendProtoReader + Send, W: BackendProtoWriter + ProtoFlush + Send, { - println!("Handshake complete:\n{request:?}"); + println!("Client connected: {:?}", request); loop { - println!("Waiting for next message"); let next: FrontendMessage = reader.read_proto().await?; match next { FrontendMessage::Terminate => { - println!("Received Terminate"); break; } FrontendMessage::Query(data) => { - println!("Received Query: {:?}", data.query); let result = handle_query(writer, &state, data.query.into(), &token).await; match result { Ok(_) => {} From 562e73213892d9e8ba9fc6f3e08ecd0dd47ec8e0 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Sat, 27 Jan 2024 21:22:00 +0100 Subject: [PATCH 18/21] Validation now outputs a Validated Interpreter Operation --- minisql/src/operation.rs | 32 ++++---- minisql/src/schema.rs | 10 +++ parser/src/core.rs | 5 +- parser/src/lib.rs | 1 + parser/src/syntax.rs | 36 +++++++++ parser/src/validation.rs | 169 +++++++++++++++------------------------ 6 files changed, 133 insertions(+), 120 deletions(-) create mode 100644 parser/src/syntax.rs diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 7c45fe3..332aff0 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -18,25 +18,13 @@ pub enum Operation { // DropTable(TableName), } -pub enum OperationForInterpreter { - Select(TablePosition, ColumnSelectionForInterpreter, Option), - Insert(TablePosition, InsertionValuesForInterpreter), - Delete(TablePosition, Option), - CreateTable(TableName, TableSchema), - CreateIndex(TablePosition, ColumnPosition), -} - pub type InsertionValues = Vec<(ColumnName, Value)>; -pub type InsertionValuesForInterpreter = Vec; - pub enum ColumnSelection { All, Columns(Vec), } -pub type ColumnSelectionForInterpreter = Vec; - pub enum Condition { // And(Box, Box), // Or(Box, Box), @@ -48,11 +36,23 @@ pub enum Condition { // StringCondition(StringCondition), } -pub enum ConditionForInterpreter { - Eq(ColumnPosition, Value), -} - // enum StringCondition { // Prefix(ColumnName, String), // Substring(ColumnName, String), // } + +pub enum OperationForInterpreter { + Select(TablePosition, ColumnSelectionForInterpreter, Option), + Insert(TablePosition, InsertionValuesForInterpreter), + Delete(TablePosition, Option), + CreateTable(TableName, TableSchema), + CreateIndex(TablePosition, ColumnPosition), +} + +pub type InsertionValuesForInterpreter = Vec; + +pub type ColumnSelectionForInterpreter = Vec; + +pub enum ConditionForInterpreter { + Eq(ColumnPosition, Value), +} diff --git a/minisql/src/schema.rs b/minisql/src/schema.rs index 90022d2..0d44b76 100644 --- a/minisql/src/schema.rs +++ b/minisql/src/schema.rs @@ -48,6 +48,16 @@ impl TableSchema { self.column_name_position_mapping.get_by_left(column_name).copied() } + pub fn all_selection(&self) -> ColumnSelectionForInterpreter { + self.column_name_position_mapping.iter().map(|(_, column)| *column).collect() + } + + // TODO: Rename to get_column + pub fn get_column0(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> { + let column = self.get_column_position(column_name)?; + Some((column, self.column_type(column))) + } + pub fn get_type_at(&self, column_name: &ColumnName) -> Option { let position = self.get_column_position(column_name)?; self.types.get(position).copied() diff --git a/parser/src/core.rs b/parser/src/core.rs index 248c5c6..258c928 100644 --- a/parser/src/core.rs +++ b/parser/src/core.rs @@ -1,4 +1,5 @@ use minisql::{operation::Operation, schema::TableSchema}; +use crate::syntax::RawQuerySyntax; use nom::{branch::alt, multi::many0, IResult}; use thiserror::Error; @@ -34,7 +35,9 @@ pub fn parse_and_validate(query: String, db_metadata: &Vec<(String, &TableSchema Error::ParsingError(err.to_string()) })?; - validate_operation(&op, db_metadata)?; + // TODO + // validate_operation(&op, db_metadata)?; + todo!(); Ok(op) } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 94b121b..18eab0c 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -2,6 +2,7 @@ mod parsing; mod validation; mod core; +mod syntax; pub use core::parse_and_validate; pub use core::Error; diff --git a/parser/src/syntax.rs b/parser/src/syntax.rs new file mode 100644 index 0000000..039a06d --- /dev/null +++ b/parser/src/syntax.rs @@ -0,0 +1,36 @@ +use minisql::{type_system::Value, schema::{TableSchema, ColumnName, TableName}}; + +// TODO: Move this out into separate file and rename to something like Syntax, SyntaxTree, +// OperationSyntax, RawOperationSyntax +pub enum RawQuerySyntax { + Select(TableName, ColumnSelection, Option), + Insert(TableName, InsertionValues), + Delete(TableName, Option), + // Update(...), + CreateTable(TableName, TableSchema), + CreateIndex(TableName, ColumnName), + // DropTable(TableName), +} + +pub type InsertionValues = Vec<(ColumnName, Value)>; + +pub enum ColumnSelection { + All, + Columns(Vec), +} + +pub enum Condition { + // And(Box, Box), + // Or(Box, Box), + // Not(Box), + Eq(ColumnName, Value), + // LessOrEqual(ColumnName, DbValue), + // Less(ColumnName, DbValue), + + // StringCondition(StringCondition), +} + +// enum StringCondition { +// Prefix(ColumnName, String), +// Substring(ColumnName, String), +// } diff --git a/parser/src/validation.rs b/parser/src/validation.rs index 2c20c24..017491b 100644 --- a/parser/src/validation.rs +++ b/parser/src/validation.rs @@ -1,9 +1,9 @@ - use std::collections::HashSet; +use std::collections::HashMap; use thiserror::Error; -use minisql::{operation::{ColumnSelection, Condition, InsertionValues, Operation}, schema::{TableSchema, ColumnName, TableName}, type_system::DbType}; - +use crate::syntax::{ColumnSelection, Condition, InsertionValues, RawQuerySyntax}; +use minisql::{operation::{ColumnSelectionForInterpreter, ConditionForInterpreter, InsertionValuesForInterpreter, OperationForInterpreter}, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::TablePosition}; #[derive(Debug, Error)] pub enum ValidationError { @@ -25,69 +25,55 @@ pub enum ValidationError { RequiredColumnsAreMissing(Vec) } -pub type DbSchema<'a> = Vec<(TableName, &'a TableSchema)>; +pub type DbSchema<'a> = Vec<(TableName, TablePosition, &'a TableSchema)>; -/// Validates the operation based on db_metadata -pub fn validate_operation(operation: &Operation, db_schema: &DbSchema) -> Result<(), ValidationError> { - match operation { - Operation::Select(table_name, column_selection, condition) => { - validate_select(table_name, column_selection, condition, db_schema)?; +/// Validates and converts the raw syntax into a proper interpreter operation based on db schema. +pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result { + match query { + RawQuerySyntax::Select(table_name, column_selection, condition) => { + validate_select(table_name, column_selection, condition, db_schema) }, - Operation::Insert(table_name, insertion_values) => { - validate_insert(&table_name, insertion_values, db_schema)?; + RawQuerySyntax::Insert(table_name, insertion_values) => { + validate_insert(table_name, insertion_values, db_schema) }, - Operation::Delete(table_name, condition) => { - validate_delete(table_name, condition, db_schema)?; + RawQuerySyntax::Delete(table_name, condition) => { + validate_delete(table_name, condition, db_schema) }, - // Operation::Update(table_name, insertion_values, condition) => { - // validate_update(table_name, insertion_values, db_metadata)?; - // }, - Operation::CreateTable(table_name, schema) => { - validate_create(table_name, schema, db_schema)?; + RawQuerySyntax::CreateTable(table_name, schema) => { + validate_create(table_name, schema, db_schema) }, - Operation::CreateIndex(table_name, column_name) => { - validate_create_index(table_name, column_name, db_schema)?; + RawQuerySyntax::CreateIndex(table_name, column_name) => { + validate_create_index(table_name, column_name, db_schema) }, - // Operation::DropTable(table_name) => { - // validate_drop(table_name, db_schema)?; - // } } - Ok(()) } -fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Result<&'a TableSchema, ValidationError> { - db_schema.iter().find(|(tname, _)| table_name.eq(tname)) +fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Result<(TablePosition, &'a TableSchema), ValidationError> { + db_schema.iter().find(|(tname, _, _)| table_name.eq(tname)) .ok_or(ValidationError::TableDoesNotExist(table_name.to_string())) - .map(|(_, table_schema)| table_schema).copied() + .map(|(_, table_position, table_schema)| (*table_position, *table_schema)) } - -// pub fn validate_drop(table_name: &str, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> { -// db_metadata.iter().find(|(tname, _)| table_name.eq(tname)) -// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?; -// Ok(()) -// } - -pub fn validate_create(table_name: &TableName, schema: &TableSchema, db_schema: &DbSchema) -> Result<(), ValidationError> { - if let Some(_) = get_table_schema(db_schema, table_name) { +pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result { + if let Some(_) = get_table_schema(db_schema, &table_name) { return Err(ValidationError::TableAlreadyExists(table_name.to_string())); } - find_first_duplicate(&schema.get_columns()) + find_first_duplicate(&table_schema.get_columns()) .map_or_else( || Ok(()), |duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string())) )?; // TODO: Ensure it has a primary key?? - Ok(()) + Ok(OperationForInterpreter::CreateTable(table_name, table_schema)) } -pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelection, condition: &Option, db_schema: &Vec<(TableName, &TableSchema)>) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; +pub fn validate_select(table_name: TableName, column_selection: ColumnSelection, condition: Option, db_schema: &DbSchema) -> Result { + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; match column_selection { ColumnSelection::Columns(columns) => { - let non_existant_columns: Vec = + let non_existant_columns: Vec = columns.iter().filter_map(|column| if schema.does_column_exist(&column) { Some(column.clone()) @@ -97,52 +83,21 @@ pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelectio if non_existant_columns.len() > 0 { Err(ValidationError::ColumnsDoNotExist(non_existant_columns)) } else { - validate_condition(condition, schema) + let selection: ColumnSelectionForInterpreter = + columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect(); + let validated_condition = validate_condition(condition, schema)?; + Ok(OperationForInterpreter::Select(table_position, selection, validated_condition)) } } - ColumnSelection::All => Ok(()) + ColumnSelection::All => { + let validated_condition = validate_condition(condition, schema)?; + Ok(OperationForInterpreter::Select(table_position, schema.all_selection(), validated_condition)) + } } } -// pub fn validate_update(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> { -// let schema = validate_table_exists(db_schema, table_name)?; -// let mut column_names = HashSet::new(); -// // Find duplicate columns -// for (name, _) in insertion_values { -// if column_names.contains(name) { -// return Err(ValidationError::DuplicateColumn(name.clone())); -// } else { -// column_names.insert(name.clone()); -// } -// } -// // Ensure columns exist in schema -// let column_value_type: Vec<_> = insertion_values.iter().map(|(column, value)| { -// (column, value, schema.column_name_position_mapping.iter().find(|(name, _) | { -// (*name).eq(column) -// }).map(|(_, t)| schema.types.get(*t as usize))) -// }).collect(); -// if let Some((name, _, _)) = column_value_type.iter().find(|(_, _, t)| { -// t.is_none() -// }) { -// return Err(ValidationError::ColumnsDoNotExist(vec![(*name).clone())]); -// } - -// // Check types -// if let Some((_, _, _)) = column_value_type.iter().find(|(_, value, t)| { -// if let Some(Some(column_type)) = t { -// !type_of(value).eq(column_type) -// } else { -// false -// } -// }) { -// // TODO: Add column name information -// return Err(ValidationError::TypeMismatch); -// } -// Ok(()) -// } - -pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValues, db_schema: &DbSchema) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; +pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues, db_schema: &DbSchema) -> Result { + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; // Check for duplicate columns in insertion_values. let columns_in_query_vec: Vec<&ColumnName> = insertion_values.iter().map(|(column_name, _)| column_name).collect(); @@ -157,56 +112,64 @@ pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValue let columns_in_schema: HashSet<&ColumnName> = HashSet::from_iter(schema.get_columns()); let non_existant_columns = Vec::from_iter(columns_in_query.difference(&columns_in_schema)); if non_existant_columns.len() > 0 { - return Err(ValidationError::ColumnsDoNotExist(non_existant_columns.iter().map(|str| str.to_string()).collect())); + return Err(ValidationError::ColumnsDoNotExist(non_existant_columns.iter().map(|column_name| column_name.to_string()).collect())); } let missing_required_columns = Vec::from_iter(columns_in_schema.difference(&columns_in_query)); if missing_required_columns.len() > 0 { return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect())); } - // Check types + // Check types and prepare for creation of InsertionValues for the interpreter + let mut values_map: HashMap<_, Value> = HashMap::new(); for (column_name, value) in insertion_values { - let expected_type = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error. + let (column, expected_type) = schema.get_column0(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error. let value_type = value.to_type(); if value_type != expected_type { return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }); } + values_map.insert(column, value); } - Ok(()) + // These are values ordered by the column position + let values: InsertionValuesForInterpreter = values_map.into_values().collect(); + + Ok(OperationForInterpreter::Insert(table_position, values)) } -pub fn validate_delete(table_name: &TableName, condition: &Option, db_schema: &DbSchema) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; - validate_condition(condition, schema)?; - Ok(()) +pub fn validate_delete(table_name: TableName, condition: Option, db_schema: &DbSchema) -> Result { + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; + let validated_condition = validate_condition(condition, schema)?; + Ok(OperationForInterpreter::Delete(table_position, validated_condition)) } -fn validate_condition(condition: &Option, schema: &TableSchema) -> Result<(), ValidationError> { +fn validate_condition(condition: Option, schema: &TableSchema) -> Result, ValidationError> { match condition { Some(condition) => { match condition { Condition::Eq(column_name, value) => { - let expected_type: DbType = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; + let (column, expected_type) = schema.get_column0(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; let value_type: DbType = value.to_type(); - if !expected_type.eq(&value_type) { + if expected_type.eq(&value_type) { + Ok(Some(ConditionForInterpreter::Eq(column, value))) + } else { return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }); } } } } - None => {} + None => Ok(None) } - Ok(()) } -fn validate_create_index(table_name: &TableName, column_name: &ColumnName, db_schema: &DbSchema) -> Result<(), ValidationError> { - let schema = validate_table_exists(db_schema, table_name)?; - if schema.does_column_exist(column_name) { - Ok(()) - } else { - Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])) - } +fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result { + // TODO: You should disallow indexing of Number columns. + let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; + schema + .get_column_position(&column_name) + .map_or_else( + || Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])), + |column| Ok(OperationForInterpreter::CreateIndex(table_position, column)) + ) } // ===Helpers=== @@ -225,6 +188,6 @@ where A: Eq + std::hash::Hash } fn get_table_schema<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Option<&'a TableSchema> { - let (_, table_schema) = db_schema.iter().find(|(tname, _)| table_name.eq(tname))?; + let (_, _, table_schema) = db_schema.iter().find(|(tname, _, _)| table_name.eq(tname))?; Some(table_schema) } From 9771a89716193ae377202952105118579a938ae3 Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Sat, 27 Jan 2024 21:47:33 +0100 Subject: [PATCH 19/21] Use RawQuerySyntax for parsing --- minisql/src/interpreter.rs | 16 +++++++++------- parser/src/core.rs | 13 +++++-------- parser/src/parsing/common.rs | 7 +++++-- parser/src/parsing/create.rs | 13 +++++++------ parser/src/parsing/delete.rs | 10 +++++----- parser/src/parsing/index.rs | 24 ++++++++++++------------ parser/src/parsing/insert.rs | 18 ++++++++++-------- parser/src/parsing/select.rs | 16 ++++++++-------- parser/src/validation.rs | 4 +--- server/src/main.rs | 6 +++--- 10 files changed, 65 insertions(+), 62 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 7907ec9..70393cc 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -2,7 +2,7 @@ use crate::error::Error; use crate::internals::row::{ColumnPosition, Row}; use crate::schema::{TableName, TableSchema}; use crate::internals::table::Table; -use crate::operation::{ColumnSelection, Condition, Operation, OperationForInterpreter, ConditionForInterpreter, ColumnSelectionForInterpreter}; +use crate::operation::{ColumnSelection, Condition, Operation, OperationForInterpreter, ConditionForInterpreter}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Value}; use bimap::BiMap; @@ -27,6 +27,8 @@ pub enum Response<'a> { IndexCreated, } +pub type DbSchema<'a> = Vec<(TableName, TablePosition, &'a TableSchema)>; + impl std::fmt::Debug for Response<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { use Response::*; @@ -56,13 +58,13 @@ impl State { } /// TODO: return a reference to avoid allocations - pub fn metadata<'a>(&'a self) -> Vec<(String, &'a TableSchema)> { - let mut m = Vec::new(); - for (name, pos) in &self.table_name_position_mapping { - let table_schema = self.tables.get(*pos).unwrap().schema(); - m.push((name.clone(), table_schema)); + pub fn db_schema<'a>(&'a self) -> DbSchema { + let mut schema: DbSchema = Vec::new(); + for (table_name, &table_position) in &self.table_name_position_mapping { + let table_schema = self.tables[table_position].schema(); + schema.push((table_name.clone(), table_position, table_schema)); } - m + schema } // TODO: Get rid of this diff --git a/parser/src/core.rs b/parser/src/core.rs index 258c928..f173f38 100644 --- a/parser/src/core.rs +++ b/parser/src/core.rs @@ -1,4 +1,4 @@ -use minisql::{operation::Operation, schema::TableSchema}; +use minisql::{operation::OperationForInterpreter, interpreter::DbSchema}; use crate::syntax::RawQuerySyntax; use nom::{branch::alt, multi::many0, IResult}; use thiserror::Error; @@ -13,7 +13,7 @@ pub enum Error { ValidationError(#[from] ValidationError) } -pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, Operation> { +pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, RawQuerySyntax> { alt(( parse_insert, parse_create, @@ -25,20 +25,17 @@ pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, Operation> { ))(input) } -pub fn parse_statements<'a>(input: &'a str) -> IResult<&str, Vec> { +pub fn parse_statements<'a>(input: &'a str) -> IResult<&str, Vec> { many0(parse_statement)(input) } -pub fn parse_and_validate(query: String, db_metadata: &Vec<(String, &TableSchema)>) -> Result { +pub fn parse_and_validate(query: String, db_schema: &DbSchema) -> Result { let (_, op) = parse_statement(query.as_str()) .map_err(|err| { Error::ParsingError(err.to_string()) })?; - // TODO - // validate_operation(&op, db_metadata)?; - todo!(); - Ok(op) + Ok(validate_operation(op, db_schema)?) } // #[test] diff --git a/parser/src/parsing/common.rs b/parser/src/parsing/common.rs index f71d587..6787105 100644 --- a/parser/src/parsing/common.rs +++ b/parser/src/parsing/common.rs @@ -6,8 +6,9 @@ use nom::{ bytes::complete::tag, IResult, branch::alt, }; -use minisql::{operation::Condition, type_system::DbType}; +use minisql::type_system::DbType; +use crate::syntax::Condition; use super::literal::parse_db_value; pub fn parse_table_name(input: &str) -> IResult<&str, &str> { @@ -67,7 +68,9 @@ fn parse_equality(input: &str) -> IResult<&str, Condition> { #[cfg(test)] mod tests { - use minisql::{operation::Condition, type_system::DbType}; + use minisql::type_system::DbType; + + use crate::syntax::Condition; use crate::parsing::common::{parse_db_type, parse_equality}; #[test] diff --git a/parser/src/parsing/create.rs b/parser/src/parsing/create.rs index 94e3538..e950f98 100644 --- a/parser/src/parsing/create.rs +++ b/parser/src/parsing/create.rs @@ -1,4 +1,4 @@ -use minisql::{operation::Operation, schema::{ColumnName, TableSchema}, type_system::DbType}; +use minisql::{schema::{ColumnName, TableSchema}, type_system::DbType}; use nom::{ bytes::complete::tag, character::complete::{char, multispace0, multispace1}, @@ -8,8 +8,9 @@ use nom::{ }; use super::common::{parse_table_name, parse_identifier, parse_db_type}; +use crate::syntax::RawQuerySyntax; -pub fn parse_create(input: &str) -> IResult<&str, Operation> { +pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> { let (input, _) = tag("CREATE")(input)?; let (input, _) = multispace1(input)?; let (input, _) = tag("TABLE")(input)?; @@ -41,7 +42,7 @@ pub fn parse_create(input: &str) -> IResult<&str, Operation> { ); Ok(( input, - Operation::CreateTable(table_name.to_string(), schema), + RawQuerySyntax::CreateTable(table_name.to_string(), schema), )) } @@ -68,8 +69,8 @@ pub fn parse_column_definition(input: &str) -> IResult<&str, (ColumnName, DbType #[cfg(test)] mod tests { - use minisql::operation::Operation; use crate::parsing::create::parse_create; + use crate::syntax::RawQuerySyntax; #[test] fn test_parse_create_no_spaces() { @@ -94,9 +95,9 @@ mod tests { #[test] fn test_parse_create() { let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );").expect("should parse"); - assert!(matches!(create, Operation::CreateTable(_ ,_))); + assert!(matches!(create, RawQuerySyntax::CreateTable(_ ,_))); match create { - Operation::CreateTable(name, schema) => { + RawQuerySyntax::CreateTable(name, schema) => { assert_eq!(name, "Table1"); assert_eq!(schema.number_of_columns(), 2); assert_eq!(schema.column_position_from_column_name(&"id".to_string()).unwrap(), 0); diff --git a/parser/src/parsing/delete.rs b/parser/src/parsing/delete.rs index af71cec..4dc07d2 100644 --- a/parser/src/parsing/delete.rs +++ b/parser/src/parsing/delete.rs @@ -1,13 +1,13 @@ -use minisql::operation::Operation; use nom::{ bytes::complete::tag, character::complete::{char, multispace0, multispace1}, IResult, }; +use crate::syntax::RawQuerySyntax; use super::common::{parse_table_name, parse_condition}; -pub fn parse_delete(input: &str) -> IResult<&str, Operation> { +pub fn parse_delete(input: &str) -> IResult<&str, RawQuerySyntax> { let (input, _) = tag("DELETE")(input)?; let (input, _) = multispace1(input)?; let (input, _) = tag("FROM")(input)?; @@ -19,19 +19,19 @@ pub fn parse_delete(input: &str) -> IResult<&str, Operation> { let (input, _) = char(';')(input)?; Ok(( input, - Operation::Delete(table_name.to_string(), condition), + RawQuerySyntax::Delete(table_name.to_string(), condition), )) } #[cfg(test)] mod tests { - use minisql::operation::Operation; + use crate::syntax::RawQuerySyntax; use crate::parsing::delete::parse_delete; #[test] fn test_parse_delete() { let (_, operation) = parse_delete("DELETE FROM \"T1\" WHERE id = 1 ;").expect("should parse"); - assert!(matches!(operation, Operation::Delete(_, _))) + assert!(matches!(operation, RawQuerySyntax::Delete(_, _))) } // TODO: add test with condition diff --git a/parser/src/parsing/index.rs b/parser/src/parsing/index.rs index 3130a6b..68233ba 100644 --- a/parser/src/parsing/index.rs +++ b/parser/src/parsing/index.rs @@ -1,4 +1,4 @@ -use minisql::operation::Operation; +use crate::syntax::RawQuerySyntax; use nom::{ bytes::complete::tag, character::complete::{char, multispace0, multispace1}, @@ -7,7 +7,7 @@ use nom::{ use super::common::{parse_identifier, parse_table_name}; -pub fn parse_create_index(input: &str) -> IResult<&str, Operation> { +pub fn parse_create_index(input: &str) -> IResult<&str, RawQuerySyntax> { let (input, _) = tag("CREATE")(input)?; let unique = |input| -> IResult<&str, bool> { let (input, _) = multispace1(input)?; @@ -31,23 +31,23 @@ pub fn parse_create_index(input: &str) -> IResult<&str, Operation> { let (input, _) = char(')')(input)?; let (input, _) = multispace0(input)?; let (input, _) = char(';')(input)?; - let operation = Operation::CreateIndex(table_name.to_string(), column_name.to_string()); + let operation = RawQuerySyntax::CreateIndex(table_name.to_string(), column_name.to_string()); Ok((input, operation)) } #[cfg(test)] mod tests { - use minisql::operation::Operation; + use crate::syntax::RawQuerySyntax; use crate::parsing::index::parse_create_index; #[test] fn test_create_index() { - let (_, operation) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);").expect("should parse"); - assert!(matches!(operation, Operation::CreateIndex(_, _))); - match operation { - Operation::CreateIndex(table_name, column_name) => { + let (_, syntax) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);").expect("should parse"); + assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _))); + match syntax { + RawQuerySyntax::CreateIndex(table_name, column_name) => { assert_eq!(table_name, "contacts"); assert_eq!(column_name, "email"); } @@ -57,10 +57,10 @@ mod tests { #[test] fn test_create_index_with_spaces() { - let (_, operation) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;").expect("should parse"); - assert!(matches!(operation, Operation::CreateIndex(_, _))); - match operation { - Operation::CreateIndex(table_name, column_name) => { + let (_, syntax) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;").expect("should parse"); + assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _))); + match syntax { + RawQuerySyntax::CreateIndex(table_name, column_name) => { assert_eq!(table_name, "contacts"); assert_eq!(column_name, "email"); } diff --git a/parser/src/parsing/insert.rs b/parser/src/parsing/insert.rs index 365be68..5f1f64a 100644 --- a/parser/src/parsing/insert.rs +++ b/parser/src/parsing/insert.rs @@ -1,5 +1,6 @@ use super::{literal::parse_db_value, common::{parse_table_name, parse_identifier}}; -use minisql::{operation::Operation, type_system::Value}; +use crate::syntax::RawQuerySyntax; +use minisql::type_system::Value; use nom::{ bytes::complete::tag, character::complete::{multispace0, multispace1, char}, @@ -9,7 +10,7 @@ use nom::{ IResult, }; -pub fn parse_insert(input: &str) -> IResult<&str, Operation> { +pub fn parse_insert(input: &str) -> IResult<&str, RawQuerySyntax> { let (input, _) = tag("INSERT")(input)?; let (input, _) = multispace1(input)?; let (input, _) = tag("INTO")(input)?; @@ -33,7 +34,7 @@ pub fn parse_insert(input: &str) -> IResult<&str, Operation> { let (input, _) = char(';')(input)?; Ok(( input, - Operation::Insert(table_name.to_string(), column_names.into_iter().zip(values).collect()), + RawQuerySyntax::Insert(table_name.to_string(), column_names.into_iter().zip(values).collect()), )) } @@ -48,17 +49,18 @@ pub fn parse_values(input: &str) -> IResult<&str, Vec> { #[cfg(test)] mod tests { - use minisql::{operation::Operation, type_system::{IndexableValue, Value}}; + use minisql::type_system::{IndexableValue, Value}; + use crate::syntax::RawQuerySyntax; use super::parse_insert; #[test] fn test_parse_insert() { let sql = "INSERT INTO \"MyTable\" (id, data) VALUES(1, \"Text\");"; - let operation = parse_insert(sql).expect("should parse"); - match operation { - ("", Operation::Insert(table_name, insertion_values)) => { + let syntax = parse_insert(sql).expect("should parse"); + match syntax { + ("", RawQuerySyntax::Insert(table_name, insertion_values)) => { assert_eq!(table_name, "MyTable"); assert_eq!( insertion_values, @@ -78,7 +80,7 @@ mod tests { let sql = "INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" ) ;"; let operation = parse_insert(sql).expect("should parse"); match operation { - ("", Operation::Insert(table_name, insertion_values)) => { + ("", RawQuerySyntax::Insert(table_name, insertion_values)) => { assert_eq!(table_name, "MyTable"); assert_eq!(insertion_values, vec![ diff --git a/parser/src/parsing/select.rs b/parser/src/parsing/select.rs index c3c4292..e7a09a8 100644 --- a/parser/src/parsing/select.rs +++ b/parser/src/parsing/select.rs @@ -1,5 +1,5 @@ use super::common::{parse_table_name, parse_column_name, parse_condition}; -use minisql::operation::{ColumnSelection, Operation}; +use crate::syntax::{ColumnSelection, RawQuerySyntax}; use nom::{ branch::alt, bytes::complete::tag, @@ -11,7 +11,7 @@ use nom::{ IResult, }; -pub fn parse_select(input: &str) -> IResult<&str, Operation> { +pub fn parse_select(input: &str) -> IResult<&str, RawQuerySyntax> { let (input, _) = tag("SELECT")(input)?; let (input, _) = multispace1(input)?; @@ -27,7 +27,7 @@ pub fn parse_select(input: &str) -> IResult<&str, Operation> { let (input, _) = tag(";")(input)?; Ok(( input, - Operation::Select(table_name.to_string(), column_selection, condition), + RawQuerySyntax::Select(table_name.to_string(), column_selection, condition), )) } @@ -44,7 +44,7 @@ pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection> #[cfg(test)] mod tests { - use minisql::operation::{ColumnSelection, Operation}; + use crate::syntax::{ColumnSelection, RawQuerySyntax}; use crate::parsing::{common::{parse_column_name, parse_table_name}, select::parse_select}; @@ -53,7 +53,7 @@ mod tests { let sql = "SELECT * FROM \"MyTable\";"; let operation = parse_select(sql).expect("should parse"); match operation { - ("", Operation::Select(table_name, column_selection, maybe_condition)) => { + ("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => { assert_eq!(table_name, "MyTable"); assert!(matches!(column_selection, ColumnSelection::All)); assert!(matches!(maybe_condition, None)); @@ -80,7 +80,7 @@ mod tests { let sql = "SELECT name , email FROM \"AddressBook\" ;"; let operation = parse_select(sql).expect("should parse"); match operation { - ("", Operation::Select(table_name, column_selection, maybe_condition)) => { + ("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => { assert_eq!(table_name, "AddressBook"); assert!(matches!(column_selection, ColumnSelection::Columns(_))); match column_selection { @@ -102,11 +102,11 @@ mod tests { #[test] fn test_parse_select_where() { - use minisql::operation::Condition; + use crate::syntax::Condition; let sql = "SELECT * FROM \"AddressBook\" WHERE id = 5 ;"; let operation = parse_select(sql).expect("should parse"); match operation { - ("", Operation::Select(table_name, column_selection, maybe_condition)) => { + ("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => { assert_eq!(table_name, "AddressBook"); assert!(matches!(column_selection, ColumnSelection::All)); assert!(matches!(maybe_condition, Some(Condition::Eq(_, _)))); diff --git a/parser/src/validation.rs b/parser/src/validation.rs index 017491b..9e6a79f 100644 --- a/parser/src/validation.rs +++ b/parser/src/validation.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use thiserror::Error; use crate::syntax::{ColumnSelection, Condition, InsertionValues, RawQuerySyntax}; -use minisql::{operation::{ColumnSelectionForInterpreter, ConditionForInterpreter, InsertionValuesForInterpreter, OperationForInterpreter}, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::TablePosition}; +use minisql::{operation::{ColumnSelectionForInterpreter, ConditionForInterpreter, InsertionValuesForInterpreter, OperationForInterpreter}, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}}; #[derive(Debug, Error)] pub enum ValidationError { @@ -25,8 +25,6 @@ pub enum ValidationError { RequiredColumnsAreMissing(Vec) } -pub type DbSchema<'a> = Vec<(TableName, TablePosition, &'a TableSchema)>; - /// Validates and converts the raw syntax into a proper interpreter operation based on db schema. pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result { match query { diff --git a/server/src/main.rs b/server/src/main.rs index d256374..fcbb6bf 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -53,10 +53,10 @@ async fn handle_stream(mut stream: TcpStream) -> anyhow::Result<()> { } FrontendMessage::Query(data) => { println!("Received Query: {:?}", data); - let metadata = state.metadata(); - match parse_and_validate(data.query.as_str().to_string(), &metadata) { + let db_schema = state.db_schema(); + match parse_and_validate(data.query.as_str().to_string(), &db_schema) { Ok(operation) => { - match state.interpret(operation) { + match state.interpret_for_interpreter(operation) { Ok(_) => { send_query_response(&mut writer).await?; } From 11dc992476f56e9b2f1052128d8164253d5f114d Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Sat, 27 Jan 2024 22:46:19 +0100 Subject: [PATCH 20/21] Cleanup --- minisql/src/error.rs | 6 - minisql/src/internals/row.rs | 4 +- minisql/src/internals/table.rs | 5 +- minisql/src/interpreter.rs | 296 +++++++++++---------------------- minisql/src/operation.rs | 53 +----- minisql/src/schema.rs | 124 +------------- parser/src/core.rs | 4 +- parser/src/parsing/create.rs | 4 +- parser/src/validation.rs | 62 +++---- server/src/main.rs | 2 +- 10 files changed, 157 insertions(+), 403 deletions(-) diff --git a/minisql/src/error.rs b/minisql/src/error.rs index 6000c0e..6660f9d 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,17 +1,11 @@ use crate::internals::row::ColumnPosition; use crate::schema::{ColumnName, TableName}; -use crate::operation::InsertionValues; use crate::type_system::{DbType, Uuid, Value}; #[derive(Debug)] pub enum Error { - TableDoesNotExist(TableName), - ColumnDoesNotExist(TableName, ColumnName), ColumnPositionDoesNotExist(TableName, ColumnPosition), ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value), AttemptingToInsertAlreadyPresentId(TableName, Uuid), - MissingTypeAnnotationOfColumn(TableName, ColumnPosition), - MissingColumnInInsertValues(TableName, ColumnName, InsertionValues), - MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues), AttemptToIndexNonIndexableColumn(TableName, ColumnName), } diff --git a/minisql/src/internals/row.rs b/minisql/src/internals/row.rs index c0c81c0..508ca40 100644 --- a/minisql/src/internals/row.rs +++ b/minisql/src/internals/row.rs @@ -1,5 +1,5 @@ use crate::type_system::Value; -use crate::operation::InsertionValuesForInterpreter; +use crate::operation::InsertionValues; use std::ops::{Index, IndexMut}; use std::slice::SliceIndex; @@ -43,7 +43,7 @@ impl Row { Row(vec![]) } - pub fn new_from_insertion_values(insertion_values: InsertionValuesForInterpreter) -> Self { + pub fn new_from_insertion_values(insertion_values: InsertionValues) -> Self { Row(insertion_values) } diff --git a/minisql/src/internals/table.rs b/minisql/src/internals/table.rs index 2b5a5bc..ac6ca5a 100644 --- a/minisql/src/internals/table.rs +++ b/minisql/src/internals/table.rs @@ -206,6 +206,7 @@ impl Table { match value { IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))), _ => { + // TODO: This validation step is not really necessary. let column_name: ColumnName = self .schema .column_name_from_column_position(column_position)?; @@ -222,8 +223,8 @@ impl Table { match self.indexes.get(&column_position) { Some(index) => { // Note that we are cloning the ids here! This can be very wasteful in some cases. - // It would be possible to just return a reference, - // but this seems fairly non-trivial. + // Theoretically it would be possible to return a reference, + // but after attempting to do this it seems very non-trivial. let ids = index.get(value).cloned(); Ok(ids) } diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 70393cc..b96dc40 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,10 +1,8 @@ -use crate::error::Error; -use crate::internals::row::{ColumnPosition, Row}; +use crate::internals::row::Row; use crate::schema::{TableName, TableSchema}; use crate::internals::table::Table; -use crate::operation::{ColumnSelection, Condition, Operation, OperationForInterpreter, ConditionForInterpreter}; +use crate::operation::{Operation, Condition}; use crate::result::DbResult; -use crate::type_system::{DbType, IndexableValue, Value}; use bimap::BiMap; // Use `TablePosition` as index @@ -57,7 +55,6 @@ impl State { } } - /// TODO: return a reference to avoid allocations pub fn db_schema<'a>(&'a self) -> DbSchema { let mut schema: DbSchema = Vec::new(); for (table_name, &table_position) in &self.table_name_position_mapping { @@ -67,34 +64,10 @@ impl State { schema } - // TODO: Get rid of this - fn table_from_name<'a>(&'a self, table_name: &TableName) -> DbResult<&'a Table> { - match self.table_name_position_mapping.get_by_left(table_name) { - Some(table_position) => { - let table = &self.tables[*table_position]; - Ok(table) - } - None => Err(Error::TableDoesNotExist(table_name.clone())), - } - } - fn table_at<'a>(&'a self, table_position: TablePosition) -> &'a Table { &self.tables[table_position] } - fn table_from_name_mut<'b: 'a, 'a>( - &'b mut self, - table_name: &TableName, - ) -> DbResult<&'a mut Table> { - match self.table_name_position_mapping.get_by_left(table_name) { - Some(table_position) => { - let table = &mut self.tables[*table_position]; - Ok(table) - } - None => Err(Error::TableDoesNotExist(table_name.clone())), - } - } - fn table_at_mut<'a>(&'a mut self, table_position: TablePosition) -> &'a mut Table { &mut self.tables[table_position] } @@ -106,9 +79,9 @@ impl State { self.tables.push(table); } - pub fn interpret_for_interpreter<'a>(&'a mut self, operation: OperationForInterpreter) -> DbResult> { + pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult> { // TODO: lock stuff - use OperationForInterpreter::*; + use Operation::*; match operation { Select(table_position, column_selection, maybe_condition) => { @@ -120,7 +93,7 @@ impl State { Box::new(x) as Box + 'a + Send> }, - Some(ConditionForInterpreter::Eq(eq_column, value)) => { + Some(Condition::Eq(eq_column, value)) => { let x = table.select_rows_where_eq( column_selection, @@ -136,7 +109,7 @@ impl State { Insert(table_position, values) => { let table: &mut Table = self.table_at_mut(table_position); - let (id, row) = table.schema().row_from_insertion_values_for_interpreter(values)?; + let (id, row) = table.schema().row_from_insertion_values(values)?; table.insert_row_at(id, row)?; Ok(Response::Inserted) } @@ -145,7 +118,7 @@ impl State { let rows_affected = match maybe_condition { None => table.delete_all_rows(), - Some(ConditionForInterpreter::Eq(eq_column, value)) => { + Some(Condition::Eq(eq_column, value)) => { table.delete_rows_where_eq(eq_column, value)? } }; @@ -165,85 +138,15 @@ impl State { } } } - - pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult> { - // TODO: lock stuff - use Operation::*; - - match operation { - Select(table_name, column_selection, maybe_condition) => { - let table: &Table = self.table_from_name(&table_name)?; - - let selected_column_positions: Vec = table - .schema() - .column_positions_from_column_selection(&column_selection)?; - let selected_rows = match maybe_condition { - None => { - let x = table.select_all_rows(selected_column_positions); - Box::new(x) as Box + 'a + Send> - }, - - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = table - .schema() - .column_position_from_column_name(&eq_column_name)?; - let x = - table.select_rows_where_eq( - selected_column_positions, - eq_column_position, - value, - )?; - Box::new(x) as Box + 'a + Send> - } - }; - - Ok(Response::Selected(selected_rows)) - } - Insert(table_name, values) => { - let table: &mut Table = self.table_from_name_mut(&table_name)?; - - let (id, row) = table.schema().row_from_insertion_values(values)?; - table.insert_row_at(id, row)?; - Ok(Response::Inserted) - } - Delete(table_name, maybe_condition) => { - let table: &mut Table = self.table_from_name_mut(&table_name)?; - - let rows_affected = match maybe_condition { - None => table.delete_all_rows(), - Some(Condition::Eq(eq_column_name, value)) => { - let eq_column_position = table - .schema() - .column_position_from_column_name(&eq_column_name)?; - table.delete_rows_where_eq(eq_column_position, value)? - } - }; - - Ok(Response::Deleted(rows_affected)) - } - CreateTable(table_name, table_schema) => { - let table = Table::new(table_schema); - self.attach_table(table_name, table); - - Ok(Response::TableCreated) - } - CreateIndex(table_name, column_name) => { - let table: &mut Table = self.table_from_name_mut(&table_name)?; - let column_position: ColumnPosition = table - .schema() - .column_position_from_column_name(&column_name)?; - - table.attach_index(column_position)?; - Ok(Response::IndexCreated) - } - } - } } #[cfg(test)] mod tests { use super::*; + use crate::internals::row::ColumnPosition; use std::collections::HashSet; + use crate::type_system::{DbType, IndexableValue, Value}; + use crate::operation::Operation; fn users_schema() -> TableSchema { let id: ColumnPosition = 0; @@ -284,12 +187,13 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); let users = users_schema.table_name().clone(); + let users_position = 0; state - .interpret(Operation::CreateTable(users.clone(), users_schema)) + .interpret(Operation::CreateTable(users, users_schema.clone())) .unwrap(); let response: Response = state - .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) + .interpret(Operation::Select(users_position, users_schema.all_selection(), None)) .unwrap(); assert!(matches!(response, Response::Selected(_))); let Response::Selected(rows) = response else { @@ -299,18 +203,6 @@ mod tests { assert!(rows.len() == 0); } - #[test] - fn test_select_nonexistant_table() { - let mut state = State::new(); - - let response: DbResult = state.interpret(Operation::Select( - "table_that_doesnt_exist".to_string(), - ColumnSelection::All, - None, - )); - assert!(matches!(response, Err(Error::TableDoesNotExist(_)))); - } - #[test] fn test_insert_select_basic1() { use IndexableValue::*; @@ -318,10 +210,11 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name().clone(); + let users = 0; + state - .interpret(Operation::CreateTable(users.clone(), users_schema)) + .interpret(Operation::CreateTable("users".to_string(), users_schema.clone())) .unwrap(); let (id, name, age) = ( @@ -331,17 +224,17 @@ mod tests { ); state .interpret(Operation::Insert( - users.clone(), + users, vec![ - ("id".to_string(), id.clone()), - ("name".to_string(), name.clone()), - ("age".to_string(), age.clone()), + id.clone(), + name.clone(), + age.clone(), ], )) .unwrap(); let response: Response = state - .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) + .interpret(Operation::Select(users, users_schema.all_selection(), None)) .unwrap(); assert!(matches!(response, Response::Selected(_))); @@ -351,7 +244,7 @@ mod tests { let rows: Vec<_> = rows.collect(); assert!(rows.len() == 1); let row = &rows[0]; - + assert!(row.len() == 3); assert!(row[0] == id); assert!(row[1] == name); @@ -360,7 +253,6 @@ mod tests { #[test] fn test_insert_select_basic2() { - use ColumnSelection::*; use Condition::*; use IndexableValue::*; use Operation::*; @@ -368,10 +260,13 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name().clone(); + let users_position: TablePosition = 0; + + let id_column: ColumnPosition = 0; + let name_column: ColumnPosition = 1; state - .interpret(CreateTable(users.clone(), users_schema)) + .interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone())) .unwrap(); let (id0, name0, age0) = ( @@ -381,11 +276,11 @@ mod tests { ); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), + id0.clone(), + name0.clone(), + age0.clone(), ], )) .unwrap(); @@ -397,17 +292,17 @@ mod tests { ); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), + id1.clone(), + name1.clone(), + age1.clone(), ], )) .unwrap(); { - let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); + let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap(); assert!(matches!(response, Response::Selected(_))); let Response::Selected(rows) = response else { @@ -432,9 +327,9 @@ mod tests { { let response: Response = state .interpret(Select( - users.clone(), - All, - Some(Eq("id".to_string(), id0.clone())), + users_position, + users_schema.all_selection(), + Some(Eq(id_column, id0.clone())), )) .unwrap(); assert!(matches!(response, Response::Selected(_))); @@ -454,9 +349,9 @@ mod tests { { let response: Response = state .interpret(Select( - users.clone(), - Columns(vec!["name".to_string(), "id".to_string()]), - Some(Eq("id".to_string(), id0.clone())), + users_position, + vec![name_column, id_column], + Some(Eq(id_column, id0.clone())), )) .unwrap(); assert!(matches!(response, Response::Selected(_))); @@ -475,7 +370,6 @@ mod tests { #[test] fn test_delete() { - use ColumnSelection::*; use Condition::*; use IndexableValue::*; use Operation::*; @@ -483,10 +377,12 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name().clone(); + let users_position: TablePosition = 0; + + let id_column: ColumnPosition = 0; state - .interpret(CreateTable(users.clone(), users_schema)) + .interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone())) .unwrap(); let (id0, name0, age0) = ( @@ -496,11 +392,11 @@ mod tests { ); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), + id0.clone(), + name0.clone(), + age0.clone(), ], )) .unwrap(); @@ -512,11 +408,11 @@ mod tests { ); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), + id1.clone(), + name1.clone(), + age1.clone(), ], )) .unwrap(); @@ -524,14 +420,14 @@ mod tests { { let delete_response: Response = state .interpret(Delete( - users.clone(), - Some(Eq("id".to_string(), id0.clone())), + users_position, + Some(Eq(id_column, id0.clone())), )) .unwrap(); assert!(matches!(delete_response, Response::Deleted(1))); } - let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); + let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap(); assert!(matches!(response, Response::Selected(_))); let Response::Selected(rows) = response else { @@ -555,14 +451,16 @@ mod tests { let mut state = State::new(); let users_schema = users_schema(); - let users = users_schema.table_name().clone(); + let users_position: TablePosition = 0; + + let name_column: ColumnPosition = 1; state - .interpret(CreateTable(users.clone(), users_schema)) + .interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone())) .unwrap(); state - .interpret(CreateIndex(users.clone(), "name".to_string())) + .interpret(CreateIndex(users_position, name_column)) .unwrap(); let (id0, name0, age0) = ( @@ -572,11 +470,11 @@ mod tests { ); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), + id0.clone(), + name0.clone(), + age0.clone(), ], )) .unwrap(); @@ -588,11 +486,11 @@ mod tests { ); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), + id1.clone(), + name1.clone(), + age1.clone(), ], )) .unwrap(); @@ -617,33 +515,35 @@ mod tests { } pub fn example() { - use ColumnSelection::*; + use crate::type_system::{IndexableValue, Value, DbType}; + use crate::internals::row::ColumnPosition; use Condition::*; use IndexableValue::*; use Operation::*; use Value::*; - let users_schema: TableSchema = { - let id: ColumnPosition = 0; - let name: ColumnPosition = 1; - let age: ColumnPosition = 2; + let id_column: ColumnPosition = 0; + let name_column: ColumnPosition = 1; + let age_column: ColumnPosition = 2; + let users_schema: TableSchema = { TableSchema::new( "users".to_string(), - id, + id_column, vec!( - ("id".to_string(), id), - ("name".to_string(), name), - ("age".to_string(), age), + ("id".to_string(), id_column), + ("name".to_string(), name_column), + ("age".to_string(), age_column), ), vec![DbType::Uuid, DbType::String, DbType::Int], ) }; + let users_position: TablePosition = 0; let users = users_schema.table_name().clone(); let mut state = State::new(); state - .interpret(Operation::CreateTable(users.clone(), users_schema)) + .interpret(Operation::CreateTable(users, users_schema.clone())) .unwrap(); let (id0, name0, age0) = ( @@ -654,11 +554,11 @@ pub fn example() { println!("==INSERT Plato=="); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id0.clone()), - ("name".to_string(), name0.clone()), - ("age".to_string(), age0.clone()), + id0.clone(), + name0.clone(), + age0.clone(), ], )) .unwrap(); @@ -671,11 +571,11 @@ pub fn example() { println!("==INSERT Aristotle=="); state .interpret(Insert( - users.clone(), + users_position, vec![ - ("id".to_string(), id1.clone()), - ("name".to_string(), name1.clone()), - ("age".to_string(), age1.clone()), + id1.clone(), + name1.clone(), + age1.clone(), ], )) .unwrap(); @@ -683,7 +583,7 @@ pub fn example() { { let response: Response = state - .interpret(Operation::Select(users.clone(), ColumnSelection::All, None)) + .interpret(Operation::Select(users_position, users_schema.all_selection(), None)) .unwrap(); println!("==SELECT ALL=="); println!("{:?}", response); @@ -692,9 +592,9 @@ pub fn example() { { let response: Response = state .interpret(Select( - users.clone(), - All, - Some(Eq("id".to_string(), id0.clone())), + users_position, + users_schema.all_selection(), + Some(Eq(id_column, id0.clone())), )) .unwrap(); println!("==SELECT Plato=="); @@ -708,16 +608,16 @@ pub fn example() { // "infer" them? let _delete_response: Response = state .interpret(Delete( - users.clone(), - Some(Eq("id".to_string(), id0.clone())), + users_position, + Some(Eq(id_column, id0.clone())), )) .unwrap(); println!("==DELETE Plato=="); } let response: Response = state .interpret(Select( - users.clone(), - Columns(vec!["name".to_string(), "id".to_string()]), + users_position, + vec![name_column, id_column], None, )) .unwrap(); diff --git a/minisql/src/operation.rs b/minisql/src/operation.rs index 332aff0..6bbf918 100644 --- a/minisql/src/operation.rs +++ b/minisql/src/operation.rs @@ -1,58 +1,21 @@ -use crate::schema::{ColumnName, TableName, TableSchema}; +use crate::schema::{TableName, TableSchema}; use crate::type_system::Value; use crate::internals::row::ColumnPosition; use crate::interpreter::TablePosition; -// ==============SQL operations================ -// TODO: Note that every operation has a table name. -// Perhaps consider factoring the table name out -// and think of the operations as operating on a unique table. -// TODO: `TableName` should be replaced by `TablePosition` +// Validated operation. Constructed by validation crate. pub enum Operation { - Select(TableName, ColumnSelection, Option), - Insert(TableName, InsertionValues), - Delete(TableName, Option), - // Update(...), - CreateTable(TableName, TableSchema), - CreateIndex(TableName, ColumnName), - // DropTable(TableName), -} - -pub type InsertionValues = Vec<(ColumnName, Value)>; - -pub enum ColumnSelection { - All, - Columns(Vec), -} - -pub enum Condition { - // And(Box, Box), - // Or(Box, Box), - // Not(Box), - Eq(ColumnName, Value), - // LessOrEqual(ColumnName, DbValue), - // Less(ColumnName, DbValue), - - // StringCondition(StringCondition), -} - -// enum StringCondition { -// Prefix(ColumnName, String), -// Substring(ColumnName, String), -// } - -pub enum OperationForInterpreter { - Select(TablePosition, ColumnSelectionForInterpreter, Option), - Insert(TablePosition, InsertionValuesForInterpreter), - Delete(TablePosition, Option), + Select(TablePosition, ColumnSelection, Option), + Insert(TablePosition, InsertionValues), + Delete(TablePosition, Option), CreateTable(TableName, TableSchema), CreateIndex(TablePosition, ColumnPosition), } -pub type InsertionValuesForInterpreter = Vec; +pub type InsertionValues = Vec; -pub type ColumnSelectionForInterpreter = Vec; +pub type ColumnSelection = Vec; -pub enum ConditionForInterpreter { +pub enum Condition { Eq(ColumnPosition, Value), } diff --git a/minisql/src/schema.rs b/minisql/src/schema.rs index 0d44b76..97cd87b 100644 --- a/minisql/src/schema.rs +++ b/minisql/src/schema.rs @@ -1,14 +1,13 @@ use crate::error::Error; use crate::internals::row::{ColumnPosition, Row}; -use crate::operation::{ColumnSelection, InsertionValues, InsertionValuesForInterpreter, ColumnSelectionForInterpreter}; +use crate::operation::{InsertionValues, ColumnSelection}; use crate::result::DbResult; use crate::type_system::{DbType, IndexableValue, Uuid, Value}; use bimap::BiMap; -use std::collections::HashMap; // Note that it is nice to split metadata from the data because // then you can give the metadata to the parser without giving it the data. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TableSchema { table_name: TableName, // used for descriptive errors primary_key: ColumnPosition, @@ -48,12 +47,13 @@ impl TableSchema { self.column_name_position_mapping.get_by_left(column_name).copied() } - pub fn all_selection(&self) -> ColumnSelectionForInterpreter { - self.column_name_position_mapping.iter().map(|(_, column)| *column).collect() + pub fn all_selection(&self) -> ColumnSelection { + let mut selection: ColumnSelection = self.column_name_position_mapping.iter().map(|(_, column)| *column).collect(); + selection.sort(); + selection } - // TODO: Rename to get_column - pub fn get_column0(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> { + pub fn get_column(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> { let column = self.get_column_position(column_name)?; Some((column, self.column_type(column))) } @@ -63,48 +63,10 @@ impl TableSchema { self.types.get(position).copied() } - // TODO: Get rid of this after validation is merged - fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { - match self.column_name_position_mapping.get_by_left(column_name) { - Some(column_position) => match self.types.get(*column_position) { - Some(type_) => Ok((*type_, *column_position)), - None => Err(Error::MissingTypeAnnotationOfColumn( - self.table_name.clone(), - *column_position, - )), - }, - None => Err(Error::ColumnDoesNotExist( - self.table_name.clone(), - column_name.clone(), - )), - } - } - - // TODO: Get rid of this after validation is merged - pub fn column_position_from_column_name( - &self, - column_name: &ColumnName, - ) -> DbResult { - self.get_column(column_name) - .map(|(_, column_position)| column_position) - } - pub fn is_primary(&self, column_position: ColumnPosition) -> bool { self.primary_key == column_position } - fn column_positions_from_column_names( - &self, - column_names: &[ColumnName], - ) -> DbResult> { - let mut positions: Vec = Vec::with_capacity(column_names.len()); - for column_name in column_names { - let column_position = self.column_position_from_column_name(column_name)?; - positions.push(column_position) - } - Ok(positions) - } - pub fn column_name_from_column_position( &self, column_position: ColumnPosition, @@ -121,34 +83,13 @@ impl TableSchema { } } - pub fn column_positions_from_column_selection( - &self, - column_selection: &ColumnSelection, - ) -> DbResult> { - match column_selection { - ColumnSelection::All => { - let mut column_positions: Vec = self - .column_name_position_mapping - .iter() - .map(|(_, column_position)| *column_position) - .collect(); - column_positions.sort(); - Ok(column_positions) - } - - ColumnSelection::Columns(column_names) => { - self.column_positions_from_column_names(column_names) - } - } - } - pub fn number_of_columns(&self) -> usize { self.column_name_position_mapping.len() } - pub fn row_from_insertion_values_for_interpreter( + pub fn row_from_insertion_values( &self, - insertion_values: InsertionValuesForInterpreter, + insertion_values: InsertionValues, ) -> DbResult<(Uuid, Row)> { let row: Row = Row::new_from_insertion_values(insertion_values); @@ -160,51 +101,4 @@ impl TableSchema { Ok((id, row)) } - - pub fn row_from_insertion_values( - &self, - insertion_values: InsertionValues, - ) -> DbResult<(Uuid, Row)> { - // TODO: There should be proper validation of the insertion_values. - // And it shouldn't really be done here. - // - // In the below we don't check for duplicate column names - // - let number_of_columns = self.number_of_columns(); - if number_of_columns != insertion_values.len() { - return Err(Error::MismatchBetweenInsertValuesAndColumns( - self.table_name.clone(), - insertion_values, - )); - } - - let mut row: Row = Row::with_number_of_columns(number_of_columns); - - let mut values: HashMap = HashMap::new(); - for (column_name, db_value) in &insertion_values { - values.insert(column_name.clone(), db_value.clone()); - } - - for column_position in 0..number_of_columns { - let column_name: ColumnName = self.column_name_from_column_position(column_position)?; - match values.get(&column_name) { - Some(db_value) => row.push(db_value.clone()), - None => { - return Err(Error::MissingColumnInInsertValues( - self.table_name.clone(), - column_name, - insertion_values, - )) - } - } - } - - let id: Uuid = match row.get(self.primary_key) { - Some(Value::Indexable(IndexableValue::Uuid(id))) => *id, - Some(_) => unreachable!(), - None => unreachable!(), - }; - - Ok((id, row)) - } } diff --git a/parser/src/core.rs b/parser/src/core.rs index f173f38..2cd2432 100644 --- a/parser/src/core.rs +++ b/parser/src/core.rs @@ -1,4 +1,4 @@ -use minisql::{operation::OperationForInterpreter, interpreter::DbSchema}; +use minisql::{operation::Operation, interpreter::DbSchema}; use crate::syntax::RawQuerySyntax; use nom::{branch::alt, multi::many0, IResult}; use thiserror::Error; @@ -29,7 +29,7 @@ pub fn parse_statements<'a>(input: &'a str) -> IResult<&str, Vec many0(parse_statement)(input) } -pub fn parse_and_validate(query: String, db_schema: &DbSchema) -> Result { +pub fn parse_and_validate(query: String, db_schema: &DbSchema) -> Result { let (_, op) = parse_statement(query.as_str()) .map_err(|err| { Error::ParsingError(err.to_string()) diff --git a/parser/src/parsing/create.rs b/parser/src/parsing/create.rs index e950f98..c82df31 100644 --- a/parser/src/parsing/create.rs +++ b/parser/src/parsing/create.rs @@ -100,8 +100,8 @@ mod tests { RawQuerySyntax::CreateTable(name, schema) => { assert_eq!(name, "Table1"); assert_eq!(schema.number_of_columns(), 2); - assert_eq!(schema.column_position_from_column_name(&"id".to_string()).unwrap(), 0); - assert_eq!(schema.column_position_from_column_name(&"column1".to_string()).unwrap(), 1); + assert_eq!(schema.get_column_position(&"id".to_string()).unwrap(), 0); + assert_eq!(schema.get_column_position(&"column1".to_string()).unwrap(), 1); } _ => {} } diff --git a/parser/src/validation.rs b/parser/src/validation.rs index 9e6a79f..0f60f00 100644 --- a/parser/src/validation.rs +++ b/parser/src/validation.rs @@ -2,8 +2,10 @@ use std::collections::HashSet; use std::collections::HashMap; use thiserror::Error; -use crate::syntax::{ColumnSelection, Condition, InsertionValues, RawQuerySyntax}; -use minisql::{operation::{ColumnSelectionForInterpreter, ConditionForInterpreter, InsertionValuesForInterpreter, OperationForInterpreter}, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}}; +use crate::syntax; +use crate::syntax::RawQuerySyntax; +use minisql::operation; +use minisql::{operation::Operation, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}}; #[derive(Debug, Error)] pub enum ValidationError { @@ -26,7 +28,7 @@ pub enum ValidationError { } /// Validates and converts the raw syntax into a proper interpreter operation based on db schema. -pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result { +pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result { match query { RawQuerySyntax::Select(table_name, column_selection, condition) => { validate_select(table_name, column_selection, condition, db_schema) @@ -52,7 +54,7 @@ fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName .map(|(_, table_position, table_schema)| (*table_position, *table_schema)) } -pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result { +pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result { if let Some(_) = get_table_schema(db_schema, &table_name) { return Err(ValidationError::TableAlreadyExists(table_name.to_string())); } @@ -64,13 +66,13 @@ pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_sche )?; // TODO: Ensure it has a primary key?? - Ok(OperationForInterpreter::CreateTable(table_name, table_schema)) + Ok(Operation::CreateTable(table_name, table_schema)) } -pub fn validate_select(table_name: TableName, column_selection: ColumnSelection, condition: Option, db_schema: &DbSchema) -> Result { +pub fn validate_select(table_name: TableName, column_selection: syntax::ColumnSelection, condition: Option, db_schema: &DbSchema) -> Result { let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; match column_selection { - ColumnSelection::Columns(columns) => { + syntax::ColumnSelection::Columns(columns) => { let non_existant_columns: Vec = columns.iter().filter_map(|column| if schema.does_column_exist(&column) { @@ -81,20 +83,20 @@ pub fn validate_select(table_name: TableName, column_selection: ColumnSelection, if non_existant_columns.len() > 0 { Err(ValidationError::ColumnsDoNotExist(non_existant_columns)) } else { - let selection: ColumnSelectionForInterpreter = + let selection: operation::ColumnSelection = columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect(); let validated_condition = validate_condition(condition, schema)?; - Ok(OperationForInterpreter::Select(table_position, selection, validated_condition)) + Ok(Operation::Select(table_position, selection, validated_condition)) } } - ColumnSelection::All => { + syntax::ColumnSelection::All => { let validated_condition = validate_condition(condition, schema)?; - Ok(OperationForInterpreter::Select(table_position, schema.all_selection(), validated_condition)) + Ok(Operation::Select(table_position, schema.all_selection(), validated_condition)) } } } -pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues, db_schema: &DbSchema) -> Result { +pub fn validate_insert(table_name: TableName, insertion_values: syntax::InsertionValues, db_schema: &DbSchema) -> Result { let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; // Check for duplicate columns in insertion_values. @@ -120,7 +122,7 @@ pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues, // Check types and prepare for creation of InsertionValues for the interpreter let mut values_map: HashMap<_, Value> = HashMap::new(); for (column_name, value) in insertion_values { - let (column, expected_type) = schema.get_column0(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error. + let (column, expected_type) = schema.get_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error. let value_type = value.to_type(); if value_type != expected_type { return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }); @@ -129,26 +131,26 @@ pub fn validate_insert(table_name: TableName, insertion_values: InsertionValues, } // These are values ordered by the column position - let values: InsertionValuesForInterpreter = values_map.into_values().collect(); + let values: operation::InsertionValues = values_map.into_values().collect(); - Ok(OperationForInterpreter::Insert(table_position, values)) + Ok(Operation::Insert(table_position, values)) } -pub fn validate_delete(table_name: TableName, condition: Option, db_schema: &DbSchema) -> Result { +pub fn validate_delete(table_name: TableName, condition: Option, db_schema: &DbSchema) -> Result { let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; let validated_condition = validate_condition(condition, schema)?; - Ok(OperationForInterpreter::Delete(table_position, validated_condition)) + Ok(Operation::Delete(table_position, validated_condition)) } -fn validate_condition(condition: Option, schema: &TableSchema) -> Result, ValidationError> { +fn validate_condition(condition: Option, schema: &TableSchema) -> Result, ValidationError> { match condition { Some(condition) => { match condition { - Condition::Eq(column_name, value) => { - let (column, expected_type) = schema.get_column0(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; + syntax::Condition::Eq(column_name, value) => { + let (column, expected_type) = schema.get_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; let value_type: DbType = value.to_type(); if expected_type.eq(&value_type) { - Ok(Some(ConditionForInterpreter::Eq(column, value))) + Ok(Some(operation::Condition::Eq(column, value))) } else { return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type }); } @@ -159,27 +161,27 @@ fn validate_condition(condition: Option, schema: &TableSchema) -> Res } } -fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result { +fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result { // TODO: You should disallow indexing of Number columns. let (table_position, schema) = validate_table_exists(db_schema, &table_name)?; schema .get_column_position(&column_name) .map_or_else( || Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])), - |column| Ok(OperationForInterpreter::CreateIndex(table_position, column)) + |column| Ok(Operation::CreateIndex(table_position, column)) ) } // ===Helpers=== -fn find_first_duplicate(xs: &[A]) -> Option<&A> -where A: Eq + std::hash::Hash +fn find_first_duplicate(ts: &[T]) -> Option<&T> +where T: Eq + std::hash::Hash { - let mut already_seen_elements: HashSet<&A> = HashSet::new(); - for x in xs { - if already_seen_elements.contains(x) { - return Some(x); + let mut already_seen_elements: HashSet<&T> = HashSet::new(); + for t in ts { + if already_seen_elements.contains(t) { + return Some(t); } else { - already_seen_elements.insert(&x); + already_seen_elements.insert(&t); } } None diff --git a/server/src/main.rs b/server/src/main.rs index fcbb6bf..add7b2b 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -56,7 +56,7 @@ async fn handle_stream(mut stream: TcpStream) -> anyhow::Result<()> { let db_schema = state.db_schema(); match parse_and_validate(data.query.as_str().to_string(), &db_schema) { Ok(operation) => { - match state.interpret_for_interpreter(operation) { + match state.interpret(operation) { Ok(_) => { send_query_response(&mut writer).await?; } From fdfdaa9fc02a6db5d91de6bbc4b1da3fc1a39e4d Mon Sep 17 00:00:00 2001 From: Yuriy Dupyn <2153100+omedusyo@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:42:03 +0100 Subject: [PATCH 21/21] Return table schema in SELECT response --- minisql/src/interpreter.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index b96dc40..dd0e870 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -18,7 +18,7 @@ pub struct State { // #[derive(Debug)] pub enum Response<'a> { - Selected(Box + 'a + Send>), + Selected(&'a TableSchema, Box + 'a + Send>), Inserted, Deleted(usize), // how many were deleted TableCreated, @@ -31,7 +31,7 @@ impl std::fmt::Debug for Response<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { use Response::*; match self { - Selected(_rows) => + Selected(_schema, _rows) => // TODO: How can we iterate through the rows without having to take ownership of // them? f.write_str("Some rows... trust me"), @@ -104,7 +104,7 @@ impl State { } }; - Ok(Response::Selected(selected_rows)) + Ok(Response::Selected(table.schema(), selected_rows)) }, Insert(table_position, values) => { let table: &mut Table = self.table_at_mut(table_position); @@ -195,8 +195,8 @@ mod tests { let response: Response = state .interpret(Operation::Select(users_position, users_schema.all_selection(), None)) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -237,8 +237,8 @@ mod tests { .interpret(Operation::Select(users, users_schema.all_selection(), None)) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -304,8 +304,8 @@ mod tests { { let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -332,8 +332,8 @@ mod tests { Some(Eq(id_column, id0.clone())), )) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -354,8 +354,8 @@ mod tests { Some(Eq(id_column, id0.clone())), )) .unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -429,8 +429,8 @@ mod tests { let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap(); - assert!(matches!(response, Response::Selected(_))); - let Response::Selected(rows) = response else { + assert!(matches!(response, Response::Selected(_, _))); + let Response::Selected(_schema, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect();