Skip to main content

rupg_analyze/ddl/
schema_elements.rs

1//! The elements of `CREATE SCHEMA`, as `transformCreateSchemaStmtElements` checks and sorts them. The caller runs them after the statement makes the schema, as `CreateSchemaCommand` runs them as subcommands.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_sql::nodes::{
5    AlterTableCmd, AlterTableStmt, AlterTableType, ConstrType, Constraint, CreateStmt, Node,
6    ObjectType, RangeVar,
7};
8use rupg_sql::{Lexer, character, token};
9
10use super::table::constraint_attrs;
11use crate::coerce::AtOpt;
12use crate::typename::place;
13
14/// The elements of `CREATE SCHEMA`, which the caller runs after the statement made the schema.
15#[derive(Debug)]
16pub struct SchemaElements {
17    /// The owner of the schema. The elements run as this role.
18    pub owner: u32,
19    /// The name of the schema, which comes first in `search_path` while the elements run.
20    pub schema: String,
21    /// The statements in the order that they run, each with its part of the text of the statement.
22    pub elements: Vec<(Node, String)>,
23}
24
25/// `transformCreateSchemaStmtElements`: checks that the elements make their objects in the schema `schema` and sorts them so that an element comes after the objects that it refers to: sequences, tables, views, indexes, triggers, grants and then the foreign keys of the tables. `text` is the text of the statement.
26pub(super) fn schema_elements(
27    elements: &[Node],
28    schema: &str,
29    text: &str,
30) -> Result<Vec<(Node, String)>> {
31    let texts = element_texts(text, elements.len());
32    let mut groups: [Vec<(Node, String)>; 7] = Default::default();
33    for (element, text) in elements.iter().zip(texts) {
34        let (group, relation) = match element {
35            Node::CreateSeqStmt(stmt) => (0, stmt.sequence.as_deref()),
36            Node::CreateStmt(stmt) => (1, stmt.relation.as_deref()),
37            Node::ViewStmt(stmt) => (2, stmt.view.as_deref()),
38            Node::IndexStmt(stmt) => (3, stmt.relation.as_deref()),
39            Node::CreateTrigStmt(stmt) => (4, stmt.relation.as_deref()),
40            Node::GrantStmt(_) => (5, None),
41            _ => return Err(Error::internal("unrecognized node type in CREATE SCHEMA")),
42        };
43        if let Some(relation) = relation {
44            check_schema_name(relation, schema)?;
45        }
46        let element = match element {
47            Node::CreateStmt(stmt) => {
48                let mut keys = Vec::new();
49                let table = without_foreign_keys(stmt, &mut keys)?;
50                groups[6].extend(keys.into_iter().map(|key| (key, String::new())));
51                Node::CreateStmt(Box::new(table))
52            }
53            element => element.clone(),
54        };
55        groups[group].push((element, text));
56    }
57    Ok(groups.into_iter().flatten().collect())
58}
59
60/// `checkSchemaNameRV`: an element can name only the new schema, and it cannot make a temporary relation.
61fn check_schema_name(relation: &RangeVar, schema: &str) -> Result<()> {
62    let at = place(relation.location);
63    if let Some(name) = relation.schemaname.as_deref()
64        && name != schema
65    {
66        return Err(Error::new(
67            SqlState::INVALID_SCHEMA_DEFINITION,
68            format!(
69                "CREATE specifies a schema ({name}) different from the one being created ({schema})"
70            ),
71        )
72        .at_opt(at));
73    }
74    if relation.relpersistence == b't' {
75        return Err(Error::new(
76            SqlState::INVALID_TABLE_DEFINITION,
77            "cannot create temporary relation in non-temporary schema",
78        )
79        .at_opt(at));
80    }
81    Ok(())
82}
83
84/// `transformCreateSchemaCreateTable`: the table without its foreign keys. Each foreign key becomes an `ALTER TABLE ... ADD CONSTRAINT` in `keys`, so that it can refer to a table of a later element.
85fn without_foreign_keys(stmt: &CreateStmt, keys: &mut Vec<Node>) -> Result<CreateStmt> {
86    let add = |con: Constraint| {
87        Node::AlterTableStmt(Box::new(AlterTableStmt {
88            relation: stmt.relation.clone(),
89            cmds: vec![Some(Node::AlterTableCmd(Box::new(AlterTableCmd {
90                subtype: AlterTableType::AT_AddConstraint,
91                def: Some(Node::Constraint(Box::new(con))),
92                ..AlterTableCmd::default()
93            })))],
94            objtype: ObjectType::OBJECT_TABLE,
95            missing_ok: false,
96        }))
97    };
98    let mut table = stmt.clone();
99    table.tableElts.clear();
100    for element in &stmt.tableElts {
101        match element {
102            Some(Node::Constraint(con)) if con.contype == ConstrType::CONSTR_FOREIGN => {
103                keys.push(add((**con).clone()));
104            }
105            Some(Node::ColumnDef(def)) => {
106                let mut cons: Vec<Constraint> = def
107                    .constraints
108                    .iter()
109                    .flatten()
110                    .filter_map(|n| match n {
111                        Node::Constraint(c) => Some((**c).clone()),
112                        _ => None,
113                    })
114                    .collect();
115                constraint_attrs(&mut cons)?;
116                // The attributes after a foreign key go with it, as `transformConstraintAttrs` put them into the key.
117                let mut after_key = false;
118                let mut kept = Vec::new();
119                for mut con in cons {
120                    match con.contype {
121                        ConstrType::CONSTR_FOREIGN => {
122                            let name = def.colname.as_deref().unwrap_or_default();
123                            con.fk_attrs = vec![Some(Node::String(name.into()))];
124                            keys.push(add(con));
125                            after_key = true;
126                        }
127                        ConstrType::CONSTR_ATTR_DEFERRABLE
128                        | ConstrType::CONSTR_ATTR_NOT_DEFERRABLE
129                        | ConstrType::CONSTR_ATTR_DEFERRED
130                        | ConstrType::CONSTR_ATTR_IMMEDIATE
131                        | ConstrType::CONSTR_ATTR_ENFORCED
132                        | ConstrType::CONSTR_ATTR_NOT_ENFORCED => {
133                            if !after_key {
134                                kept.push(con);
135                            }
136                        }
137                        _ => {
138                            after_key = false;
139                            kept.push(con);
140                        }
141                    }
142                }
143                let mut def = (**def).clone();
144                def.constraints =
145                    kept.into_iter().map(|con| Some(Node::Constraint(Box::new(con)))).collect();
146                table.tableElts.push(Some(Node::ColumnDef(Box::new(def))));
147            }
148            element => table.tableElts.push(element.clone()),
149        }
150    }
151    Ok(table)
152}
153
154/// The part of `text` that each of the `count` elements of `CREATE SCHEMA` has. An element starts at `CREATE` or `GRANT` outside parentheses, but not at the first word, at a privilege `CREATE` after `GRANT` or a comma, or at the `GRANT` of `WITH GRANT OPTION`. When the words do not give `count` elements, each element has the whole text.
155fn element_texts(text: &str, count: usize) -> Vec<String> {
156    let (open, close, comma) = (character(b'('), character(b')'), character(b','));
157    let mut lexer = Lexer::new(text);
158    let mut starts = Vec::new();
159    let mut depth = 0usize;
160    let mut previous = None;
161    while let Ok(word) = lexer.next_token() {
162        let kind = Some(word.kind);
163        if word.kind == 0 {
164            break;
165        } else if kind == open {
166            depth += 1;
167        } else if kind == close {
168            depth = depth.saturating_sub(1);
169        } else if depth == 0 && previous.is_some() {
170            let create =
171                word.kind == token::CREATE && previous != Some(token::GRANT) && previous != comma;
172            let grant = word.kind == token::GRANT && previous != Some(token::WITH);
173            if create || grant {
174                starts.push(word.start);
175            }
176        }
177        previous = kind;
178    }
179    if starts.len() != count {
180        return vec![text.to_string(); count];
181    }
182    let ends = starts.iter().skip(1).copied().chain([text.len()]);
183    starts.iter().zip(ends).map(|(&start, end)| text[start..end].trim_end().to_string()).collect()
184}