Merge branch 'main' into redesign-tables

This commit is contained in:
Yuriy Dupyn 2024-02-02 19:05:13 +01:00
commit 3076bcd83e
17 changed files with 618 additions and 247 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

@ -84,18 +84,18 @@ impl Table {
value: Value,
) -> DbResult<impl Iterator<Item = RestrictedRow> + '_> {
let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_columns);
match value {
Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? {
match value.to_indexable() {
Some(indexable_value) => match self.fetch_ids_from_index(column, &indexable_value)? {
Some(ids) => Ok(self
.get_rows_by_ids(ids)
.into_iter()
.map(restrict_columns_of_row)),
None => Ok(self
.get_rows_by_value(column, &Value::Indexable(value))
.get_rows_by_value(column, &value)
.into_iter()
.map(restrict_columns_of_row)),
},
_ => Ok(self
None => Ok(self
.get_rows_by_value(column, &value)
.into_iter()
.map(restrict_columns_of_row)),
@ -112,8 +112,8 @@ impl Table {
}
for (column, column_index) in &mut self.indexes {
if let Value::Indexable(val) = &row[*column] {
column_index.add(val.clone(), id)
if let Some(indexable_value) = &row[*column].to_indexable() {
column_index.add(indexable_value.clone(), id)
}
}
@ -126,8 +126,8 @@ impl Table {
match self.rows.remove(&id) {
Some(row) => {
for (column, column_index) in &mut self.indexes {
if let Value::Indexable(value) = &row[*column] {
let _ = column_index.remove(value, id);
if let Some(indexable_value) = &row[*column].to_indexable() {
let _ = column_index.remove(indexable_value, id);
};
}
1
@ -167,12 +167,12 @@ impl Table {
}
pub fn delete_rows_where_eq(&mut self, column: Column, value: Value) -> DbResult<usize> {
match value {
Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? {
match value.to_indexable() {
Some(indexable_value) => match self.fetch_ids_from_index(column, &indexable_value)? {
Some(ids) => Ok(self.delete_rows_by_ids(ids)),
None => Ok(self.delete_rows_by_value(column, &Value::Indexable(value))),
None => Ok(self.delete_rows_by_value(column, &value)),
},
_ => Ok(self.delete_rows_by_value(column, &value)),
None => Ok(self.delete_rows_by_value(column, &value)),
}
}
@ -225,9 +225,9 @@ fn update_index_from_table(
column: Column,
) -> DbResult<()> {
for (id, row) in &table.rows {
let value = match &row[column] {
Value::Indexable(value) => value.clone(),
_ => {
let value = match &row[column].to_indexable() {
Some(indexable_value) => indexable_value.clone(),
None => {
let column_name: ColumnName = table.schema.column_name_from_column(column);
return Err(RuntimeError::AttemptToIndexNonIndexableColumn(
table.table_name().to_string(),

View file

@ -210,9 +210,6 @@ mod tests {
#[test]
fn test_insert_select_basic1() {
use IndexableValue::*;
use Value::*;
let mut state = State::new();
let users_schema = users_schema();
let users = 0;
@ -222,9 +219,9 @@ mod tests {
.unwrap();
let (id, name, age) = (
Indexable(Uuid(0)),
Indexable(String("Plato".to_string())),
Indexable(Int(64)),
Value::Uuid(0),
Value::String("Plato".to_string()),
Value::Int(64),
);
state
.interpret(Operation::Insert(
@ -254,9 +251,7 @@ mod tests {
#[test]
fn test_insert_select_basic2() {
use Condition::*;
use IndexableValue::*;
use Operation::*;
use Value::*;
let mut state = State::new();
let users_schema = users_schema();
@ -268,9 +263,9 @@ mod tests {
state.interpret(CreateTable(users_schema.clone())).unwrap();
let (id0, name0, age0) = (
Indexable(Uuid(0)),
Indexable(String("Plato".to_string())),
Indexable(Int(64)),
Value::Uuid(0),
Value::String("Plato".to_string()),
Value::Int(64),
);
state
.interpret(Insert(
@ -280,9 +275,9 @@ mod tests {
.unwrap();
let (id1, name1, age1) = (
Indexable(Uuid(1)),
Indexable(String("Aristotle".to_string())),
Indexable(Int(20)),
Value::Uuid(1),
Value::String("Aristotle".to_string()),
Value::Int(20),
);
state
.interpret(Insert(
@ -364,9 +359,7 @@ mod tests {
#[test]
fn test_delete() {
use Condition::*;
use IndexableValue::*;
use Operation::*;
use Value::*;
let mut state = State::new();
let users_schema = users_schema();
@ -377,9 +370,9 @@ mod tests {
state.interpret(CreateTable(users_schema.clone())).unwrap();
let (id0, name0, age0) = (
Indexable(Uuid(0)),
Indexable(String("Plato".to_string())),
Indexable(Int(64)),
Value::Uuid(0),
Value::String("Plato".to_string()),
Value::Int(64),
);
state
.interpret(Insert(
@ -389,9 +382,9 @@ mod tests {
.unwrap();
let (id1, name1, age1) = (
Indexable(Uuid(1)),
Indexable(String("Aristotle".to_string())),
Indexable(Int(20)),
Value::Uuid(1),
Value::String("Aristotle".to_string()),
Value::Int(20),
);
state
.interpret(Insert(
@ -427,9 +420,7 @@ mod tests {
#[test]
fn test_index() {
use IndexableValue::*;
use Operation::*;
use Value::*;
let mut state = State::new();
let users_schema = users_schema();
@ -444,9 +435,9 @@ mod tests {
.unwrap();
let (id0, name0, age0) = (
Indexable(Uuid(0)),
Indexable(String("Plato".to_string())),
Indexable(Int(64)),
Value::Uuid(0),
Value::String("Plato".to_string()),
Value::Int(64),
);
state
.interpret(Insert(
@ -456,9 +447,9 @@ mod tests {
.unwrap();
let (id1, name1, age1) = (
Indexable(Uuid(1)),
Indexable(String("Aristotle".to_string())),
Indexable(Int(20)),
Value::Uuid(1),
Value::String("Aristotle".to_string()),
Value::Int(20),
);
state
.interpret(Insert(
@ -480,7 +471,7 @@ mod tests {
let aristotle_id = 1;
let plato_ids = index
.get(&String("Plato".to_string()))
.get(&IndexableValue::String("Plato".to_string()))
.cloned()
.unwrap_or(HashSet::new());
assert!(plato_ids.contains(&plato_id));
@ -490,11 +481,9 @@ mod tests {
}
pub fn example() {
use crate::type_system::{DbType, IndexableValue, Value};
use crate::type_system::{DbType, Value};
use Condition::*;
use IndexableValue::*;
use Operation::*;
use Value::*;
let id_column: Column = 0;
let name_column: Column = 1;
@ -520,9 +509,9 @@ pub fn example() {
.unwrap();
let (id0, name0, age0) = (
Indexable(Uuid(0)),
Indexable(String("Plato".to_string())),
Indexable(Int(64)),
Value::Uuid(0),
Value::String("Plato".to_string()),
Value::Int(64),
);
println!("==INSERT Plato==");
state
@ -533,9 +522,9 @@ pub fn example() {
.unwrap();
let (id1, name1, age1) = (
Indexable(Uuid(1)),
Indexable(String("Aristotle".to_string())),
Indexable(Int(20)),
Value::Uuid(1),
Value::String("Aristotle".to_string()),
Value::Int(20),
);
println!("==INSERT Aristotle==");
state

View file

@ -1,7 +1,7 @@
use crate::internals::row::Row;
use crate::operation::{ColumnSelection, InsertionValues};
use crate::result::DbResult;
use crate::type_system::{DbType, IndexableValue, Uuid, Value};
use crate::type_system::{DbType, IndexableValue, Uuid};
use bimap::BiMap;
use serde::{Deserialize, Serialize};
@ -51,7 +51,7 @@ impl TableSchema {
}
pub fn column_type(&self, column: Column) -> DbType {
self.types[column]
self.types[column].clone()
}
pub fn get_columns(&self) -> Vec<&ColumnName> {
@ -88,7 +88,7 @@ impl TableSchema {
pub fn get_type_at(&self, column_name: &ColumnName) -> Option<DbType> {
let position = self.get_column(column_name)?;
self.types.get(position).copied()
self.types.get(position).cloned()
}
pub fn is_primary(&self, column: Column) -> bool {
@ -115,8 +115,13 @@ impl TableSchema {
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(value) => {
match value.to_indexable() {
Some(IndexableValue::Uuid(id)) => id,
Some(_) => unreachable!(), // SAFETY: Should be guaranteed by validation (type-safety)
None => unreachable!(), // SAFETY: Should be guaranteed by validation (type-safety)
}
}
None => unreachable!(), // SAFETY: Should be guaranteed by validation (missing columns)
};

View file

@ -1,62 +1,157 @@
use crate::error::TypeConversionError;
use proto::message::primitive::pgoid::PgOid;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
// ==============Types================
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DbType {
String,
Int,
Number,
Uuid,
Option(Box<DbType>),
}
// ==============Values================
pub type Uuid = u64;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub enum Value {
// Note that it doesn't really make sense to compare floats on equality without specifying
// precision. You can ofcourse convert a float to string or to a bytevector and then compare
// equality of those, but that's not the right equality. And ofcourse Rust designers are aware
// of this, so floats don't implement the Eq trait.
// This ofcourse complicates indexing of Number columns.
//
// Either we'd have to design a specific key-value map data-structure where keys are floats,
// s.t. to index with a given float K you also specify a tolerance error so that the resulting
// value set will contain all values whose keys are close to K within that tolerence. This
// seems highly non-trivial.
//
// So we choose to make a distinction between indexable and non-indexable types, and Number is
// not indexable.
Number(f64),
Indexable(IndexableValue),
String(String),
Int(u64),
Uuid(Uuid),
Some(Box<Value>),
// Note that without polymorphic types,
// we can't type None on its own. Hence we require additional type information in the value
// itself.
None(DbType),
}
#[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Eq, Clone, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub enum IndexableValue {
String(String),
Int(u64),
Uuid(Uuid),
Some(Box<IndexableValue>),
None(DbType), // See Value::None
}
impl PartialOrd for IndexableValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(IndexableValue::String(s0), IndexableValue::String(s1)) => s0.partial_cmp(s1),
(IndexableValue::Int(n0), IndexableValue::Int(n1)) => n0.partial_cmp(n1),
(IndexableValue::Uuid(id0), IndexableValue::Uuid(id1)) => id0.partial_cmp(id1),
(IndexableValue::None(_), IndexableValue::None(_)) => Some(Ordering::Equal),
(IndexableValue::None(_), IndexableValue::Some(_)) => Some(Ordering::Less),
(IndexableValue::Some(_), IndexableValue::None(_)) => Some(Ordering::Greater),
(IndexableValue::Some(v0), IndexableValue::Some(v1)) => v0.partial_cmp(v1),
_ => None,
}
}
}
impl Ord for IndexableValue {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(IndexableValue::String(s0), IndexableValue::String(s1)) => s0.cmp(s1),
(IndexableValue::Int(n0), IndexableValue::Int(n1)) => n0.cmp(n1),
(IndexableValue::Uuid(id0), IndexableValue::Uuid(id1)) => id0.cmp(id1),
(IndexableValue::None(_), IndexableValue::None(_)) => Ordering::Equal,
(IndexableValue::None(_), IndexableValue::Some(_)) => Ordering::Less,
(IndexableValue::Some(_), IndexableValue::None(_)) => Ordering::Greater,
(IndexableValue::Some(v0), IndexableValue::Some(v1)) => v0.cmp(v1),
_ =>
// SAFETY:
// We are using indexable values as keys in key-value maps.
// When validation is done, it can't happen that we will be comparing two values
// of different types.
// Ofcourse another option is to artificialy order e.g.
// None < Some(...) < String < Int < Uuid
// where ... is again None < Some(...) < String < Int < Uuid
// where ...
// infinitely deep total order. But this is pointless for our usecase.
{
unreachable!()
}
}
}
}
impl DbType {
fn new_n_option(n: usize, inside: DbType) -> DbType {
if n == 0 {
inside
} else {
DbType::Option(Box::new(DbType::new_n_option(n - 1, inside)))
}
}
pub fn is_indexable(&self) -> bool {
match self {
Self::String => true,
Self::Int => true,
Self::Number => false,
Self::Uuid => true,
Self::Option(type_) => type_.is_indexable(),
}
}
pub fn type_oid(&self) -> i32 {
fn to_json_key_string(self) -> String {
match self {
Self::String => 25,
Self::Int => 23,
Self::Number => 701,
Self::Uuid => 2950,
DbType::String => format!("String()"),
DbType::Int => format!("Int()"),
DbType::Number => format!("Number()"),
DbType::Uuid => format!("Uuid()"),
DbType::Option(type_) => format!("Option({})", type_.to_json_key_string()),
}
}
fn parse_json_key_string(str: &str) -> Result<DbType, String> {
if !str.ends_with(')') {
return Err(format!("Invalid DbType: {}", str));
}
if str.starts_with("String(") {
Ok(DbType::String)
} else if str.starts_with("Int(") {
Ok(DbType::Int)
} else if str.starts_with("Number(") {
Ok(DbType::Number)
} else if str.starts_with("Uuid(") {
Ok(DbType::Uuid)
} else if str.starts_with("Option(") {
let s = &str[7..str.len() - 1];
Ok(DbType::Option(Box::new(Self::parse_json_key_string(s)?)))
} else {
Err(format!("Invalid DbType: {}", str))
}
}
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),
(2950, 16) => Some(DbType::Uuid),
_ => None,
}
}
pub fn type_oid(&self) -> PgOid {
match self {
Self::String => PgOid::Simple(25),
Self::Int => PgOid::Simple(23),
Self::Number => PgOid::Simple(701),
Self::Uuid => PgOid::Simple(2950),
Self::Option(t) => {
let (inner_type, inner_nest) = t.type_oid().as_nested();
PgOid::Nested(inner_type, inner_nest + 1)
}
}
}
@ -66,6 +161,7 @@ impl DbType {
Self::Int => 8,
Self::Number => 8,
Self::Uuid => 16,
Self::Option(t) => t.type_size(),
}
}
}
@ -74,137 +170,266 @@ impl Value {
pub fn to_type(&self) -> DbType {
match self {
Self::Number(_) => DbType::Number,
Self::Indexable(val) => match val {
IndexableValue::String(_) => DbType::String,
IndexableValue::Int(_) => DbType::Int,
IndexableValue::Uuid(_) => DbType::Uuid,
},
Self::String(_) => DbType::String,
Self::Int(_) => DbType::Int,
Self::Uuid(_) => DbType::Uuid,
Self::Some(val) => DbType::Option(Box::new(val.to_type())),
Self::None(type_) => type_.clone(),
}
}
pub fn to_indexable(&self) -> Option<IndexableValue> {
match self {
Self::Number(_) => None,
Self::String(s) => Some(IndexableValue::String(s.to_string())),
Self::Int(n) => Some(IndexableValue::Int(*n)),
Self::Uuid(id) => Some(IndexableValue::Uuid(*id)),
Self::Some(val) => val.to_indexable(),
Self::None(type_) => Some(IndexableValue::None(type_.clone())),
}
}
fn to_json_key_string(self) -> String {
match self {
Self::Number(x) => format!("Number({x})"),
Self::String(s) => format!("String({s})"),
Self::Int(i) => format!("Int({i})"),
Self::Uuid(u) => format!("Uuid({u})"),
Self::Some(val) => format!("Some({})", val.to_json_key_string()),
Self::None(_type_) => "None()".to_string(),
}
}
// We don't really need this, since only indexable values are keys in maps. It is here for consistency.
fn parse_json_key_string(str: &str) -> Result<Self, String> {
if !str.ends_with(')') {
return Err(format!("Invalid IndexableValue: {}", str));
}
if str.starts_with("Number(") {
let s = str[7..str.len() - 1].to_string();
let n = s
.parse::<f64>()
.map_err(|e| format!("Invalid Number: {}", e))?;
Ok(Self::Number(n))
} else if str.starts_with("String(") {
let s = str[7..str.len() - 1].to_string();
Ok(Self::String(s))
} else if str.starts_with("Int(") {
let s = str[4..str.len() - 1].to_string();
let i = s
.parse::<u64>()
.map_err(|e| format!("Invalid Int: {}", e))?;
Ok(Self::Int(i))
} else if str.starts_with("Uuid(") {
let s = str[5..str.len() - 1].to_string();
let u = s
.parse::<u64>()
.map_err(|e| format!("Invalid UUID: {}", e))?;
Ok(Self::Uuid(u))
} else if str.starts_with("Some(") {
let s = str[5..str.len() - 1].to_string();
let val: Value = Self::parse_json_key_string(&s)?;
Ok(Self::Some(Box::new(val)))
} else if str.starts_with("None(") {
let s = str[5..str.len() - 1].to_string();
let type_: DbType = TryFrom::try_from(s)?;
Ok(Value::None(type_))
} else {
Err(format!("Invalid IndexableValue: {}", str))
}
}
pub fn as_text_bytes(&self) -> Vec<u8> {
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(),
},
Self::String(s) => format!("{s}\0").into_bytes(),
Self::Int(i) => format!("{i}").into_bytes(),
Self::Uuid(u) => format!("u{u}").into_bytes(),
Self::None(_) => b"None".into(),
Self::Some(val) => {
let mut bytes = Vec::from(b"Some(");
bytes.append(&mut val.as_text_bytes());
bytes.push(b')');
bytes
}
}
}
pub fn from_text_bytes(
bytes: &[u8],
type_oid: i32,
type_oid: PgOid,
type_size: i16,
) -> Result<Value, TypeConversionError> {
match (type_oid, type_size) {
let text = std::str::from_utf8(bytes)?;
let (type_part, nest_part) = type_oid.as_nested();
Self::internal_from_text_bytes(text, type_part, nest_part, type_size)
}
fn internal_from_text_bytes(
text: &str,
type_part: u16,
nest_part: u16,
type_size: i16,
) -> Result<Value, TypeConversionError> {
if text == "None" {
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 {
Ok(Value::None(DbType::new_n_option(
nest_part as usize,
db_type,
)))
};
}
if text.starts_with("Some(") {
let inner = &text[5..text.len() - 1];
let val = Self::internal_from_text_bytes(inner, type_part, nest_part - 1, type_size)?;
return Ok(Value::Some(Box::new(val)));
}
match (type_part, type_size) {
(701, 8) => {
let s = std::str::from_utf8(bytes)?;
let n = s.parse::<f64>()?;
let n = text.parse::<f64>()?;
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())))
let s = &text[..text.len() - 1]; // remove null terminator
Ok(Value::String(s.to_string()))
}
(23, 8) => {
let s = std::str::from_utf8(bytes)?;
let n = s.parse::<u64>()?;
Ok(Value::Indexable(IndexableValue::Int(n)))
let n = text.parse::<u64>()?;
Ok(Value::Int(n))
}
(2950, 16) => {
let s = std::str::from_utf8(bytes)?;
let n = s.parse::<Uuid>()?;
Ok(Value::Indexable(IndexableValue::Uuid(n)))
let n = text[1..].parse::<u64>()?;
Ok(Value::Uuid(n))
}
(oid, size) => Err(TypeConversionError::UnknownType { oid, size }),
(oid, size) => Err(TypeConversionError::UnknownType {
oid: PgOid::Nested(oid, nest_part),
size,
}),
}
}
}
impl IndexableValue {
fn to_value(self) -> Value {
match self {
IndexableValue::String(str) => Value::String(str),
IndexableValue::Int(n) => Value::Int(n),
IndexableValue::Uuid(id) => Value::Uuid(id),
IndexableValue::Some(indexable_value) => {
Value::Some(Box::new(indexable_value.to_value()))
}
IndexableValue::None(type_) => Value::None(type_),
}
}
fn to_json_key_string(self) -> String {
match self {
IndexableValue::String(s) => format!("String({s})"),
IndexableValue::Int(i) => format!("Int({i})"),
IndexableValue::Uuid(u) => format!("Uuid({u})"),
IndexableValue::Some(val) => format!("Some({})", val.to_json_key_string()),
IndexableValue::None(_type_) => "None()".to_string(),
}
}
fn parse_json_key_string(str: &str) -> Result<Self, String> {
if !str.ends_with(')') {
return Err(format!("Invalid IndexableValue: {}", str));
}
if str.starts_with("String(") {
let s = str[7..str.len() - 1].to_string();
Ok(IndexableValue::String(s))
} else if str.starts_with("Int(") {
let s = str[4..str.len() - 1].to_string();
let i = s
.parse::<u64>()
.map_err(|e| format!("Invalid Int: {}", e))?;
Ok(IndexableValue::Int(i))
} else if str.starts_with("Uuid(") {
let s = str[5..str.len() - 1].to_string();
let u = s
.parse::<u64>()
.map_err(|e| format!("Invalid UUID: {}", e))?;
Ok(IndexableValue::Uuid(u))
} else if str.starts_with("Some(") {
let s = str[5..str.len() - 1].to_string();
let val: IndexableValue = Self::parse_json_key_string(&s)?;
Ok(IndexableValue::Some(Box::new(val)))
} else if str.starts_with("None(") {
let s = str[5..str.len() - 1].to_string();
let type_: DbType = DbType::parse_json_key_string(&s)?;
Ok(IndexableValue::None(type_))
} else {
Err(format!("Invalid IndexableValue: {}", str))
}
}
}
impl From<IndexableValue> for Value {
fn from(indexable_value: IndexableValue) -> Self {
indexable_value.to_value()
}
}
// Own string serialization so enums can be used as keys in maps
impl From<IndexableValue> for String {
fn from(value: IndexableValue) -> Self {
match value {
IndexableValue::String(s) => format!("String({s})"),
IndexableValue::Int(i) => format!("Int({i})"),
IndexableValue::Uuid(u) => format!("Uuid({u})"),
value.to_json_key_string()
}
}
impl From<DbType> for String {
fn from(type_: DbType) -> Self {
type_.to_json_key_string()
}
}
impl TryFrom<String> for DbType {
type Error = String;
fn try_from(str: String) -> Result<Self, Self::Error> {
Self::parse_json_key_string(&str)
}
}
impl TryFrom<String> for IndexableValue {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
if !value.ends_with(')') {
return Err(format!("Invalid IndexableValue: {}", value));
}
if value.starts_with("String(") {
let s = value[7..value.len() - 1].to_string();
return Ok(Self::String(s));
}
if value.starts_with("Int(") {
let s = value[4..value.len() - 1].to_string();
let i = s
.parse::<u64>()
.map_err(|e| format!("Invalid Int: {}", e))?;
return Ok(Self::Int(i));
}
if value.starts_with("Uuid(") {
let s = value[5..value.len() - 1].to_string();
let u = s
.parse::<u64>()
.map_err(|e| format!("Invalid UUID: {}", e))?;
return Ok(Self::Uuid(u));
}
Err(format!("Invalid IndexableValue: {}", value))
fn try_from(str: String) -> Result<Self, Self::Error> {
Self::parse_json_key_string(&str)
}
}
impl From<Value> for String {
fn from(value: Value) -> Self {
match value {
Value::Number(n) => format!("Number({n})"),
Value::Indexable(i) => format!("Indexable({})", String::from(i)),
}
value.to_json_key_string()
}
}
impl TryFrom<String> for Value {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
if !value.ends_with(')') {
return Err(format!("Invalid Value: {}", value));
}
if value.starts_with("Number(") {
let s = value[7..value.len() - 1].to_string();
let n = s
.parse::<f64>()
.map_err(|e| format!("Invalid Number: {}", e))?;
return Ok(Self::Number(n));
}
if value.starts_with("Indexable(") {
let s = value[10..value.len() - 1].to_string();
let i = IndexableValue::try_from(s)?;
return Ok(Self::Indexable(i));
}
Err(format!("Invalid Value: {}", value))
Value::parse_json_key_string(&value)
}
}
#[cfg(test)]
mod tests {
use super::{IndexableValue, Value};
use super::{DbType, Value};
use crate::error::TypeConversionError::UnknownType;
use crate::type_system::Value::{Indexable, Number};
use proto::message::primitive::pgoid::PgOid;
#[test]
fn test_encode_number() {
@ -217,14 +442,15 @@ mod tests {
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
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);
}
#[test]
fn test_encode_string() {
let value = Value::Indexable(IndexableValue::String("hello".to_string()));
let value = Value::String("hello".to_string());
let vtype = value.to_type();
let oid = vtype.type_oid();
let size = vtype.type_size();
@ -233,14 +459,15 @@ mod tests {
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
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);
}
#[test]
fn test_encode_string_utf8() {
let value = Value::Indexable(IndexableValue::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string()));
let value = Value::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string());
let vtype = value.to_type();
let oid = vtype.type_oid();
let size = vtype.type_size();
@ -250,13 +477,13 @@ mod tests {
assert_eq!(value, from_bytes);
assert_eq!(oid, 25);
assert_eq!(oid.as_simple(), 25);
assert_eq!(size, -2);
}
#[test]
fn test_encode_int() {
let value = Value::Indexable(IndexableValue::Int(123));
let value = Value::Int(123);
let vtype = value.to_type();
let oid = vtype.type_oid();
let size = vtype.type_size();
@ -265,14 +492,15 @@ mod tests {
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
assert_eq!(value, from_bytes);
assert_eq!(bytes, b"123");
assert_eq!(oid, 23);
assert_eq!(oid.as_simple(), 23);
assert_eq!(size, 8);
}
#[test]
fn test_encode_uuid() {
let value = Value::Indexable(IndexableValue::Uuid(123));
let value = Value::Uuid(123);
let vtype = value.to_type();
let oid = vtype.type_oid();
let size = vtype.type_size();
@ -281,14 +509,15 @@ mod tests {
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
assert_eq!(value, from_bytes);
assert_eq!(bytes, b"u123");
assert_eq!(oid, 2950);
assert_eq!(oid.as_simple(), 2950);
assert_eq!(size, 16);
}
#[test]
fn test_mismatched_size() {
let value = Value::Indexable(IndexableValue::Uuid(123));
let value = Value::Uuid(123);
let vtype = value.to_type();
let oid = vtype.type_oid();
let size = 8;
@ -296,22 +525,24 @@ 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
})
))
}
#[test]
fn test_value_stringification() {
let pairs = vec![
(Number(1.0), "Number(1)"),
(
Indexable(IndexableValue::String("hello".to_string())),
"Indexable(String(hello))",
),
(Indexable(IndexableValue::Int(123)), "Indexable(Int(123))"),
(Indexable(IndexableValue::Uuid(123)), "Indexable(Uuid(123))"),
(Value::Number(1.0), "Number(1)"),
(Value::String("hello".to_string()), "String(hello)"),
(Value::Int(123), "Int(123)"),
(Value::Uuid(123), "Uuid(123)"),
];
for (value, string) in pairs {
@ -322,4 +553,75 @@ mod tests {
assert_eq!(deserialized, Ok(value));
}
}
#[test]
fn test_encode_nested_value() {
let value = Value::Some(Box::new(Value::Some(Box::new(Value::Int(123)))));
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();
assert_eq!(value, from_bytes);
assert_eq!(bytes, b"Some(Some(123))");
assert_eq!(oid.as_simple(), PgOid::Nested(23, 2).as_simple());
assert_eq!(size, 8);
}
#[test]
fn test_encode_nested_none() {
let value = Value::Some(Box::new(Value::Some(Box::new(Value::None(
DbType::Option(Box::new(DbType::Int)),
)))));
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();
assert_eq!(value, from_bytes);
assert_eq!(bytes, b"Some(Some(None))");
assert_eq!(oid.as_simple(), PgOid::Nested(23, 3).as_simple());
assert_eq!(size, 8);
}
#[test]
fn test_encode_surface_none() {
let value = Value::Some(Box::new(Value::None(DbType::Number)));
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();
assert_eq!(value, from_bytes);
assert_eq!(bytes, b"Some(None)");
assert_eq!(oid.as_simple(), PgOid::Nested(701, 1).as_simple());
assert_eq!(size, 8);
}
#[test]
fn test_encode_none() {
let value = Value::None(DbType::Number);
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();
assert_eq!(value, from_bytes);
assert_eq!(bytes, b"None");
assert_eq!(oid.as_simple(), 701);
assert_eq!(size, 8);
}
}

View file

@ -82,11 +82,11 @@ mod tests {
#[test]
fn test_parse_equality() {
use minisql::type_system::{IndexableValue, Value};
use minisql::type_system::Value;
match parse_equality("id = 1") {
Ok(("", Condition::Eq(column_name, value))) => {
assert!(column_name.eq("id"));
assert_eq!(value, Value::Indexable(IndexableValue::Int(1)))
assert_eq!(value, Value::Int(1))
}
_ => {
panic!("should parse");

View file

@ -57,7 +57,7 @@ pub fn parse_values(input: &str) -> IResult<&str, Vec<Value>> {
#[cfg(test)]
mod tests {
use minisql::type_system::{IndexableValue, Value};
use minisql::type_system::Value;
use super::parse_insert;
use crate::syntax::RawQuerySyntax;
@ -72,10 +72,10 @@ mod tests {
assert_eq!(
insertion_values,
vec![
("id".to_string(), Value::Indexable(IndexableValue::Int(1))),
("id".to_string(), Value::Int(1)),
(
"data".to_string(),
Value::Indexable(IndexableValue::String("Text".to_string()))
Value::String("Text".to_string())
)
]
);
@ -97,10 +97,10 @@ mod tests {
assert_eq!(
insertion_values,
vec![
("id".to_string(), Value::Indexable(IndexableValue::Int(1))),
("id".to_string(), Value::Int(1)),
(
"data".to_string(),
Value::Indexable(IndexableValue::String("Text".to_string()))
Value::String("Text".to_string())
)
]
);

View file

@ -1,4 +1,4 @@
use minisql::type_system::{IndexableValue, Value};
use minisql::type_system::Value;
use nom::{
branch::alt,
character::complete::{char, digit1, none_of, u64},
@ -34,13 +34,13 @@ pub fn parse_number(input: &str) -> IResult<&str, Value> {
let value = format!("{}{}", sign.unwrap_or('+'), digits)
.parse::<u64>()
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
Ok((input, Value::Indexable(IndexableValue::Int(value))))
Ok((input, Value::Int(value)))
}
}
}
pub fn parse_int(input: &str) -> IResult<&str, Value> {
u64(input).map(|(input, v)| (input, Value::Indexable(IndexableValue::Int(v))))
u64(input).map(|(input, v)| (input, Value::Int(v)))
}
fn escape_tab(input: &str) -> IResult<&str, char> {
@ -85,19 +85,19 @@ pub fn parse_string(input: &str) -> IResult<&str, Value> {
// Combine the characters into a string
let value: String = content.into_iter().collect();
Ok((input, Value::Indexable(IndexableValue::String(value))))
Ok((input, Value::String(value)))
}
pub fn parse_uuid(input: &str) -> IResult<&str, Value> {
let (input, value) = pair(char('u'), u64)(input)
.map(|(input, (_, v))| (input, Value::Indexable(IndexableValue::Uuid(v))))?;
.map(|(input, (_, v))| (input, Value::Uuid(v)))?;
Ok((input, value))
}
#[cfg(test)]
mod tests {
use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid};
use minisql::type_system::{IndexableValue, Value};
use minisql::type_system::Value;
#[test]
fn test_string_parser() {
@ -105,21 +105,21 @@ mod tests {
parse_string(r#""simple""#),
Ok((
"",
Value::Indexable(IndexableValue::String(String::from("simple")))
Value::String(String::from("simple"))
))
);
assert_eq!(
parse_string(r#""\"\t\r\n\\""#),
Ok((
"",
Value::Indexable(IndexableValue::String(String::from("\"\t\r\n\\")))
Value::String(String::from("\"\t\r\n\\"))
))
);
assert_eq!(
parse_string(r#""name is \"John\".""#),
Ok((
"",
Value::Indexable(IndexableValue::String(String::from("name is \"John\".")))
Value::String(String::from("name is \"John\"."))
))
);
}
@ -128,7 +128,7 @@ mod tests {
fn test_parse_db_value() {
let (input, value) = parse_db_value("5").expect("should parse");
assert_eq!(input, "");
assert_eq!(value, Value::Indexable(IndexableValue::Int(5)));
assert_eq!(value, Value::Int(5));
let (input, value) = parse_db_value("5.5").expect("should parse");
assert_eq!(input, "");
@ -140,9 +140,9 @@ mod tests {
assert_eq!(input, "");
assert_eq!(
value,
Value::Indexable(IndexableValue::String(
Value::String(
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
))
)
);
}
@ -183,7 +183,7 @@ mod tests {
fn test_parse_int() {
assert_eq!(
parse_db_value("5134616"),
Ok(("", Value::Indexable(IndexableValue::Int(5134616))))
Ok(("", Value::Int(5134616)))
);
}
@ -191,7 +191,7 @@ mod tests {
fn test_parse_uuid() {
assert_eq!(
parse_uuid("u131515"),
Ok(("", Value::Indexable(IndexableValue::Uuid(131515))))
Ok(("", Value::Uuid(131515)))
)
}
}

View file

@ -105,7 +105,7 @@ fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result<TableSchema
} in raw_table_schema.columns
{
if is_primary {
primary_keys.push((column_name.clone(), type_))
primary_keys.push((column_name.clone(), type_.clone()))
}
columns.push(column_name);
types.push(type_);
@ -352,12 +352,10 @@ mod tests {
use minisql::operation;
use minisql::operation::Operation;
use minisql::schema::TableSchema;
use minisql::type_system::{IndexableValue, Value};
use minisql::type_system::Value;
use Condition::*;
use IndexableValue::*;
use RawQuerySyntax::*;
use Value::*;
fn users_schema() -> TableSchema {
TableSchema::new(
@ -567,7 +565,7 @@ mod tests {
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::All,
Some(Eq("age".to_string(), Indexable(Int(25)))),
Some(Eq("age".to_string(), Value::Int(25))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
@ -579,7 +577,7 @@ mod tests {
assert!(table_position == users_position);
assert!(column_selection == vec![id, name, age]);
assert!(condition == Some(operation::Condition::Eq(age, Indexable(Int(25)))));
assert!(condition == Some(operation::Condition::Eq(age, Value::Int(25))));
}
#[test]
@ -634,7 +632,7 @@ mod tests {
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::All,
Some(Eq("does_not_exist".to_string(), Indexable(Int(25)))),
Some(Eq("does_not_exist".to_string(), Value::Int(25))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
@ -648,7 +646,7 @@ mod tests {
let syntax: RawQuerySyntax = Select(
"users".to_string(),
ColumnSelection::All,
Some(Eq("age".to_string(), Indexable(String("25".to_string())))),
Some(Eq("age".to_string(), Value::String("25".to_string()))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
@ -665,9 +663,9 @@ mod tests {
let syntax: RawQuerySyntax = Insert(
"users".to_string(),
vec![
("name".to_string(), Indexable(String("Alice".to_string()))),
("id".to_string(), Indexable(Uuid(0))),
("age".to_string(), Indexable(Int(25))),
("name".to_string(), Value::String("Alice".to_string())),
("id".to_string(), Value::Uuid(0)),
("age".to_string(), Value::Int(25)),
],
);
let result = validate_operation(syntax, &db_schema);
@ -685,9 +683,9 @@ mod tests {
assert!(
values
== vec![
Indexable(Uuid(0)),
Indexable(String("Alice".to_string())),
Indexable(Int(25))
Value::Uuid(0),
Value::String("Alice".to_string()),
Value::Int(25)
]
);
}
@ -700,10 +698,10 @@ mod tests {
let syntax: RawQuerySyntax = Insert(
"users".to_string(),
vec![
("name".to_string(), Indexable(String("Alice".to_string()))),
("id".to_string(), Indexable(Uuid(0))),
("age".to_string(), Indexable(Int(25))),
("does_not_exist".to_string(), Indexable(Int(25))),
("name".to_string(), Value::String("Alice".to_string())),
("id".to_string(), Value::Uuid(0)),
("age".to_string(), Value::Int(25)),
("does_not_exist".to_string(), Value::Int(25)),
],
);
let result = validate_operation(syntax, &db_schema);
@ -718,9 +716,9 @@ mod tests {
let syntax: RawQuerySyntax = Insert(
"users".to_string(),
vec![
("name".to_string(), Indexable(String("Alice".to_string()))),
("id".to_string(), Indexable(Uuid(0))),
("age".to_string(), Number(25.0)),
("name".to_string(), Value::String("Alice".to_string())),
("id".to_string(), Value::Uuid(0)),
("age".to_string(), Value::Number(25.0)),
],
);
let result = validate_operation(syntax, &db_schema);
@ -756,7 +754,7 @@ mod tests {
let syntax: RawQuerySyntax = Delete(
"users".to_string(),
Some(Eq("age".to_string(), Indexable(Int(25)))),
Some(Eq("age".to_string(), Value::Int(25))),
);
let result = validate_operation(syntax, &db_schema);
assert!(matches!(
@ -772,7 +770,7 @@ mod tests {
assert!(table_position == users_position);
assert!(column == age);
assert!(value == Indexable(Int(25)));
assert!(value == Value::Int(25));
}
// ====CreateIndex====

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>()
}