Merge branch 'validation-cleanup' into 'main'

Validation cleanup

See merge request x433485/minisql!15
This commit is contained in:
Yuriy Dupyn 2024-01-28 20:10:14 +01:00
commit 6c82404767
9 changed files with 67 additions and 70 deletions

View file

@ -136,7 +136,7 @@ struct TableMetaData {
columns: Vec<(ColumnName, DbType, ColumnPosition)>
}
fn column_position(TableMetaData, ColumnName) -> ColumnPosition
fn column(TableMetaData, ColumnName) -> ColumnPosition
struct Table {
meta: TableMetaData,

View file

@ -4,8 +4,7 @@ use std::ops::{Index, IndexMut};
use std::slice::SliceIndex;
use serde::{Deserialize, Serialize};
use crate::restricted_row::RestrictedRow;
pub type ColumnPosition = usize;
use crate::schema::Column;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Row(Vec<Value>);
@ -42,15 +41,15 @@ impl FromIterator<Value> for Row {
impl Row {
pub fn new() -> Self {
Row(vec![])
Self(vec![])
}
pub fn new_from_insertion_values(insertion_values: InsertionValues) -> Self {
Row(insertion_values)
Self(insertion_values)
}
pub fn with_number_of_columns(number_of_columns: usize) -> Self {
Row(Vec::with_capacity(number_of_columns))
Self(Vec::with_capacity(number_of_columns))
}
pub fn push(&mut self, value: Value) {
@ -61,13 +60,13 @@ impl Row {
self.0.len()
}
pub fn get(&self, column: ColumnPosition) -> Option<&Value> {
pub fn get(&self, column: Column) -> Option<&Value> {
self.0.get(column)
}
pub fn restrict_columns(&self, columns: &Vec<ColumnPosition>) -> RestrictedRow {
pub fn restrict_columns(&self, columns: &Vec<Column>) -> RestrictedRow {
// If the index from `columns` is non-existant in `row`, it will just ignore it.
let mut subrow: Vec<(ColumnPosition, Value)> = vec![];
let mut subrow: Vec<(Column, Value)> = vec![];
for column in columns {
if let Some(value) = self.get(*column) {
subrow.push((*column, value.clone()));

View file

@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize};
use crate::error::RuntimeError;
use crate::internals::column_index::ColumnIndex;
use crate::internals::row::{ColumnPosition, Row};
use crate::internals::row::Row;
use crate::restricted_row::RestrictedRow;
use crate::schema::{ColumnName, TableSchema, TableName};
use crate::schema::{Column, ColumnName, TableSchema, TableName};
use crate::result::DbResult;
use crate::type_system::{IndexableValue, Uuid, Value};
@ -14,7 +14,7 @@ pub struct Table {
schema: TableSchema,
rows: Rows, // TODO: Consider wrapping this in a lock. Also consider if we need to have the
// same lock for both rows and indexes
indexes: HashMap<ColumnPosition, ColumnIndex>,
indexes: HashMap<Column, ColumnIndex>,
}
pub type Rows = BTreeMap<Uuid, Row>;
@ -36,7 +36,7 @@ impl Table {
&self.rows
}
pub fn indexes(&self) -> &HashMap<ColumnPosition, ColumnIndex> {
pub fn indexes(&self) -> &HashMap<Column, ColumnIndex> {
&self.indexes
}
@ -55,7 +55,7 @@ impl Table {
.collect()
}
fn get_rows_by_value(&self, column: ColumnPosition, value: &Value) -> Vec<Row> {
fn get_rows_by_value(&self, column: Column, value: &Value) -> Vec<Row> {
// brute-force search
self.rows
.values()
@ -69,7 +69,7 @@ impl Table {
.collect()
}
pub fn select_all_rows<'a>(&'a self, selected_columns: Vec<ColumnPosition>) -> impl Iterator<Item=RestrictedRow> + 'a {
pub fn select_all_rows<'a>(&'a self, selected_columns: Vec<Column>) -> impl Iterator<Item=RestrictedRow> + 'a {
self.rows
.values()
.map(move |row| row.restrict_columns(&selected_columns))
@ -77,8 +77,8 @@ impl Table {
pub fn select_rows_where_eq<'a>(
&'a self,
selected_columns: Vec<ColumnPosition>,
column: ColumnPosition,
selected_columns: Vec<Column>,
column: Column,
value: Value,
) -> DbResult<impl Iterator<Item=RestrictedRow> + 'a> {
let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_columns);
@ -149,7 +149,7 @@ impl Table {
total_count
}
fn delete_rows_by_value(&mut self, column: ColumnPosition, value: &Value) -> usize {
fn delete_rows_by_value(&mut self, column: Column, value: &Value) -> usize {
let matched_ids: HashSet<Uuid> = self
.rows
.iter()
@ -173,7 +173,7 @@ impl Table {
pub fn delete_rows_where_eq(
&mut self,
column: ColumnPosition,
column: Column,
value: Value,
) -> DbResult<usize> {
match value {
@ -186,7 +186,7 @@ impl Table {
}
// ======Indexing======
pub fn attach_index(&mut self, column: ColumnPosition) -> DbResult<()> {
pub fn attach_index(&mut self, column: Column) -> DbResult<()> {
if self.indexes.get(&column).is_some() {
let column_name = self.schema.column_name_from_column(column).clone();
let table_name = self.schema.table_name().clone();
@ -200,7 +200,7 @@ impl Table {
fn fetch_ids_from_index(
&self,
column: ColumnPosition,
column: Column,
value: &IndexableValue,
) -> DbResult<Option<HashSet<Uuid>>> {
if self.schema.is_primary(column) {
@ -228,7 +228,7 @@ impl Table {
fn update_index_from_table(
column_index: &mut ColumnIndex,
table: &Table,
column: ColumnPosition,
column: Column,
) -> DbResult<()> {
for (id, row) in &table.rows {
let value = match &row[column] {
@ -237,7 +237,6 @@ fn update_index_from_table(
let column_name: ColumnName = table
.schema
.column_name_from_column(column);
// TODO: Perhaps this should be handled in validation?
return Err(RuntimeError::AttemptToIndexNonIndexableColumn(
table.table_name().to_string(),
column_name,

View file

@ -1,5 +1,4 @@
use crate::internals::row::ColumnPosition;
use crate::schema::{TableName, TableSchema};
use crate::schema::{Column, TableName, TablePosition, TableSchema};
use crate::internals::table::Table;
use crate::operation::{Operation, Condition};
use crate::result::DbResult;
@ -9,7 +8,6 @@ use crate::restricted_row::RestrictedRow;
// Use `TablePosition` as index
pub type Tables = Vec<Table>;
pub type TablePosition = usize;
// ==============Interpreter================
#[derive(Debug, Serialize, Deserialize)]
@ -145,7 +143,7 @@ impl State {
#[cfg(test)]
mod tests {
use super::*;
use crate::internals::row::ColumnPosition;
use crate::schema::Column;
use std::collections::HashSet;
use crate::type_system::{DbType, IndexableValue, Value};
use crate::operation::Operation;
@ -259,8 +257,8 @@ mod tests {
let users_schema = users_schema();
let users_position: TablePosition = 0;
let id_column: ColumnPosition = 0;
let name_column: ColumnPosition = 1;
let id_column: Column = 0;
let name_column: Column = 1;
state
.interpret(CreateTable(users_schema.clone()))
@ -377,7 +375,7 @@ mod tests {
let users_schema = users_schema();
let users_position: TablePosition = 0;
let id_column: ColumnPosition = 0;
let id_column: Column = 0;
state
.interpret(CreateTable(users_schema.clone()))
@ -451,7 +449,7 @@ mod tests {
let users_schema = users_schema();
let users_position: TablePosition = 0;
let name_column: ColumnPosition = 1;
let name_column: Column = 1;
state
.interpret(CreateTable(users_schema.clone()))
@ -497,7 +495,7 @@ mod tests {
let table = &state.tables[0];
assert!(table.rows().len() == 2);
let user: ColumnPosition = 1;
let user: Column = 1;
assert!(table.indexes().contains_key(&user));
let index = table.indexes().get(&user).unwrap();
@ -519,8 +517,8 @@ pub fn example() {
use Operation::*;
use Value::*;
let id_column: ColumnPosition = 0;
let name_column: ColumnPosition = 1;
let id_column: Column = 0;
let name_column: Column = 1;
// let age_column: ColumnPosition = 2;
let users_schema: TableSchema = {

View file

@ -1,7 +1,5 @@
use crate::schema::TableSchema;
use crate::schema::{Column, TablePosition, TableSchema};
use crate::type_system::Value;
use crate::internals::row::ColumnPosition;
use crate::interpreter::TablePosition;
// Validated operation. Constructed by validation crate.
#[derive(Debug, PartialEq)]
@ -10,15 +8,15 @@ pub enum Operation {
Insert(TablePosition, InsertionValues),
Delete(TablePosition, Option<Condition>),
CreateTable(TableSchema),
CreateIndex(TablePosition, ColumnPosition),
CreateIndex(TablePosition, Column),
}
// Assumes that these are sorted by column position.
pub type InsertionValues = Vec<Value>;
pub type ColumnSelection = Vec<ColumnPosition>;
pub type ColumnSelection = Vec<Column>;
#[derive(Debug, PartialEq)]
pub enum Condition {
Eq(ColumnPosition, Value),
Eq(Column, Value),
}

View file

@ -1,14 +1,14 @@
use std::ops::Index;
use std::slice::SliceIndex;
use crate::internals::row::ColumnPosition;
use crate::schema::Column;
use crate::type_system::Value;
#[derive(Debug, Clone)]
pub struct RestrictedRow(Vec<(ColumnPosition, Value)>);
pub struct RestrictedRow(Vec<(Column, Value)>);
impl<Idx> Index<Idx> for RestrictedRow
where
Idx: SliceIndex<[(ColumnPosition, Value)]>,
Idx: SliceIndex<[(Column, Value)]>,
{
type Output = Idx::Output;
@ -17,8 +17,8 @@ where
}
}
impl From<Vec<(ColumnPosition, Value)>> for RestrictedRow {
fn from(v: Vec<(ColumnPosition, Value)>) -> Self {
impl From<Vec<(Column, Value)>> for RestrictedRow {
fn from(v: Vec<(Column, Value)>) -> Self {
RestrictedRow(v)
}
}
@ -28,7 +28,7 @@ impl RestrictedRow {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item=&(ColumnPosition, Value)> {
pub fn iter(&self) -> impl Iterator<Item=&(Column, Value)> {
self.0.iter()
}
}

View file

@ -1,4 +1,4 @@
use crate::internals::row::{ColumnPosition, Row};
use crate::internals::row::Row;
use crate::operation::{InsertionValues, ColumnSelection};
use crate::result::DbResult;
use crate::type_system::{DbType, IndexableValue, Uuid, Value};
@ -10,21 +10,24 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TableSchema {
table_name: TableName, // used for descriptive errors
primary_key: ColumnPosition,
column_name_position_mapping: BiMap<ColumnName, ColumnPosition>,
primary_key: Column,
column_name_position_mapping: BiMap<ColumnName, Column>,
types: Vec<DbType>,
}
pub type TableName = String;
pub type TablePosition = usize;
pub type ColumnName = String;
pub type Column = usize;
impl TableSchema {
pub fn new(table_name: TableName, primary_column_name: ColumnName, columns: Vec<ColumnName>, types: Vec<DbType>) -> Self {
let mut column_name_position_mapping: BiMap<ColumnName, ColumnPosition> = BiMap::new();
let mut column_name_position_mapping: BiMap<ColumnName, Column> = BiMap::new();
for (column, column_name) in columns.into_iter().enumerate() {
column_name_position_mapping.insert(column_name, column);
}
let primary_key: ColumnPosition = match column_name_position_mapping.get_by_left(&primary_column_name).copied() {
let primary_key: Column = match column_name_position_mapping.get_by_left(&primary_column_name).copied() {
Some(primary_key) => primary_key,
None => unreachable!() // SAFETY: Existence of unique primary key is ensured in validation.
};
@ -35,7 +38,7 @@ impl TableSchema {
&self.table_name
}
pub fn column_type(&self, column: ColumnPosition) -> DbType {
pub fn column_type(&self, column: Column) -> DbType {
self.types[column]
}
@ -47,7 +50,7 @@ impl TableSchema {
self.column_name_position_mapping.contains_left(column_name)
}
pub fn get_column_position(&self, column_name: &ColumnName) -> Option<ColumnPosition> {
pub fn get_column(&self, column_name: &ColumnName) -> Option<Column> {
self.column_name_position_mapping.get_by_left(column_name).copied()
}
@ -57,22 +60,22 @@ impl TableSchema {
selection
}
pub fn get_column(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> {
let column = self.get_column_position(column_name)?;
pub fn get_typed_column(&self, column_name: &ColumnName) -> Option<(Column, DbType)> {
let column = self.get_column(column_name)?;
Some((column, self.column_type(column)))
}
pub fn get_type_at(&self, column_name: &ColumnName) -> Option<DbType> {
let position = self.get_column_position(column_name)?;
let position = self.get_column(column_name)?;
self.types.get(position).copied()
}
pub fn is_primary(&self, column: ColumnPosition) -> bool {
pub fn is_primary(&self, column: Column) -> bool {
self.primary_key == column
}
// Assumes `column` comes from a validated source.
pub fn column_name_from_column(&self, column: ColumnPosition) -> ColumnName {
pub fn column_name_from_column(&self, column: Column) -> ColumnName {
match self
.column_name_position_mapping
.get_by_right(&column)

View file

@ -4,7 +4,7 @@ use thiserror::Error;
use crate::syntax;
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}};
use minisql::{operation::Operation, type_system::Value, schema::{TableSchema, ColumnName, Column, TableName, TablePosition}, type_system::DbType, interpreter::DbSchema};
#[derive(Debug, Error)]
pub enum ValidationError {
@ -122,7 +122,7 @@ fn validate_select(table_name: TableName, column_selection: syntax::ColumnSelect
Err(ValidationError::ColumnsDoNotExist(non_existant_columns))
} else {
let selection: operation::ColumnSelection =
columns.iter().filter_map(|column_name| schema.get_column_position(column_name)).collect();
columns.iter().filter_map(|column_name| schema.get_column(column_name)).collect();
let validated_condition = validate_condition(condition, schema)?;
Ok(Operation::Select(table_position, selection, validated_condition))
}
@ -158,12 +158,12 @@ fn validate_insert(table_name: TableName, insertion_values: syntax::InsertionVal
}
// Check types and prepare for creation of InsertionValues for the interpreter
let mut values_map: BTreeMap<_, Value> = BTreeMap::new(); // The reason for using BTreeMap
let mut values_map: BTreeMap<Column, Value> = BTreeMap::new(); // The reason for using BTreeMap
// instead of HashMap is that we need
// to get the values in a vector
// sorted by the key.
for (column_name, value) in insertion_values {
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 (column, expected_type) = schema.get_typed_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();
if value_type != expected_type {
return Err(ValidationError::TypeMismatch { column_name: column_name.to_string(), received_type: value_type, expected_type });
@ -189,7 +189,7 @@ fn validate_condition(condition: Option<syntax::Condition>, schema: &TableSchema
Some(condition) => {
match condition {
syntax::Condition::Eq(column_name, value) => {
let (column, expected_type) = schema.get_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
let (column, expected_type) = schema.get_typed_column(&column_name).ok_or(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()]))?;
let value_type: DbType = value.to_type();
if expected_type.eq(&value_type) {
Ok(Some(operation::Condition::Eq(column, value)))
@ -206,7 +206,7 @@ fn validate_condition(condition: Option<syntax::Condition>, schema: &TableSchema
fn validate_create_index(table_name: TableName, column_name: ColumnName, db_schema: &DbSchema) -> Result<Operation, ValidationError> {
let (table_position, schema) = validate_table_exists(db_schema, &table_name)?;
schema
.get_column(&column_name)
.get_typed_column(&column_name)
.map_or_else(
|| Err(ValidationError::ColumnsDoNotExist(vec![column_name.to_string()])),
|(column, type_)| {

View file

@ -1,7 +1,7 @@
use async_trait::async_trait;
use minisql::restricted_row::RestrictedRow;
use minisql::schema::TableSchema;
use minisql::type_system::{Value};
use minisql::schema::{Column, TableSchema};
use minisql::type_system::Value;
use proto::message::backend::{BackendMessage, ColumnDescription, CommandCompleteData, DataRowData, ErrorResponseData, ReadyForQueryData, RowDescriptionData};
use proto::message::primitive::pglist::PgList;
use proto::writer::backend::BackendProtoWriter;
@ -62,7 +62,7 @@ impl<W> ServerProto for W where W: BackendProtoWriter + Send {
async fn write_table_header(&mut self, table_schema: &TableSchema, row: &RestrictedRow) -> anyhow::Result<()> {
let columns = row.iter()
.map(|(index, value)| value_to_column_description(table_schema, value, *index))
.map(|(column, value)| value_to_column_description(table_schema, value, *column))
.collect::<anyhow::Result<Vec<ColumnDescription>>>()?;
self.write_proto(RowDescriptionData { columns: columns.into() }.into()).await?;
@ -88,11 +88,11 @@ impl<W> ServerProto for W where W: BackendProtoWriter + Send {
}
}
fn value_to_column_description(schema: &TableSchema, value: &Value, index: usize) -> anyhow::Result<ColumnDescription> {
let name = schema.column_name_from_column(index);
fn value_to_column_description(schema: &TableSchema, value: &Value, column: Column) -> anyhow::Result<ColumnDescription> {
let name = schema.column_name_from_column(column);
let table_oid = schema.table_name().as_bytes().as_ptr() as i32;
let column_index = index.try_into()?;
let column_index = column.try_into()?;
let type_oid = value.type_oid();
let type_size = value.type_size();