Refine design

This commit is contained in:
Yuriy Dupyn 2023-10-23 23:27:09 +02:00
parent 2a9220e55f
commit ded7faf505
5 changed files with 139 additions and 4 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "minisql"
version = "0.1.0"

8
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]

121
DESIGN.md
View file

@ -69,11 +69,45 @@ which will store the database as a file `path/to/db/my-db.db` and open a TCP ser
how will the parsing output look like?
Consider something like
```
// TODO: Parser has access to all table metadata
// Could also be called `SQLAbstractSyntaxTree`
enum Operations {
Select(Vector<FieldName>, TableName),
Update(...)
enum Operation {
Select(TableName, ColumnSelection, Option<Condition>),
Insert(TableName, Vec<(ColumnName, DbValue)>), // String because we don't yet know which type of value this is for sure
Delete(TableName, Option<Condition>),
// Update(...),
}
enum ColumnSelection {
All,
Columns(Vec<ColumnName>),
}
enum Condition = {
// And(Condition, Condition),
// Or(Condition, Condition),
// Not(Condition),
Eq(ColumnName, DbValue)
// LessOrEqual(ColumnName, DbValue)
// Less(ColumnName, DbValue)
// StringCondition(StringCondition)
}
enum StringCondition {
Prefix(ColumnName, String)
Substring(ColumnName, String)
}
enum Condition
ColumnName, DbValue
INSERT 123
```
* We also have to write an interpreter for these operations. How will the db-state be represented in memory?
For example how can we implement a table?
@ -82,11 +116,46 @@ enum Operations {
enum DbValue {
DbString(String),
DbNumber(Float),
DbByte(u8),
DbUUID(u32)
}
// We also need a type of db-types
enum DbType {
TString,
TNumber,
TId,
}
value_to_type(db_val: DbValue) -> DbType
// table-metadata and data
type TableName = String
// 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 TableMetaData {
name: TableName, // TODO: Is this really necessary? probably not
columns: Vec<(ColumnName, DbType, ColumnPosition)>
}
fn column_position(TableMetaData, ColumnName) -> ColumnPosition
struct Table {
meta: TableMetaData,
rows: Rows // defined below
indexes:
BTree<ColumnName, Index> // TODO: Consider generalizing ColumnName to semething that would also apply to a pair of ColumnNames etc
}
type Tables = HashMap<TableName, Table>
// We also need a function that for a given value computes its type (for validation)
type ColumnName = String
type ColumnPosition = u32
// The below type is a type of a table row
type Row = HashMap<ColumnName, DbValue>
@ -94,6 +163,9 @@ type Row = HashMap<ColumnName, DbValue>
// Or you know... some appropriate Dictionary Type
HashMap::make![("id", 1), ("name", "Alice"), ("salary", 20.0)] : Row
type Rows =
BTree<Id, Row>
// possible optimization: have a mapping
// column names ~> indexes
// so that we could represent rows as
@ -112,6 +184,47 @@ e.g. Row ~> vec![DbUUID 1, DbSTring "Alice"]
Vec<Vec<DbValue>>
```
* Interpreter
```
trait SqlConsumer {
// TODO:
???
}
fn interpret<T: SqlConsumer>(operation: Operation, tables: &mut Tables, consumer: T) -> () {
// TODO: lock stuff
match operation {
Select(table_name, column_selection, maybe_condition) => {
let table: Table = ...
// TODO: Wrap this into a response
select(table, column_selection, maybe_condition, consumer)
},
Insert(table_name, Vec<(ColumnName, DbValue)>) => {
insert(table, ???)
}
Delete(table_name, maybe_condition) => {
}
}
}
response = interpret(...)
knows_how_to_respond(response, client)
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 select(table: Table, ColumnName
```
* TODO: Consider streaming the response to the client and not just dumping 10K rows at once.

6
src/main.rs Normal file
View file

@ -0,0 +1,6 @@
fn main() {
println!("Hello, world!");
}