Improve Create Table

This commit is contained in:
Yuriy Dupyn 2024-01-28 17:14:02 +01:00
parent 4a099468b2
commit 7b5b2bf9f3
6 changed files with 197 additions and 107 deletions

View file

@ -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(_))));
}