Merge branch 'optimize-operation' into 'main'
Introduce new simplified Operation type for Interpreter See merge request x433485/minisql!10
This commit is contained in:
commit
10ba1dd3e4
17 changed files with 326 additions and 456 deletions
|
|
@ -3,27 +3,16 @@ use std::str::Utf8Error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::internals::row::ColumnPosition;
|
use crate::internals::row::ColumnPosition;
|
||||||
use crate::schema::{ColumnName, TableName};
|
use crate::schema::{ColumnName, TableName};
|
||||||
use crate::operation::InsertionValues;
|
|
||||||
use crate::type_system::{DbType, Uuid, Value};
|
use crate::type_system::{DbType, Uuid, Value};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("table {0} does not exist")]
|
|
||||||
TableDoesNotExist(TableName),
|
|
||||||
#[error("column {1} of table {0} does not exist")]
|
|
||||||
ColumnDoesNotExist(TableName, ColumnName),
|
|
||||||
#[error("column position {1} of table {0} does not exist")]
|
#[error("column position {1} of table {0} does not exist")]
|
||||||
ColumnPositionDoesNotExist(TableName, ColumnPosition),
|
ColumnPositionDoesNotExist(TableName, ColumnPosition),
|
||||||
#[error("column {1} of table {0} has unexpected type {2:?} and value {3:?}")]
|
#[error("column {1} of table {0} has unexpected type {2:?} and value {3:?}")]
|
||||||
ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value),
|
ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value),
|
||||||
#[error("table {0} already contains row with id {1}")]
|
#[error("table {0} already contains row with id {1}")]
|
||||||
AttemptingToInsertAlreadyPresentId(TableName, Uuid),
|
AttemptingToInsertAlreadyPresentId(TableName, Uuid),
|
||||||
#[error("table {0} is missing annotation for column {1}")]
|
|
||||||
MissingTypeAnnotationOfColumn(TableName, ColumnPosition),
|
|
||||||
#[error("table {0} is missing column {1} in insert values {2:?}")]
|
|
||||||
MissingColumnInInsertValues(TableName, ColumnName, InsertionValues),
|
|
||||||
#[error("table {0} has mismatch between insert values {1:?} and columns")]
|
|
||||||
MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues),
|
|
||||||
#[error("table {0} cannot be indexed on column {1}")]
|
#[error("table {0} cannot be indexed on column {1}")]
|
||||||
AttemptToIndexNonIndexableColumn(TableName, ColumnName),
|
AttemptToIndexNonIndexableColumn(TableName, ColumnName),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::type_system::Value;
|
use crate::type_system::Value;
|
||||||
|
use crate::operation::InsertionValues;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::slice::SliceIndex;
|
use std::slice::SliceIndex;
|
||||||
use crate::restricted_row::RestrictedRow;
|
use crate::restricted_row::RestrictedRow;
|
||||||
|
|
@ -43,6 +44,10 @@ impl Row {
|
||||||
Row(vec![])
|
Row(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_from_insertion_values(insertion_values: InsertionValues) -> Self {
|
||||||
|
Row(insertion_values)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_number_of_columns(number_of_columns: usize) -> Self {
|
pub fn with_number_of_columns(number_of_columns: usize) -> Self {
|
||||||
Row(Vec::with_capacity(number_of_columns))
|
Row(Vec::with_capacity(number_of_columns))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,7 @@ impl Table {
|
||||||
match value {
|
match value {
|
||||||
IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))),
|
IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))),
|
||||||
_ => {
|
_ => {
|
||||||
|
// TODO: This validation step is not really necessary.
|
||||||
let column_name: ColumnName = self
|
let column_name: ColumnName = self
|
||||||
.schema
|
.schema
|
||||||
.column_name_from_column_position(column_position)?;
|
.column_name_from_column_position(column_position)?;
|
||||||
|
|
@ -223,8 +224,8 @@ impl Table {
|
||||||
match self.indexes.get(&column_position) {
|
match self.indexes.get(&column_position) {
|
||||||
Some(index) => {
|
Some(index) => {
|
||||||
// Note that we are cloning the ids here! This can be very wasteful in some cases.
|
// Note that we are cloning the ids here! This can be very wasteful in some cases.
|
||||||
// It would be possible to just return a reference,
|
// Theoretically it would be possible to return a reference,
|
||||||
// but this seems fairly non-trivial.
|
// but after attempting to do this it seems very non-trivial.
|
||||||
let ids = index.get(value).cloned();
|
let ids = index.get(value).cloned();
|
||||||
Ok(ids)
|
Ok(ids)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::error::Error;
|
|
||||||
use crate::internals::row::ColumnPosition;
|
use crate::internals::row::ColumnPosition;
|
||||||
use crate::schema::{TableName, TableSchema};
|
use crate::schema::{TableName, TableSchema};
|
||||||
use crate::internals::table::Table;
|
use crate::internals::table::Table;
|
||||||
use crate::operation::{ColumnSelection, Condition, Operation};
|
use crate::operation::{Operation, Condition};
|
||||||
use crate::result::DbResult;
|
use crate::result::DbResult;
|
||||||
use crate::type_system::{DbType, IndexableValue, Value};
|
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use crate::restricted_row::RestrictedRow;
|
use crate::restricted_row::RestrictedRow;
|
||||||
|
|
||||||
|
|
@ -28,6 +26,8 @@ pub enum Response<'a> {
|
||||||
IndexCreated,
|
IndexCreated,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type DbSchema<'a> = Vec<(TableName, TablePosition, &'a TableSchema)>;
|
||||||
|
|
||||||
impl std::fmt::Debug for Response<'_> {
|
impl std::fmt::Debug for Response<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
use Response::*;
|
use Response::*;
|
||||||
|
|
@ -56,37 +56,21 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: return a reference to avoid allocations
|
pub fn db_schema<'a>(&'a self) -> DbSchema {
|
||||||
pub fn metadata<'a>(&'a self) -> Vec<(String, &'a TableSchema)> {
|
let mut schema: DbSchema = Vec::new();
|
||||||
let mut m = Vec::new();
|
for (table_name, &table_position) in &self.table_name_position_mapping {
|
||||||
for (name, pos) in &self.table_name_position_mapping {
|
let table_schema = self.tables[table_position].schema();
|
||||||
let table_schema = self.tables.get(*pos).unwrap().schema();
|
schema.push((table_name.clone(), table_position, table_schema));
|
||||||
m.push((name.clone(), table_schema));
|
|
||||||
}
|
}
|
||||||
m
|
schema
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_from_name<'a>(&'a self, table_name: &TableName) -> DbResult<&'a Table> {
|
fn table_at<'a>(&'a self, table_position: TablePosition) -> &'a Table {
|
||||||
match self.table_name_position_mapping.get_by_left(table_name) {
|
&self.tables[table_position]
|
||||||
Some(table_position) => {
|
|
||||||
let table = &self.tables[*table_position];
|
|
||||||
Ok(table)
|
|
||||||
}
|
|
||||||
None => Err(Error::TableDoesNotExist(table_name.clone())),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_from_name_mut<'b: 'a, 'a>(
|
fn table_at_mut<'a>(&'a mut self, table_position: TablePosition) -> &'a mut Table {
|
||||||
&'b mut self,
|
&mut self.tables[table_position]
|
||||||
table_name: &TableName,
|
|
||||||
) -> DbResult<&'a mut Table> {
|
|
||||||
match self.table_name_position_mapping.get_by_left(table_name) {
|
|
||||||
Some(table_position) => {
|
|
||||||
let table = &mut self.tables[*table_position];
|
|
||||||
Ok(table)
|
|
||||||
}
|
|
||||||
None => Err(Error::TableDoesNotExist(table_name.clone())),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attach_table(&mut self, table_name: TableName, table: Table) {
|
fn attach_table(&mut self, table_name: TableName, table: Table) {
|
||||||
|
|
@ -96,31 +80,25 @@ impl State {
|
||||||
self.tables.push(table);
|
self.tables.push(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult<Response<'a>> {
|
pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult<Response<'a>> {
|
||||||
// TODO: lock stuff
|
// TODO: lock stuff
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
|
|
||||||
match operation {
|
match operation {
|
||||||
Select(table_name, column_selection, maybe_condition) => {
|
Select(table_position, column_selection, maybe_condition) => {
|
||||||
let table: &Table = self.table_from_name(&table_name)?;
|
let table: &Table = self.table_at(table_position);
|
||||||
|
|
||||||
let selected_column_positions: Vec<ColumnPosition> = table
|
|
||||||
.schema()
|
|
||||||
.column_positions_from_column_selection(&column_selection)?;
|
|
||||||
let selected_rows = match maybe_condition {
|
let selected_rows = match maybe_condition {
|
||||||
None => {
|
None => {
|
||||||
let x = table.select_all_rows(selected_column_positions);
|
let rows = table.select_all_rows(column_selection);
|
||||||
Box::new(x) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
Box::new(rows) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
||||||
},
|
},
|
||||||
|
|
||||||
Some(Condition::Eq(eq_column_name, value)) => {
|
Some(Condition::Eq(eq_column, value)) => {
|
||||||
let eq_column_position = table
|
|
||||||
.schema()
|
|
||||||
.column_position_from_column_name(&eq_column_name)?;
|
|
||||||
let x =
|
let x =
|
||||||
table.select_rows_where_eq(
|
table.select_rows_where_eq(
|
||||||
selected_column_positions,
|
column_selection,
|
||||||
eq_column_position,
|
eq_column,
|
||||||
value,
|
value,
|
||||||
)?;
|
)?;
|
||||||
Box::new(x) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
Box::new(x) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
||||||
|
|
@ -128,24 +106,21 @@ impl State {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Response::Selected(table.schema(), selected_rows))
|
Ok(Response::Selected(table.schema(), selected_rows))
|
||||||
}
|
},
|
||||||
Insert(table_name, values) => {
|
Insert(table_position, values) => {
|
||||||
let table: &mut Table = self.table_from_name_mut(&table_name)?;
|
let table: &mut Table = self.table_at_mut(table_position);
|
||||||
|
|
||||||
let (id, row) = table.schema().row_from_insertion_values(values)?;
|
let (id, row) = table.schema().row_from_insertion_values(values)?;
|
||||||
table.insert_row_at(id, row)?;
|
table.insert_row_at(id, row)?;
|
||||||
Ok(Response::Inserted)
|
Ok(Response::Inserted)
|
||||||
}
|
}
|
||||||
Delete(table_name, maybe_condition) => {
|
Delete(table_position, maybe_condition) => {
|
||||||
let table: &mut Table = self.table_from_name_mut(&table_name)?;
|
let table: &mut Table = self.table_at_mut(table_position);
|
||||||
|
|
||||||
let rows_affected = match maybe_condition {
|
let rows_affected = match maybe_condition {
|
||||||
None => table.delete_all_rows(),
|
None => table.delete_all_rows(),
|
||||||
Some(Condition::Eq(eq_column_name, value)) => {
|
Some(Condition::Eq(eq_column, value)) => {
|
||||||
let eq_column_position = table
|
table.delete_rows_where_eq(eq_column, value)?
|
||||||
.schema()
|
|
||||||
.column_position_from_column_name(&eq_column_name)?;
|
|
||||||
table.delete_rows_where_eq(eq_column_position, value)?
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -157,13 +132,9 @@ impl State {
|
||||||
|
|
||||||
Ok(Response::TableCreated)
|
Ok(Response::TableCreated)
|
||||||
}
|
}
|
||||||
CreateIndex(table_name, column_name) => {
|
CreateIndex(table_position, column) => {
|
||||||
let table: &mut Table = self.table_from_name_mut(&table_name)?;
|
let table: &mut Table = self.table_at_mut(table_position);
|
||||||
let column_position: ColumnPosition = table
|
table.attach_index(column)?;
|
||||||
.schema()
|
|
||||||
.column_position_from_column_name(&column_name)?;
|
|
||||||
|
|
||||||
table.attach_index(column_position)?;
|
|
||||||
Ok(Response::IndexCreated)
|
Ok(Response::IndexCreated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +144,10 @@ impl State {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::internals::row::ColumnPosition;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use crate::type_system::{DbType, IndexableValue, Value};
|
||||||
|
use crate::operation::Operation;
|
||||||
|
|
||||||
fn users_schema() -> TableSchema {
|
fn users_schema() -> TableSchema {
|
||||||
let id: ColumnPosition = 0;
|
let id: ColumnPosition = 0;
|
||||||
|
|
@ -214,12 +188,13 @@ mod tests {
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
let users = users_schema.table_name().clone();
|
let users = users_schema.table_name().clone();
|
||||||
|
let users_position = 0;
|
||||||
|
|
||||||
state
|
state
|
||||||
.interpret(Operation::CreateTable(users.clone(), users_schema))
|
.interpret(Operation::CreateTable(users, users_schema.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None))
|
.interpret(Operation::Select(users_position, users_schema.all_selection(), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _)));
|
||||||
let Response::Selected(_schema, rows) = response else {
|
let Response::Selected(_schema, rows) = response else {
|
||||||
|
|
@ -229,18 +204,6 @@ mod tests {
|
||||||
assert!(rows.len() == 0);
|
assert!(rows.len() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_select_nonexistant_table() {
|
|
||||||
let mut state = State::new();
|
|
||||||
|
|
||||||
let response: DbResult<Response> = state.interpret(Operation::Select(
|
|
||||||
"table_that_doesnt_exist".to_string(),
|
|
||||||
ColumnSelection::All,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
assert!(matches!(response, Err(Error::TableDoesNotExist(_))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_select_basic1() {
|
fn test_insert_select_basic1() {
|
||||||
use IndexableValue::*;
|
use IndexableValue::*;
|
||||||
|
|
@ -248,10 +211,11 @@ mod tests {
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
let users = users_schema.table_name().clone();
|
let users = 0;
|
||||||
|
|
||||||
|
|
||||||
state
|
state
|
||||||
.interpret(Operation::CreateTable(users.clone(), users_schema))
|
.interpret(Operation::CreateTable("users".to_string(), users_schema.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id, name, age) = (
|
let (id, name, age) = (
|
||||||
|
|
@ -261,17 +225,17 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Operation::Insert(
|
.interpret(Operation::Insert(
|
||||||
users.clone(),
|
users,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id.clone()),
|
id.clone(),
|
||||||
("name".to_string(), name.clone()),
|
name.clone(),
|
||||||
("age".to_string(), age.clone()),
|
age.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None))
|
.interpret(Operation::Select(users, users_schema.all_selection(), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _)));
|
||||||
|
|
@ -281,7 +245,7 @@ mod tests {
|
||||||
let rows: Vec<_> = rows.collect();
|
let rows: Vec<_> = rows.collect();
|
||||||
assert!(rows.len() == 1);
|
assert!(rows.len() == 1);
|
||||||
let row = &rows[0];
|
let row = &rows[0];
|
||||||
|
|
||||||
assert!(row.len() == 3);
|
assert!(row.len() == 3);
|
||||||
assert!(row[0].1 == id);
|
assert!(row[0].1 == id);
|
||||||
assert!(row[1].1 == name);
|
assert!(row[1].1 == name);
|
||||||
|
|
@ -290,7 +254,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_select_basic2() {
|
fn test_insert_select_basic2() {
|
||||||
use ColumnSelection::*;
|
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
use IndexableValue::*;
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
|
|
@ -298,10 +261,13 @@ mod tests {
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
let users = users_schema.table_name().clone();
|
let users_position: TablePosition = 0;
|
||||||
|
|
||||||
|
let id_column: ColumnPosition = 0;
|
||||||
|
let name_column: ColumnPosition = 1;
|
||||||
|
|
||||||
state
|
state
|
||||||
.interpret(CreateTable(users.clone(), users_schema))
|
.interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
|
|
@ -311,11 +277,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id0.clone()),
|
id0.clone(),
|
||||||
("name".to_string(), name0.clone()),
|
name0.clone(),
|
||||||
("age".to_string(), age0.clone()),
|
age0.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -327,17 +293,17 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id1.clone()),
|
id1.clone(),
|
||||||
("name".to_string(), name1.clone()),
|
name1.clone(),
|
||||||
("age".to_string(), age1.clone()),
|
age1.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
{
|
{
|
||||||
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap();
|
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
||||||
|
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _)));
|
||||||
let Response::Selected(_, rows) = response else {
|
let Response::Selected(_, rows) = response else {
|
||||||
|
|
@ -363,9 +329,9 @@ mod tests {
|
||||||
{
|
{
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Select(
|
.interpret(Select(
|
||||||
users.clone(),
|
users_position,
|
||||||
All,
|
users_schema.all_selection(),
|
||||||
Some(Eq("id".to_string(), id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _)));
|
||||||
|
|
@ -385,9 +351,9 @@ mod tests {
|
||||||
{
|
{
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Select(
|
.interpret(Select(
|
||||||
users.clone(),
|
users_position,
|
||||||
Columns(vec!["name".to_string(), "id".to_string()]),
|
vec![name_column, id_column],
|
||||||
Some(Eq("id".to_string(), id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _)));
|
||||||
|
|
@ -406,7 +372,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete() {
|
fn test_delete() {
|
||||||
use ColumnSelection::*;
|
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
use IndexableValue::*;
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
|
|
@ -414,10 +379,12 @@ mod tests {
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
let users = users_schema.table_name().clone();
|
let users_position: TablePosition = 0;
|
||||||
|
|
||||||
|
let id_column: ColumnPosition = 0;
|
||||||
|
|
||||||
state
|
state
|
||||||
.interpret(CreateTable(users.clone(), users_schema))
|
.interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
|
|
@ -427,11 +394,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id0.clone()),
|
id0.clone(),
|
||||||
("name".to_string(), name0.clone()),
|
name0.clone(),
|
||||||
("age".to_string(), age0.clone()),
|
age0.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -443,11 +410,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id1.clone()),
|
id1.clone(),
|
||||||
("name".to_string(), name1.clone()),
|
name1.clone(),
|
||||||
("age".to_string(), age1.clone()),
|
age1.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -455,14 +422,14 @@ mod tests {
|
||||||
{
|
{
|
||||||
let delete_response: Response = state
|
let delete_response: Response = state
|
||||||
.interpret(Delete(
|
.interpret(Delete(
|
||||||
users.clone(),
|
users_position,
|
||||||
Some(Eq("id".to_string(), id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(delete_response, Response::Deleted(1)));
|
assert!(matches!(delete_response, Response::Deleted(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap();
|
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
||||||
|
|
||||||
assert!(matches!(response, Response::Selected(_, _)));
|
assert!(matches!(response, Response::Selected(_, _)));
|
||||||
let Response::Selected(_, rows) = response else {
|
let Response::Selected(_, rows) = response else {
|
||||||
|
|
@ -486,14 +453,16 @@ mod tests {
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let users_schema = users_schema();
|
let users_schema = users_schema();
|
||||||
let users = users_schema.table_name().clone();
|
let users_position: TablePosition = 0;
|
||||||
|
|
||||||
|
let name_column: ColumnPosition = 1;
|
||||||
|
|
||||||
state
|
state
|
||||||
.interpret(CreateTable(users.clone(), users_schema))
|
.interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
state
|
state
|
||||||
.interpret(CreateIndex(users.clone(), "name".to_string()))
|
.interpret(CreateIndex(users_position, name_column))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
|
|
@ -503,11 +472,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id0.clone()),
|
id0.clone(),
|
||||||
("name".to_string(), name0.clone()),
|
name0.clone(),
|
||||||
("age".to_string(), age0.clone()),
|
age0.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -519,11 +488,11 @@ mod tests {
|
||||||
);
|
);
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id1.clone()),
|
id1.clone(),
|
||||||
("name".to_string(), name1.clone()),
|
name1.clone(),
|
||||||
("age".to_string(), age1.clone()),
|
age1.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -548,33 +517,34 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn example() {
|
pub fn example() {
|
||||||
use ColumnSelection::*;
|
use crate::type_system::{IndexableValue, Value, DbType};
|
||||||
use Condition::*;
|
use Condition::*;
|
||||||
use IndexableValue::*;
|
use IndexableValue::*;
|
||||||
use Operation::*;
|
use Operation::*;
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
|
||||||
let users_schema: TableSchema = {
|
let id_column: ColumnPosition = 0;
|
||||||
let id: ColumnPosition = 0;
|
let name_column: ColumnPosition = 1;
|
||||||
let name: ColumnPosition = 1;
|
let age_column: ColumnPosition = 2;
|
||||||
let age: ColumnPosition = 2;
|
|
||||||
|
|
||||||
|
let users_schema: TableSchema = {
|
||||||
TableSchema::new(
|
TableSchema::new(
|
||||||
"users".to_string(),
|
"users".to_string(),
|
||||||
id,
|
id_column,
|
||||||
vec!(
|
vec!(
|
||||||
("id".to_string(), id),
|
("id".to_string(), id_column),
|
||||||
("name".to_string(), name),
|
("name".to_string(), name_column),
|
||||||
("age".to_string(), age),
|
("age".to_string(), age_column),
|
||||||
),
|
),
|
||||||
vec![DbType::Uuid, DbType::String, DbType::Int],
|
vec![DbType::Uuid, DbType::String, DbType::Int],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let users_position: TablePosition = 0;
|
||||||
let users = users_schema.table_name().clone();
|
let users = users_schema.table_name().clone();
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
state
|
state
|
||||||
.interpret(Operation::CreateTable(users.clone(), users_schema))
|
.interpret(Operation::CreateTable(users, users_schema.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (id0, name0, age0) = (
|
let (id0, name0, age0) = (
|
||||||
|
|
@ -585,11 +555,11 @@ pub fn example() {
|
||||||
println!("==INSERT Plato==");
|
println!("==INSERT Plato==");
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id0.clone()),
|
id0.clone(),
|
||||||
("name".to_string(), name0.clone()),
|
name0.clone(),
|
||||||
("age".to_string(), age0.clone()),
|
age0.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -602,11 +572,11 @@ pub fn example() {
|
||||||
println!("==INSERT Aristotle==");
|
println!("==INSERT Aristotle==");
|
||||||
state
|
state
|
||||||
.interpret(Insert(
|
.interpret(Insert(
|
||||||
users.clone(),
|
users_position,
|
||||||
vec![
|
vec![
|
||||||
("id".to_string(), id1.clone()),
|
id1.clone(),
|
||||||
("name".to_string(), name1.clone()),
|
name1.clone(),
|
||||||
("age".to_string(), age1.clone()),
|
age1.clone(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -614,7 +584,7 @@ pub fn example() {
|
||||||
|
|
||||||
{
|
{
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None))
|
.interpret(Operation::Select(users_position, users_schema.all_selection(), None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("==SELECT ALL==");
|
println!("==SELECT ALL==");
|
||||||
println!("{:?}", response);
|
println!("{:?}", response);
|
||||||
|
|
@ -623,9 +593,9 @@ pub fn example() {
|
||||||
{
|
{
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Select(
|
.interpret(Select(
|
||||||
users.clone(),
|
users_position,
|
||||||
All,
|
users_schema.all_selection(),
|
||||||
Some(Eq("id".to_string(), id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("==SELECT Plato==");
|
println!("==SELECT Plato==");
|
||||||
|
|
@ -639,16 +609,16 @@ pub fn example() {
|
||||||
// "infer" them?
|
// "infer" them?
|
||||||
let _delete_response: Response = state
|
let _delete_response: Response = state
|
||||||
.interpret(Delete(
|
.interpret(Delete(
|
||||||
users.clone(),
|
users_position,
|
||||||
Some(Eq("id".to_string(), id0.clone())),
|
Some(Eq(id_column, id0.clone())),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("==DELETE Plato==");
|
println!("==DELETE Plato==");
|
||||||
}
|
}
|
||||||
let response: Response = state
|
let response: Response = state
|
||||||
.interpret(Select(
|
.interpret(Select(
|
||||||
users.clone(),
|
users_position,
|
||||||
Columns(vec!["name".to_string(), "id".to_string()]),
|
vec![name_column, id_column],
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,23 @@
|
||||||
use crate::schema::{ColumnName, TableName, TableSchema};
|
use crate::schema::{TableName, TableSchema};
|
||||||
use crate::type_system::Value;
|
use crate::type_system::Value;
|
||||||
|
use crate::internals::row::ColumnPosition;
|
||||||
|
use crate::interpreter::TablePosition;
|
||||||
|
|
||||||
// ==============SQL operations================
|
// Validated operation. Constructed by validation crate.
|
||||||
// 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.
|
|
||||||
// TODO: `TableName` should be replaced by `TablePosition`
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
Select(TableName, ColumnSelection, Option<Condition>),
|
Select(TablePosition, ColumnSelection, Option<Condition>),
|
||||||
Insert(TableName, InsertionValues),
|
Insert(TablePosition, InsertionValues),
|
||||||
Delete(TableName, Option<Condition>),
|
Delete(TablePosition, Option<Condition>),
|
||||||
// Update(...),
|
|
||||||
CreateTable(TableName, TableSchema),
|
CreateTable(TableName, TableSchema),
|
||||||
CreateIndex(TableName, ColumnName),
|
CreateIndex(TablePosition, ColumnPosition),
|
||||||
// DropTable(TableName),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type InsertionValues = Vec<(ColumnName, Value)>;
|
pub type InsertionValues = Vec<Value>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub type ColumnSelection = Vec<ColumnPosition>;
|
||||||
pub enum ColumnSelection {
|
|
||||||
All,
|
|
||||||
Columns(Vec<ColumnName>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Condition {
|
pub enum Condition {
|
||||||
// And(Box<Condition>, Box<Condition>),
|
Eq(ColumnPosition, Value),
|
||||||
// Or(Box<Condition>, Box<Condition>),
|
|
||||||
// Not(Box<Condition>),
|
|
||||||
Eq(ColumnName, Value),
|
|
||||||
// LessOrEqual(ColumnName, DbValue),
|
|
||||||
// Less(ColumnName, DbValue),
|
|
||||||
|
|
||||||
// StringCondition(StringCondition),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// enum StringCondition {
|
|
||||||
// Prefix(ColumnName, String),
|
|
||||||
// Substring(ColumnName, String),
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::internals::row::{ColumnPosition, Row};
|
use crate::internals::row::{ColumnPosition, Row};
|
||||||
use crate::operation::{ColumnSelection, InsertionValues};
|
use crate::operation::{InsertionValues, ColumnSelection};
|
||||||
use crate::result::DbResult;
|
use crate::result::DbResult;
|
||||||
use crate::type_system::{DbType, IndexableValue, Uuid, Value};
|
use crate::type_system::{DbType, IndexableValue, Uuid, Value};
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
// Note that it is nice to split metadata from the data because
|
// 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.
|
// then you can give the metadata to the parser without giving it the data.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TableSchema {
|
pub struct TableSchema {
|
||||||
table_name: TableName, // used for descriptive errors
|
table_name: TableName, // used for descriptive errors
|
||||||
primary_key: ColumnPosition,
|
primary_key: ColumnPosition,
|
||||||
|
|
@ -48,53 +47,26 @@ impl TableSchema {
|
||||||
self.column_name_position_mapping.get_by_left(column_name).copied()
|
self.column_name_position_mapping.get_by_left(column_name).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn all_selection(&self) -> ColumnSelection {
|
||||||
|
let mut selection: ColumnSelection = self.column_name_position_mapping.iter().map(|(_, column)| *column).collect();
|
||||||
|
selection.sort();
|
||||||
|
selection
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_column(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> {
|
||||||
|
let column = self.get_column_position(column_name)?;
|
||||||
|
Some((column, self.column_type(column)))
|
||||||
|
}
|
||||||
|
|
||||||
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_position(column_name)?;
|
let position = self.get_column_position(column_name)?;
|
||||||
self.types.get(position).copied()
|
self.types.get(position).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get rid of this after validation is merged
|
|
||||||
fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> {
|
|
||||||
match self.column_name_position_mapping.get_by_left(column_name) {
|
|
||||||
Some(column_position) => match self.types.get(*column_position) {
|
|
||||||
Some(type_) => Ok((*type_, *column_position)),
|
|
||||||
None => Err(Error::MissingTypeAnnotationOfColumn(
|
|
||||||
self.table_name.clone(),
|
|
||||||
*column_position,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
None => Err(Error::ColumnDoesNotExist(
|
|
||||||
self.table_name.clone(),
|
|
||||||
column_name.clone(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Get rid of this after validation is merged
|
|
||||||
pub fn column_position_from_column_name(
|
|
||||||
&self,
|
|
||||||
column_name: &ColumnName,
|
|
||||||
) -> DbResult<ColumnPosition> {
|
|
||||||
self.get_column(column_name)
|
|
||||||
.map(|(_, column_position)| column_position)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_primary(&self, column_position: ColumnPosition) -> bool {
|
pub fn is_primary(&self, column_position: ColumnPosition) -> bool {
|
||||||
self.primary_key == column_position
|
self.primary_key == column_position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn column_positions_from_column_names(
|
|
||||||
&self,
|
|
||||||
column_names: &[ColumnName],
|
|
||||||
) -> DbResult<Vec<ColumnPosition>> {
|
|
||||||
let mut positions: Vec<ColumnPosition> = Vec::with_capacity(column_names.len());
|
|
||||||
for column_name in column_names {
|
|
||||||
let column_position = self.column_position_from_column_name(column_name)?;
|
|
||||||
positions.push(column_position)
|
|
||||||
}
|
|
||||||
Ok(positions)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn column_name_from_column_position(
|
pub fn column_name_from_column_position(
|
||||||
&self,
|
&self,
|
||||||
column_position: ColumnPosition,
|
column_position: ColumnPosition,
|
||||||
|
|
@ -111,27 +83,6 @@ impl TableSchema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn column_positions_from_column_selection(
|
|
||||||
&self,
|
|
||||||
column_selection: &ColumnSelection,
|
|
||||||
) -> DbResult<Vec<ColumnPosition>> {
|
|
||||||
match column_selection {
|
|
||||||
ColumnSelection::All => {
|
|
||||||
let mut column_positions: Vec<ColumnPosition> = self
|
|
||||||
.column_name_position_mapping
|
|
||||||
.iter()
|
|
||||||
.map(|(_, column_position)| *column_position)
|
|
||||||
.collect();
|
|
||||||
column_positions.sort();
|
|
||||||
Ok(column_positions)
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnSelection::Columns(column_names) => {
|
|
||||||
self.column_positions_from_column_names(column_names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn number_of_columns(&self) -> usize {
|
pub fn number_of_columns(&self) -> usize {
|
||||||
self.column_name_position_mapping.len()
|
self.column_name_position_mapping.len()
|
||||||
}
|
}
|
||||||
|
|
@ -140,44 +91,12 @@ impl TableSchema {
|
||||||
&self,
|
&self,
|
||||||
insertion_values: InsertionValues,
|
insertion_values: InsertionValues,
|
||||||
) -> DbResult<(Uuid, Row)> {
|
) -> DbResult<(Uuid, Row)> {
|
||||||
// TODO: There should be proper validation of the insertion_values.
|
let row: Row = Row::new_from_insertion_values(insertion_values);
|
||||||
// And it shouldn't really be done here.
|
|
||||||
//
|
|
||||||
// In the below we don't check for duplicate column names
|
|
||||||
//
|
|
||||||
let number_of_columns = self.number_of_columns();
|
|
||||||
if number_of_columns != insertion_values.len() {
|
|
||||||
return Err(Error::MismatchBetweenInsertValuesAndColumns(
|
|
||||||
self.table_name.clone(),
|
|
||||||
insertion_values,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut row: Row = Row::with_number_of_columns(number_of_columns);
|
|
||||||
|
|
||||||
let mut values: HashMap<ColumnName, Value> = HashMap::new();
|
|
||||||
for (column_name, db_value) in &insertion_values {
|
|
||||||
values.insert(column_name.clone(), db_value.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
for column_position in 0..number_of_columns {
|
|
||||||
let column_name: ColumnName = self.column_name_from_column_position(column_position)?;
|
|
||||||
match values.get(&column_name) {
|
|
||||||
Some(db_value) => row.push(db_value.clone()),
|
|
||||||
None => {
|
|
||||||
return Err(Error::MissingColumnInInsertValues(
|
|
||||||
self.table_name.clone(),
|
|
||||||
column_name,
|
|
||||||
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::Indexable(IndexableValue::Uuid(id))) => *id,
|
||||||
Some(_) => unreachable!(),
|
Some(_) => unreachable!(), // SAFETY: Should be guaranteed by validation
|
||||||
None => unreachable!(),
|
None => unreachable!(), // SAFETY: Should be guaranteed by validation
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((id, row))
|
Ok((id, row))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use minisql::{operation::Operation, schema::TableSchema};
|
use minisql::{operation::Operation, interpreter::DbSchema};
|
||||||
|
use crate::syntax::RawQuerySyntax;
|
||||||
use nom::{branch::alt, multi::many0, IResult};
|
use nom::{branch::alt, multi::many0, IResult};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ pub enum Error {
|
||||||
ValidationError(#[from] ValidationError)
|
ValidationError(#[from] ValidationError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, Operation> {
|
pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, RawQuerySyntax> {
|
||||||
alt((
|
alt((
|
||||||
parse_insert,
|
parse_insert,
|
||||||
parse_create,
|
parse_create,
|
||||||
|
|
@ -24,18 +25,17 @@ pub fn parse_statement<'a>(input: &'a str) -> IResult<&str, Operation> {
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_statements<'a>(input: &'a str) -> IResult<&str, Vec<Operation>> {
|
pub fn parse_statements<'a>(input: &'a str) -> IResult<&str, Vec<RawQuerySyntax>> {
|
||||||
many0(parse_statement)(input)
|
many0(parse_statement)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_and_validate(query: String, db_metadata: &Vec<(String, &TableSchema)>) -> Result<Operation, Error> {
|
pub fn parse_and_validate(query: String, db_schema: &DbSchema) -> Result<Operation, Error> {
|
||||||
let (_, op) = parse_statement(query.as_str())
|
let (_, op) = parse_statement(query.as_str())
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Error::ParsingError(err.to_string())
|
Error::ParsingError(err.to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
validate_operation(&op, db_metadata)?;
|
Ok(validate_operation(op, db_schema)?)
|
||||||
Ok(op)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
mod parsing;
|
mod parsing;
|
||||||
mod validation;
|
mod validation;
|
||||||
mod core;
|
mod core;
|
||||||
|
mod syntax;
|
||||||
|
|
||||||
pub use core::parse_and_validate;
|
pub use core::parse_and_validate;
|
||||||
pub use core::Error;
|
pub use core::Error;
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
IResult, branch::alt,
|
IResult, branch::alt,
|
||||||
};
|
};
|
||||||
use minisql::{operation::Condition, type_system::DbType};
|
use minisql::type_system::DbType;
|
||||||
|
|
||||||
|
use crate::syntax::Condition;
|
||||||
use super::literal::parse_db_value;
|
use super::literal::parse_db_value;
|
||||||
|
|
||||||
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
pub fn parse_table_name(input: &str) -> IResult<&str, &str> {
|
||||||
|
|
@ -67,7 +68,9 @@ fn parse_equality(input: &str) -> IResult<&str, Condition> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::{operation::Condition, type_system::DbType};
|
use minisql::type_system::DbType;
|
||||||
|
|
||||||
|
use crate::syntax::Condition;
|
||||||
use crate::parsing::common::{parse_db_type, parse_equality};
|
use crate::parsing::common::{parse_db_type, parse_equality};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use minisql::{operation::Operation, schema::{ColumnName, TableSchema}, type_system::DbType};
|
use minisql::{schema::{ColumnName, TableSchema}, type_system::DbType};
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{char, multispace0, multispace1},
|
character::complete::{char, multispace0, multispace1},
|
||||||
|
|
@ -8,8 +8,9 @@ use nom::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::common::{parse_table_name, parse_identifier, parse_db_type};
|
use super::common::{parse_table_name, parse_identifier, parse_db_type};
|
||||||
|
use crate::syntax::RawQuerySyntax;
|
||||||
|
|
||||||
pub fn parse_create(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, _) = tag("CREATE")(input)?;
|
let (input, _) = tag("CREATE")(input)?;
|
||||||
let (input, _) = multispace1(input)?;
|
let (input, _) = multispace1(input)?;
|
||||||
let (input, _) = tag("TABLE")(input)?;
|
let (input, _) = tag("TABLE")(input)?;
|
||||||
|
|
@ -41,7 +42,7 @@ pub fn parse_create(input: &str) -> IResult<&str, Operation> {
|
||||||
);
|
);
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Operation::CreateTable(table_name.to_string(), schema),
|
RawQuerySyntax::CreateTable(table_name.to_string(), schema),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,8 +69,8 @@ pub fn parse_column_definition(input: &str) -> IResult<&str, (ColumnName, DbType
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::Operation;
|
|
||||||
use crate::parsing::create::parse_create;
|
use crate::parsing::create::parse_create;
|
||||||
|
use crate::syntax::RawQuerySyntax;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create_no_spaces() {
|
fn test_parse_create_no_spaces() {
|
||||||
|
|
@ -94,13 +95,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_create() {
|
fn test_parse_create() {
|
||||||
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );").expect("should parse");
|
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );").expect("should parse");
|
||||||
assert!(matches!(create, Operation::CreateTable(_ ,_)));
|
assert!(matches!(create, RawQuerySyntax::CreateTable(_ ,_)));
|
||||||
match create {
|
match create {
|
||||||
Operation::CreateTable(name, schema) => {
|
RawQuerySyntax::CreateTable(name, schema) => {
|
||||||
assert_eq!(name, "Table1");
|
assert_eq!(name, "Table1");
|
||||||
assert_eq!(schema.number_of_columns(), 2);
|
assert_eq!(schema.number_of_columns(), 2);
|
||||||
assert_eq!(schema.column_position_from_column_name(&"id".to_string()).unwrap(), 0);
|
assert_eq!(schema.get_column_position(&"id".to_string()).unwrap(), 0);
|
||||||
assert_eq!(schema.column_position_from_column_name(&"column1".to_string()).unwrap(), 1);
|
assert_eq!(schema.get_column_position(&"column1".to_string()).unwrap(), 1);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use minisql::operation::Operation;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{char, multispace0, multispace1},
|
character::complete::{char, multispace0, multispace1},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::syntax::RawQuerySyntax;
|
||||||
use super::common::{parse_table_name, parse_condition};
|
use super::common::{parse_table_name, parse_condition};
|
||||||
|
|
||||||
pub fn parse_delete(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_delete(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, _) = tag("DELETE")(input)?;
|
let (input, _) = tag("DELETE")(input)?;
|
||||||
let (input, _) = multispace1(input)?;
|
let (input, _) = multispace1(input)?;
|
||||||
let (input, _) = tag("FROM")(input)?;
|
let (input, _) = tag("FROM")(input)?;
|
||||||
|
|
@ -19,19 +19,19 @@ pub fn parse_delete(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = char(';')(input)?;
|
let (input, _) = char(';')(input)?;
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Operation::Delete(table_name.to_string(), condition),
|
RawQuerySyntax::Delete(table_name.to_string(), condition),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::Operation;
|
use crate::syntax::RawQuerySyntax;
|
||||||
use crate::parsing::delete::parse_delete;
|
use crate::parsing::delete::parse_delete;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_delete() {
|
fn test_parse_delete() {
|
||||||
let (_, operation) = parse_delete("DELETE FROM \"T1\" WHERE id = 1 ;").expect("should parse");
|
let (_, operation) = parse_delete("DELETE FROM \"T1\" WHERE id = 1 ;").expect("should parse");
|
||||||
assert!(matches!(operation, Operation::Delete(_, _)))
|
assert!(matches!(operation, RawQuerySyntax::Delete(_, _)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add test with condition
|
// TODO: add test with condition
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use minisql::operation::Operation;
|
use crate::syntax::RawQuerySyntax;
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{char, multispace0, multispace1},
|
character::complete::{char, multispace0, multispace1},
|
||||||
|
|
@ -7,7 +7,7 @@ use nom::{
|
||||||
|
|
||||||
use super::common::{parse_identifier, parse_table_name};
|
use super::common::{parse_identifier, parse_table_name};
|
||||||
|
|
||||||
pub fn parse_create_index(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_create_index(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, _) = tag("CREATE")(input)?;
|
let (input, _) = tag("CREATE")(input)?;
|
||||||
let unique = |input| -> IResult<&str, bool> {
|
let unique = |input| -> IResult<&str, bool> {
|
||||||
let (input, _) = multispace1(input)?;
|
let (input, _) = multispace1(input)?;
|
||||||
|
|
@ -31,23 +31,23 @@ pub fn parse_create_index(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = char(')')(input)?;
|
let (input, _) = char(')')(input)?;
|
||||||
let (input, _) = multispace0(input)?;
|
let (input, _) = multispace0(input)?;
|
||||||
let (input, _) = char(';')(input)?;
|
let (input, _) = char(';')(input)?;
|
||||||
let operation = Operation::CreateIndex(table_name.to_string(), column_name.to_string());
|
let operation = RawQuerySyntax::CreateIndex(table_name.to_string(), column_name.to_string());
|
||||||
Ok((input, operation))
|
Ok((input, operation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::Operation;
|
use crate::syntax::RawQuerySyntax;
|
||||||
use crate::parsing::index::parse_create_index;
|
use crate::parsing::index::parse_create_index;
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_index() {
|
fn test_create_index() {
|
||||||
let (_, operation) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);").expect("should parse");
|
let (_, syntax) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" (email);").expect("should parse");
|
||||||
assert!(matches!(operation, Operation::CreateIndex(_, _)));
|
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
|
||||||
match operation {
|
match syntax {
|
||||||
Operation::CreateIndex(table_name, column_name) => {
|
RawQuerySyntax::CreateIndex(table_name, column_name) => {
|
||||||
assert_eq!(table_name, "contacts");
|
assert_eq!(table_name, "contacts");
|
||||||
assert_eq!(column_name, "email");
|
assert_eq!(column_name, "email");
|
||||||
}
|
}
|
||||||
|
|
@ -57,10 +57,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_index_with_spaces() {
|
fn test_create_index_with_spaces() {
|
||||||
let (_, operation) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;").expect("should parse");
|
let (_, syntax) = parse_create_index("CREATE UNIQUE INDEX idxcontactsemail ON \"contacts\" ( email ) ;").expect("should parse");
|
||||||
assert!(matches!(operation, Operation::CreateIndex(_, _)));
|
assert!(matches!(syntax, RawQuerySyntax::CreateIndex(_, _)));
|
||||||
match operation {
|
match syntax {
|
||||||
Operation::CreateIndex(table_name, column_name) => {
|
RawQuerySyntax::CreateIndex(table_name, column_name) => {
|
||||||
assert_eq!(table_name, "contacts");
|
assert_eq!(table_name, "contacts");
|
||||||
assert_eq!(column_name, "email");
|
assert_eq!(column_name, "email");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use super::{literal::parse_db_value, common::{parse_table_name, parse_identifier}};
|
use super::{literal::parse_db_value, common::{parse_table_name, parse_identifier}};
|
||||||
use minisql::{operation::Operation, type_system::Value};
|
use crate::syntax::RawQuerySyntax;
|
||||||
|
use minisql::type_system::Value;
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{multispace0, multispace1, char},
|
character::complete::{multispace0, multispace1, char},
|
||||||
|
|
@ -9,7 +10,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_insert(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_insert(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, _) = tag("INSERT")(input)?;
|
let (input, _) = tag("INSERT")(input)?;
|
||||||
let (input, _) = multispace1(input)?;
|
let (input, _) = multispace1(input)?;
|
||||||
let (input, _) = tag("INTO")(input)?;
|
let (input, _) = tag("INTO")(input)?;
|
||||||
|
|
@ -33,7 +34,7 @@ pub fn parse_insert(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = char(';')(input)?;
|
let (input, _) = char(';')(input)?;
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Operation::Insert(table_name.to_string(), column_names.into_iter().zip(values).collect()),
|
RawQuerySyntax::Insert(table_name.to_string(), column_names.into_iter().zip(values).collect()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,17 +49,18 @@ pub fn parse_values(input: &str) -> IResult<&str, Vec<Value>> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::{operation::Operation, type_system::{IndexableValue, Value}};
|
use minisql::type_system::{IndexableValue, Value};
|
||||||
|
|
||||||
|
use crate::syntax::RawQuerySyntax;
|
||||||
use super::parse_insert;
|
use super::parse_insert;
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_insert() {
|
fn test_parse_insert() {
|
||||||
let sql = "INSERT INTO \"MyTable\" (id, data) VALUES(1, \"Text\");";
|
let sql = "INSERT INTO \"MyTable\" (id, data) VALUES(1, \"Text\");";
|
||||||
let operation = parse_insert(sql).expect("should parse");
|
let syntax = parse_insert(sql).expect("should parse");
|
||||||
match operation {
|
match syntax {
|
||||||
("", Operation::Insert(table_name, insertion_values)) => {
|
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
||||||
assert_eq!(table_name, "MyTable");
|
assert_eq!(table_name, "MyTable");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
insertion_values,
|
insertion_values,
|
||||||
|
|
@ -78,7 +80,7 @@ mod tests {
|
||||||
let sql = "INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" ) ;";
|
let sql = "INSERT INTO \"MyTable\" ( id, data ) VALUES ( 1, \"Text\" ) ;";
|
||||||
let operation = parse_insert(sql).expect("should parse");
|
let operation = parse_insert(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", Operation::Insert(table_name, insertion_values)) => {
|
("", RawQuerySyntax::Insert(table_name, insertion_values)) => {
|
||||||
assert_eq!(table_name, "MyTable");
|
assert_eq!(table_name, "MyTable");
|
||||||
assert_eq!(insertion_values,
|
assert_eq!(insertion_values,
|
||||||
vec![
|
vec![
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::common::{parse_table_name, parse_column_name, parse_condition};
|
use super::common::{parse_table_name, parse_column_name, parse_condition};
|
||||||
use minisql::operation::{ColumnSelection, Operation};
|
use crate::syntax::{ColumnSelection, RawQuerySyntax};
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
|
|
@ -11,7 +11,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_select(input: &str) -> IResult<&str, Operation> {
|
pub fn parse_select(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||||
let (input, _) = tag("SELECT")(input)?;
|
let (input, _) = tag("SELECT")(input)?;
|
||||||
let (input, _) = multispace1(input)?;
|
let (input, _) = multispace1(input)?;
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub fn parse_select(input: &str) -> IResult<&str, Operation> {
|
||||||
let (input, _) = tag(";")(input)?;
|
let (input, _) = tag(";")(input)?;
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Operation::Select(table_name.to_string(), column_selection, condition),
|
RawQuerySyntax::Select(table_name.to_string(), column_selection, condition),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ pub fn try_parse_column_selection(input: &str) -> IResult<&str, ColumnSelection>
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use minisql::operation::{ColumnSelection, Operation};
|
use crate::syntax::{ColumnSelection, RawQuerySyntax};
|
||||||
use crate::parsing::{common::{parse_column_name, parse_table_name}, select::parse_select};
|
use crate::parsing::{common::{parse_column_name, parse_table_name}, select::parse_select};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ mod tests {
|
||||||
let sql = "SELECT * FROM \"MyTable\";";
|
let sql = "SELECT * FROM \"MyTable\";";
|
||||||
let operation = parse_select(sql).expect("should parse");
|
let operation = parse_select(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", Operation::Select(table_name, column_selection, maybe_condition)) => {
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
assert_eq!(table_name, "MyTable");
|
assert_eq!(table_name, "MyTable");
|
||||||
assert!(matches!(column_selection, ColumnSelection::All));
|
assert!(matches!(column_selection, ColumnSelection::All));
|
||||||
assert!(matches!(maybe_condition, None));
|
assert!(matches!(maybe_condition, None));
|
||||||
|
|
@ -80,7 +80,7 @@ mod tests {
|
||||||
let sql = "SELECT name , email FROM \"AddressBook\" ;";
|
let sql = "SELECT name , email FROM \"AddressBook\" ;";
|
||||||
let operation = parse_select(sql).expect("should parse");
|
let operation = parse_select(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", Operation::Select(table_name, column_selection, maybe_condition)) => {
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
assert_eq!(table_name, "AddressBook");
|
assert_eq!(table_name, "AddressBook");
|
||||||
assert!(matches!(column_selection, ColumnSelection::Columns(_)));
|
assert!(matches!(column_selection, ColumnSelection::Columns(_)));
|
||||||
match column_selection {
|
match column_selection {
|
||||||
|
|
@ -102,11 +102,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_select_where() {
|
fn test_parse_select_where() {
|
||||||
use minisql::operation::Condition;
|
use crate::syntax::Condition;
|
||||||
let sql = "SELECT * FROM \"AddressBook\" WHERE id = 5 ;";
|
let sql = "SELECT * FROM \"AddressBook\" WHERE id = 5 ;";
|
||||||
let operation = parse_select(sql).expect("should parse");
|
let operation = parse_select(sql).expect("should parse");
|
||||||
match operation {
|
match operation {
|
||||||
("", Operation::Select(table_name, column_selection, maybe_condition)) => {
|
("", RawQuerySyntax::Select(table_name, column_selection, maybe_condition)) => {
|
||||||
assert_eq!(table_name, "AddressBook");
|
assert_eq!(table_name, "AddressBook");
|
||||||
assert!(matches!(column_selection, ColumnSelection::All));
|
assert!(matches!(column_selection, ColumnSelection::All));
|
||||||
assert!(matches!(maybe_condition, Some(Condition::Eq(_, _))));
|
assert!(matches!(maybe_condition, Some(Condition::Eq(_, _))));
|
||||||
|
|
|
||||||
36
parser/src/syntax.rs
Normal file
36
parser/src/syntax.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use minisql::{type_system::Value, schema::{TableSchema, ColumnName, TableName}};
|
||||||
|
|
||||||
|
// TODO: Move this out into separate file and rename to something like Syntax, SyntaxTree,
|
||||||
|
// OperationSyntax, RawOperationSyntax
|
||||||
|
pub enum RawQuerySyntax {
|
||||||
|
Select(TableName, ColumnSelection, Option<Condition>),
|
||||||
|
Insert(TableName, InsertionValues),
|
||||||
|
Delete(TableName, Option<Condition>),
|
||||||
|
// Update(...),
|
||||||
|
CreateTable(TableName, TableSchema),
|
||||||
|
CreateIndex(TableName, ColumnName),
|
||||||
|
// DropTable(TableName),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type InsertionValues = Vec<(ColumnName, Value)>;
|
||||||
|
|
||||||
|
pub enum ColumnSelection {
|
||||||
|
All,
|
||||||
|
Columns(Vec<ColumnName>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Condition {
|
||||||
|
// And(Box<Condition>, Box<Condition>),
|
||||||
|
// Or(Box<Condition>, Box<Condition>),
|
||||||
|
// Not(Box<Condition>),
|
||||||
|
Eq(ColumnName, Value),
|
||||||
|
// LessOrEqual(ColumnName, DbValue),
|
||||||
|
// Less(ColumnName, DbValue),
|
||||||
|
|
||||||
|
// StringCondition(StringCondition),
|
||||||
|
}
|
||||||
|
|
||||||
|
// enum StringCondition {
|
||||||
|
// Prefix(ColumnName, String),
|
||||||
|
// Substring(ColumnName, String),
|
||||||
|
// }
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::collections::HashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use minisql::{operation::{ColumnSelection, Condition, InsertionValues, Operation}, schema::{TableSchema, ColumnName, TableName}, type_system::DbType};
|
use crate::syntax;
|
||||||
|
use crate::syntax::RawQuerySyntax;
|
||||||
|
use minisql::operation;
|
||||||
|
use minisql::{operation::Operation, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
|
|
@ -25,69 +27,53 @@ pub enum ValidationError {
|
||||||
RequiredColumnsAreMissing(Vec<ColumnName>)
|
RequiredColumnsAreMissing(Vec<ColumnName>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DbSchema<'a> = Vec<(TableName, &'a TableSchema)>;
|
/// Validates and converts the raw syntax into a proper interpreter operation based on db schema.
|
||||||
|
pub fn validate_operation(query: RawQuerySyntax, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||||
/// Validates the operation based on db_metadata
|
match query {
|
||||||
pub fn validate_operation(operation: &Operation, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
RawQuerySyntax::Select(table_name, column_selection, condition) => {
|
||||||
match operation {
|
validate_select(table_name, column_selection, condition, db_schema)
|
||||||
Operation::Select(table_name, column_selection, condition) => {
|
|
||||||
validate_select(table_name, column_selection, condition, db_schema)?;
|
|
||||||
},
|
},
|
||||||
Operation::Insert(table_name, insertion_values) => {
|
RawQuerySyntax::Insert(table_name, insertion_values) => {
|
||||||
validate_insert(&table_name, insertion_values, db_schema)?;
|
validate_insert(table_name, insertion_values, db_schema)
|
||||||
},
|
},
|
||||||
Operation::Delete(table_name, condition) => {
|
RawQuerySyntax::Delete(table_name, condition) => {
|
||||||
validate_delete(table_name, condition, db_schema)?;
|
validate_delete(table_name, condition, db_schema)
|
||||||
},
|
},
|
||||||
// Operation::Update(table_name, insertion_values, condition) => {
|
RawQuerySyntax::CreateTable(table_name, schema) => {
|
||||||
// validate_update(table_name, insertion_values, db_metadata)?;
|
validate_create(table_name, schema, db_schema)
|
||||||
// },
|
|
||||||
Operation::CreateTable(table_name, schema) => {
|
|
||||||
validate_create(table_name, schema, db_schema)?;
|
|
||||||
},
|
},
|
||||||
Operation::CreateIndex(table_name, column_name) => {
|
RawQuerySyntax::CreateIndex(table_name, column_name) => {
|
||||||
validate_create_index(table_name, column_name, db_schema)?;
|
validate_create_index(table_name, column_name, db_schema)
|
||||||
},
|
},
|
||||||
// Operation::DropTable(table_name) => {
|
|
||||||
// validate_drop(table_name, db_schema)?;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Result<&'a TableSchema, ValidationError> {
|
fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Result<(TablePosition, &'a TableSchema), ValidationError> {
|
||||||
db_schema.iter().find(|(tname, _)| table_name.eq(tname))
|
db_schema.iter().find(|(tname, _, _)| table_name.eq(tname))
|
||||||
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))
|
.ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))
|
||||||
.map(|(_, table_schema)| table_schema).copied()
|
.map(|(_, table_position, table_schema)| (*table_position, *table_schema))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_create(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||||
// pub fn validate_drop(table_name: &str, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> {
|
if let Some(_) = get_table_schema(db_schema, &table_name) {
|
||||||
// db_metadata.iter().find(|(tname, _)| table_name.eq(tname))
|
|
||||||
// .ok_or(ValidationError::TableDoesNotExist(table_name.to_string()))?;
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn validate_create(table_name: &TableName, schema: &TableSchema, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
|
||||||
if let Some(_) = get_table_schema(db_schema, table_name) {
|
|
||||||
return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
|
return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
find_first_duplicate(&schema.get_columns())
|
find_first_duplicate(&table_schema.get_columns())
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|| Ok(()),
|
|| Ok(()),
|
||||||
|duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string()))
|
|duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string()))
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// TODO: Ensure it has a primary key??
|
// TODO: Ensure it has a primary key??
|
||||||
Ok(())
|
Ok(Operation::CreateTable(table_name, table_schema))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelection, condition: &Option<Condition>, db_schema: &Vec<(TableName, &TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_select(table_name: TableName, column_selection: syntax::ColumnSelection, condition: Option<syntax::Condition>, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||||
let schema = validate_table_exists(db_schema, table_name)?;
|
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
|
||||||
match column_selection {
|
match column_selection {
|
||||||
ColumnSelection::Columns(columns) => {
|
syntax::ColumnSelection::Columns(columns) => {
|
||||||
let non_existant_columns: Vec<String> =
|
let non_existant_columns: Vec<ColumnName> =
|
||||||
columns.iter().filter_map(|column|
|
columns.iter().filter_map(|column|
|
||||||
if schema.does_column_exist(&column) {
|
if schema.does_column_exist(&column) {
|
||||||
Some(column.clone())
|
Some(column.clone())
|
||||||
|
|
@ -97,52 +83,21 @@ pub fn validate_select(table_name: &TableName, column_selection: &ColumnSelectio
|
||||||
if non_existant_columns.len() > 0 {
|
if non_existant_columns.len() > 0 {
|
||||||
Err(ValidationError::ColumnsDoNotExist(non_existant_columns))
|
Err(ValidationError::ColumnsDoNotExist(non_existant_columns))
|
||||||
} else {
|
} else {
|
||||||
validate_condition(condition, schema)
|
let selection: operation::ColumnSelection =
|
||||||
|
columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect();
|
||||||
|
let validated_condition = validate_condition(condition, schema)?;
|
||||||
|
Ok(Operation::Select(table_position, selection, validated_condition))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ColumnSelection::All => Ok(())
|
syntax::ColumnSelection::All => {
|
||||||
|
let validated_condition = validate_condition(condition, schema)?;
|
||||||
|
Ok(Operation::Select(table_position, schema.all_selection(), validated_condition))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn validate_update(table_name: &str, insertion_values: &InsertionValues, db_metadata: &Vec<(String, TableSchema)>) -> Result<(), ValidationError> {
|
pub fn validate_insert(table_name: TableName, insertion_values: syntax::InsertionValues, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||||
// let schema = validate_table_exists(db_schema, table_name)?;
|
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
|
||||||
// let mut column_names = HashSet::new();
|
|
||||||
// // Find duplicate columns
|
|
||||||
// for (name, _) in insertion_values {
|
|
||||||
// if column_names.contains(name) {
|
|
||||||
// return Err(ValidationError::DuplicateColumn(name.clone()));
|
|
||||||
// } else {
|
|
||||||
// column_names.insert(name.clone());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // Ensure columns exist in schema
|
|
||||||
// let column_value_type: Vec<_> = insertion_values.iter().map(|(column, value)| {
|
|
||||||
// (column, value, schema.column_name_position_mapping.iter().find(|(name, _) | {
|
|
||||||
// (*name).eq(column)
|
|
||||||
// }).map(|(_, t)| schema.types.get(*t as usize)))
|
|
||||||
// }).collect();
|
|
||||||
// if let Some((name, _, _)) = column_value_type.iter().find(|(_, _, t)| {
|
|
||||||
// t.is_none()
|
|
||||||
// }) {
|
|
||||||
// return Err(ValidationError::ColumnsDoNotExist(vec![(*name).clone())]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Check types
|
|
||||||
// if let Some((_, _, _)) = column_value_type.iter().find(|(_, value, t)| {
|
|
||||||
// if let Some(Some(column_type)) = t {
|
|
||||||
// !type_of(value).eq(column_type)
|
|
||||||
// } else {
|
|
||||||
// false
|
|
||||||
// }
|
|
||||||
// }) {
|
|
||||||
// // TODO: Add column name information
|
|
||||||
// return Err(ValidationError::TypeMismatch);
|
|
||||||
// }
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValues, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
|
||||||
let schema = validate_table_exists(db_schema, table_name)?;
|
|
||||||
|
|
||||||
// Check for duplicate columns in insertion_values.
|
// Check for duplicate columns in insertion_values.
|
||||||
let columns_in_query_vec: Vec<&ColumnName> = insertion_values.iter().map(|(column_name, _)| column_name).collect();
|
let columns_in_query_vec: Vec<&ColumnName> = insertion_values.iter().map(|(column_name, _)| column_name).collect();
|
||||||
|
|
@ -157,74 +112,82 @@ pub fn validate_insert(table_name: &TableName, insertion_values: &InsertionValue
|
||||||
let columns_in_schema: HashSet<&ColumnName> = HashSet::from_iter(schema.get_columns());
|
let columns_in_schema: HashSet<&ColumnName> = HashSet::from_iter(schema.get_columns());
|
||||||
let non_existant_columns = Vec::from_iter(columns_in_query.difference(&columns_in_schema));
|
let non_existant_columns = Vec::from_iter(columns_in_query.difference(&columns_in_schema));
|
||||||
if non_existant_columns.len() > 0 {
|
if non_existant_columns.len() > 0 {
|
||||||
return Err(ValidationError::ColumnsDoNotExist(non_existant_columns.iter().map(|str| str.to_string()).collect()));
|
return Err(ValidationError::ColumnsDoNotExist(non_existant_columns.iter().map(|column_name| column_name.to_string()).collect()));
|
||||||
}
|
}
|
||||||
let missing_required_columns = Vec::from_iter(columns_in_schema.difference(&columns_in_query));
|
let missing_required_columns = Vec::from_iter(columns_in_schema.difference(&columns_in_query));
|
||||||
if missing_required_columns.len() > 0 {
|
if missing_required_columns.len() > 0 {
|
||||||
return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect()));
|
return Err(ValidationError::RequiredColumnsAreMissing(missing_required_columns.iter().map(|str| str.to_string()).collect()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check types
|
// Check types and prepare for creation of InsertionValues for the interpreter
|
||||||
|
let mut values_map: HashMap<_, Value> = HashMap::new();
|
||||||
for (column_name, value) in insertion_values {
|
for (column_name, value) in insertion_values {
|
||||||
let expected_type = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error.
|
let (column, expected_type) = schema.get_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?; // By the previous validation steps this is never gonna trigger an error.
|
||||||
let value_type = value.to_type();
|
let value_type = value.to_type();
|
||||||
if value_type != expected_type {
|
if value_type != expected_type {
|
||||||
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
|
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
|
||||||
}
|
}
|
||||||
|
values_map.insert(column, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
// These are values ordered by the column position
|
||||||
|
let values: operation::InsertionValues = values_map.into_values().collect();
|
||||||
|
|
||||||
|
Ok(Operation::Insert(table_position, values))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_delete(table_name: &TableName, condition: &Option<Condition>, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
pub fn validate_delete(table_name: TableName, condition: Option<syntax::Condition>, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||||
let schema = validate_table_exists(db_schema, table_name)?;
|
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
|
||||||
validate_condition(condition, schema)?;
|
let validated_condition = validate_condition(condition, schema)?;
|
||||||
Ok(())
|
Ok(Operation::Delete(table_position, validated_condition))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_condition(condition: &Option<Condition>, schema: &TableSchema) -> Result<(), ValidationError> {
|
fn validate_condition(condition: Option<syntax::Condition>, schema: &TableSchema) -> Result<Option<operation::Condition>, ValidationError> {
|
||||||
match condition {
|
match condition {
|
||||||
Some(condition) => {
|
Some(condition) => {
|
||||||
match condition {
|
match condition {
|
||||||
Condition::Eq(column_name, value) => {
|
syntax::Condition::Eq(column_name, value) => {
|
||||||
let expected_type: DbType = schema.get_type_at(column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
|
let (column, expected_type) = schema.get_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
|
||||||
let value_type: DbType = value.to_type();
|
let value_type: DbType = value.to_type();
|
||||||
if !expected_type.eq(&value_type) {
|
if expected_type.eq(&value_type) {
|
||||||
|
Ok(Some(operation::Condition::Eq(column, value)))
|
||||||
|
} else {
|
||||||
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
|
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => Ok(None)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_create_index(table_name: &TableName, column_name: &ColumnName, db_schema: &DbSchema) -> Result<(), ValidationError> {
|
fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||||
let schema = validate_table_exists(db_schema, table_name)?;
|
// TODO: You should disallow indexing of Number columns.
|
||||||
if schema.does_column_exist(column_name) {
|
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
|
||||||
Ok(())
|
schema
|
||||||
} else {
|
.get_column_position(&column_name)
|
||||||
Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))
|
.map_or_else(
|
||||||
}
|
|| Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])),
|
||||||
|
|column| Ok(Operation::CreateIndex(table_position, column))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===Helpers===
|
// ===Helpers===
|
||||||
fn find_first_duplicate<A>(xs: &[A]) -> Option<&A>
|
fn find_first_duplicate<T>(ts: &[T]) -> Option<&T>
|
||||||
where A: Eq + std::hash::Hash
|
where T: Eq + std::hash::Hash
|
||||||
{
|
{
|
||||||
let mut already_seen_elements: HashSet<&A> = HashSet::new();
|
let mut already_seen_elements: HashSet<&T> = HashSet::new();
|
||||||
for x in xs {
|
for t in ts {
|
||||||
if already_seen_elements.contains(x) {
|
if already_seen_elements.contains(t) {
|
||||||
return Some(x);
|
return Some(t);
|
||||||
} else {
|
} else {
|
||||||
already_seen_elements.insert(&x);
|
already_seen_elements.insert(&t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_table_schema<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Option<&'a TableSchema> {
|
fn get_table_schema<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) -> Option<&'a TableSchema> {
|
||||||
let (_, table_schema) = db_schema.iter().find(|(tname, _)| table_name.eq(tname))?;
|
let (_, _, table_schema) = db_schema.iter().find(|(tname, _, _)| table_name.eq(tname))?;
|
||||||
Some(table_schema)
|
Some(table_schema)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,8 @@ where
|
||||||
{
|
{
|
||||||
let operation = {
|
let operation = {
|
||||||
let state = state.read().await;
|
let state = state.read().await;
|
||||||
let metadata = state.metadata();
|
let db_schema = state.db_schema();
|
||||||
parse_and_validate(query, &metadata)?
|
parse_and_validate(query, &db_schema)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = state.write().await;
|
let mut state = state.write().await;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue