1use crate::object_id::ObjectId;
2use crate::postgres_client_wrapper::FromPgChar;
3use crate::quoting::AttemptedKeywordUsage::ColumnName;
4use crate::quoting::{quote_value_string, IdentifierQuoter, Quotable, QuotableIter};
5use crate::{ElefantToolsError, PostgresSchema, PostgresTable};
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8use std::cmp::Ordering;
9use std::str::FromStr;
10
11#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
12pub struct PostgresForeignKey {
13 pub name: String,
14 pub columns: Vec<PostgresForeignKeyColumn>,
15 pub referenced_schema: Option<String>,
16 pub referenced_table: String,
17 pub referenced_columns: Vec<PostgresForeignKeyReferencedColumn>,
18 pub update_action: ReferenceAction,
19 pub delete_action: ReferenceAction,
20 pub comment: Option<String>,
21 pub is_enforced: bool,
22 pub constraint_definition: Option<String>,
24 pub object_id: ObjectId,
25}
26
27impl Default for PostgresForeignKey {
28 fn default() -> Self {
29 Self {
30 name: String::new(),
31 columns: Vec::new(),
32 referenced_schema: None,
33 referenced_table: String::new(),
34 referenced_columns: Vec::new(),
35 update_action: ReferenceAction::NoAction,
36 delete_action: ReferenceAction::NoAction,
37 comment: None,
38 is_enforced: true,
39 constraint_definition: None,
40 object_id: ObjectId::default(),
41 }
42 }
43}
44
45impl PostgresForeignKey {
46 pub fn get_create_statement(
47 &self,
48 table: &PostgresTable,
49 schema: &PostgresSchema,
50 identifier_quoter: &IdentifierQuoter,
51 ) -> String {
52 if let Some(ref constraint_def) = self.constraint_definition {
53 let mut sql = format!(
54 "alter table {}.{} add constraint {} {} not valid;",
55 schema.name.quote(identifier_quoter, ColumnName),
56 table.name.quote(identifier_quoter, ColumnName),
57 self.name.quote(identifier_quoter, ColumnName),
58 constraint_def
59 );
60
61 if let Some(comment) = &self.comment {
62 sql.push_str("\ncomment on constraint ");
63 sql.push_str(&self.name.quote(identifier_quoter, ColumnName));
64 sql.push_str(" on ");
65 sql.push_str(&schema.name.quote(identifier_quoter, ColumnName));
66 sql.push('.');
67 sql.push_str(&table.name.quote(identifier_quoter, ColumnName));
68 sql.push_str(" is ");
69 sql.push_str("e_value_string(comment));
70 sql.push(';');
71 }
72
73 return sql;
74 }
75
76 let mut sql = format!(
77 "alter table {}.{} add constraint {} foreign key (",
78 schema.name.quote(identifier_quoter, ColumnName),
79 table.name.quote(identifier_quoter, ColumnName),
80 self.name.quote(identifier_quoter, ColumnName)
81 );
82
83 let columns = self
84 .columns
85 .iter()
86 .sorted_by_key(|c| c.ordinal_position)
87 .map(|c| c.name.as_str())
88 .quote(identifier_quoter, ColumnName)
89 .join(", ");
90
91 sql.push_str(&columns);
92 sql.push_str(") references ");
93 let referenced_schema = self.referenced_schema.as_ref().unwrap_or(&schema.name);
94 sql.push_str(&referenced_schema.quote(identifier_quoter, ColumnName));
95 sql.push('.');
96 sql.push_str(&self.referenced_table.quote(identifier_quoter, ColumnName));
97 sql.push_str(" (");
98
99 let referenced_columns = self
100 .referenced_columns
101 .iter()
102 .sorted_by_key(|c| c.ordinal_position)
103 .map(|c| c.name.as_str())
104 .quote(identifier_quoter, ColumnName)
105 .join(", ");
106
107 sql.push_str(&referenced_columns);
108 sql.push(')');
109
110 if self.update_action != ReferenceAction::NoAction {
111 sql.push_str(" on update ");
112 sql.push_str(match self.update_action {
113 ReferenceAction::NoAction => unreachable!(),
114 ReferenceAction::Restrict => "restrict",
115 ReferenceAction::Cascade => "cascade",
116 ReferenceAction::SetNull => "set null",
117 ReferenceAction::SetDefault => "set default",
118 });
119 }
120
121 if self.delete_action != ReferenceAction::NoAction {
122 sql.push_str(" on delete ");
123 sql.push_str(match self.delete_action {
124 ReferenceAction::NoAction => unreachable!(),
125 ReferenceAction::Restrict => "restrict",
126 ReferenceAction::Cascade => "cascade",
127 ReferenceAction::SetNull => "set null",
128 ReferenceAction::SetDefault => "set default",
129 });
130 }
131
132 if self.columns.iter().any(|c| !c.affected_by_delete_action) {
133 let affected_columns = self
134 .columns
135 .iter()
136 .filter(|c| c.affected_by_delete_action)
137 .map(|c| c.name.as_str())
138 .quote(identifier_quoter, ColumnName)
139 .join(", ");
140
141 sql.push('(');
142 sql.push_str(&affected_columns);
143 sql.push(')');
144 }
145
146 if !self.is_enforced {
147 sql.push_str(" not enforced");
148 }
149
150 sql.push_str(" not valid");
151
152 sql.push(';');
153
154 if let Some(comment) = &self.comment {
155 sql.push_str("\ncomment on constraint ");
156 sql.push_str(&self.name.quote(identifier_quoter, ColumnName));
157 sql.push_str(" on ");
158 sql.push_str(&schema.name.quote(identifier_quoter, ColumnName));
159 sql.push('.');
160 sql.push_str(&table.name.quote(identifier_quoter, ColumnName));
161 sql.push_str(" is ");
162 sql.push_str("e_value_string(comment));
163 sql.push(';');
164 }
165
166 sql
167 }
168}
169
170impl Ord for PostgresForeignKey {
171 fn cmp(&self, other: &Self) -> Ordering {
172 self.name.cmp(&other.name)
173 }
174}
175
176impl PartialOrd for PostgresForeignKey {
177 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
178 Some(self.cmp(other))
179 }
180}
181
182#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
183pub struct PostgresForeignKeyColumn {
184 pub name: String,
185 pub ordinal_position: i32,
186 pub affected_by_delete_action: bool,
187}
188
189impl Ord for PostgresForeignKeyColumn {
190 fn cmp(&self, other: &Self) -> Ordering {
191 self.ordinal_position.cmp(&other.ordinal_position)
192 }
193}
194
195impl PartialOrd for PostgresForeignKeyColumn {
196 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
197 Some(self.cmp(other))
198 }
199}
200
201#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
202pub struct PostgresForeignKeyReferencedColumn {
203 pub name: String,
204 pub ordinal_position: i32,
205}
206
207impl Ord for PostgresForeignKeyReferencedColumn {
208 fn cmp(&self, other: &Self) -> Ordering {
209 self.ordinal_position.cmp(&other.ordinal_position)
210 }
211}
212
213impl PartialOrd for PostgresForeignKeyReferencedColumn {
214 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
215 Some(self.cmp(other))
216 }
217}
218
219#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
220pub enum ReferenceAction {
221 NoAction,
222 Restrict,
223 Cascade,
224 SetNull,
225 SetDefault,
226}
227
228impl FromStr for ReferenceAction {
229 type Err = crate::ElefantToolsError;
230
231 fn from_str(s: &str) -> Result<Self, Self::Err> {
232 match s {
233 "a" | "NO ACTION" => Ok(ReferenceAction::NoAction),
234 "r" | "RESTRICT" => Ok(ReferenceAction::Restrict),
235 "c" | "CASCADE" => Ok(ReferenceAction::Cascade),
236 "n" | "SET NULL" => Ok(ReferenceAction::SetNull),
237 "d" | "SET DEFAULT" => Ok(ReferenceAction::SetDefault),
238 _ => Err(crate::ElefantToolsError::UnknownForeignKeyAction(
239 s.to_string(),
240 )),
241 }
242 }
243}
244
245impl FromPgChar for ReferenceAction {
246 fn from_pg_char(c: char) -> Result<Self, ElefantToolsError> {
247 match c {
248 'a' => Ok(ReferenceAction::NoAction),
249 'r' => Ok(ReferenceAction::Restrict),
250 'c' => Ok(ReferenceAction::Cascade),
251 'n' => Ok(ReferenceAction::SetNull),
252 'd' => Ok(ReferenceAction::SetDefault),
253 _ => Err(ElefantToolsError::UnknownForeignKeyAction(c.to_string())),
254 }
255 }
256}