Skip to main content

pg2sqlite_core/pg/
normalize.rs

1/// Schema filtering and identifier normalization for parsed PG DDL.
2use crate::ir::SchemaModel;
3
4/// Options for schema normalization.
5pub struct NormalizeOptions {
6    /// Schema to include (default: "public").
7    pub schema: Option<String>,
8    /// If true, include all schemas (bypass schema filtering).
9    pub include_all_schemas: bool,
10}
11
12impl Default for NormalizeOptions {
13    fn default() -> Self {
14        Self {
15            schema: Some("public".to_string()),
16            include_all_schemas: false,
17        }
18    }
19}
20
21/// Filter and normalize the schema model based on options.
22pub fn normalize(model: &mut SchemaModel, opts: &NormalizeOptions) {
23    if opts.include_all_schemas {
24        return;
25    }
26
27    let target_schema = opts.schema.as_deref().unwrap_or("public");
28
29    // Filter tables by schema
30    model.tables.retain(|t| {
31        match &t.name.schema {
32            Some(s) => s.normalized == target_schema,
33            None => true, // Unqualified names are assumed to be in the target schema
34        }
35    });
36
37    // Filter indexes by table schema
38    model.indexes.retain(|idx| match &idx.table.schema {
39        Some(s) => s.normalized == target_schema,
40        None => true,
41    });
42
43    // Filter sequences by schema
44    model.sequences.retain(|seq| match &seq.name.schema {
45        Some(s) => s.normalized == target_schema,
46        None => true,
47    });
48
49    // Filter enums by schema
50    model.enums.retain(|e| match &e.name.schema {
51        Some(s) => s.normalized == target_schema,
52        None => true,
53    });
54
55    // Filter domains by schema
56    model.domains.retain(|d| match &d.name.schema {
57        Some(s) => s.normalized == target_schema,
58        None => true,
59    });
60
61    // Filter alter constraints by table schema
62    model.alter_constraints.retain(|ac| match &ac.table.schema {
63        Some(s) => s.normalized == target_schema,
64        None => true,
65    });
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::pg::parser;
72
73    #[test]
74    fn test_normalize_filters_schema() {
75        let sql = r#"
76            CREATE TABLE public.users (id INTEGER);
77            CREATE TABLE other.accounts (id INTEGER);
78        "#;
79        let (mut model, _) = parser::parse(sql);
80        assert_eq!(model.tables.len(), 2);
81
82        normalize(&mut model, &NormalizeOptions::default());
83        assert_eq!(model.tables.len(), 1);
84        assert_eq!(model.tables[0].name.name.normalized, "users");
85    }
86
87    #[test]
88    fn test_normalize_include_all_schemas() {
89        let sql = r#"
90            CREATE TABLE public.users (id INTEGER);
91            CREATE TABLE other.accounts (id INTEGER);
92        "#;
93        let (mut model, _) = parser::parse(sql);
94        normalize(
95            &mut model,
96            &NormalizeOptions {
97                schema: None,
98                include_all_schemas: true,
99            },
100        );
101        assert_eq!(model.tables.len(), 2);
102    }
103
104    #[test]
105    fn test_normalize_unqualified_passes() {
106        let sql = "CREATE TABLE users (id INTEGER);";
107        let (mut model, _) = parser::parse(sql);
108        normalize(&mut model, &NormalizeOptions::default());
109        assert_eq!(model.tables.len(), 1);
110    }
111
112    #[test]
113    fn test_normalize_custom_schema() {
114        let sql = r#"
115            CREATE TABLE myschema.users (id INTEGER);
116            CREATE TABLE public.accounts (id INTEGER);
117        "#;
118        let (mut model, _) = parser::parse(sql);
119        normalize(
120            &mut model,
121            &NormalizeOptions {
122                schema: Some("myschema".to_string()),
123                include_all_schemas: false,
124            },
125        );
126        assert_eq!(model.tables.len(), 1);
127        assert_eq!(model.tables[0].name.name.normalized, "users");
128    }
129}