Improve Create Table
This commit is contained in:
parent
4a099468b2
commit
7b5b2bf9f3
6 changed files with 197 additions and 107 deletions
|
|
@ -1,4 +1,3 @@
|
|||
use minisql::{schema::{ColumnName, TableSchema}, type_system::DbType};
|
||||
use nom::{
|
||||
bytes::complete::tag,
|
||||
character::complete::{char, multispace0, multispace1},
|
||||
|
|
@ -8,7 +7,7 @@ use nom::{
|
|||
};
|
||||
|
||||
use super::common::{parse_table_name, parse_identifier, parse_db_type};
|
||||
use crate::syntax::RawQuerySyntax;
|
||||
use crate::syntax::{RawTableSchema, ColumnSchema, RawQuerySyntax};
|
||||
|
||||
pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> {
|
||||
let (input, _) = tag("CREATE")(input)?;
|
||||
|
|
@ -20,33 +19,21 @@ pub fn parse_create(input: &str) -> IResult<&str, RawQuerySyntax> {
|
|||
let (input, _) = char('(')(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, column_definitions) = parse_column_definitions(input)?;
|
||||
let mut column_name_position_mapping = Vec::new();
|
||||
let mut types: Vec<DbType> = Vec::new();
|
||||
let mut primary_key = None;
|
||||
for (position, (column_name, db_type, pk)) in column_definitions.iter().enumerate() {
|
||||
types.push(db_type.clone());
|
||||
if *pk {
|
||||
primary_key = Some(position);
|
||||
}
|
||||
column_name_position_mapping.push((column_name.clone(), position));
|
||||
}
|
||||
|
||||
let (input, _) = char(')')(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char(';')(input)?;
|
||||
let schema = TableSchema::new(
|
||||
table_name.to_string(),
|
||||
primary_key.unwrap_or_default(),
|
||||
column_name_position_mapping,
|
||||
types
|
||||
);
|
||||
let schema = RawTableSchema {
|
||||
table_name: table_name.to_string(),
|
||||
columns: column_definitions,
|
||||
};
|
||||
Ok((
|
||||
input,
|
||||
RawQuerySyntax::CreateTable(table_name.to_string(), schema),
|
||||
RawQuerySyntax::CreateTable(schema),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn parse_column_definitions(input: &str) -> IResult<&str, Vec<(ColumnName, DbType, bool)>> {
|
||||
fn parse_column_definitions(input: &str) -> IResult<&str, Vec<ColumnSchema>> {
|
||||
separated_list0(terminated(char(','), multispace0), parse_column_definition)(input)
|
||||
}
|
||||
|
||||
|
|
@ -58,13 +45,13 @@ fn parse_primary_key(input: &str) -> IResult<&str, &str> {
|
|||
Ok((input, "PRIMARY KEY"))
|
||||
}
|
||||
|
||||
pub fn parse_column_definition(input: &str) -> IResult<&str, (ColumnName, DbType, bool)> {
|
||||
fn parse_column_definition(input: &str) -> IResult<&str, ColumnSchema> {
|
||||
let (input, identifier) = parse_identifier(input)?;
|
||||
let (input, _) = multispace1(input)?;
|
||||
let (input, db_type) = parse_db_type(input)?;
|
||||
let (input, pk) = opt(parse_primary_key)(input).map(|(input, pk)| (input, pk.is_some()))?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
Ok((input, (identifier.to_string(), db_type, pk)))
|
||||
Ok((input, ColumnSchema { column_name: identifier.to_string(), type_: db_type, is_primary: pk }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -95,16 +82,23 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_create() {
|
||||
let (_, create) = parse_create("CREATE TABLE \"Table1\"( id UUID , column1 INT );").expect("should parse");
|
||||
assert!(matches!(create, RawQuerySyntax::CreateTable(_ ,_)));
|
||||
assert!(matches!(create, RawQuerySyntax::CreateTable(_)));
|
||||
match create {
|
||||
RawQuerySyntax::CreateTable(name, schema) => {
|
||||
assert_eq!(name, "Table1");
|
||||
RawQuerySyntax::CreateTable(schema) => {
|
||||
assert_eq!(schema.table_name, "Table1");
|
||||
assert_eq!(schema.number_of_columns(), 2);
|
||||
assert_eq!(schema.get_column_position(&"id".to_string()).unwrap(), 0);
|
||||
assert_eq!(schema.get_column_position(&"column1".to_string()).unwrap(), 1);
|
||||
|
||||
let result_id = schema.get_column(&"id".to_string());
|
||||
assert!(matches!(result_id, Some(_)));
|
||||
let Some(id_column) = result_id else { panic!() };
|
||||
assert_eq!(id_column.column_name, "id".to_string());
|
||||
|
||||
let result_column1 = schema.get_column(&"column1".to_string());
|
||||
assert!(matches!(result_column1, Some(_)));
|
||||
let Some(column1_column) = result_column1 else { panic!() };
|
||||
assert_eq!(column1_column.column_name, "column1".to_string());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,29 @@
|
|||
use minisql::{type_system::Value, schema::{TableSchema, ColumnName, TableName}};
|
||||
use minisql::{type_system::{Value, DbType}, schema::{ColumnName, TableName}};
|
||||
|
||||
// TODO: Move this out into separate file and rename to something like Syntax, SyntaxTree,
|
||||
// OperationSyntax, RawOperationSyntax
|
||||
// ===Table Schema===
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RawTableSchema {
|
||||
pub table_name: TableName,
|
||||
pub columns: Vec<ColumnSchema>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ColumnSchema {
|
||||
pub column_name: ColumnName,
|
||||
pub type_: DbType,
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
// ===Query===
|
||||
pub enum RawQuerySyntax {
|
||||
Select(TableName, ColumnSelection, Option<Condition>),
|
||||
Insert(TableName, InsertionValues),
|
||||
Delete(TableName, Option<Condition>),
|
||||
// Update(...),
|
||||
CreateTable(TableName, TableSchema),
|
||||
CreateTable(RawTableSchema),
|
||||
CreateIndex(TableName, ColumnName),
|
||||
// DropTable(TableName),
|
||||
}
|
||||
|
||||
pub type InsertionValues = Vec<(ColumnName, Value)>;
|
||||
|
||||
pub enum ColumnSelection {
|
||||
|
|
@ -34,3 +46,17 @@ pub enum Condition {
|
|||
// Prefix(ColumnName, String),
|
||||
// Substring(ColumnName, String),
|
||||
// }
|
||||
|
||||
impl RawTableSchema {
|
||||
pub fn number_of_columns(&self) -> usize {
|
||||
self.columns.len()
|
||||
}
|
||||
|
||||
pub fn get_column(&self, column_name: &ColumnName) -> Option<ColumnSchema> {
|
||||
self.columns.iter().find(|column_schema| column_name == &column_schema.column_name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_columns(&self) -> Vec<&ColumnName> {
|
||||
self.columns.iter().map(|ColumnSchema { column_name, .. }| column_name).collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::collections::{HashSet, BTreeMap};
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::syntax;
|
||||
use crate::syntax::RawQuerySyntax;
|
||||
use crate::syntax::{RawTableSchema, ColumnSchema, RawQuerySyntax};
|
||||
use minisql::operation;
|
||||
use minisql::{operation::Operation, type_system::Value, schema::{TableSchema, ColumnName, TableName}, type_system::DbType, interpreter::{TablePosition, DbSchema}};
|
||||
|
||||
|
|
@ -16,6 +16,10 @@ pub enum ValidationError {
|
|||
ColumnsDoNotExist(Vec<ColumnName>),
|
||||
#[error("duplicate column {0}")]
|
||||
DuplicateColumn(ColumnName),
|
||||
#[error("primary key missing in table {0}")]
|
||||
PrimaryKeyMissing(TableName),
|
||||
#[error("multiple primary keys found in table {0}")]
|
||||
MultiplePrimaryKeysFound(TableName),
|
||||
#[error("type mismatch at column `{column_name:?}` (expected {expected_type:?}, found {received_type:?})")]
|
||||
TypeMismatch {
|
||||
column_name: ColumnName,
|
||||
|
|
@ -38,8 +42,8 @@ pub fn validate_operation(syntax: RawQuerySyntax, db_schema: &DbSchema) -> Resul
|
|||
RawQuerySyntax::Delete(table_name, condition) => {
|
||||
validate_delete(table_name, condition, db_schema)
|
||||
},
|
||||
RawQuerySyntax::CreateTable(table_name, schema) => {
|
||||
validate_create_table(table_name, schema, db_schema)
|
||||
RawQuerySyntax::CreateTable(schema) => {
|
||||
validate_create_table(schema, db_schema)
|
||||
},
|
||||
RawQuerySyntax::CreateIndex(table_name, column_name) => {
|
||||
validate_create_index(table_name, column_name, db_schema)
|
||||
|
|
@ -53,19 +57,52 @@ fn validate_table_exists<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName
|
|||
.map(|(_, table_position, table_schema)| (*table_position, *table_schema))
|
||||
}
|
||||
|
||||
fn validate_create_table(table_name: TableName, table_schema: TableSchema, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||
fn validate_create_table(raw_table_schema: RawTableSchema, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||
let table_name: &TableName = &raw_table_schema.table_name;
|
||||
if let Some(_) = get_table_schema(db_schema, &table_name) {
|
||||
return Err(ValidationError::TableAlreadyExists(table_name.to_string()));
|
||||
}
|
||||
|
||||
find_first_duplicate(&table_schema.get_columns())
|
||||
let table_schema: TableSchema = validate_table_schema(raw_table_schema)?;
|
||||
Ok(Operation::CreateTable(table_schema))
|
||||
}
|
||||
|
||||
fn validate_table_schema(raw_table_schema: RawTableSchema) -> Result<TableSchema, ValidationError> {
|
||||
// check for duplicate columns
|
||||
find_first_duplicate(&raw_table_schema.get_columns())
|
||||
.map_or_else(
|
||||
|| Ok(()),
|
||||
|duplicate_column| Err(ValidationError::DuplicateColumn(duplicate_column.to_string()))
|
||||
)?;
|
||||
|
||||
// TODO: Ensure it has a primary key??
|
||||
Ok(Operation::CreateTable(table_name, table_schema))
|
||||
let mut primary_keys: Vec<(ColumnName, DbType)> = vec![];
|
||||
let mut columns: Vec<ColumnName> = vec![];
|
||||
let mut types: Vec<DbType> = vec![];
|
||||
for ColumnSchema { column_name, type_, is_primary } in raw_table_schema.columns {
|
||||
if is_primary {
|
||||
primary_keys.push((column_name.clone(), type_))
|
||||
}
|
||||
columns.push(column_name);
|
||||
types.push(type_);
|
||||
}
|
||||
|
||||
// Ensure it has exactly one primary key that has correct type.
|
||||
if primary_keys.len() == 0 {
|
||||
return Err(ValidationError::PrimaryKeyMissing(raw_table_schema.table_name.clone()))
|
||||
} else if primary_keys.len() > 1 {
|
||||
return Err(ValidationError::MultiplePrimaryKeysFound(raw_table_schema.table_name.clone()))
|
||||
} else {
|
||||
let (primary_column_name, primary_key_type) = primary_keys[0].clone();
|
||||
if primary_key_type == DbType::Uuid {
|
||||
Ok(TableSchema::new(raw_table_schema.table_name, primary_column_name, columns, types))
|
||||
} else {
|
||||
Err(ValidationError::TypeMismatch {
|
||||
column_name: raw_table_schema.table_name.clone(),
|
||||
received_type: primary_key_type,
|
||||
expected_type: DbType::Uuid,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_select(table_name: TableName, column_selection: syntax::ColumnSelection, condition: Option<syntax::Condition>, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
|
||||
|
|
@ -197,7 +234,7 @@ fn get_table_schema<'a>(db_schema: &DbSchema<'a>, table_name: &'a TableName) ->
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::syntax::{RawQuerySyntax, ColumnSelection, Condition};
|
||||
use crate::syntax::{RawTableSchema, ColumnSchema, RawQuerySyntax, ColumnSelection, Condition};
|
||||
use minisql::type_system::{Value, IndexableValue};
|
||||
use minisql::operation::Operation;
|
||||
use minisql::operation;
|
||||
|
|
@ -210,22 +247,30 @@ mod tests {
|
|||
use Condition::*;
|
||||
|
||||
fn users_schema() -> TableSchema {
|
||||
let id = 0;
|
||||
let name = 1;
|
||||
let age = 2;
|
||||
|
||||
TableSchema::new(
|
||||
"users".to_string(),
|
||||
id,
|
||||
"id".to_string(),
|
||||
vec!(
|
||||
("id".to_string(), id),
|
||||
("name".to_string(), name),
|
||||
("age".to_string(), age),
|
||||
"id".to_string(),
|
||||
"name".to_string(),
|
||||
"age".to_string(),
|
||||
),
|
||||
vec![DbType::Uuid, DbType::String, DbType::Int],
|
||||
)
|
||||
}
|
||||
|
||||
fn raw_users_schema() -> RawTableSchema {
|
||||
RawTableSchema {
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![
|
||||
ColumnSchema { column_name: "id".to_string(), type_: DbType::Uuid, is_primary: true },
|
||||
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: false },
|
||||
ColumnSchema { column_name: "age".to_string(), type_: DbType::Int, is_primary: false },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn db_schema(users_schema: &TableSchema) -> DbSchema {
|
||||
vec![
|
||||
("users".to_string(), 0, users_schema),
|
||||
|
|
@ -236,52 +281,79 @@ mod tests {
|
|||
vec![]
|
||||
}
|
||||
|
||||
// ====CreateTable====
|
||||
#[test]
|
||||
fn test_create_basic() {
|
||||
let users_schema: TableSchema = users_schema();
|
||||
let db_schema: DbSchema = empty_db_schema();
|
||||
|
||||
let syntax: RawQuerySyntax = CreateTable("users".to_string(), users_schema.clone());
|
||||
let syntax: RawQuerySyntax = CreateTable(raw_users_schema());
|
||||
let result = validate_operation(syntax, &db_schema);
|
||||
assert!(matches!(result, Ok(Operation::CreateTable(_, _))));
|
||||
assert!(matches!(result, Ok(Operation::CreateTable(_))));
|
||||
|
||||
let Ok(Operation::CreateTable(table_name, _)) = result else { panic!() };
|
||||
assert!(table_name == "users".to_string());
|
||||
let Ok(Operation::CreateTable(schema)) = result else { panic!() };
|
||||
assert!(schema.table_name() == "users");
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_create_duplicates_in_schema() {
|
||||
// let id = 0;
|
||||
// let name = 1;
|
||||
#[test]
|
||||
fn test_create_duplicates_in_schema() {
|
||||
let raw_users_schema = RawTableSchema {
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![
|
||||
ColumnSchema { column_name: "id".to_string(), type_: DbType::Uuid, is_primary: true },
|
||||
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: false },
|
||||
ColumnSchema { column_name: "name".to_string(), type_: DbType::Number, is_primary: false },
|
||||
],
|
||||
};
|
||||
|
||||
// let users_schema = TableSchema::new(
|
||||
// "users".to_string(),
|
||||
// id,
|
||||
// vec!(
|
||||
// ("id".to_string(), id),
|
||||
// ("name".to_string(), name),
|
||||
// ("name".to_string(), name + 1),
|
||||
// ),
|
||||
// vec![DbType::Uuid, DbType::String, DbType::Int],
|
||||
// );
|
||||
let db_schema: DbSchema = empty_db_schema();
|
||||
|
||||
// let db_schema: DbSchema = empty_db_schema();
|
||||
let syntax: RawQuerySyntax = CreateTable(raw_users_schema);
|
||||
let result = validate_operation(syntax, &db_schema);
|
||||
println!("{:?}", result);
|
||||
assert!(matches!(result, Err(ValidationError::DuplicateColumn(_))));
|
||||
}
|
||||
|
||||
// let syntax: RawQuerySyntax = CreateTable("users".to_string(), users_schema.clone());
|
||||
// let result = validate_operation(syntax, &db_schema);
|
||||
// println!("{:?}", result);
|
||||
// assert!(matches!(result, Err(ValidationError::DuplicateColumn(_))));
|
||||
#[test]
|
||||
fn test_create_primary_key_is_uuid() {
|
||||
let raw_users_schema = RawTableSchema {
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![
|
||||
ColumnSchema { column_name: "id".to_string(), type_: DbType::Int, is_primary: true },
|
||||
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: false },
|
||||
ColumnSchema { column_name: "age".to_string(), type_: DbType::Int, is_primary: false },
|
||||
],
|
||||
};
|
||||
|
||||
// // TODO
|
||||
// }
|
||||
let db_schema: DbSchema = empty_db_schema();
|
||||
|
||||
let syntax: RawQuerySyntax = CreateTable(raw_users_schema);
|
||||
let result = validate_operation(syntax, &db_schema);
|
||||
assert!(matches!(result, Err(ValidationError::TypeMismatch { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_multiple_primary_keys() {
|
||||
let raw_users_schema = RawTableSchema {
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![
|
||||
ColumnSchema { column_name: "id".to_string(), type_: DbType::Int, is_primary: true },
|
||||
ColumnSchema { column_name: "name".to_string(), type_: DbType::String, is_primary: true },
|
||||
ColumnSchema { column_name: "age".to_string(), type_: DbType::Int, is_primary: false },
|
||||
],
|
||||
};
|
||||
|
||||
let db_schema: DbSchema = empty_db_schema();
|
||||
|
||||
let syntax: RawQuerySyntax = CreateTable(raw_users_schema);
|
||||
let result = validate_operation(syntax, &db_schema);
|
||||
assert!(matches!(result, Err(ValidationError::MultiplePrimaryKeysFound(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_already_exists() {
|
||||
let users_schema: TableSchema = users_schema();
|
||||
let db_schema: DbSchema = db_schema(&users_schema);
|
||||
|
||||
let syntax: RawQuerySyntax = CreateTable("users".to_string(), users_schema.clone());
|
||||
let syntax: RawQuerySyntax = CreateTable(raw_users_schema());
|
||||
let result = validate_operation(syntax, &db_schema);
|
||||
assert!(matches!(result, Err(ValidationError::TableAlreadyExists(_))));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue