diff --git a/common/build.rs b/common/build.rs index 094cd8c..20be794 100644 --- a/common/build.rs +++ b/common/build.rs @@ -45,6 +45,14 @@ fn main() -> Result<(), Box> { ".komp_ac.table_definition.TableDefinitionResponse", "#[derive(serde::Serialize, serde::Deserialize)]" ) + .type_attribute( + ".komp_ac.table_script.PostTableScriptRequest", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) + .type_attribute( + ".komp_ac.table_script.TableScriptResponse", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) .compile_protos( &[ "proto/common.proto", diff --git a/common/src/proto/komp_ac.table_script.rs b/common/src/proto/komp_ac.table_script.rs index e832650..d6901c9 100644 --- a/common/src/proto/komp_ac.table_script.rs +++ b/common/src/proto/komp_ac.table_script.rs @@ -1,4 +1,5 @@ // This file is @generated by prost-build. +#[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PostTableScriptRequest { #[prost(int64, tag = "1")] @@ -10,6 +11,7 @@ pub struct PostTableScriptRequest { #[prost(string, tag = "4")] pub description: ::prost::alloc::string::String, } +#[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TableScriptResponse { #[prost(int64, tag = "1")] diff --git a/server/src/table_script/handlers/dependency_analyzer.rs b/server/src/table_script/handlers/dependency_analyzer.rs index ab4efa2..dd82f5e 100644 --- a/server/src/table_script/handlers/dependency_analyzer.rs +++ b/server/src/table_script/handlers/dependency_analyzer.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use tonic::Status; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; /// Represents the state of a node during dependency graph traversal. @@ -40,18 +41,38 @@ impl DependencyType { DependencyType::SqlQuery { .. } => "sql_query", } } +} - /// Generates context JSON for database storage. - pub fn context_json(&self) -> Value { +/// Strongly-typed JSON for script_dependencies.context_info +/// Using untagged so JSON stays minimal (no "type" field), and we can still +/// deserialize it into a proper enum. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ScriptDependencyContext { + ColumnAccess { column: String }, + IndexedAccess { column: String, index: i64 }, + SqlQuery { query_fragment: String }, +} + +impl DependencyType { + /// Convert this dependency into its JSON context struct. + pub fn to_context(&self) -> ScriptDependencyContext { match self { DependencyType::ColumnAccess { column } => { - json!({ "column": column }) + ScriptDependencyContext::ColumnAccess { + column: column.clone(), + } } DependencyType::IndexedAccess { column, index } => { - json!({ "column": column, "index": index }) + ScriptDependencyContext::IndexedAccess { + column: column.clone(), + index: *index, + } } DependencyType::SqlQuery { query_fragment } => { - json!({ "query_fragment": query_fragment }) + ScriptDependencyContext::SqlQuery { + query_fragment: query_fragment.clone(), + } } } } @@ -554,7 +575,7 @@ impl DependencyAnalyzer { table_id, target_id, dep.dependency_type.as_str(), - dep.dependency_type.context_json() + serde_json::to_value(dep.dependency_type.to_context()).unwrap() ) .execute(&mut **tx) .await diff --git a/server/src/table_script/mod.rs b/server/src/table_script/mod.rs index 4d35650..9e8f918 100644 --- a/server/src/table_script/mod.rs +++ b/server/src/table_script/mod.rs @@ -1,4 +1,7 @@ // src/table_script/mod.rs + pub mod handlers; +pub mod repo; pub use handlers::*; +pub use repo::*; diff --git a/server/src/table_script/repo.rs b/server/src/table_script/repo.rs new file mode 100644 index 0000000..25adb67 --- /dev/null +++ b/server/src/table_script/repo.rs @@ -0,0 +1,49 @@ +// src/table_script/repo.rs +use anyhow::Result; +use sqlx::PgPool; + +use crate::table_script::handlers::dependency_analyzer::ScriptDependencyContext; + +#[derive(Debug, Clone)] +pub struct ScriptDependencyRecord { + pub script_id: i64, + pub source_table_id: i64, + pub target_table_id: i64, + pub dependency_type: String, + pub context: Option, +} + +pub async fn get_dependencies_for_script( + db: &PgPool, + script_id: i64, +) -> Result> { + let rows = sqlx::query!( + r#" + SELECT script_id, source_table_id, target_table_id, dependency_type, context_info + FROM script_dependencies + WHERE script_id = $1 + ORDER BY source_table_id, target_table_id + "#, + script_id + ) + .fetch_all(db) + .await?; + + let mut out = Vec::new(); + for r in rows { + let context = match r.context_info { + Some(value) => Some(serde_json::from_value::(value)?), + None => None, + }; + + out.push(ScriptDependencyRecord { + script_id: r.script_id, + source_table_id: r.source_table_id, + target_table_id: r.target_table_id, + dependency_type: r.dependency_type, + context, + }); + } + + Ok(out) +}