Rename column_position ~> column variables, and introduce runtime error AttemptToIndexAlreadyIndexedColumn

This commit is contained in:
Yuriy Dupyn 2024-01-28 15:27:03 +01:00
parent 052236d892
commit 12c91ce70e
5 changed files with 71 additions and 38 deletions

View file

@ -10,6 +10,8 @@ pub enum RuntimeError {
AttemptingToInsertAlreadyPresentId(TableName, Uuid), AttemptingToInsertAlreadyPresentId(TableName, Uuid),
#[error("table {0} cannot be indexed on column {1}")] #[error("table {0} cannot be indexed on column {1}")]
AttemptToIndexNonIndexableColumn(TableName, ColumnName), AttemptToIndexNonIndexableColumn(TableName, ColumnName),
#[error("table {0} already indexes column {1}")]
AttemptToIndexAlreadyIndexedColumn(TableName, ColumnName),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -60,16 +60,16 @@ impl Row {
self.0.len() self.0.len()
} }
pub fn get(&self, column_position: ColumnPosition) -> Option<&Value> { pub fn get(&self, column: ColumnPosition) -> Option<&Value> {
self.0.get(column_position) self.0.get(column)
} }
pub fn restrict_columns(&self, columns: &Vec<ColumnPosition>) -> RestrictedRow { pub fn restrict_columns(&self, columns: &Vec<ColumnPosition>) -> RestrictedRow {
// If the index from `columns` is non-existant in `row`, it will just ignore it. // 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<(ColumnPosition, Value)> = vec![];
for column_position in columns { for column in columns {
if let Some(value) = self.get(*column_position) { if let Some(value) = self.get(*column) {
subrow.push((*column_position, value.clone())); subrow.push((*column, value.clone()));
} }
} }

View file

@ -54,12 +54,12 @@ impl Table {
.collect() .collect()
} }
fn get_rows_by_value(&self, column_position: ColumnPosition, value: &Value) -> Vec<Row> { fn get_rows_by_value(&self, column: ColumnPosition, value: &Value) -> Vec<Row> {
// brute-force search // brute-force search
self.rows self.rows
.values() .values()
.filter_map(|row| { .filter_map(|row| {
if row.get(column_position) == Some(value) { if row.get(column) == Some(value) {
Some(row.clone()) Some(row.clone())
} else { } else {
None None
@ -68,21 +68,21 @@ impl Table {
.collect() .collect()
} }
pub fn select_all_rows<'a>(&'a self, selected_column_positions: Vec<ColumnPosition>) -> impl Iterator<Item=RestrictedRow> + 'a { pub fn select_all_rows<'a>(&'a self, selected_columns: Vec<ColumnPosition>) -> impl Iterator<Item=RestrictedRow> + 'a {
self.rows self.rows
.values() .values()
.map(move |row| row.restrict_columns(&selected_column_positions)) .map(move |row| row.restrict_columns(&selected_columns))
} }
pub fn select_rows_where_eq<'a>( pub fn select_rows_where_eq<'a>(
&'a self, &'a self,
selected_column_positions: Vec<ColumnPosition>, selected_columns: Vec<ColumnPosition>,
column_position: ColumnPosition, column: ColumnPosition,
value: Value, value: Value,
) -> DbResult<impl Iterator<Item=RestrictedRow> + 'a> { ) -> DbResult<impl Iterator<Item=RestrictedRow> + 'a> {
let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_column_positions); let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_columns);
match value { match value {
Value::Indexable(value) => match self.fetch_ids_from_index(column_position, &value)? { Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? {
Some(ids) => Some(ids) =>
Ok(self Ok(self
.get_rows_by_ids(ids) .get_rows_by_ids(ids)
@ -91,14 +91,14 @@ impl Table {
), ),
None => None =>
Ok(self Ok(self
.get_rows_by_value(column_position, &Value::Indexable(value)) .get_rows_by_value(column, &Value::Indexable(value))
.into_iter() .into_iter()
.map(restrict_columns_of_row) .map(restrict_columns_of_row)
), ),
}, },
_ => _ =>
Ok(self Ok(self
.get_rows_by_value(column_position, &value) .get_rows_by_value(column, &value)
.into_iter() .into_iter()
.map(restrict_columns_of_row) .map(restrict_columns_of_row)
), ),
@ -129,8 +129,8 @@ impl Table {
fn delete_row_by_id(&mut self, id: Uuid) -> usize { fn delete_row_by_id(&mut self, id: Uuid) -> usize {
match self.rows.remove(&id) { match self.rows.remove(&id) {
Some(row) => { Some(row) => {
for (column_position, column_index) in &mut self.indexes { for (column, column_index) in &mut self.indexes {
if let Value::Indexable(value) = &row[*column_position] { if let Value::Indexable(value) = &row[*column] {
let _ = column_index.remove(value, id); let _ = column_index.remove(value, id);
}; };
} }
@ -148,12 +148,12 @@ impl Table {
total_count total_count
} }
fn delete_rows_by_value(&mut self, column_position: ColumnPosition, value: &Value) -> usize { fn delete_rows_by_value(&mut self, column: ColumnPosition, value: &Value) -> usize {
let matched_ids: HashSet<Uuid> = self let matched_ids: HashSet<Uuid> = self
.rows .rows
.iter() .iter()
.filter_map(|(id, row)| { .filter_map(|(id, row)| {
if row.get(column_position) == Some(value) { if row.get(column) == Some(value) {
Some(*id) Some(*id)
} else { } else {
None None
@ -172,38 +172,43 @@ impl Table {
pub fn delete_rows_where_eq( pub fn delete_rows_where_eq(
&mut self, &mut self,
column_position: ColumnPosition, column: ColumnPosition,
value: Value, value: Value,
) -> DbResult<usize> { ) -> DbResult<usize> {
match value { match value {
Value::Indexable(value) => match self.fetch_ids_from_index(column_position, &value)? { Value::Indexable(value) => match self.fetch_ids_from_index(column, &value)? {
Some(ids) => Ok(self.delete_rows_by_ids(ids)), Some(ids) => Ok(self.delete_rows_by_ids(ids)),
None => Ok(self.delete_rows_by_value(column_position, &Value::Indexable(value))), None => Ok(self.delete_rows_by_value(column, &Value::Indexable(value))),
}, },
_ => Ok(self.delete_rows_by_value(column_position, &value)), _ => Ok(self.delete_rows_by_value(column, &value)),
} }
} }
// ======Indexing====== // ======Indexing======
pub fn attach_index(&mut self, column_position: ColumnPosition) -> DbResult<()> { pub fn attach_index(&mut self, column: ColumnPosition) -> 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();
return Err(RuntimeError::AttemptToIndexAlreadyIndexedColumn(table_name, column_name))
}
let mut column_index: ColumnIndex = ColumnIndex::new(); let mut column_index: ColumnIndex = ColumnIndex::new();
update_index_from_table(&mut column_index, self, column_position)?; update_index_from_table(&mut column_index, self, column)?;
self.indexes.insert(column_position, column_index); self.indexes.insert(column, column_index);
Ok(()) Ok(())
} }
fn fetch_ids_from_index( fn fetch_ids_from_index(
&self, &self,
column_position: ColumnPosition, column: ColumnPosition,
value: &IndexableValue, value: &IndexableValue,
) -> DbResult<Option<HashSet<Uuid>>> { ) -> DbResult<Option<HashSet<Uuid>>> {
if self.schema.is_primary(column_position) { if self.schema.is_primary(column) {
match value { match value {
IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))), IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))),
_ => unreachable!() // SAFETY: Validation guarantees primary column has correct Uuid type. _ => unreachable!() // SAFETY: Validation guarantees primary column has correct Uuid type.
} }
} else { } else {
match self.indexes.get(&column_position) { match self.indexes.get(&column) {
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.
// Theoretically it would be possible to return a reference, // Theoretically it would be possible to return a reference,

View file

@ -20,8 +20,8 @@ pub type ColumnName = String;
impl TableSchema { impl TableSchema {
pub fn new(table_name: TableName, primary_key: ColumnPosition, column_name_position_map: Vec<(ColumnName, ColumnPosition)>, types: Vec<DbType>) -> Self { pub fn new(table_name: TableName, primary_key: ColumnPosition, column_name_position_map: Vec<(ColumnName, ColumnPosition)>, types: Vec<DbType>) -> Self {
let mut column_name_position_mapping: BiMap<ColumnName, ColumnPosition> = BiMap::new(); let mut column_name_position_mapping: BiMap<ColumnName, ColumnPosition> = BiMap::new();
for (column_name, column_position) in column_name_position_map { for (column_name, column) in column_name_position_map {
column_name_position_mapping.insert(column_name, column_position); column_name_position_mapping.insert(column_name, column);
} }
Self { table_name, primary_key, column_name_position_mapping, types } Self { table_name, primary_key, column_name_position_mapping, types }
} }
@ -30,8 +30,8 @@ impl TableSchema {
&self.table_name &self.table_name
} }
pub fn column_type(&self, column_position: ColumnPosition) -> DbType { pub fn column_type(&self, column: ColumnPosition) -> DbType {
self.types[column_position] self.types[column]
} }
pub fn get_columns(&self) -> Vec<&ColumnName> { pub fn get_columns(&self) -> Vec<&ColumnName> {
@ -62,8 +62,8 @@ impl TableSchema {
self.types.get(position).copied() self.types.get(position).copied()
} }
pub fn is_primary(&self, column_position: ColumnPosition) -> bool { pub fn is_primary(&self, column: ColumnPosition) -> bool {
self.primary_key == column_position self.primary_key == column
} }
// Assumes `column` comes from a validated source. // Assumes `column` comes from a validated source.

View file

@ -479,13 +479,39 @@ mod tests {
let result = validate_operation(syntax, &db_schema); let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::Delete(_, Some(operation::Condition::Eq(_, _)))))); assert!(matches!(result, Ok(Operation::Delete(_, Some(operation::Condition::Eq(_, _))))));
let Ok(Operation::Delete(table_position, Some(operation::Condition::Eq(column_position, value)))) = result else { panic!() }; let Ok(Operation::Delete(table_position, Some(operation::Condition::Eq(column, value)))) = result else { panic!() };
assert!(table_position == users_position); assert!(table_position == users_position);
assert!(column_position == age); assert!(column == age);
assert!(value == Indexable(Int(25))); assert!(value == Indexable(Int(25)));
// assert!(condition == None);
} }
// ====CreateIndex==== // ====CreateIndex====
#[test]
fn test_create_index() {
let users_schema: TableSchema = users_schema();
let db_schema: DbSchema = db_schema(&users_schema);
let users_position = 0;
let age = 2;
let syntax: RawQuerySyntax = CreateIndex("users".to_string(), "age".to_string());
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Ok(Operation::CreateIndex(_, _))));
let Ok(Operation::CreateIndex(table_position, column)) = result else { panic!() };
assert!(table_position == users_position);
assert!(column == age);
}
#[test]
fn test_create_index_nonexistent_column() {
let users_schema: TableSchema = users_schema();
let db_schema: DbSchema = db_schema(&users_schema);
let syntax: RawQuerySyntax = CreateIndex("users".to_string(), "does_not_exist".to_string());
let result = validate_operation(syntax, &db_schema);
assert!(matches!(result, Err(ValidationError::ColumnsDoNotExist(_))));
}
} }