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