Skip to main content

f_ck/dsl/
query.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
6pub struct QueryPlan {
7    pub sources: Vec<Source>,
8    pub destination_schema: Vec<DestinationField>,
9    pub primary_keys: PrimaryKeySpec,
10    pub mappings: Vec<Mapping>,
11}
12
13#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
14pub struct Source {
15    pub id: String,
16    pub path: PathBuf,
17    pub format: String,
18}
19
20#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
21pub struct DestinationField {
22    pub name: String,
23    pub data_type: String,
24}
25
26#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
27#[serde(rename_all = "camelCase")]
28pub enum PrimaryKeyLogic { 
29    Or, 
30    And 
31}
32
33#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
34pub struct PrimaryKeySpec {
35    pub logic: PrimaryKeyLogic,
36    pub keys: Vec<String>,
37}
38
39#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
40pub struct Mapping {
41    pub destination_field: String,
42    pub policy: MergePolicy,
43    pub source_fields: Vec<SourceFieldSpec>,
44}
45
46#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
47pub struct SourceFieldSpec {
48    pub id: String,
49    pub source_file_id: String,
50    pub column_name: String,
51}
52
53#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
54#[serde(tag = "type")]
55#[serde(rename_all = "camelCase")]
56pub enum MergePolicy {
57    FirstMatch { priority: Vec<String> },
58    Sum,
59    Count,
60    Average,
61    Min,
62    Max,
63}
64
65impl QueryPlan {
66    pub fn from_json(json: &str) -> anyhow::Result<Self> {
67        Ok(serde_json::from_str(json)?)
68    }
69
70    pub fn to_json(&self) -> anyhow::Result<String> {
71        Ok(serde_json::to_string_pretty(self)?)
72    }
73
74    /// Generate JSON schema for QueryPlan validation
75    pub fn json_schema() -> anyhow::Result<String> {
76        let schema = schemars::schema_for!(QueryPlan);
77        Ok(serde_json::to_string_pretty(&schema)?)
78    }
79
80    pub fn validate(&self) -> anyhow::Result<()> {
81        if self.sources.is_empty() {
82            return Err(anyhow::anyhow!("QueryPlan must have at least one data source"));
83        }
84
85        if self.mappings.is_empty() {
86            return Err(anyhow::anyhow!("QueryPlan must have at least one field mapping"));
87        }
88
89        if self.primary_keys.keys.is_empty() {
90            return Err(anyhow::anyhow!("QueryPlan must have at least one primary key"));
91        }
92
93        // Validate that source IDs referenced in mappings exist
94        let source_ids: std::collections::HashSet<_> = 
95            self.sources.iter().map(|s| &s.id).collect();
96        
97        for mapping in &self.mappings {
98            for source_field in &mapping.source_fields {
99                if !source_ids.contains(&source_field.source_file_id) {
100                    return Err(anyhow::anyhow!(
101                        "Mapping references unknown source: {}", 
102                        source_field.source_file_id
103                    ));
104                }
105            }
106        }
107
108        // Validate that destination fields referenced in primary keys exist
109        let dest_field_names: std::collections::HashSet<_> = 
110            self.destination_schema.iter().map(|f| &f.name).collect();
111        
112        for key in &self.primary_keys.keys {
113            if !dest_field_names.contains(key) {
114                return Err(anyhow::anyhow!(
115                    "Primary key references unknown destination field: {}", 
116                    key
117                ));
118            }
119        }
120
121        Ok(())
122    }
123}