1use crate::helpers::StringExt;
2use crate::object_id::ObjectId;
3use crate::quoting::AttemptedKeywordUsage::ColumnName;
4use crate::quoting::{quote_value_string, IdentifierQuoter, Quotable};
5use crate::{PostgresSchema, PostgresTable};
6use serde::{Deserialize, Serialize};
7use std::cmp::Ordering;
8
9#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
10pub struct PostgresIndex {
11 pub name: String,
12 pub key_columns: Vec<PostgresIndexKeyColumn>,
13 pub index_type: String,
14 pub predicate: Option<String>,
15 pub included_columns: Vec<PostgresIndexIncludedColumn>,
16 pub index_constraint_type: PostgresIndexType,
17 pub storage_parameters: Vec<String>,
18 pub comment: Option<String>,
19 pub constraint_definition: Option<String>,
21 pub object_id: ObjectId,
22}
23
24#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize, Deserialize)]
25#[serde(tag = "type")]
26pub enum PostgresIndexType {
27 PrimaryKey,
28 Unique {
29 nulls_distinct: bool,
30 },
31 #[default]
32 Index,
33}
34
35impl Ord for PostgresIndex {
36 fn cmp(&self, other: &Self) -> Ordering {
37 self.name.cmp(&other.name)
38 }
39}
40
41impl PartialOrd for PostgresIndex {
42 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
43 Some(self.cmp(other))
44 }
45}
46
47impl PostgresIndex {
48 pub fn get_create_index_command(
49 &self,
50 schema: &PostgresSchema,
51 table: &PostgresTable,
52 identifier_quoter: &IdentifierQuoter,
53 ) -> String {
54 if PostgresIndexType::PrimaryKey == self.index_constraint_type {
55 if let Some(ref constraint_def) = self.constraint_definition {
56 return format!(
57 "alter table {}.{} add constraint {} {};",
58 schema.name.quote(identifier_quoter, ColumnName),
59 table.name.quote(identifier_quoter, ColumnName),
60 self.name.quote(identifier_quoter, ColumnName),
61 constraint_def
62 );
63 }
64
65 return format!(
66 "alter table {}.{} add constraint {} primary key ({});",
67 schema.name.quote(identifier_quoter, ColumnName),
68 table.name.quote(identifier_quoter, ColumnName),
69 self.name.quote(identifier_quoter, ColumnName),
70 self.key_columns
71 .iter()
72 .map(|c| c.name.quote(identifier_quoter, ColumnName))
73 .collect::<Vec<String>>()
74 .join(", ")
75 );
76 }
77
78 let index_type = match self.index_constraint_type {
79 PostgresIndexType::Unique { .. } => "unique ",
80 _ => "",
81 };
82
83 let mut command = format!(
84 "create {}index {} on {}.{} using {} (",
85 index_type,
86 self.name.quote(identifier_quoter, ColumnName),
87 schema.name.quote(identifier_quoter, ColumnName),
88 table.name.quote(identifier_quoter, ColumnName),
89 self.index_type
90 );
91
92 for (i, column) in self.key_columns.iter().enumerate() {
93 if i > 0 {
94 command.push_str(", ");
95 }
96
97 command.push_str(&column.name);
98
99 match column.direction {
100 Some(PostgresIndexColumnDirection::Ascending) => {
101 command.push_str(" asc");
102 }
103 Some(PostgresIndexColumnDirection::Descending) => {
104 command.push_str(" desc");
105 }
106 _ => {}
107 }
108
109 match column.nulls_order {
110 Some(PostgresIndexNullsOrder::First) => {
111 command.push_str(" nulls first");
112 }
113 Some(PostgresIndexNullsOrder::Last) => {
114 command.push_str(" nulls last");
115 }
116 _ => {}
117 }
118 }
119
120 command.push(')');
121
122 if !self.included_columns.is_empty() {
123 command.push_str(" include (");
124
125 command.push_join(
126 ", ",
127 self.included_columns
128 .iter()
129 .map(|c| c.name.quote(identifier_quoter, ColumnName)),
130 );
131
132 command.push(')');
133 }
134
135 if let PostgresIndexType::Unique {
136 nulls_distinct: false,
137 } = self.index_constraint_type
138 {
139 command.push_str(" nulls not distinct")
140 }
141
142 if !self.storage_parameters.is_empty() {
143 command.push_str(" with (");
144 command.push_join(", ", self.storage_parameters.iter());
145 command.push(')');
146 }
147
148 if let Some(ref predicate) = self.predicate {
149 command.push_str(" where ");
150 command.push_str(predicate);
151 }
152
153 command.push(';');
154
155 if let Some(comment) = &self.comment {
156 command.push_str("\ncomment on index ");
157 command.push_str(&self.name.quote(identifier_quoter, ColumnName));
158 command.push_str(" is ");
159 command.push_str("e_value_string(comment));
160 command.push(';');
161 }
162
163 command
164 }
165}
166
167#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
168pub struct PostgresIndexKeyColumn {
169 pub name: String,
170 pub ordinal_position: i32,
171 pub direction: Option<PostgresIndexColumnDirection>,
172 pub nulls_order: Option<PostgresIndexNullsOrder>,
173}
174
175impl Ord for PostgresIndexKeyColumn {
176 fn cmp(&self, other: &Self) -> Ordering {
177 self.ordinal_position.cmp(&other.ordinal_position)
178 }
179}
180
181impl PartialOrd for PostgresIndexKeyColumn {
182 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
183 Some(self.cmp(other))
184 }
185}
186
187#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
188pub enum PostgresIndexColumnDirection {
189 Ascending,
190 Descending,
191}
192
193#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
194pub enum PostgresIndexNullsOrder {
195 First,
196 Last,
197}
198
199#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
200pub struct PostgresIndexIncludedColumn {
201 pub name: String,
202 pub ordinal_position: i32,
203}
204
205impl Ord for PostgresIndexIncludedColumn {
206 fn cmp(&self, other: &Self) -> Ordering {
207 self.ordinal_position.cmp(&other.ordinal_position)
208 }
209}
210
211impl PartialOrd for PostgresIndexIncludedColumn {
212 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
213 Some(self.cmp(other))
214 }
215}