Skip to main content

elefant_tools/models/
index.rs

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    /// For temporal primary keys, stores the constraint definition from pg_get_constraintdef().
20    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(&quote_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}