chore: create cargo workspace

This commit is contained in:
Jindřich Moravec 2023-12-11 23:20:19 +01:00
parent cb7b50109e
commit f0db73b38e
3 changed files with 13 additions and 8 deletions

8
minisql/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "minisql"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

259
minisql/src/main.rs Normal file
View file

@ -0,0 +1,259 @@
use std::collections::{BTreeMap, HashMap};
// ==============SQL operations================
// TODO: Note that every operation has a table name.
// Perhaps consider factoring the table name out
// and think of the operations as operating on a unique table.
enum Operation {
Select(TableName, ColumnSelection, Option<Condition>),
Insert(TableName, InsertionValues),
Delete(TableName, Option<Condition>),
// Update(...),
CreateTable(TableName, TableSchema),
CreateIndex(TableName, ColumnName), // TODO: Is this sufficient?
// DropTable(TableName),
}
type InsertionValues = Vec<(ColumnName, DbValue)>;
enum ColumnSelection {
All,
Columns(Vec<ColumnName>),
}
enum Condition {
// And(Box<Condition>, Box<Condition>),
// Or(Box<Condition>, Box<Condition>),
// Not(Box<Condition>),
Eq(ColumnName, DbValue),
// LessOrEqual(ColumnName, DbValue),
// Less(ColumnName, DbValue),
// StringCondition(StringCondition),
}
// enum StringCondition {
// Prefix(ColumnName, String),
// Substring(ColumnName, String),
// }
// ==============Values and Types================
type UUID = u64;
// TODO: What about nulls? I would rather not have that as in SQL, it sucks.
// I would rather have non-nullable values by default,
// and something like an explicit Option type for nulls.
enum DbValue {
String(String),
Int(u64),
Number(f64),
UUID(UUID),
// TODO: what bout null?
}
// TODO: Can this be autogenerated from the values?
enum DbType {
String,
Int,
Number,
UUID,
}
impl DbValue {
// TODO: Can this be autogenerated?
fn to_type(self) -> DbType {
match self {
Self::String(_) => DbType::String,
Self::Int(_) => DbType::Int,
Self::Number(_) => DbType::Number,
Self::UUID(_) => DbType::UUID,
}
}
}
// ==============Tables================
// table-metadata and data
type TableName = String;
type TablePosition = u32;
struct Table {
schema: TableSchema,
rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the
// same lock for both rows and indexes
indexes:
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
struct ColumnIndex {
index: BTreeMap<DbValue, UUID>
}
// Note that it is nice to split metadata from the data because
// then you can give the metadata to the parser without giving it the data.
struct TableSchema {
columns: HashMap<ColumnName, (DbType, ColumnPosition)>
}
// TODO
fn column_position(table_meta: TableSchema, column_name: ColumnName) -> Option<ColumnPosition> {
todo!()
}
// Use `TablePosition` as index
type Tables = Vec<Table>;
type ColumnName = String;
type ColumnPosition = u32;
// Use `ColumnPosition` as index
type Row = Vec<DbValue>;
type Rows =
// TODO: This should be some sort of an interface to a dictionary
// s.t. in the background it may modify stuff in memory or talk to the disk
BTreeMap<UUID, Row>;
// interface
// insert(id, value)
// ==============Interpreter================
struct State {
table_positions: HashMap<TableName, TablePosition>,
tables: Vec<Table>,
}
impl State {
fn table_from_name<'b: 'a, 'a>(&'b self, table_name: TableName) -> Option<&'a Table> {
todo!()
}
fn attach_table(&mut self, table: Table) {
todo!()
}
}
// TODO: Give a better name to something that you can respond to with rows
trait SqlConsumer {
// TODO:
}
// TODO: This should return a reference to the table
// 'tables_life contains 'table_life
fn get_table<'tables_life: 'table_life, 'table_life>(tables: &'tables_life Tables, table_name: &TableName) -> &'table_life Table {
// let table_position:
todo!()
}
// TODO: Decide if we want for this to return a response (but then you have to deal with lifetimes,
// because you'll be forced to put an iterator/slice into the Response data-structure.
// 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
fn interpret(table_name: TableName, operation: Operation, state: &mut State, consumer: impl SqlConsumer) -> () {
// TODO: lock stuff
use Operation::*;
match operation {
Select(table_name, column_selection, maybe_condition) => {
let table: &Table = todo!();
table.select_where(column_selection, maybe_condition, consumer)
},
Insert(table_name, values) => {
let table: &mut Table = todo!();
table.insert(values, consumer)
},
Delete(table_name, maybe_condition) => {
let table: &mut Table = todo!();
table.delete_where(maybe_condition, consumer)
},
CreateTable(table_name, table_schema) => {
let table = Table::new(table_name, table_schema);
state.attach_table(table);
todo!()
},
CreateIndex(table_name, column_name) => {
let table: &mut Table = todo!();
let index: ColumnIndex = ColumnIndex::new(table, column_name);
table.attach_index(index);
}, // TODO: Is this sufficient?
//
}
}
impl ColumnIndex {
fn new(table: &Table, column_name: ColumnName) -> ColumnIndex {
todo!()
}
}
impl Table {
fn new(table_name: TableName, table_schema: TableSchema) -> Table {
todo!()
}
fn attach_index(&mut self, column_index: ColumnIndex) {
todo!()
}
fn select_where(&self, column_selection: ColumnSelection, maybe_condition: Option<Condition>, consumer: impl SqlConsumer) {
match maybe_condition {
None => {
// .iter() will give us an iterator over all the rows
// two choices
// 1. optimized version
// self.iter_with_columns(column_selection).for_each(|row| {
// consumer.send(row)
// });
// 2.
// self.iter()
// .map(|row| row.select_columns(column_selection))
// .for_each(|reduced_row| {
// consumer.send(row)
// });
todo!()
},
Some(Condition::Eq(column_name, value)) => {
// is column_name primary key? then it is easy
// self.get(id)
// is column_name indexed? Then get the index, and then it is not easy, because you
// may get a set of ids.
// what if it is not primary nor indexed? then you need to brute force your way
// through the whole table?
todo!()
}
}
}
fn insert(&mut self, values: InsertionValues, consumer: impl SqlConsumer) {
// 1. You need to update indices
// 2. you simply insert the data
todo!()
}
fn delete_where(&mut self, maybe_condition: Option<Condition>, consumer: impl SqlConsumer) {
// kinda similar to select with respect to the conditions
// update index
todo!()
}
}
// enum Response {
// Selected(impl Iter<???>), // TODO: How to do this? Some reference to an iterator somehow... slice..?
// Inserted(???),
// Deleted(usize), // how many were deleted
// }
fn main() {
println!("Hello, world!");
}