Merge branch 'main' into redesign-tables
This commit is contained in:
commit
3076bcd83e
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"
|
||||
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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
)
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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====
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub(crate) mod data;
|
||||
pub mod pglist;
|
||||
pub mod pgoid;
|
||||
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"] }
|
||||
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" }
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue