diff --git a/Cargo.lock b/Cargo.lock index 5ea9fdc..14ae99d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,7 @@ name = "minisql" version = "0.1.0" dependencies = [ "bimap", + "proto", "serde", "thiserror", ] @@ -429,6 +430,9 @@ version = "0.1.0" dependencies = [ "async-trait", "bincode", + "rand", + "rand_pcg", + "rand_seeder", "thiserror", "tokio", ] @@ -568,8 +572,6 @@ dependencies = [ "parser", "proto", "rand", - "rand_pcg", - "rand_seeder", "serde_json", "tokio", ] diff --git a/minisql/Cargo.toml b/minisql/Cargo.toml index 3bfcbfa..f44d711 100644 --- a/minisql/Cargo.toml +++ b/minisql/Cargo.toml @@ -10,3 +10,4 @@ rust-version = "1.74" bimap = { version = "0.6.3", features = ["serde"] } serde = { version = "1.0.196", features = ["derive"] } thiserror = "1.0.50" +proto = { path = "../proto" } diff --git a/minisql/src/error.rs b/minisql/src/error.rs index adc2933..d74af4e 100644 --- a/minisql/src/error.rs +++ b/minisql/src/error.rs @@ -1,5 +1,6 @@ use crate::schema::{ColumnName, TableName}; use crate::type_system::Uuid; +use proto::message::primitive::pgoid::PgOid; use std::num::{ParseFloatError, ParseIntError}; use std::str::Utf8Error; use thiserror::Error; @@ -23,5 +24,5 @@ pub enum TypeConversionError { #[error("failed to parse int from text")] IntDecodeFailed(#[from] ParseIntError), #[error("unknown type with oid {oid} and size {size}")] - UnknownType { oid: i32, size: i16 }, + UnknownType { oid: PgOid, size: i16 }, } diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index a48102a..e30edea 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -1,4 +1,5 @@ use crate::error::TypeConversionError; +use proto::message::primitive::pgoid::PgOid; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -130,8 +131,9 @@ impl DbType { } } - fn from_oid_and_size(oid: i32, size: i16) -> Option { - match (oid, size) { + fn from_oid_and_size(oid: PgOid, size: i16) -> Option { + let (type_part, _) = oid.as_nested(); + match (type_part, size) { (25, -2) => Some(DbType::String), (23, 8) => Some(DbType::Int), (701, 8) => Some(DbType::Number), @@ -140,17 +142,15 @@ impl DbType { } } - pub fn type_oid(&self) -> i32 { + pub fn type_oid(&self) -> PgOid { match self { - Self::String => 25, - Self::Int => 23, - Self::Number => 701, - Self::Uuid => 2950, + Self::String => PgOid::Simple(25), + Self::Int => PgOid::Simple(23), + Self::Number => PgOid::Simple(701), + Self::Uuid => PgOid::Simple(2950), Self::Option(t) => { - let oid = t.type_oid(); - let type_part = (oid & 0x0000FFFF) as u16; - let nest_part = ((oid as u32 & 0xFFFF0000) >> 16) as u16 + 1; - (type_part as u32 | ((nest_part as u32) << 16)) as i32 + let (inner_type, inner_nest) = t.type_oid().as_nested(); + PgOid::Nested(inner_type, inner_nest + 1) } } } @@ -258,12 +258,11 @@ impl Value { pub fn from_text_bytes( bytes: &[u8], - type_oid: i32, + type_oid: PgOid, type_size: i16, ) -> Result { let text = std::str::from_utf8(bytes)?; - let type_part = (type_oid & 0x0000FFFF) as u16; - let nest_part = ((type_oid as u32 & 0xFFFF0000) >> 16) as u16; + let (type_part, nest_part) = type_oid.as_nested(); Self::internal_from_text_bytes(text, type_part, nest_part, type_size) } @@ -275,13 +274,13 @@ impl Value { type_size: i16, ) -> Result { if text == "None" { - let db_type = - DbType::from_oid_and_size(type_part as i32, type_size).ok_or_else(|| { - TypeConversionError::UnknownType { - oid: type_part as i32, - size: type_size, - } - })?; + let oid = PgOid::Nested(type_part, nest_part); + let db_type = DbType::from_oid_and_size(oid, type_size).ok_or_else(|| { + TypeConversionError::UnknownType { + oid, + size: type_size, + } + })?; return if nest_part == 0 { Ok(Value::None(db_type)) } else { @@ -316,7 +315,7 @@ impl Value { Ok(Value::Uuid(n)) } (oid, size) => Err(TypeConversionError::UnknownType { - oid: oid as i32, + oid: PgOid::Nested(oid, nest_part), size, }), } @@ -430,6 +429,7 @@ impl TryFrom for Value { mod tests { use super::{DbType, Value}; use crate::error::TypeConversionError::UnknownType; + use proto::message::primitive::pgoid::PgOid; #[test] fn test_encode_number() { @@ -444,7 +444,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"123.456"); - assert_eq!(oid, 701); + assert_eq!(oid.as_simple(), 701); assert_eq!(size, 8); } @@ -461,7 +461,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"hello\0"); - assert_eq!(oid, 25); + assert_eq!(oid.as_simple(), 25); assert_eq!(size, -2); } @@ -477,7 +477,7 @@ mod tests { assert_eq!(value, from_bytes); - assert_eq!(oid, 25); + assert_eq!(oid.as_simple(), 25); assert_eq!(size, -2); } @@ -494,7 +494,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"123"); - assert_eq!(oid, 23); + assert_eq!(oid.as_simple(), 23); assert_eq!(size, 8); } @@ -511,7 +511,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"u123"); - assert_eq!(oid, 2950); + assert_eq!(oid.as_simple(), 2950); assert_eq!(size, 16); } @@ -525,9 +525,14 @@ mod tests { let bytes = value.as_text_bytes(); let from_bytes = Value::from_text_bytes(&bytes, oid, size); + println!("{from_bytes:?}"); + assert!(matches!( from_bytes, - Err(UnknownType { oid: 2950, size: 8 }) + Err(UnknownType { + oid: PgOid::Nested(2950, 0), + size: 8 + }) )) } @@ -562,11 +567,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"Some(Some(123))"); - let expected_type_oid = 23; - let expected_nest_oid = 2; - let expected_oid = expected_type_oid | (expected_nest_oid << 16); - - assert_eq!(oid, expected_oid); + assert_eq!(oid.as_simple(), PgOid::Nested(23, 2).as_simple()); assert_eq!(size, 8); } @@ -585,11 +586,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"Some(Some(None))"); - let expected_type_oid = 23; - let expected_nest_oid = 3; - let expected_oid = expected_type_oid | (expected_nest_oid << 16); - - assert_eq!(oid, expected_oid); + assert_eq!(oid.as_simple(), PgOid::Nested(23, 3).as_simple()); assert_eq!(size, 8); } @@ -606,11 +603,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"Some(None)"); - let expected_type_oid = 701; - let expected_nest_oid = 1; - let expected_oid = expected_type_oid | (expected_nest_oid << 16); - - assert_eq!(oid, expected_oid); + assert_eq!(oid.as_simple(), PgOid::Nested(701, 1).as_simple()); assert_eq!(size, 8); } @@ -628,7 +621,7 @@ mod tests { assert_eq!(value, from_bytes); assert_eq!(bytes, b"None"); - assert_eq!(oid, 701); + assert_eq!(oid.as_simple(), 701); assert_eq!(size, 8); } } diff --git a/proto/Cargo.toml b/proto/Cargo.toml index c8c9134..40f5991 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -8,3 +8,6 @@ bincode = "2.0.0-rc.3" tokio = { version = "1.34.0", features = ["io-util", "macros", "test-util"] } async-trait = "0.1.74" thiserror = "1.0.50" +rand_seeder = "0.2.3" +rand_pcg = "0.3.1" +rand = "0.8.5" diff --git a/proto/src/message/backend.rs b/proto/src/message/backend.rs index 869fe14..cfa1078 100644 --- a/proto/src/message/backend.rs +++ b/proto/src/message/backend.rs @@ -1,6 +1,7 @@ use crate::message::errors::{ProtoDeserializeError, ProtoSerializeError}; use crate::message::primitive::data::MessageData; use crate::message::primitive::pglist::PgList; +use crate::message::primitive::pgoid::PgOid; use crate::message::primitive::pgstring::PgString; use crate::message::proto_message::ProtoMessage; use bincode::{Decode, Encode}; @@ -186,9 +187,9 @@ impl From for BackendMessage { #[derive(Debug, Clone, Encode, Decode)] pub struct ColumnDescription { pub name: PgString, - pub table_oid: i32, + pub table_oid: PgOid, pub column_index: i16, - pub type_oid: i32, + pub type_oid: PgOid, pub type_size: i16, pub type_modifier: i32, pub format_code: i16, @@ -330,9 +331,9 @@ mod tests { let backend = BackendMessage::RowDescription(RowDescriptionData { columns: PgList::from(vec![ColumnDescription { name: PgString::from("Some name"), - table_oid: 123, + table_oid: PgOid::Simple(123), column_index: 456, - type_oid: 789, + type_oid: PgOid::Simple(789), type_size: 101, type_modifier: 112, format_code: 113, @@ -347,9 +348,9 @@ mod tests { let columns: Vec = columns.into(); let column = &columns[0]; column.name.as_str() == "Some name" - && column.table_oid == 123 + && column.table_oid.as_simple() == 123 && column.column_index == 456 - && column.type_oid == 789 + && column.type_oid.as_simple() == 789 && column.type_size == 101 && column.type_modifier == 112 && column.format_code == 113 diff --git a/proto/src/message/primitive/mod.rs b/proto/src/message/primitive/mod.rs index 4e84a1b..11b0f1a 100644 --- a/proto/src/message/primitive/mod.rs +++ b/proto/src/message/primitive/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod data; pub mod pglist; +pub mod pgoid; pub mod pgstring; diff --git a/proto/src/message/primitive/pgoid.rs b/proto/src/message/primitive/pgoid.rs new file mode 100644 index 0000000..36806da --- /dev/null +++ b/proto/src/message/primitive/pgoid.rs @@ -0,0 +1,79 @@ +use bincode::de::Decoder; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; +use rand::Rng; +use rand_pcg::Pcg64; +use rand_seeder::Seeder; +use std::fmt::Display; + +#[derive(Debug, Clone, Copy, BorrowDecode)] +pub enum PgOid { + Simple(i32), + Nested(u16, u16), +} + +impl PgOid { + /// Generates a pseudo-random OID from a name. + pub fn from_unique_name(name: &str) -> PgOid { + let mut rng: Pcg64 = Seeder::from(name).make_rng(); + let oid = rng.gen::(); + PgOid::Simple(oid) + } + + pub fn as_nested(&self) -> (u16, u16) { + match self { + PgOid::Nested(a, b) => (*a, *b), + PgOid::Simple(oid) => { + let type_part = (oid & 0x0000FFFF) as u16; + let nest_part = (((*oid as u32) & 0xFFFF0000) >> 16) as u16; + (type_part, nest_part) + } + } + } + + pub fn as_simple(&self) -> i32 { + match self { + PgOid::Simple(oid) => *oid, + PgOid::Nested(type_part, nest_part) => { + let oid = (*type_part as u32) | ((*nest_part as u32) << 16); + oid as i32 + } + } + } +} + +impl Display for PgOid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let oid = self.as_simple(); + write!(f, "{}", oid) + } +} + +impl Encode for PgOid { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + let oid = self.as_simple(); + oid.encode(encoder) + } +} + +impl Decode for PgOid { + fn decode(decoder: &mut D) -> Result { + let oid = i32::decode(decoder)?; + Ok(PgOid::Simple(oid)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pgoid() { + let oid = PgOid::Simple(0x12345678); + let (type_part, nest_part) = oid.as_nested(); + assert_eq!(type_part, 0x5678); + assert_eq!(nest_part, 0x1234); + assert_eq!(oid.as_simple(), 0x12345678); + } +} diff --git a/server/Cargo.toml b/server/Cargo.toml index 2b76e8e..67c87d7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,8 +11,6 @@ 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/proto_wrapper.rs b/server/src/proto_wrapper.rs index 960522e..1ddf59c 100644 --- a/server/src/proto_wrapper.rs +++ b/server/src/proto_wrapper.rs @@ -7,10 +7,8 @@ use proto::message::backend::{ ReadyForQueryData, RowDescriptionData, }; use proto::message::primitive::pglist::PgList; +use proto::message::primitive::pgoid::PgOid; use proto::writer::backend::BackendProtoWriter; -use rand::Rng; -use rand_pcg::Pcg64; -use rand_seeder::Seeder; use std::fmt; pub enum CompleteStatus { @@ -123,7 +121,7 @@ fn column_to_description( column: Column, ) -> anyhow::Result { let table_name = schema.table_name(); - let table_oid = table_name_to_oid(table_name); + let table_oid = PgOid::from_unique_name(table_name); let column_type = schema.column_type(column); let name = schema.column_name_from_column(column); @@ -142,10 +140,3 @@ fn column_to_description( format_code: 0, // text format }) } - -/// Hashes the table name into single i32 used as a substitute for the table OID -/// in the PostgreSQL protocol. -fn table_name_to_oid(table_name: &str) -> i32 { - let mut rng: Pcg64 = Seeder::from(table_name).make_rng(); - rng.gen::() -}