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