Merge branch 'optional_values' into 'main'
Introduce Optional type See merge request x433485/minisql!28
This commit is contained in:
commit
5d040f15f5
17 changed files with 618 additions and 247 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -284,6 +284,7 @@ name = "minisql"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bimap",
|
"bimap",
|
||||||
|
"proto",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
@ -429,6 +430,9 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
"rand",
|
||||||
|
"rand_pcg",
|
||||||
|
"rand_seeder",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
@ -568,8 +572,6 @@ dependencies = [
|
||||||
"parser",
|
"parser",
|
||||||
"proto",
|
"proto",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_pcg",
|
|
||||||
"rand_seeder",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ rust-version = "1.74"
|
||||||
bimap = { version = "0.6.3", features = ["serde"] }
|
bimap = { version = "0.6.3", features = ["serde"] }
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
|
proto = { path = "../proto" }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::schema::{ColumnName, TableName};
|
use crate::schema::{ColumnName, TableName};
|
||||||
use crate::type_system::Uuid;
|
use crate::type_system::Uuid;
|
||||||
|
use proto::message::primitive::pgoid::PgOid;
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -23,5 +24,5 @@ pub enum TypeConversionError {
|
||||||
#[error("failed to parse int from text")]
|
#[error("failed to parse int from text")]
|
||||||
IntDecodeFailed(#[from] ParseIntError),
|
IntDecodeFailed(#[from] ParseIntError),
|
||||||
#[error("unknown type with oid {oid} and size {size}")]
|
#[error("unknown type with oid {oid} and size {size}")]
|
||||||
UnknownType { oid: i32, size: i16 },
|
UnknownType { oid: PgOid, size: i16 },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,18 +84,18 @@ impl Table {
|
||||||
value: Value,
|
value: Value,
|
||||||
) -> DbResult<impl Iterator<Item = RestrictedRow> + '_> {
|
) -> DbResult<impl Iterator<Item = RestrictedRow> + '_> {
|
||||||
let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_columns);
|
let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_columns);
|
||||||
match value {
|
match value.to_indexable() {
|
||||||
Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? {
|
Some(indexable_value) => match self.fetch_ids_from_index(column, &indexable_value)? {
|
||||||
Some(ids) => Ok(self
|
Some(ids) => Ok(self
|
||||||
.get_rows_by_ids(ids)
|
.get_rows_by_ids(ids)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(restrict_columns_of_row)),
|
.map(restrict_columns_of_row)),
|
||||||
None => Ok(self
|
None => Ok(self
|
||||||
.get_rows_by_value(column, &Value::Indexable(value))
|
.get_rows_by_value(column, &value)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(restrict_columns_of_row)),
|
.map(restrict_columns_of_row)),
|
||||||
},
|
},
|
||||||
_ => Ok(self
|
None => Ok(self
|
||||||
.get_rows_by_value(column, &value)
|
.get_rows_by_value(column, &value)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(restrict_columns_of_row)),
|
.map(restrict_columns_of_row)),
|
||||||
|
|
@ -112,8 +112,8 @@ impl Table {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (column, column_index) in &mut self.indexes {
|
for (column, column_index) in &mut self.indexes {
|
||||||
if let Value::Indexable(val) = &row[*column] {
|
if let Some(indexable_value) = &row[*column].to_indexable() {
|
||||||
column_index.add(val.clone(), id)
|
column_index.add(indexable_value.clone(), id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,8 +126,8 @@ impl Table {
|
||||||
match self.rows.remove(&id) {
|
match self.rows.remove(&id) {
|
||||||
Some(row) => {
|
Some(row) => {
|
||||||
for (column, column_index) in &mut self.indexes {
|
for (column, column_index) in &mut self.indexes {
|
||||||
if let Value::Indexable(value) = &row[*column] {
|
if let Some(indexable_value) = &row[*column].to_indexable() {
|
||||||
let _ = column_index.remove(value, id);
|
let _ = column_index.remove(indexable_value, id);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
1
|
1
|
||||||
|
|
@ -167,12 +167,12 @@ impl Table {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_rows_where_eq(&mut self, column: Column, value: Value) -> DbResult<usize> {
|
pub fn delete_rows_where_eq(&mut self, column: Column, value: Value) -> DbResult<usize> {
|
||||||
match value {
|
match value.to_indexable() {
|
||||||
Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? {
|
Some(indexable_value) => match self.fetch_ids_from_index(column, &indexable_value)? {
|
||||||
Some(ids) => Ok(self.delete_rows_by_ids(ids)),
|
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,
|
column: Column,
|
||||||
) -> DbResult<()> {
|
) -> DbResult<()> {
|
||||||
for (id, row) in &table.rows {
|
for (id, row) in &table.rows {
|
||||||
let value = match &row[column] {
|
let value = match &row[column].to_indexable() {
|
||||||
Value::Indexable(value) => value.clone(),
|
Some(indexable_value) => indexable_value.clone(),
|
||||||
_ => {
|
None => {
|
||||||
let column_name: ColumnName = table.schema.column_name_from_column(column);
|
let column_name: ColumnName = table.schema.column_name_from_column(column);
|
||||||
return Err(RuntimeError::AttemptToIndexNonIndexableColumn(
|
return Err(RuntimeError::AttemptToIndexNonIndexableColumn(
|
||||||
table.table_name().to_string(),
|
table.table_name().to_string(),
|
||||||
|
|
|
||||||
|
|
@ -210,9 +210,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_select_basic1() {
|
fn test_insert_select_basic1() {
|
||||||
use IndexableValue::*;
|
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
let users = 0;
|
let users = 0;
|
||||||
|
|
@ -222,9 +219,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id, name, age) = (
|
let (id, name, age) = (
|
||||||
Indexable(Uuid(0)),
|
Value::Uuid(0),
|
||||||
Indexable(String("Plato".to_string())),
|
Value::String("Plato".to_string()),
|
||||||
Indexable(Int(64)),
|
Value::Int(64),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Operation::Insert(
|
.interpret(Operation::Insert(
|
||||||
|
|
@ -254,9 +251,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_select_basic2() {
|
fn test_insert_select_basic2() {
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
|
|
@ -268,9 +263,9 @@ mod tests {
|
||||||
state.interpret(CreateTable(users_schema.clone())).unwrap();
|
state.interpret(CreateTable(users_schema.clone())).unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
Indexable(Uuid(0)),
|
Value::Uuid(0),
|
||||||
Indexable(String("Plato".to_string())),
|
Value::String("Plato".to_string()),
|
||||||
Indexable(Int(64)),
|
Value::Int(64),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
|
|
@ -280,9 +275,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id1, name1, age1) = (
|
let (id1, name1, age1) = (
|
||||||
Indexable(Uuid(1)),
|
Value::Uuid(1),
|
||||||
Indexable(String("Aristotle".to_string())),
|
Value::String("Aristotle".to_string()),
|
||||||
Indexable(Int(20)),
|
Value::Int(20),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
|
|
@ -364,9 +359,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete() {
|
fn test_delete() {
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
|
|
@ -377,9 +370,9 @@ mod tests {
|
||||||
state.interpret(CreateTable(users_schema.clone())).unwrap();
|
state.interpret(CreateTable(users_schema.clone())).unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
Indexable(Uuid(0)),
|
Value::Uuid(0),
|
||||||
Indexable(String("Plato".to_string())),
|
Value::String("Plato".to_string()),
|
||||||
Indexable(Int(64)),
|
Value::Int(64),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
|
|
@ -389,9 +382,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id1, name1, age1) = (
|
let (id1, name1, age1) = (
|
||||||
Indexable(Uuid(1)),
|
Value::Uuid(1),
|
||||||
Indexable(String("Aristotle".to_string())),
|
Value::String("Aristotle".to_string()),
|
||||||
Indexable(Int(20)),
|
Value::Int(20),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
|
|
@ -427,9 +420,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_index() {
|
fn test_index() {
|
||||||
use IndexableValue::*;
|
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
|
|
@ -444,9 +435,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
Indexable(Uuid(0)),
|
Value::Uuid(0),
|
||||||
Indexable(String("Plato".to_string())),
|
Value::String("Plato".to_string()),
|
||||||
Indexable(Int(64)),
|
Value::Int(64),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
|
|
@ -456,9 +447,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id1, name1, age1) = (
|
let (id1, name1, age1) = (
|
||||||
Indexable(Uuid(1)),
|
Value::Uuid(1),
|
||||||
Indexable(String("Aristotle".to_string())),
|
Value::String("Aristotle".to_string()),
|
||||||
Indexable(Int(20)),
|
Value::Int(20),
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
|
|
@ -480,7 +471,7 @@ mod tests {
|
||||||
let aristotle_id = 1;
|
let aristotle_id = 1;
|
||||||
|
|
||||||
let plato_ids = index
|
let plato_ids = index
|
||||||
.get(&String("Plato".to_string()))
|
.get(&IndexableValue::String("Plato".to_string()))
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or(HashSet::new());
|
.unwrap_or(HashSet::new());
|
||||||
assert!(plato_ids.contains(&plato_id));
|
assert!(plato_ids.contains(&plato_id));
|
||||||
|
|
@ -490,11 +481,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn example() {
|
pub fn example() {
|
||||||
use crate::type_system::{DbType, IndexableValue, Value};
|
use crate::type_system::{DbType, Value};
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
let id_column: Column = 0;
|
let id_column: Column = 0;
|
||||||
let name_column: Column = 1;
|
let name_column: Column = 1;
|
||||||
|
|
@ -520,9 +509,9 @@ pub fn example() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
Indexable(Uuid(0)),
|
Value::Uuid(0),
|
||||||
Indexable(String("Plato".to_string())),
|
Value::String("Plato".to_string()),
|
||||||
Indexable(Int(64)),
|
Value::Int(64),
|
||||||
);
|
);
|
||||||
println!("==INSERT Plato==");
|
println!("==INSERT Plato==");
|
||||||
state
|
state
|
||||||
|
|
@ -533,9 +522,9 @@ pub fn example() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id1, name1, age1) = (
|
let (id1, name1, age1) = (
|
||||||
Indexable(Uuid(1)),
|
Value::Uuid(1),
|
||||||
Indexable(String("Aristotle".to_string())),
|
Value::String("Aristotle".to_string()),
|
||||||
Indexable(Int(20)),
|
Value::Int(20),
|
||||||
);
|
);
|
||||||
println!("==INSERT Aristotle==");
|
println!("==INSERT Aristotle==");
|
||||||
state
|
state
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::internals::row::Row;
|
use crate::internals::row::Row;
|
||||||
use crate::operation::{ColumnSelection, InsertionValues};
|
use crate::operation::{ColumnSelection, InsertionValues};
|
||||||
use crate::result::DbResult;
|
use crate::result::DbResult;
|
||||||
use crate::type_system::{DbType, IndexableValue, Uuid, Value};
|
use crate::type_system::{DbType, IndexableValue, Uuid};
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl TableSchema {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_type(&self, column: Column) -> DbType {
|
pub fn column_type(&self, column: Column) -> DbType {
|
||||||
self.types[column]
|
self.types[column].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_columns(&self) -> Vec<&ColumnName> {
|
pub fn get_columns(&self) -> Vec<&ColumnName> {
|
||||||
|
|
@ -88,7 +88,7 @@ impl TableSchema {
|
||||||
|
|
||||||
pub fn get_type_at(&self, column_name: &ColumnName) -> Option<DbType> {
|
pub fn get_type_at(&self, column_name: &ColumnName) -> Option<DbType> {
|
||||||
let position = self.get_column(column_name)?;
|
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 {
|
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 row: Row = Row::new_from_insertion_values(insertion_values);
|
||||||
|
|
||||||
let id: Uuid = match row.get(self.primary_key) {
|
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)
|
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)
|
None => unreachable!(), // SAFETY: Should be guaranteed by validation (missing columns)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,157 @@
|
||||||
use crate::error::TypeConversionError;
|
use crate::error::TypeConversionError;
|
||||||
|
use proto::message::primitive::pgoid::PgOid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
// ==============Types================
|
// ==============Types================
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum DbType {
|
pub enum DbType {
|
||||||
String,
|
String,
|
||||||
Int,
|
Int,
|
||||||
Number,
|
Number,
|
||||||
Uuid,
|
Uuid,
|
||||||
|
Option(Box<DbType>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============Values================
|
// ==============Values================
|
||||||
pub type Uuid = u64;
|
pub type Uuid = u64;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(try_from = "String", into = "String")]
|
#[serde(try_from = "String", into = "String")]
|
||||||
pub enum Value {
|
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),
|
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")]
|
#[serde(try_from = "String", into = "String")]
|
||||||
pub enum IndexableValue {
|
pub enum IndexableValue {
|
||||||
String(String),
|
String(String),
|
||||||
Int(u64),
|
Int(u64),
|
||||||
Uuid(Uuid),
|
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 {
|
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 {
|
pub fn is_indexable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::String => true,
|
Self::String => true,
|
||||||
Self::Int => true,
|
Self::Int => true,
|
||||||
Self::Number => false,
|
Self::Number => false,
|
||||||
Self::Uuid => true,
|
Self::Uuid => true,
|
||||||
|
Self::Option(type_) => type_.is_indexable(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn type_oid(&self) -> i32 {
|
fn to_json_key_string(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::String => 25,
|
DbType::String => format!("String()"),
|
||||||
Self::Int => 23,
|
DbType::Int => format!("Int()"),
|
||||||
Self::Number => 701,
|
DbType::Number => format!("Number()"),
|
||||||
Self::Uuid => 2950,
|
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::Int => 8,
|
||||||
Self::Number => 8,
|
Self::Number => 8,
|
||||||
Self::Uuid => 16,
|
Self::Uuid => 16,
|
||||||
|
Self::Option(t) => t.type_size(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,137 +170,266 @@ impl Value {
|
||||||
pub fn to_type(&self) -> DbType {
|
pub fn to_type(&self) -> DbType {
|
||||||
match self {
|
match self {
|
||||||
Self::Number(_) => DbType::Number,
|
Self::Number(_) => DbType::Number,
|
||||||
Self::Indexable(val) => match val {
|
Self::String(_) => DbType::String,
|
||||||
IndexableValue::String(_) => DbType::String,
|
Self::Int(_) => DbType::Int,
|
||||||
IndexableValue::Int(_) => DbType::Int,
|
Self::Uuid(_) => DbType::Uuid,
|
||||||
IndexableValue::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> {
|
pub fn as_text_bytes(&self) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
Self::Number(n) => format!("{n}").into_bytes(),
|
Self::Number(n) => format!("{n}").into_bytes(),
|
||||||
Self::Indexable(i) => match i {
|
Self::String(s) => format!("{s}\0").into_bytes(),
|
||||||
IndexableValue::String(s) => format!("{s}\0").into_bytes(),
|
Self::Int(i) => format!("{i}").into_bytes(),
|
||||||
IndexableValue::Int(i) => format!("{i}").into_bytes(),
|
Self::Uuid(u) => format!("u{u}").into_bytes(),
|
||||||
IndexableValue::Uuid(u) => format!("{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(
|
pub fn from_text_bytes(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
type_oid: i32,
|
type_oid: PgOid,
|
||||||
type_size: i16,
|
type_size: i16,
|
||||||
) -> Result<Value, TypeConversionError> {
|
) -> 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) => {
|
(701, 8) => {
|
||||||
let s = std::str::from_utf8(bytes)?;
|
let n = text.parse::<f64>()?;
|
||||||
let n = s.parse::<f64>()?;
|
|
||||||
Ok(Value::Number(n))
|
Ok(Value::Number(n))
|
||||||
}
|
}
|
||||||
(25, -2) => {
|
(25, -2) => {
|
||||||
let s = std::str::from_utf8(bytes)?;
|
let s = &text[..text.len() - 1]; // remove null terminator
|
||||||
let s = &s[..s.len() - 1]; // remove null terminator
|
Ok(Value::String(s.to_string()))
|
||||||
Ok(Value::Indexable(IndexableValue::String(s.to_string())))
|
|
||||||
}
|
}
|
||||||
(23, 8) => {
|
(23, 8) => {
|
||||||
let s = std::str::from_utf8(bytes)?;
|
let n = text.parse::<u64>()?;
|
||||||
let n = s.parse::<u64>()?;
|
Ok(Value::Int(n))
|
||||||
Ok(Value::Indexable(IndexableValue::Int(n)))
|
|
||||||
}
|
}
|
||||||
(2950, 16) => {
|
(2950, 16) => {
|
||||||
let s = std::str::from_utf8(bytes)?;
|
let n = text[1..].parse::<u64>()?;
|
||||||
let n = s.parse::<Uuid>()?;
|
Ok(Value::Uuid(n))
|
||||||
Ok(Value::Indexable(IndexableValue::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
|
// Own string serialization so enums can be used as keys in maps
|
||||||
impl From<IndexableValue> for String {
|
impl From<IndexableValue> for String {
|
||||||
fn from(value: IndexableValue) -> Self {
|
fn from(value: IndexableValue) -> Self {
|
||||||
match value {
|
value.to_json_key_string()
|
||||||
IndexableValue::String(s) => format!("String({s})"),
|
|
||||||
IndexableValue::Int(i) => format!("Int({i})"),
|
|
||||||
IndexableValue::Uuid(u) => format!("Uuid({u})"),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl TryFrom<String> for IndexableValue {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(str: String) -> Result<Self, Self::Error> {
|
||||||
if !value.ends_with(')') {
|
Self::parse_json_key_string(&str)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Value> for String {
|
impl From<Value> for String {
|
||||||
fn from(value: Value) -> Self {
|
fn from(value: Value) -> Self {
|
||||||
match value {
|
value.to_json_key_string()
|
||||||
Value::Number(n) => format!("Number({n})"),
|
|
||||||
Value::Indexable(i) => format!("Indexable({})", String::from(i)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for Value {
|
impl TryFrom<String> for Value {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
if !value.ends_with(')') {
|
Value::parse_json_key_string(&value)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{IndexableValue, Value};
|
use super::{DbType, Value};
|
||||||
use crate::error::TypeConversionError::UnknownType;
|
use crate::error::TypeConversionError::UnknownType;
|
||||||
use crate::type_system::Value::{Indexable, Number};
|
use proto::message::primitive::pgoid::PgOid;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_number() {
|
fn test_encode_number() {
|
||||||
|
|
@ -217,14 +442,15 @@ mod tests {
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
||||||
assert_eq!(value, from_bytes);
|
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);
|
assert_eq!(size, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_string() {
|
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 vtype = value.to_type();
|
||||||
let oid = vtype.type_oid();
|
let oid = vtype.type_oid();
|
||||||
let size = vtype.type_size();
|
let size = vtype.type_size();
|
||||||
|
|
@ -233,14 +459,15 @@ mod tests {
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
||||||
assert_eq!(value, from_bytes);
|
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);
|
assert_eq!(size, -2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_string_utf8() {
|
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 vtype = value.to_type();
|
||||||
let oid = vtype.type_oid();
|
let oid = vtype.type_oid();
|
||||||
let size = vtype.type_size();
|
let size = vtype.type_size();
|
||||||
|
|
@ -250,13 +477,13 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(value, from_bytes);
|
assert_eq!(value, from_bytes);
|
||||||
|
|
||||||
assert_eq!(oid, 25);
|
assert_eq!(oid.as_simple(), 25);
|
||||||
assert_eq!(size, -2);
|
assert_eq!(size, -2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_int() {
|
fn test_encode_int() {
|
||||||
let value = Value::Indexable(IndexableValue::Int(123));
|
let value = Value::Int(123);
|
||||||
let vtype = value.to_type();
|
let vtype = value.to_type();
|
||||||
let oid = vtype.type_oid();
|
let oid = vtype.type_oid();
|
||||||
let size = vtype.type_size();
|
let size = vtype.type_size();
|
||||||
|
|
@ -265,14 +492,15 @@ mod tests {
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
||||||
assert_eq!(value, from_bytes);
|
assert_eq!(value, from_bytes);
|
||||||
|
assert_eq!(bytes, b"123");
|
||||||
|
|
||||||
assert_eq!(oid, 23);
|
assert_eq!(oid.as_simple(), 23);
|
||||||
assert_eq!(size, 8);
|
assert_eq!(size, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_uuid() {
|
fn test_encode_uuid() {
|
||||||
let value = Value::Indexable(IndexableValue::Uuid(123));
|
let value = Value::Uuid(123);
|
||||||
let vtype = value.to_type();
|
let vtype = value.to_type();
|
||||||
let oid = vtype.type_oid();
|
let oid = vtype.type_oid();
|
||||||
let size = vtype.type_size();
|
let size = vtype.type_size();
|
||||||
|
|
@ -281,14 +509,15 @@ mod tests {
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||||
|
|
||||||
assert_eq!(value, from_bytes);
|
assert_eq!(value, from_bytes);
|
||||||
|
assert_eq!(bytes, b"u123");
|
||||||
|
|
||||||
assert_eq!(oid, 2950);
|
assert_eq!(oid.as_simple(), 2950);
|
||||||
assert_eq!(size, 16);
|
assert_eq!(size, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mismatched_size() {
|
fn test_mismatched_size() {
|
||||||
let value = Value::Indexable(IndexableValue::Uuid(123));
|
let value = Value::Uuid(123);
|
||||||
let vtype = value.to_type();
|
let vtype = value.to_type();
|
||||||
let oid = vtype.type_oid();
|
let oid = vtype.type_oid();
|
||||||
let size = 8;
|
let size = 8;
|
||||||
|
|
@ -296,22 +525,24 @@ mod tests {
|
||||||
let bytes = value.as_text_bytes();
|
let bytes = value.as_text_bytes();
|
||||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size);
|
let from_bytes = Value::from_text_bytes(&bytes, oid, size);
|
||||||
|
|
||||||
|
println!("{from_bytes:?}");
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
from_bytes,
|
from_bytes,
|
||||||
Err(UnknownType { oid: 2950, size: 8 })
|
Err(UnknownType {
|
||||||
|
oid: PgOid::Nested(2950, 0),
|
||||||
|
size: 8
|
||||||
|
})
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_value_stringification() {
|
fn test_value_stringification() {
|
||||||
let pairs = vec![
|
let pairs = vec![
|
||||||
(Number(1.0), "Number(1)"),
|
(Value::Number(1.0), "Number(1)"),
|
||||||
(
|
(Value::String("hello".to_string()), "String(hello)"),
|
||||||
Indexable(IndexableValue::String("hello".to_string())),
|
(Value::Int(123), "Int(123)"),
|
||||||
"Indexable(String(hello))",
|
(Value::Uuid(123), "Uuid(123)"),
|
||||||
),
|
|
||||||
(Indexable(IndexableValue::Int(123)), "Indexable(Int(123))"),
|
|
||||||
(Indexable(IndexableValue::Uuid(123)), "Indexable(Uuid(123))"),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (value, string) in pairs {
|
for (value, string) in pairs {
|
||||||
|
|
@ -322,4 +553,75 @@ mod tests {
|
||||||
assert_eq!(deserialized, Ok(value));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_equality() {
|
fn test_parse_equality() {
|
||||||
use minisql::type_system::{IndexableValue, Value};
|
use minisql::type_system::Value;
|
||||||
match parse_equality("id = 1") {
|
match parse_equality("id = 1") {
|
||||||
Ok(("", Condition::Eq(column_name, value))) => {
|
Ok(("", Condition::Eq(column_name, value))) => {
|
||||||
assert!(column_name.eq("id"));
|
assert!(column_name.eq("id"));
|
||||||
assert_eq!(value, Value::Indexable(IndexableValue::Int(1)))
|
assert_eq!(value, Value::Int(1))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("should parse");
|
panic!("should parse");
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ pub fn parse_values(input: &str) -> IResult<&str, Vec<Value>> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::type_system::{IndexableValue, Value};
|
use minisql::type_system::Value;
|
||||||
|
|
||||||
use super::parse_insert;
|
use super::parse_insert;
|
||||||
use crate::syntax::RawQuerySyntax;
|
use crate::syntax::RawQuerySyntax;
|
||||||
|
|
@ -72,10 +72,10 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
insertion_values,
|
insertion_values,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), Value::Indexable(IndexableValue::Int(1))),
|
("id".to_string(), Value::Int(1)),
|
||||||
(
|
(
|
||||||
"data".to_string(),
|
"data".to_string(),
|
||||||
Value::Indexable(IndexableValue::String("Text".to_string()))
|
Value::String("Text".to_string())
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -97,10 +97,10 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
insertion_values,
|
insertion_values,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), Value::Indexable(IndexableValue::Int(1))),
|
("id".to_string(), Value::Int(1)),
|
||||||
(
|
(
|
||||||
"data".to_string(),
|
"data".to_string(),
|
||||||
Value::Indexable(IndexableValue::String("Text".to_string()))
|
Value::String("Text".to_string())
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use minisql::type_system::{IndexableValue, Value};
|
use minisql::type_system::Value;
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
character::complete::{char, digit1, none_of, u64},
|
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)
|
let value = format!("{}{}", sign.unwrap_or('+'), digits)
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|_| nom::Err::Failure(make_error(input, nom::error::ErrorKind::Fail)))?;
|
.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> {
|
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> {
|
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
|
// Combine the characters into a string
|
||||||
let value: String = content.into_iter().collect();
|
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> {
|
pub fn parse_uuid(input: &str) -> IResult<&str, Value> {
|
||||||
let (input, value) = pair(char('u'), u64)(input)
|
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))
|
Ok((input, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid};
|
use crate::parsing::literal::{parse_db_value, parse_string, parse_uuid};
|
||||||
use minisql::type_system::{IndexableValue, Value};
|
use minisql::type_system::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_parser() {
|
fn test_string_parser() {
|
||||||
|
|
@ -105,21 +105,21 @@ mod tests {
|
||||||
parse_string(r#""simple""#),
|
parse_string(r#""simple""#),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Value::Indexable(IndexableValue::String(String::from("simple")))
|
Value::String(String::from("simple"))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_string(r#""\"\t\r\n\\""#),
|
parse_string(r#""\"\t\r\n\\""#),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Value::Indexable(IndexableValue::String(String::from("\"\t\r\n\\")))
|
Value::String(String::from("\"\t\r\n\\"))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_string(r#""name is \"John\".""#),
|
parse_string(r#""name is \"John\".""#),
|
||||||
Ok((
|
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() {
|
fn test_parse_db_value() {
|
||||||
let (input, value) = parse_db_value("5").expect("should parse");
|
let (input, value) = parse_db_value("5").expect("should parse");
|
||||||
assert_eq!(input, "");
|
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");
|
let (input, value) = parse_db_value("5.5").expect("should parse");
|
||||||
assert_eq!(input, "");
|
assert_eq!(input, "");
|
||||||
|
|
@ -140,9 +140,9 @@ mod tests {
|
||||||
assert_eq!(input, "");
|
assert_eq!(input, "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
Value::Indexable(IndexableValue::String(
|
Value::String(
|
||||||
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
|
"abcdefghkjklmnopqrstuvwxyz!@#$%^&*()_+ ".to_string()
|
||||||
))
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +183,7 @@ mod tests {
|
||||||
fn test_parse_int() {
|
fn test_parse_int() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_db_value("5134616"),
|
parse_db_value("5134616"),
|
||||||
Ok(("", Value::Indexable(IndexableValue::Int(5134616))))
|
Ok(("", Value::Int(5134616)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ mod tests {
|
||||||
fn test_parse_uuid() {
|
fn test_parse_uuid() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_uuid("u131515"),
|
parse_uuid("u131515"),
|
||||||
Ok(("", Value::Indexable(IndexableValue::Uuid(131515))))
|
Ok(("", Value::Uuid(131515)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result<TableSchema
|
||||||
} in raw_table_schema.columns
|
} in raw_table_schema.columns
|
||||||
{
|
{
|
||||||
if is_primary {
|
if is_primary {
|
||||||
primary_keys.push((column_name.clone(), type_))
|
primary_keys.push((column_name.clone(), type_.clone()))
|
||||||
}
|
}
|
||||||
columns.push(column_name);
|
columns.push(column_name);
|
||||||
types.push(type_);
|
types.push(type_);
|
||||||
|
|
@ -352,12 +352,10 @@ mod tests {
|
||||||
use minisql::operation;
|
use minisql::operation;
|
||||||
use minisql::operation::Operation;
|
use minisql::operation::Operation;
|
||||||
use minisql::schema::TableSchema;
|
use minisql::schema::TableSchema;
|
||||||
use minisql::type_system::{IndexableValue, Value};
|
use minisql::type_system::Value;
|
||||||
|
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
|
||||||
use RawQuerySyntax::*;
|
use RawQuerySyntax::*;
|
||||||
use Value::*;
|
|
||||||
|
|
||||||
fn users_schema() -> TableSchema {
|
fn users_schema() -> TableSchema {
|
||||||
TableSchema::new(
|
TableSchema::new(
|
||||||
|
|
@ -567,7 +565,7 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Select(
|
let syntax: RawQuerySyntax = Select(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
ColumnSelection::All,
|
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);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
|
assert!(matches!(result, Ok(Operation::Select(_, _, _))));
|
||||||
|
|
@ -579,7 +577,7 @@ mod tests {
|
||||||
assert!(table_position == users_position);
|
assert!(table_position == users_position);
|
||||||
assert!(column_selection == vec![id, name, age]);
|
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]
|
#[test]
|
||||||
|
|
@ -634,7 +632,7 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Select(
|
let syntax: RawQuerySyntax = Select(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
ColumnSelection::All,
|
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);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
|
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
|
||||||
|
|
@ -648,7 +646,7 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Select(
|
let syntax: RawQuerySyntax = Select(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
ColumnSelection::All,
|
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);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
|
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
|
||||||
|
|
@ -665,9 +663,9 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Insert(
|
let syntax: RawQuerySyntax = Insert(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
vec![
|
vec![
|
||||||
("name".to_string(), Indexable(String("Alice".to_string()))),
|
("name".to_string(), Value::String("Alice".to_string())),
|
||||||
("id".to_string(), Indexable(Uuid(0))),
|
("id".to_string(), Value::Uuid(0)),
|
||||||
("age".to_string(), Indexable(Int(25))),
|
("age".to_string(), Value::Int(25)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
|
|
@ -685,9 +683,9 @@ mod tests {
|
||||||
assert!(
|
assert!(
|
||||||
values
|
values
|
||||||
== vec![
|
== vec![
|
||||||
Indexable(Uuid(0)),
|
Value::Uuid(0),
|
||||||
Indexable(String("Alice".to_string())),
|
Value::String("Alice".to_string()),
|
||||||
Indexable(Int(25))
|
Value::Int(25)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -700,10 +698,10 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Insert(
|
let syntax: RawQuerySyntax = Insert(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
vec![
|
vec![
|
||||||
("name".to_string(), Indexable(String("Alice".to_string()))),
|
("name".to_string(), Value::String("Alice".to_string())),
|
||||||
("id".to_string(), Indexable(Uuid(0))),
|
("id".to_string(), Value::Uuid(0)),
|
||||||
("age".to_string(), Indexable(Int(25))),
|
("age".to_string(), Value::Int(25)),
|
||||||
("does_not_exist".to_string(), Indexable(Int(25))),
|
("does_not_exist".to_string(), Value::Int(25)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
|
|
@ -718,9 +716,9 @@ mod tests {
|
||||||
let syntax: RawQuerySyntax = Insert(
|
let syntax: RawQuerySyntax = Insert(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
vec![
|
vec![
|
||||||
("name".to_string(), Indexable(String("Alice".to_string()))),
|
("name".to_string(), Value::String("Alice".to_string())),
|
||||||
("id".to_string(), Indexable(Uuid(0))),
|
("id".to_string(), Value::Uuid(0)),
|
||||||
("age".to_string(), Number(25.0)),
|
("age".to_string(), Value::Number(25.0)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let result = validate_operation(syntax, &db_schema);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
|
|
@ -756,7 +754,7 @@ mod tests {
|
||||||
|
|
||||||
let syntax: RawQuerySyntax = Delete(
|
let syntax: RawQuerySyntax = Delete(
|
||||||
"users".to_string(),
|
"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);
|
let result = validate_operation(syntax, &db_schema);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|
@ -772,7 +770,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(table_position == users_position);
|
assert!(table_position == users_position);
|
||||||
assert!(column == age);
|
assert!(column == age);
|
||||||
assert!(value == Indexable(Int(25)));
|
assert!(value == Value::Int(25));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====CreateIndex====
|
// ====CreateIndex====
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,6 @@ bincode = "2.0.0-rc.3"
|
||||||
tokio = { version = "1.34.0", features = ["io-util", "macros", "test-util"] }
|
tokio = { version = "1.34.0", features = ["io-util", "macros", "test-util"] }
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
|
rand_seeder = "0.2.3"
|
||||||
|
rand_pcg = "0.3.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::message::errors::{ProtoDeserializeError, ProtoSerializeError};
|
use crate::message::errors::{ProtoDeserializeError, ProtoSerializeError};
|
||||||
use crate::message::primitive::data::MessageData;
|
use crate::message::primitive::data::MessageData;
|
||||||
use crate::message::primitive::pglist::PgList;
|
use crate::message::primitive::pglist::PgList;
|
||||||
|
use crate::message::primitive::pgoid::PgOid;
|
||||||
use crate::message::primitive::pgstring::PgString;
|
use crate::message::primitive::pgstring::PgString;
|
||||||
use crate::message::proto_message::ProtoMessage;
|
use crate::message::proto_message::ProtoMessage;
|
||||||
use bincode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
|
|
@ -186,9 +187,9 @@ impl From<RowDescriptionData> for BackendMessage {
|
||||||
#[derive(Debug, Clone, Encode, Decode)]
|
#[derive(Debug, Clone, Encode, Decode)]
|
||||||
pub struct ColumnDescription {
|
pub struct ColumnDescription {
|
||||||
pub name: PgString,
|
pub name: PgString,
|
||||||
pub table_oid: i32,
|
pub table_oid: PgOid,
|
||||||
pub column_index: i16,
|
pub column_index: i16,
|
||||||
pub type_oid: i32,
|
pub type_oid: PgOid,
|
||||||
pub type_size: i16,
|
pub type_size: i16,
|
||||||
pub type_modifier: i32,
|
pub type_modifier: i32,
|
||||||
pub format_code: i16,
|
pub format_code: i16,
|
||||||
|
|
@ -330,9 +331,9 @@ mod tests {
|
||||||
let backend = BackendMessage::RowDescription(RowDescriptionData {
|
let backend = BackendMessage::RowDescription(RowDescriptionData {
|
||||||
columns: PgList::from(vec![ColumnDescription {
|
columns: PgList::from(vec![ColumnDescription {
|
||||||
name: PgString::from("Some name"),
|
name: PgString::from("Some name"),
|
||||||
table_oid: 123,
|
table_oid: PgOid::Simple(123),
|
||||||
column_index: 456,
|
column_index: 456,
|
||||||
type_oid: 789,
|
type_oid: PgOid::Simple(789),
|
||||||
type_size: 101,
|
type_size: 101,
|
||||||
type_modifier: 112,
|
type_modifier: 112,
|
||||||
format_code: 113,
|
format_code: 113,
|
||||||
|
|
@ -347,9 +348,9 @@ mod tests {
|
||||||
let columns: Vec<ColumnDescription> = columns.into();
|
let columns: Vec<ColumnDescription> = columns.into();
|
||||||
let column = &columns[0];
|
let column = &columns[0];
|
||||||
column.name.as_str() == "Some name"
|
column.name.as_str() == "Some name"
|
||||||
&& column.table_oid == 123
|
&& column.table_oid.as_simple() == 123
|
||||||
&& column.column_index == 456
|
&& column.column_index == 456
|
||||||
&& column.type_oid == 789
|
&& column.type_oid.as_simple() == 789
|
||||||
&& column.type_size == 101
|
&& column.type_size == 101
|
||||||
&& column.type_modifier == 112
|
&& column.type_modifier == 112
|
||||||
&& column.format_code == 113
|
&& column.format_code == 113
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
pub(crate) mod data;
|
pub(crate) mod data;
|
||||||
pub mod pglist;
|
pub mod pglist;
|
||||||
|
pub mod pgoid;
|
||||||
pub mod pgstring;
|
pub mod pgstring;
|
||||||
|
|
|
||||||
79
proto/src/message/primitive/pgoid.rs
Normal file
79
proto/src/message/primitive/pgoid.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,6 @@ anyhow = "1.0.76"
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_seeder = "0.2.3"
|
|
||||||
rand_pcg = "0.3.1"
|
|
||||||
serde_json = "1.0.112"
|
serde_json = "1.0.112"
|
||||||
minisql = { path = "../minisql" }
|
minisql = { path = "../minisql" }
|
||||||
proto = { path = "../proto" }
|
proto = { path = "../proto" }
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,8 @@ use proto::message::backend::{
|
||||||
ReadyForQueryData, RowDescriptionData,
|
ReadyForQueryData, RowDescriptionData,
|
||||||
};
|
};
|
||||||
use proto::message::primitive::pglist::PgList;
|
use proto::message::primitive::pglist::PgList;
|
||||||
|
use proto::message::primitive::pgoid::PgOid;
|
||||||
use proto::writer::backend::BackendProtoWriter;
|
use proto::writer::backend::BackendProtoWriter;
|
||||||
use rand::Rng;
|
|
||||||
use rand_pcg::Pcg64;
|
|
||||||
use rand_seeder::Seeder;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub enum CompleteStatus {
|
pub enum CompleteStatus {
|
||||||
|
|
@ -123,7 +121,7 @@ fn column_to_description(
|
||||||
column: Column,
|
column: Column,
|
||||||
) -> anyhow::Result<ColumnDescription> {
|
) -> anyhow::Result<ColumnDescription> {
|
||||||
let table_name = schema.table_name();
|
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 column_type = schema.column_type(column);
|
||||||
let name = schema.column_name_from_column(column);
|
let name = schema.column_name_from_column(column);
|
||||||
|
|
@ -142,10 +140,3 @@ fn column_to_description(
|
||||||
format_code: 0, // text format
|
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>()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue