refactor: add standalone PgOid type

This commit is contained in:
Jindřich Moravec 2024-02-01 22:27:58 +01:00
parent bdb6a955df
commit 6d1af26fa8
10 changed files with 136 additions and 66 deletions

6
Cargo.lock generated
View file

@ -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",
]

View file

@ -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" }

View file

@ -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 },
}

View file

@ -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<DbType> {
match (oid, size) {
fn from_oid_and_size(oid: PgOid, size: i16) -> Option<DbType> {
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<Value, TypeConversionError> {
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<Value, TypeConversionError> {
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<String> 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);
}
}

View file

@ -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"

View file

@ -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<RowDescriptionData> 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<ColumnDescription> = 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

View file

@ -1,3 +1,4 @@
pub(crate) mod data;
pub mod pglist;
pub mod pgoid;
pub mod pgstring;

View file

@ -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::<i32>();
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<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
let oid = self.as_simple();
oid.encode(encoder)
}
}
impl Decode for PgOid {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
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);
}
}

View file

@ -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" }

View file

@ -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<ColumnDescription> {
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::<i32>()
}