fraiseql_core/schema/compiled/
fact_table_links.rs1use super::CompiledSchema;
11use crate::{
12 compiler::fact_table::FactTableMetadata,
13 schema::{FieldDefinition, TypeDefinition},
14};
15
16pub fn fact_field<'a>(type_def: &'a TypeDefinition, name: &str) -> Option<&'a FieldDefinition> {
20 let key = crate::utils::to_snake_case(name);
21 type_def
22 .fields
23 .iter()
24 .find(|f| crate::utils::to_snake_case(f.name.as_str()) == key)
25}
26
27fn declared_names(metadata: &FactTableMetadata) -> Vec<&str> {
29 let mut names: Vec<&str> = metadata
30 .measures
31 .iter()
32 .map(|m| m.name.as_str())
33 .chain(metadata.denormalized_filters.iter().map(|f| f.name.as_str()))
34 .chain(metadata.dimensions.paths.iter().map(|p| p.name.as_str()))
35 .chain(metadata.calendar_dimensions.iter().map(|c| c.source_column.as_str()))
36 .chain(metadata.native_measures.keys().map(String::as_str))
37 .chain(metadata.native_dimension_mapping.keys().map(String::as_str))
38 .collect();
39 names.sort_unstable();
40 names.dedup();
41 names
42}
43
44impl CompiledSchema {
45 #[must_use]
47 pub fn fact_table_type(&self, metadata: &FactTableMetadata) -> Option<&TypeDefinition> {
48 let name = metadata.type_name.as_deref()?;
49 self.types.iter().find(|t| t.name == name)
50 }
51
52 #[must_use]
56 pub fn fact_table_link_violations(&self) -> Vec<String> {
57 let mut tables: Vec<&FactTableMetadata> = self.fact_tables.values().collect();
58 tables.sort_by(|a, b| a.table_name.cmp(&b.table_name));
59 tables
60 .into_iter()
61 .filter_map(|metadata| {
62 let type_name = metadata.type_name.as_deref()?;
63 let Some(type_def) = self.fact_table_type(metadata) else {
64 return Some(format!(
65 "fact table '{}' is read as type '{type_name}', which the schema does \
66 not declare",
67 metadata.table_name
68 ));
69 };
70 let missing: Vec<&str> = declared_names(metadata)
71 .into_iter()
72 .filter(|name| fact_field(type_def, name).is_none())
73 .collect();
74 (!missing.is_empty()).then(|| {
75 format!(
76 "fact table '{}' is read as type '{type_name}', which has no field for \
77 {}: a column the type does not declare is one no read gate can reach. \
78 Declare the fields on '{type_name}', or drop them from the fact table.",
79 metadata.table_name,
80 missing.iter().map(|n| format!("'{n}'")).collect::<Vec<_>>().join(", ")
81 )
82 })
83 })
84 .collect()
85 }
86}