This commit is contained in:
Yuriy Dupyn 2023-12-28 14:03:38 +01:00
parent 14f4fe4f3b
commit 291f90ef03

View file

@ -12,7 +12,7 @@ enum Operation {
Delete(TableName, Option<Condition>), Delete(TableName, Option<Condition>),
// Update(...), // Update(...),
CreateTable(TableName, TableSchema), CreateTable(TableName, TableSchema),
CreateIndex(TableName, ColumnName), // TODO: Is this sufficient? CreateIndex(TableName, ColumnName),
// DropTable(TableName), // DropTable(TableName),
} }
@ -44,7 +44,7 @@ enum Condition {
// ==============Values and Types================ // ==============Values and Types================
type UUID = u64; type UUID = u64;
// TODO: What about nulls? I would rather not have that as in SQL, it sucks. // TODO: What about nulls? I would rather not have that in SQL, it sucks.
// I would rather have non-nullable values by default, // I would rather have non-nullable values by default,
// and something like an explicit Option type for nulls. // and something like an explicit Option type for nulls.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -62,7 +62,6 @@ enum IndexableDbValue {
// TODO: what bout null? // TODO: what bout null?
} }
// TODO: Can this be autogenerated from the values?
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum DbType { enum DbType {
String, String,
@ -72,7 +71,6 @@ enum DbType {
} }
impl DbValue { impl DbValue {
// TODO: Can this be autogenerated?
fn to_type(self) -> DbType { fn to_type(self) -> DbType {
match self { match self {
Self::Number(_) => DbType::Number, Self::Number(_) => DbType::Number,
@ -88,8 +86,6 @@ impl DbValue {
// ==============Tables================ // ==============Tables================
// table-metadata and data
type TableName = String; type TableName = String;
type TablePosition = usize; type TablePosition = usize;
@ -102,10 +98,6 @@ struct Table {
HashMap<ColumnPosition, ColumnIndex> // TODO: Consider generalizing `ColumnPosition` to something that would also apply to a pair of `ColumnNames` etc HashMap<ColumnPosition, ColumnIndex> // TODO: Consider generalizing `ColumnPosition` to something that would also apply to a pair of `ColumnNames` etc
} }
// TODO: Is this really indexed by DbValues?
// Maybe we should have a separate index type for each type of value we're indexing over
// TODO: I should have a set of UUID, not just a single UUID, e.g.
// a user table can have multiple different users with the same name.
#[derive(Debug)] #[derive(Debug)]
struct ColumnIndex { struct ColumnIndex {
index: BTreeMap<IndexableDbValue, HashSet<UUID>> index: BTreeMap<IndexableDbValue, HashSet<UUID>>
@ -139,7 +131,7 @@ type Rows =
// interface // interface
// insert(id, value) // insert(id, value)
fn select_columns(row: &Row, columns: &Vec<ColumnPosition>) -> Row { fn restrict_columns(row: &Row, columns: &Vec<ColumnPosition>) -> Row {
// If the index from `columns` is non-existant in `row`, it will just ignore it. // If the index from `columns` is non-existant in `row`, it will just ignore it.
let mut subrow: Row = vec![]; let mut subrow: Row = vec![];
for column_position in columns { for column_position in columns {
@ -199,7 +191,7 @@ impl State {
// Alternative is to pass a row-consumer to the functionas that knows how to communicate with // Alternative is to pass a row-consumer to the functionas that knows how to communicate with
// the client, but the details of communication are hidden behind an interface // the client, but the details of communication are hidden behind an interface
// //
// writer: impl SqlConsumer // writer: impl SqlResponseConsumer
fn interpret(&mut self, operation: Operation) -> DbResult<Response> { fn interpret(&mut self, operation: Operation) -> DbResult<Response> {
// TODO: lock stuff // TODO: lock stuff
use Operation::*; use Operation::*;
@ -224,13 +216,10 @@ impl State {
CreateTable(table_name, table_schema) => { CreateTable(table_name, table_schema) => {
let table = Table::new(table_schema); let table = Table::new(table_schema);
self.attach_table(table_name, table); self.attach_table(table_name, table);
// TODO: What about attaching index on the primary column?
Ok(Response::TableCreated) Ok(Response::TableCreated)
}, },
CreateIndex(table_name, column_name) => { CreateIndex(table_name, column_name) => {
// TODO: This is incomplete. It can happen that an index is created
// after the table has some rows for a while.
// In such a case the index needs to be built over all those existing rows.
let table: &mut Table = self.table_from_name_mut(&table_name)?; let table: &mut Table = self.table_from_name_mut(&table_name)?;
let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?; let column_position: ColumnPosition = table.schema.column_position_from_column_name(&column_name)?;
@ -245,14 +234,14 @@ impl State {
} }
// TODO: Give a better name to something that you can respond to with rows // TODO: Give a better name to something that you can respond to with rows
trait SqlConsumer { trait SqlResponseConsumer {
// TODO: // TODO:
} }
impl TableSchema { impl TableSchema {
fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> { fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> {
match self.column_name_position_mapping.get_by_left(column_name) { match self.column_name_position_mapping.get_by_left(column_name) {
Some(column_position) => { Some(column_position) => {
match self.types.get(*column_position) { match self.types.get(*column_position) {
Some(type_) => { Some(type_) => {
@ -389,15 +378,16 @@ impl Table {
} }
fn delete_row_by_id(&mut self, id: UUID) -> usize { fn delete_row_by_id(&mut self, id: UUID) -> usize {
if let Some(row) = self.rows.remove(&id) { match self.rows.remove(&id) {
for (column_position, column_index) in &mut self.indexes { Some(row) => {
if let DbValue::Indexable(value) = &row[*column_position] { for (column_position, column_index) in &mut self.indexes {
let _ = column_index.remove(value, id); if let DbValue::Indexable(value) = &row[*column_position] {
}; let _ = column_index.remove(value, id);
} };
1 }
} else { 1
0 },
None => 0
} }
} }
@ -416,11 +406,12 @@ impl Table {
self.delete_rows_by_ids(matched_ids) self.delete_rows_by_ids(matched_ids)
} }
fn select_where(&self, column_selection: ColumnSelection, maybe_condition: Option<Condition>) -> DbResult<Vec<Row>> { fn select_where(&self, column_selection: ColumnSelection, condition: Option<Condition>) -> DbResult<Vec<Row>> {
let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?; let selected_column_positions = self.schema.column_positions_from_column_selection(&column_selection)?;
match maybe_condition { match condition {
None => None =>
Ok(self.rows.values().map(|row| select_columns(row, &selected_column_positions)).collect()), // select all
Ok(self.rows.values().map(|row| restrict_columns(row, &selected_column_positions)).collect()),
Some(Condition::Eq(eq_column_name, value)) => { Some(Condition::Eq(eq_column_name, value)) => {
let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?;
@ -428,7 +419,7 @@ impl Table {
match value { match value {
DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { DbValue::Indexable(IndexableDbValue::UUID(uuid)) => {
match self.get_row_by_id(uuid) { match self.get_row_by_id(uuid) {
Some(row) => Ok(vec![select_columns(&row, &selected_column_positions)]), Some(row) => Ok(vec![restrict_columns(&row, &selected_column_positions)]),
None => Ok(vec![]), None => Ok(vec![]),
} }
}, },
@ -440,15 +431,15 @@ impl Table {
match self.indexes.get(&eq_column_position) { match self.indexes.get(&eq_column_position) {
Some(column_index) => { Some(column_index) => {
let ids = column_index.get(value); let ids = column_index.get(value);
Ok(self.get_rows_by_ids(ids).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) Ok(self.get_rows_by_ids(ids).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect())
}, },
None => { None => {
Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) Ok(self.get_rows_by_value(eq_column_position, &DbValue::Indexable(value)).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect())
} }
} }
}, },
_ => { _ => {
Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| select_columns(row, &selected_column_positions)).collect()) Ok(self.get_rows_by_value(eq_column_position, &value).iter().map(|row| restrict_columns(row, &selected_column_positions)).collect())
} }
} }
} }
@ -478,8 +469,6 @@ impl Table {
} }
fn delete_where(&mut self, maybe_condition: Option<Condition>) -> DbResult<usize> { fn delete_where(&mut self, maybe_condition: Option<Condition>) -> DbResult<usize> {
// kinda similar to select with respect to the conditions
// update index
match maybe_condition { match maybe_condition {
None => { None => {
// delete all // delete all
@ -491,8 +480,6 @@ impl Table {
Some(Condition::Eq(eq_column_name, value)) => { Some(Condition::Eq(eq_column_name, value)) => {
let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?; let (type_, eq_column_position) = self.schema.get_column(&eq_column_name)?;
// TODO: This shouldn't be necessary - we should index the primary column by
// default. The first case shouldn't be a special case!
if self.schema.is_primary(eq_column_position) { if self.schema.is_primary(eq_column_position) {
match value { match value {
DbValue::Indexable(IndexableDbValue::UUID(uuid)) => { DbValue::Indexable(IndexableDbValue::UUID(uuid)) => {
@ -610,7 +597,6 @@ fn main() {
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -622,7 +608,7 @@ mod tests {
TableSchema { TableSchema {
table_name: "users".to_string(), table_name: "users".to_string(),
primary_key: 0, primary_key: id,
column_name_position_mapping: { column_name_position_mapping: {
let mut mapping: BiMap<ColumnName, ColumnPosition> = BiMap::new(); let mut mapping: BiMap<ColumnName, ColumnPosition> = BiMap::new();
mapping.insert("id".to_string(), id); mapping.insert("id".to_string(), id);
@ -826,10 +812,7 @@ mod tests {
let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap(); let delete_response: Response = state.interpret(Delete(users.clone(), Some(Eq("id".to_string(), id0.clone())))).unwrap();
// TODO: WTF, why is it 0? Should be 1
assert!(matches!(delete_response, Response::Deleted(1))); assert!(matches!(delete_response, Response::Deleted(1)));
// assert!(matches!(delete_response, Response::Deleted(_)));
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap(); let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap();