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    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(&quote_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}