1use crate::error::Error;
2
3pub trait CreateIndex<'until_build> {
7 fn unique(self) -> Self;
13
14 fn if_not_exists(self) -> Self;
18
19 fn add_column(self, column: &'until_build str) -> Self;
26
27 fn set_condition(self, condition: String) -> Self;
34
35 fn build(self) -> Result<String, Error>;
39}
40
41pub struct CreateIndexData<'until_build> {
45 pub(crate) name: &'until_build str,
46 pub(crate) table_name: &'until_build str,
47 pub(crate) unique: bool,
48 pub(crate) if_not_exists: bool,
49 pub(crate) columns: Vec<&'until_build str>,
50 pub(crate) condition: Option<String>,
51}
52
53pub enum CreateIndexImpl<'until_build> {
59 #[cfg(feature = "sqlite")]
63 Sqlite(CreateIndexData<'until_build>),
64 #[cfg(feature = "postgres")]
68 Postgres(CreateIndexData<'until_build>),
69}
70
71impl<'until_build> CreateIndex<'until_build> for CreateIndexImpl<'until_build> {
72 fn unique(mut self) -> Self {
73 match self {
74 #[cfg(feature = "sqlite")]
75 CreateIndexImpl::Sqlite(ref mut d) => d.unique = true,
76 #[cfg(feature = "postgres")]
77 CreateIndexImpl::Postgres(ref mut d) => d.unique = true,
78 };
79 self
80 }
81
82 fn if_not_exists(mut self) -> Self {
83 match self {
84 #[cfg(feature = "sqlite")]
85 CreateIndexImpl::Sqlite(ref mut d) => d.if_not_exists = true,
86 #[cfg(feature = "postgres")]
87 CreateIndexImpl::Postgres(ref mut d) => d.if_not_exists = true,
88 };
89 self
90 }
91
92 fn add_column(mut self, column: &'until_build str) -> Self {
93 match self {
94 #[cfg(feature = "sqlite")]
95 CreateIndexImpl::Sqlite(ref mut d) => d.columns.push(column),
96 #[cfg(feature = "postgres")]
97 CreateIndexImpl::Postgres(ref mut d) => d.columns.push(column),
98 }
99 self
100 }
101
102 fn set_condition(mut self, condition: String) -> Self {
103 match self {
104 #[cfg(feature = "sqlite")]
105 CreateIndexImpl::Sqlite(ref mut d) => d.condition = Some(condition),
106 #[cfg(feature = "postgres")]
107 CreateIndexImpl::Postgres(ref mut d) => d.condition = Some(condition),
108 }
109 self
110 }
111
112 fn build(self) -> Result<String, Error> {
113 match self {
114 #[cfg(feature = "sqlite")]
115 CreateIndexImpl::Sqlite(d) => {
116 if d.columns.is_empty() {
117 return Err(Error::SQLBuildError(format!(
118 "Couldn't create index on {}: Missing column(s) to create the index on",
119 d.table_name
120 )));
121 }
122
123 Ok(format!(
124 "CREATE{} INDEX{} \"{}\" ON \"{}\" ({}){};",
125 if d.unique { " UNIQUE" } else { "" },
126 if d.if_not_exists {
127 " IF NOT EXISTS"
128 } else {
129 ""
130 },
131 d.name,
132 d.table_name,
133 quote_columns(&d.columns),
134 match d.condition {
135 None => String::from(""),
136 Some(cond) => format!(" WHERE {}", cond.as_str()),
137 }
138 ))
139 }
140 #[cfg(feature = "postgres")]
141 CreateIndexImpl::Postgres(d) => {
142 if d.columns.is_empty() {
143 return Err(Error::SQLBuildError(format!(
144 "Couldn't create index on {}: Missing column(s) to create the index on",
145 d.table_name
146 )));
147 }
148
149 Ok(format!(
150 "CREATE{} INDEX{} \"{}\" ON \"{}\" ({}){};",
151 if d.unique { " UNIQUE" } else { "" },
152 if d.if_not_exists {
153 " IF NOT EXISTS"
154 } else {
155 ""
156 },
157 d.name,
158 d.table_name,
159 quote_columns(&d.columns),
160 match d.condition {
161 None => String::from(""),
162 Some(cond) => format!(" WHERE {}", cond.as_str()),
163 }
164 ))
165 }
166 }
167 }
168}
169
170fn quote_columns(columns: &[&str]) -> String {
172 columns
173 .iter()
174 .map(|column| format!("\"{column}\""))
175 .collect::<Vec<_>>()
176 .join(", ")
177}
178
179#[cfg(test)]
180mod test {
181 use crate::create_index::CreateIndex;
182 use crate::DBImpl;
183
184 #[cfg(feature = "sqlite")]
185 #[test]
186 fn single_column_sqlite() {
187 assert_eq!(
188 DBImpl::SQLite
189 .create_index("user_login_idx", "user")
190 .add_column("login")
191 .build()
192 .unwrap(),
193 r#"CREATE INDEX "user_login_idx" ON "user" ("login");"#
194 );
195 }
196
197 #[cfg(feature = "postgres")]
198 #[test]
199 fn single_column_postgres() {
200 assert_eq!(
201 DBImpl::Postgres
202 .create_index("user_login_idx", "user")
203 .add_column("login")
204 .build()
205 .unwrap(),
206 r#"CREATE INDEX "user_login_idx" ON "user" ("login");"#
207 );
208 }
209
210 #[cfg(feature = "sqlite")]
211 #[test]
212 fn multiple_columns_sqlite() {
213 assert_eq!(
214 DBImpl::SQLite
215 .create_index("user_full_name_idx", "user")
216 .add_column("last_name")
217 .add_column("first_name")
218 .build()
219 .unwrap(),
220 r#"CREATE INDEX "user_full_name_idx" ON "user" ("last_name", "first_name");"#
221 );
222 }
223
224 #[cfg(feature = "postgres")]
225 #[test]
226 fn multiple_columns_postgres() {
227 assert_eq!(
228 DBImpl::Postgres
229 .create_index("user_full_name_idx", "user")
230 .add_column("last_name")
231 .add_column("first_name")
232 .build()
233 .unwrap(),
234 r#"CREATE INDEX "user_full_name_idx" ON "user" ("last_name", "first_name");"#
235 );
236 }
237
238 #[cfg(feature = "sqlite")]
239 #[test]
240 fn unique_and_if_not_exists_sqlite() {
241 assert_eq!(
242 DBImpl::SQLite
243 .create_index("user_login_idx", "user")
244 .add_column("login")
245 .unique()
246 .if_not_exists()
247 .build()
248 .unwrap(),
249 r#"CREATE UNIQUE INDEX IF NOT EXISTS "user_login_idx" ON "user" ("login");"#
250 );
251 }
252
253 #[cfg(feature = "sqlite")]
254 #[test]
255 fn condition_sqlite() {
256 assert_eq!(
257 DBImpl::SQLite
258 .create_index("user_login_idx", "user")
259 .add_column("login")
260 .set_condition("\"login\" IS NOT NULL".to_string())
261 .build()
262 .unwrap(),
263 r#"CREATE INDEX "user_login_idx" ON "user" ("login") WHERE "login" IS NOT NULL;"#
264 );
265 }
266
267 #[cfg(feature = "sqlite")]
268 #[test]
269 fn missing_columns_is_an_error() {
270 assert!(DBImpl::SQLite
271 .create_index("user_login_idx", "user")
272 .build()
273 .is_err());
274 }
275}