Skip to content

Commit

Permalink
pk/fk query chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
giuseppe-g-gelardi committed Dec 23, 2024
1 parent 5cd3432 commit c68b81d
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 6 deletions.
5 changes: 5 additions & 0 deletions src/database_operations/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ impl Database {
operation: Operation::Create,
update_data: None,
row_data: None,
fk_constraints: None,
}
}

Expand All @@ -18,6 +19,7 @@ impl Database {
operation: Operation::Read,
update_data: None,
row_data: None,
fk_constraints: None,
}
}

Expand All @@ -28,6 +30,7 @@ impl Database {
operation: Operation::Read,
update_data: None,
row_data: None,
fk_constraints: None,
}
}

Expand All @@ -38,6 +41,7 @@ impl Database {
operation: Operation::Delete,
update_data: None,
row_data: None,
fk_constraints: None,
}
}

Expand All @@ -48,6 +52,7 @@ impl Database {
operation: Operation::Update,
update_data: None,
row_data: None,
fk_constraints: None,
}
}
}
Expand Down
12 changes: 7 additions & 5 deletions src/query_operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ pub struct Query {
pub operation: Operation,
pub update_data: Option<Value>,
pub row_data: Option<Value>,
pub fk_constraints: Option<Vec<ForeignKeyConstraint>>,
}


#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct ForeignKeyConstraint {
pub table_name: String,
pub column_name: String,
pub foreign_table_name: String,
pub foreign_column_name: String,
pub references_table: String,
pub references_column: String,
// pub table_name: String,
// pub column_name: String,
// pub foreign_table_name: String,
// pub foreign_column_name: String,
}

181 changes: 180 additions & 1 deletion src/query_operations/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,28 @@ use serde_json::Value;

use crate::{Database, DatabaseError, Operation, Query, Row, Table};

use super::ForeignKeyConstraint;

impl Query {
pub fn references(
mut self,
column_name: &str,
references_table: &str,
references_column: &str,
) -> Self {
let fk = ForeignKeyConstraint {
column_name: column_name.to_string(),
references_table: references_table.to_string(),
references_column: references_column.to_string(),
};

match &mut self.fk_constraints {
Some(constraits) => constraits.push(fk),
None => self.fk_constraints = Some(vec![fk]),
}
self
}

pub fn from(mut self, table_name: &str) -> Self {
self.table_name = Some(table_name.to_string());
self
Expand Down Expand Up @@ -78,14 +99,53 @@ impl Query {
.clone()
.ok_or_else(|| DatabaseError::InvalidData("Table name not specified.".to_string()))?;

// Step 1: Validate foreign key constraints (immutable borrow)
if let Some(fk_constraints) = &self.fk_constraints {
for fk in fk_constraints {
// Get the value of the foreign key column from the row data
let fk_value = self
.row_data
.as_ref()
.and_then(|data| data.get(&fk.column_name).and_then(|v| v.as_str()))
.ok_or_else(|| {
DatabaseError::InvalidData(format!(
"Missing value for foreign key column `{}`",
fk.column_name
))
})?;

// Validate that the referenced table and column exist
let referenced_table = db.tables.get(&fk.references_table).ok_or_else(|| {
DatabaseError::TableNotFound(format!(
"Referenced table `{}` not found",
fk.references_table
))
})?;

let fk_exists = referenced_table.rows.values().any(|row| {
row.data.get(&fk.references_column).and_then(|v| v.as_str()) == Some(fk_value)
});

if !fk_exists {
return Err(DatabaseError::InvalidData(format!(
"Foreign key constraint failed: `{}` does not exist in `{}`",
fk_value, fk.references_table
)));
}
}
}

// Step 2: Borrow the table mutably to validate and insert data
let table = db
.tables
.get_mut(&table_name)
.ok_or_else(|| DatabaseError::TableNotFound(table_name.clone()))?;

if let Some(row_data) = self.row_data.clone() {
// Validate the row data itself
table.columns.validate(row_data.clone())?;

// Add the row to the table
if let Some(row_id) = row_data.get("id").and_then(|id| id.as_str()) {
table.rows.insert(row_id.to_string(), Row::new(row_data));
} else {
Expand All @@ -94,6 +154,7 @@ impl Query {
));
}

// Save the database to the file
db.save_to_file().await.map_err(DatabaseError::SaveError)?;
Ok(())
} else {
Expand Down Expand Up @@ -256,7 +317,7 @@ mod tests {
use serde::Deserialize;
use serde_json::json;

use crate::setup_temp_db;
use crate::{setup_temp_db, Column, Columns};

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
struct TestData {
Expand All @@ -272,6 +333,7 @@ mod tests {
operation: Operation::Read,
update_data: None,
row_data: None,
fk_constraints: None,
};

let updated_query = query.from("TestTable");
Expand All @@ -286,6 +348,7 @@ mod tests {
operation: Operation::Update,
update_data: None,
row_data: None,
fk_constraints: None,
};

let data = json!({ "name": "Updated Name" });
Expand All @@ -303,6 +366,7 @@ mod tests {
operation: Operation::Create,
update_data: None,
row_data: None,
fk_constraints: None,
};

let test_data = TestData {
Expand Down Expand Up @@ -360,6 +424,7 @@ mod tests {
operation: Operation::Update,
update_data: None,
row_data: None,
fk_constraints: None,
};

let data = json!({ "name": "Updated Name" });
Expand Down Expand Up @@ -497,4 +562,118 @@ mod tests {
assert!(deleted_record.is_none(), "Expected record to be deleted");
assert!(rows.is_empty(), "Expected all records to be deleted");
}

#[tokio::test]
async fn test_query_initialization() {
let query = Query {
db_file_name: "test_db.json".into(),
table_name: None,
operation: Operation::Create,
fk_constraints: None,
update_data: None,
row_data: None,
};

assert_eq!(query.table_name, None);
assert_eq!(query.fk_constraints, None);
if let Operation::Create = query.operation {
assert_eq!(query.update_data, None);
} else {
panic!("Expected operation to be `Create`");
}
}

#[test]
fn test_query_references() {
let query = Query {
db_file_name: "test_db.json".into(),
table_name: Some("ChildTable".to_string()),
operation: Operation::Create,
update_data: None,
row_data: None,
fk_constraints: None,
};

let updated_query = query.references("child_id", "ParentTable", "parent_id");

let expected_fk = ForeignKeyConstraint {
column_name: "child_id".to_string(),
references_table: "ParentTable".to_string(),
references_column: "parent_id".to_string(),
};

assert!(updated_query.fk_constraints.is_some());
assert!(updated_query.fk_constraints.unwrap().contains(&expected_fk));
}

#[tokio::test]
async fn test_execute_add_with_fk_constraints() {
let mut db = setup_temp_db().await;

db.add_table(&mut Table::new(
"ParentTable".to_string(),
Columns::new(vec![
Column::new("parent_id", true),
Column::new("name", false),
]),
))
.await
.expect("Failed to add parent table");

db.add_table(&mut Table::new(
"ChildTable".to_string(),
Columns::new(vec![
Column::new("child_id", true),
Column::new("name", false),
Column::new("parent_id", false),
]),
))
.await
.expect("Failed to add child table");

// Add a parent table and row
let parent_row = json!({ "parent_id": "1", "name": "Parent Row" });
let result = db
.add_row()
.from("ParentTable")
.data(parent_row.clone())
.execute_add()
.await;

assert!(
result.is_ok(),
"Expected successful row insertion with valid FK"
);

// Add a child row referencing the parent
let child_row = json!({ "child_id": "1", "name": "Child Row", "parent_id": "1" });
let result = db
.add_row()
.from("ChildTable")
.data(child_row.clone())
.references("parent_id", "ParentTable", "parent_id")
.execute_add()
.await;

assert!(
result.is_ok(),
"Expected successful row insertion with valid FK"
);

// Attempt to add a child row with an invalid FK
let invalid_child_row =
json!({ "child_id": "2", "name": "Child Row 2", "parent_id": "99" });
let result = db
.add_row()
.from("ChildTable")
.data(invalid_child_row)
.references("parent_id", "ParentTable", "parent_id")
.execute_add()
.await;

assert!(
result.is_err(),
"Expected failure due to invalid foreign key reference"
);
}
}

0 comments on commit c68b81d

Please sign in to comment.