Introduce optional type/value

This commit is contained in:
Yuriy Dupyn 2024-02-01 14:35:25 +01:00
parent 597c52caa0
commit 339686f5c5
4 changed files with 255 additions and 204 deletions

View file

@ -85,18 +85,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)),
@ -113,8 +113,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)
} }
} }
@ -127,8 +127,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
@ -168,12 +168,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)),
} }
} }
@ -226,9 +226,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(),

View file

@ -2,7 +2,7 @@ use crate::internals::table::Table;
use crate::operation::{ColumnSelection, Condition, Operation}; use crate::operation::{ColumnSelection, Condition, Operation};
use crate::restricted_row::RestrictedRow; use crate::restricted_row::RestrictedRow;
use crate::result::DbResult; use crate::result::DbResult;
use crate::schema::{Column, TableName, TablePosition, TableSchema}; use crate::schema::{TableName, TablePosition, TableSchema};
use bimap::BiMap; use bimap::BiMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -491,102 +491,103 @@ mod tests {
} }
} }
pub fn example() { // TODO
use crate::type_system::{DbType, IndexableValue, Value}; // pub fn example() {
use Condition::*; // use crate::type_system::{DbType, IndexableValue, Value};
use IndexableValue::*; // use Condition::*;
use Operation::*; // use IndexableValue::*;
use Value::*; // use Operation::*;
// use Value::*;
let id_column: Column = 0; // let id_column: Column = 0;
let name_column: Column = 1; // let name_column: Column = 1;
// let age_column: ColumnPosition = 2; // // let age_column: ColumnPosition = 2;
let users_schema: TableSchema = { // let users_schema: TableSchema = {
TableSchema::new( // TableSchema::new(
"users".to_string(), // "users".to_string(),
"id".to_string(), // "id".to_string(),
vec![ // vec![
"id".to_string(), // 0 // "id".to_string(), // 0
"name".to_string(), // 1 // "name".to_string(), // 1
"age".to_string(), // 2 // "age".to_string(), // 2
], // ],
vec![DbType::Uuid, DbType::String, DbType::Int], // vec![DbType::Uuid, DbType::String, DbType::Int],
) // )
}; // };
let users_position: TablePosition = 0; // let users_position: TablePosition = 0;
let mut state = State::new(); // let mut state = State::new();
state // state
.interpret(Operation::CreateTable(users_schema.clone())) // .interpret(Operation::CreateTable(users_schema.clone()))
.unwrap(); // .unwrap();
let (id0, name0, age0) = ( // let (id0, name0, age0) = (
Indexable(Uuid(0)), // Indexable(Uuid(0)),
Indexable(String("Plato".to_string())), // Indexable(String("Plato".to_string())),
Indexable(Int(64)), // Indexable(Int(64)),
); // );
println!("==INSERT Plato=="); // println!("==INSERT Plato==");
state // state
.interpret(Insert( // .interpret(Insert(
users_position, // users_position,
vec![id0.clone(), name0.clone(), age0.clone()], // vec![id0.clone(), name0.clone(), age0.clone()],
)) // ))
.unwrap(); // .unwrap();
let (id1, name1, age1) = ( // let (id1, name1, age1) = (
Indexable(Uuid(1)), // Indexable(Uuid(1)),
Indexable(String("Aristotle".to_string())), // Indexable(String("Aristotle".to_string())),
Indexable(Int(20)), // Indexable(Int(20)),
); // );
println!("==INSERT Aristotle=="); // println!("==INSERT Aristotle==");
state // state
.interpret(Insert( // .interpret(Insert(
users_position, // users_position,
vec![id1.clone(), name1.clone(), age1.clone()], // vec![id1.clone(), name1.clone(), age1.clone()],
)) // ))
.unwrap(); // .unwrap();
println!(); // println!();
{ // {
let response: Response = state // let response: Response = state
.interpret(Operation::Select( // .interpret(Operation::Select(
users_position, // users_position,
users_schema.all_selection(), // users_schema.all_selection(),
None, // None,
)) // ))
.unwrap(); // .unwrap();
println!("==SELECT ALL=="); // println!("==SELECT ALL==");
println!("{:?}", response); // println!("{:?}", response);
println!(); // println!();
} // }
{ // {
let response: Response = state // let response: Response = state
.interpret(Select( // .interpret(Select(
users_position, // users_position,
users_schema.all_selection(), // users_schema.all_selection(),
Some(Eq(id_column, id0.clone())), // Some(Eq(id_column, id0.clone())),
)) // ))
.unwrap(); // .unwrap();
println!("==SELECT Plato=="); // println!("==SELECT Plato==");
println!("{:?}", response); // println!("{:?}", response);
println!(); // println!();
} // }
{ // {
{ // {
// TODO: Why do I have to write these braces explicitely? Why doesn't Rust compiler // // TODO: Why do I have to write these braces explicitely? Why doesn't Rust compiler
// "infer" them? // // "infer" them?
let _delete_response: Response = state // let _delete_response: Response = state
.interpret(Delete(users_position, Some(Eq(id_column, id0.clone())))) // .interpret(Delete(users_position, Some(Eq(id_column, id0.clone()))))
.unwrap(); // .unwrap();
println!("==DELETE Plato=="); // println!("==DELETE Plato==");
} // }
let response: Response = state // let response: Response = state
.interpret(Select(users_position, vec![name_column, id_column], None)) // .interpret(Select(users_position, vec![name_column, id_column], None))
.unwrap(); // .unwrap();
println!("==SELECT All=="); // println!("==SELECT All==");
println!("{:?}", response); // println!("{:?}", response);
println!(); // println!();
} // }
} // }

View file

@ -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)
}; };

View file

@ -1,36 +1,55 @@
use crate::error::TypeConversionError; use crate::error::TypeConversionError;
use std::cmp::Ordering;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// ==============Types================ // ==============Types================
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Ord, PartialEq, Eq, Serialize, Deserialize)]
pub enum DbType { pub enum DbType {
String, String,
Int, Int,
Number, Number,
Uuid, Uuid,
Option(Box<DbType>)
}
impl PartialOrd for DbType {
// TODO: Explain why we need this (because of IndexableValue::None contains a type)
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
todo!()
}
} }
// ==============Values================ // ==============Values================
pub type Uuid = u64; pub type Uuid = u64;
// TODO: What about nulls? I would rather not have that in SQL, it sucks.
// I would rather have non-nullable values by default,
// and something like an explicit Option type for nulls.
#[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 {
Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to Number(f64),
// do? String(String),
Indexable(IndexableValue), 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, Ord, 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),
// TODO: what about null? Some(Box<IndexableValue>),
None(DbType), // See Value::None
}
impl PartialOrd for IndexableValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
todo!()
}
} }
impl DbType { impl DbType {
@ -40,25 +59,28 @@ impl DbType {
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 { pub fn type_oid(&self) -> i32 {
match self { // match self {
Self::String => 25, // Self::String => 25,
Self::Int => 23, // Self::Int => 23,
Self::Number => 701, // Self::Number => 701,
Self::Uuid => 2950, // Self::Uuid => 2950,
} // }
todo!()
} }
pub fn type_size(&self) -> i16 { pub fn type_size(&self) -> i16 {
match self { // match self {
Self::String => -2, // null terminated string // Self::String => -2, // null terminated string
Self::Int => 8, // Self::Int => 8,
Self::Number => 8, // Self::Number => 8,
Self::Uuid => 16, // Self::Uuid => 16,
} // }
todo!()
} }
} }
@ -66,63 +88,84 @@ 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 as_text_bytes(&self) -> Vec<u8> {
pub fn to_indexable(&self) -> Option<IndexableValue> {
match self { match self {
Self::Number(n) => format!("{n}").into_bytes(), Self::Number(_) => None,
Self::Indexable(i) => match i { Self::String(s) => Some(IndexableValue::String(s.to_string())),
IndexableValue::String(s) => format!("{s}\0").into_bytes(), Self::Int(n) => Some(IndexableValue::Int(*n)),
IndexableValue::Int(i) => format!("{i}").into_bytes(), Self::Uuid(id) => Some(IndexableValue::Uuid(*id)),
IndexableValue::Uuid(u) => format!("{u}").into_bytes(), Self::Some(val) => val.to_indexable(),
}, Self::None(type_) => Some(IndexableValue::None(type_.clone())),
} }
} }
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(),
// },
// }
todo!()
}
pub fn from_text_bytes( pub fn from_text_bytes(
bytes: &[u8], bytes: &[u8],
type_oid: i32, type_oid: i32,
type_size: i16, type_size: i16,
) -> Result<Value, TypeConversionError> { ) -> Result<Value, TypeConversionError> {
match (type_oid, type_size) { // match (type_oid, type_size) {
(701, 8) => { // (701, 8) => {
let s = std::str::from_utf8(bytes)?; // let s = std::str::from_utf8(bytes)?;
let n = s.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 = std::str::from_utf8(bytes)?;
let s = &s[..s.len() - 1]; // remove null terminator // let s = &s[..s.len() - 1]; // remove null terminator
Ok(Value::Indexable(IndexableValue::String(s.to_string()))) // Ok(Value::Indexable(IndexableValue::String(s.to_string())))
} // }
(23, 8) => { // (23, 8) => {
let s = std::str::from_utf8(bytes)?; // let s = std::str::from_utf8(bytes)?;
let n = s.parse::<u64>()?; // let n = s.parse::<u64>()?;
Ok(Value::Indexable(IndexableValue::Int(n))) // Ok(Value::Indexable(IndexableValue::Int(n)))
} // }
(2950, 16) => { // (2950, 16) => {
let s = std::str::from_utf8(bytes)?; // let s = std::str::from_utf8(bytes)?;
let n = s.parse::<Uuid>()?; // let n = s.parse::<Uuid>()?;
Ok(Value::Indexable(IndexableValue::Uuid(n))) // Ok(Value::Indexable(IndexableValue::Uuid(n)))
} // }
(oid, size) => Err(TypeConversionError::UnknownType { oid, size }), // (oid, size) => Err(TypeConversionError::UnknownType { oid, size }),
// }
todo!()
} }
}
fn indexable_value_to_string(value: IndexableValue) -> String {
match value {
IndexableValue::String(s) => format!("String({s})"),
IndexableValue::Int(i) => format!("Int({i})"),
IndexableValue::Uuid(u) => format!("Uuid({u})"),
IndexableValue::Some(val) => format!("Some({})", indexable_value_to_string(*val)),
IndexableValue::Some(val) => todo!(),
IndexableValue::None(type_) => "None()".to_string(),
} }
} }
// 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 { indexable_value_to_string(value)
IndexableValue::String(s) => format!("String({s})"),
IndexableValue::Int(i) => format!("Int({i})"),
IndexableValue::Uuid(u) => format!("Uuid({u})"),
}
} }
} }
@ -135,35 +178,37 @@ impl TryFrom<String> for IndexableValue {
if value.starts_with("String(") { if value.starts_with("String(") {
let s = value[7..value.len() - 1].to_string(); let s = value[7..value.len() - 1].to_string();
return Ok(Self::String(s)); Ok(Self::String(s))
} } else if value.starts_with("Int(") {
if value.starts_with("Int(") {
let s = value[4..value.len() - 1].to_string(); let s = value[4..value.len() - 1].to_string();
let i = s let i = s
.parse::<u64>() .parse::<u64>()
.map_err(|e| format!("Invalid Int: {}", e))?; .map_err(|e| format!("Invalid Int: {}", e))?;
return Ok(Self::Int(i)); Ok(Self::Int(i))
} } else if value.starts_with("Uuid(") {
if value.starts_with("Uuid(") {
let s = value[5..value.len() - 1].to_string(); let s = value[5..value.len() - 1].to_string();
let u = s let u = s
.parse::<u64>() .parse::<u64>()
.map_err(|e| format!("Invalid UUID: {}", e))?; .map_err(|e| format!("Invalid UUID: {}", e))?;
return Ok(Self::Uuid(u)); Ok(Self::Uuid(u))
} } else if value.starts_with("Some(") {
// TODO: This needs some recursion
todo!()
} else if value.starts_with("None(") {
todo!()
} else {
Err(format!("Invalid IndexableValue: {}", value)) 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 { // match value {
Value::Number(n) => format!("Number({n})"), // Value::Number(n) => format!("Number({n})"),
Value::Indexable(i) => format!("Indexable({})", String::from(i)), // Value::Indexable(i) => format!("Indexable({})", String::from(i)),
} // }
todo!()
} }
} }
@ -174,21 +219,21 @@ impl TryFrom<String> for Value {
return Err(format!("Invalid Value: {}", value)); return Err(format!("Invalid Value: {}", value));
} }
if value.starts_with("Number(") { // if value.starts_with("Number(") {
let s = value[7..value.len() - 1].to_string(); // let s = value[7..value.len() - 1].to_string();
let n = s // let n = s
.parse::<f64>() // .parse::<f64>()
.map_err(|e| format!("Invalid Number: {}", e))?; // .map_err(|e| format!("Invalid Number: {}", e))?;
return Ok(Self::Number(n)); // Ok(Self::Number(n))
} // } else if value.starts_with("Indexable(") {
// let s = value[10..value.len() - 1].to_string();
// let i = IndexableValue::try_from(s)?;
// Ok(Self::Indexable(i))
// } else {
// Err(format!("Invalid Value: {}", value))
// }
if value.starts_with("Indexable(") { todo!()
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))
} }
} }