1use anyhow::{bail, Context, Result};
4use flowscope_core::{
5 analyze, AnalyzeRequest, ColumnSchema, Dialect, FileSource, SchemaMetadata, SchemaTable,
6 Severity,
7};
8use std::path::Path;
9
10pub fn load_schema_from_ddl(path: &Path, dialect: Dialect) -> Result<SchemaMetadata> {
14 let content = std::fs::read_to_string(path)
15 .with_context(|| format!("Failed to read schema file: {}", path.display()))?;
16
17 parse_schema_ddl(&content, dialect)
18}
19
20fn parse_schema_ddl(content: &str, dialect: Dialect) -> Result<SchemaMetadata> {
22 let request = AnalyzeRequest {
23 sql: String::new(),
24 files: Some(vec![FileSource {
25 name: "schema.sql".to_string(),
26 content: content.to_string(),
27 }]),
28 dialect,
29 source_name: None,
30 options: None,
31 schema: Some(SchemaMetadata {
32 allow_implied: true,
33 ..Default::default()
34 }),
35 #[cfg(feature = "templating")]
36 template_config: None,
37 };
38
39 let result = analyze(&request);
40
41 if result.summary.has_errors {
43 let error_messages: Vec<String> = result
44 .issues
45 .iter()
46 .filter(|issue| issue.severity == Severity::Error)
47 .map(|issue| format!("[{}] {}", issue.code, issue.message))
48 .collect();
49
50 bail!("Failed to parse schema DDL:\n{}", error_messages.join("\n"));
51 }
52
53 let resolved = result
55 .resolved_schema
56 .context("Schema DDL produced no table definitions")?;
57
58 let schema = SchemaMetadata {
59 default_catalog: None,
60 default_schema: None,
61 search_path: None,
62 case_sensitivity: None,
63 tables: resolved
64 .tables
65 .into_iter()
66 .map(|t| SchemaTable {
67 catalog: t.catalog,
68 schema: t.schema,
69 name: t.name,
70 columns: t
71 .columns
72 .into_iter()
73 .map(|c| ColumnSchema {
74 name: c.name,
75 data_type: c.data_type,
76 is_primary_key: c.is_primary_key,
77 foreign_key: c.foreign_key,
78 })
79 .collect(),
80 })
81 .collect(),
82 allow_implied: false, };
84
85 Ok(schema)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_parse_simple_ddl() {
94 let ddl = r#"
95 CREATE TABLE users (
96 id INT,
97 name VARCHAR(100),
98 email VARCHAR(255)
99 );
100 "#;
101
102 let schema = parse_schema_ddl(ddl, Dialect::Generic).unwrap();
103 assert!(!schema.tables.is_empty());
104
105 let users_table = schema.tables.iter().find(|t| t.name == "users");
106 assert!(users_table.is_some());
107
108 let table = users_table.unwrap();
109 assert_eq!(table.columns.len(), 3);
110 }
111
112 #[test]
113 fn test_parse_multiple_tables() {
114 let ddl = r#"
115 CREATE TABLE users (id INT, name VARCHAR);
116 CREATE TABLE orders (id INT, user_id INT, total DECIMAL);
117 "#;
118
119 let schema = parse_schema_ddl(ddl, Dialect::Generic).unwrap();
120 assert!(schema.tables.len() >= 2);
121 }
122
123 #[test]
124 fn test_parse_invalid_ddl_returns_error() {
125 let ddl = "THIS IS NOT VALID SQL AT ALL ;;;";
126
127 let result = parse_schema_ddl(ddl, Dialect::Generic);
128 assert!(result.is_err());
129
130 let err_msg = result.unwrap_err().to_string();
131 assert!(
132 err_msg.contains("Failed to parse schema DDL"),
133 "Expected error message to mention parsing failure, got: {}",
134 err_msg
135 );
136 }
137}