From 8fc271695af5d92a2c7cc1f50a2def7b75d6c2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20Moravec?= Date: Sun, 28 Jan 2024 20:45:09 +0100 Subject: [PATCH] fix: empty select returns header --- Cargo.lock | 20 +++++++++++ minisql/src/interpreter.rs | 36 ++++++++++---------- minisql/src/type_system.rs | 68 ++++++++++++++++++------------------- server/Cargo.toml | 2 ++ server/src/main.rs | 4 +-- server/src/proto_wrapper.rs | 34 +++++++++++++------ 6 files changed, 99 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44c853f..5ea9fdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,24 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_seeder" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2890aaef0aa82719a50e808de264f9484b74b442e1a3a0e5ee38243ac40bdb" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -550,6 +568,8 @@ dependencies = [ "parser", "proto", "rand", + "rand_pcg", + "rand_seeder", "serde_json", "tokio", ] diff --git a/minisql/src/interpreter.rs b/minisql/src/interpreter.rs index 08602af..1544141 100644 --- a/minisql/src/interpreter.rs +++ b/minisql/src/interpreter.rs @@ -1,7 +1,7 @@ use crate::internals::row::ColumnPosition; use crate::schema::{TableName, TableSchema}; use crate::internals::table::Table; -use crate::operation::{Operation, Condition}; +use crate::operation::{Operation, Condition, ColumnSelection}; use crate::result::DbResult; use bimap::BiMap; use serde::{Deserialize, Serialize}; @@ -20,7 +20,7 @@ pub struct State { // #[derive(Debug)] pub enum Response<'a> { - Selected(&'a TableSchema, Box + 'a + Send>), + Selected(&'a TableSchema, ColumnSelection, Box + 'a + Send>), Inserted, Deleted(usize), // how many were deleted TableCreated, @@ -33,7 +33,7 @@ impl std::fmt::Debug for Response<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { use Response::*; match self { - Selected(_schema, _rows) => + Selected(_schema, _columns, _rows) => // TODO: How can we iterate through the rows without having to take ownership of // them? f.write_str("Some rows... trust me"), @@ -91,14 +91,14 @@ impl State { let selected_rows = match maybe_condition { None => { - let rows = table.select_all_rows(column_selection); + let rows = table.select_all_rows(column_selection.clone()); Box::new(rows) as Box + 'a + Send> }, Some(Condition::Eq(eq_column, value)) => { let x = table.select_rows_where_eq( - column_selection, + column_selection.clone(), eq_column, value, )?; @@ -106,7 +106,7 @@ impl State { } }; - Ok(Response::Selected(table.schema(), selected_rows)) + Ok(Response::Selected(table.schema(), column_selection, selected_rows)) }, Insert(table_position, values) => { let table: &mut Table = self.table_at_mut(table_position); @@ -192,8 +192,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(_schema, rows) = response else { + assert!(matches!(response, Response::Selected(_, _, _))); + let Response::Selected(_, _, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -234,8 +234,8 @@ mod tests { .interpret(Operation::Select(users, users_schema.all_selection(), None)) .unwrap(); - assert!(matches!(response, Response::Selected(_, _))); - let Response::Selected(_schema, rows) = response else { + assert!(matches!(response, Response::Selected(_, _, _))); + let Response::Selected(_, _, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -301,8 +301,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(_, _, rows) = response else { panic!() }; @@ -330,8 +330,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(_, _, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -352,8 +352,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(_, _, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); @@ -427,8 +427,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(_, _, rows) = response else { panic!() }; let rows: Vec<_> = rows.collect(); diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index f098ed5..d9248fc 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -41,6 +41,23 @@ impl DbType { } } + pub fn type_oid(&self) -> i32 { + match self { + Self::String => 25, + Self::Int => 23, + Self::Number => 701, + Self::Uuid => 2950, + } + } + + pub fn type_size(&self) -> i16 { + match self { + Self::String => -2, // null terminated string + Self::Int => 8, + Self::Number => 8, + Self::Uuid => 16, + } + } } impl Value { @@ -54,29 +71,6 @@ 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) -> i16 { - 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(), @@ -123,8 +117,9 @@ mod tests { #[test] fn test_encode_number() { let value = Value::Number(123.456); - let oid = value.type_oid(); - let size = value.type_size(); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); @@ -138,8 +133,9 @@ mod tests { #[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 vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); @@ -153,8 +149,9 @@ mod tests { #[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 vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); @@ -168,8 +165,9 @@ mod tests { #[test] fn test_encode_int() { let value = Value::Indexable(IndexableValue::Int(123)); - let oid = value.type_oid(); - let size = value.type_size(); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); @@ -183,8 +181,9 @@ mod tests { #[test] fn test_encode_uuid() { let value = Value::Indexable(IndexableValue::Uuid(123)); - let oid = value.type_oid(); - let size = value.type_size(); + let vtype = value.to_type(); + let oid = vtype.type_oid(); + let size = vtype.type_size(); let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap(); @@ -198,7 +197,8 @@ mod tests { #[test] fn test_mismatched_size() { let value = Value::Indexable(IndexableValue::Uuid(123)); - let oid = value.type_oid(); + let vtype = value.to_type(); + let oid = vtype.type_oid(); let size = 8; let bytes = value.as_text_bytes(); diff --git a/server/Cargo.toml b/server/Cargo.toml index 67c87d7..2b76e8e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,6 +11,8 @@ anyhow = "1.0.76" clap = { version = "4.4.18", features = ["derive"] } async-trait = "0.1.74" rand = "0.8.5" +rand_seeder = "0.2.3" +rand_pcg = "0.3.1" serde_json = "1.0.112" minisql = { path = "../minisql" } proto = { path = "../proto" } diff --git a/server/src/main.rs b/server/src/main.rs index ea69c48..da7a08a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -186,10 +186,10 @@ async fn handle_query(writer: &mut W, state: &SharedDbState, query: String, t writer.write_command_complete(CompleteStatus::Insert { oid: 0, rows: 1 }).await?; true } - Response::Selected(schema, mut rows) => { + Response::Selected(schema, columns, mut rows) => { + writer.write_table_header(&schema, &columns).await?; match rows.next() { Some(row) => { - writer.write_table_header(&schema, &row).await?; writer.write_table_row(&row).await?; let mut sent_rows = 1; diff --git a/server/src/proto_wrapper.rs b/server/src/proto_wrapper.rs index 03ce8f5..7f70613 100644 --- a/server/src/proto_wrapper.rs +++ b/server/src/proto_wrapper.rs @@ -1,7 +1,10 @@ use async_trait::async_trait; +use rand::Rng; +use rand_pcg::Pcg64; +use rand_seeder::Seeder; +use minisql::operation::ColumnSelection; 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; @@ -34,7 +37,7 @@ 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_header(&mut self, table_schema: &TableSchema, columns: &ColumnSelection) -> anyhow::Result<()>; async fn write_table_row(&mut self, row: &RestrictedRow) -> anyhow::Result<()>; async fn write_command_complete(&mut self, status: CompleteStatus) -> anyhow::Result<()>; } @@ -60,9 +63,9 @@ impl ServerProto for W where W: BackendProtoWriter + Send { 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)) + async fn write_table_header(&mut self, table_schema: &TableSchema, columns: &ColumnSelection) -> anyhow::Result<()> { + let columns = columns.iter() + .map(|column| column_to_description(table_schema, *column)) .collect::>>()?; self.write_proto(RowDescriptionData { columns: columns.into() }.into()).await?; @@ -88,13 +91,16 @@ impl ServerProto for W where W: BackendProtoWriter + Send { } } -fn value_to_column_description(schema: &TableSchema, value: &Value, index: usize) -> anyhow::Result { - let name = schema.column_name_from_column(index); +fn column_to_description(schema: &TableSchema, column: usize) -> anyhow::Result { + let table_name = schema.table_name(); + let table_oid = table_name_to_oid(table_name); - 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(); + let column_type = schema.column_type(column); + let name = schema.column_name_from_column(column); + + let column_index = column.try_into()?; + let type_oid = column_type.type_oid(); + let type_size = column_type.type_size(); Ok(ColumnDescription { name: name.to_string().into(), @@ -106,3 +112,9 @@ fn value_to_column_description(schema: &TableSchema, value: &Value, index: usize format_code: 0, // text format }) } + +/// Stable random number based on string +fn table_name_to_oid(table_name: &str) -> i32 { + let mut rng: Pcg64 = Seeder::from(table_name).make_rng(); + rng.gen::() +} \ No newline at end of file