reinhardt_db/migrations/operations/
postgres.rs1use crate::backends::schema::BaseDatabaseSchemaEditor;
23use crate::migrations::ProjectState;
24use pg_escape::quote_literal;
25use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct CreateExtension {
46 pub name: String,
48 pub schema: Option<String>,
50 pub version: Option<String>,
52}
53
54impl CreateExtension {
55 pub fn new(name: impl Into<String>) -> Self {
67 Self {
68 name: name.into(),
69 schema: None,
70 version: None,
71 }
72 }
73
74 pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
76 self.schema = Some(schema.into());
77 self
78 }
79
80 pub fn with_version(mut self, version: impl Into<String>) -> Self {
82 self.version = Some(version.into());
83 self
84 }
85
86 pub fn state_forwards(&self, _app_label: &str, _state: &mut ProjectState) {
88 }
90
91 pub fn database_forwards(&self, _schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
109 let mut parts = vec!["CREATE EXTENSION IF NOT EXISTS".to_string()];
110 parts.push(format!("\"{}\"", self.name));
112
113 if let Some(ref schema) = self.schema {
114 parts.push("SCHEMA".to_string());
115 parts.push(format!("\"{}\"", schema));
116 }
117
118 if let Some(ref version) = self.version {
119 parts.push("VERSION".to_string());
120 parts.push(quote_literal(version));
121 }
122
123 vec![format!("{};", parts.join(" "))]
124 }
125
126 pub fn database_backwards(&self, _schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
143 vec![format!("DROP EXTENSION IF EXISTS \"{}\";", self.name)]
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct DropExtension {
158 pub name: String,
160}
161
162impl DropExtension {
163 pub fn new(name: impl Into<String>) -> Self {
165 Self { name: name.into() }
166 }
167
168 pub fn state_forwards(&self, _app_label: &str, _state: &mut ProjectState) {}
170
171 pub fn database_forwards(&self, _schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
188 vec![format!("DROP EXTENSION IF EXISTS \"{}\";", self.name)]
189 }
190
191 pub fn database_backwards(&self, _schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
193 vec![format!("CREATE EXTENSION IF NOT EXISTS \"{}\";", self.name)]
194 }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct CreateCollation {
210 pub name: String,
212 pub locale: String,
214 pub provider: Option<String>,
216}
217
218impl CreateCollation {
219 pub fn new(name: impl Into<String>, locale: impl Into<String>) -> Self {
231 Self {
232 name: name.into(),
233 locale: locale.into(),
234 provider: None,
235 }
236 }
237
238 pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
240 self.provider = Some(provider.into());
241 self
242 }
243
244 pub fn state_forwards(&self, _app_label: &str, _state: &mut ProjectState) {}
246
247 pub fn database_forwards(&self, _schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
265 let mut sql = format!(
267 "CREATE COLLATION IF NOT EXISTS \"{}\" (LOCALE = {}",
268 self.name,
269 quote_literal(&self.locale)
270 );
271
272 if let Some(ref provider) = self.provider {
273 sql.push_str(&format!(", PROVIDER = {}", quote_literal(provider)));
274 }
275
276 sql.push_str(");");
277 vec![sql]
278 }
279
280 pub fn database_backwards(&self, _schema_editor: &dyn BaseDatabaseSchemaEditor) -> Vec<String> {
282 vec![format!("DROP COLLATION IF EXISTS \"{}\";", self.name)]
283 }
284}
285
286pub mod extensions {
288 use super::CreateExtension;
289
290 pub fn hstore() -> CreateExtension {
301 CreateExtension::new("hstore")
302 }
303
304 pub fn pg_trgm() -> CreateExtension {
306 CreateExtension::new("pg_trgm")
307 }
308
309 pub fn uuid_ossp() -> CreateExtension {
311 CreateExtension::new("uuid-ossp")
312 }
313
314 pub fn postgis() -> CreateExtension {
316 CreateExtension::new("postgis")
317 }
318
319 pub fn btree_gin() -> CreateExtension {
321 CreateExtension::new("btree_gin")
322 }
323
324 pub fn btree_gist() -> CreateExtension {
326 CreateExtension::new("btree_gist")
327 }
328
329 pub fn citext() -> CreateExtension {
331 CreateExtension::new("citext")
332 }
333
334 pub fn unaccent() -> CreateExtension {
336 CreateExtension::new("unaccent")
337 }
338}
339
340use crate::migrations::operation_trait::MigrationOperation;
342
343impl MigrationOperation for CreateExtension {
344 fn migration_name_fragment(&self) -> Option<String> {
345 Some(format!("create_extension_{}", self.name.to_lowercase()))
346 }
347
348 fn describe(&self) -> String {
349 format!("Create PostgreSQL extension {}", self.name)
350 }
351}
352
353impl MigrationOperation for DropExtension {
354 fn migration_name_fragment(&self) -> Option<String> {
355 Some(format!("drop_extension_{}", self.name.to_lowercase()))
356 }
357
358 fn describe(&self) -> String {
359 format!("Drop PostgreSQL extension {}", self.name)
360 }
361}
362
363impl MigrationOperation for CreateCollation {
364 fn migration_name_fragment(&self) -> Option<String> {
365 Some(format!("create_collation_{}", self.name.to_lowercase()))
366 }
367
368 fn describe(&self) -> String {
369 format!("Create collation {}", self.name)
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_create_extension_basic() {
379 let ext = CreateExtension::new("hstore");
380 assert_eq!(ext.name, "hstore");
381 assert!(ext.schema.is_none());
382 assert!(ext.version.is_none());
383 }
384
385 #[test]
386 fn test_create_extension_with_schema() {
387 let ext = CreateExtension::new("hstore").with_schema("public");
388 assert_eq!(ext.name, "hstore");
389 assert_eq!(ext.schema, Some("public".to_string()));
390 }
391
392 #[test]
393 fn test_create_extension_with_version() {
394 let ext = CreateExtension::new("postgis").with_version("3.0.0");
395 assert_eq!(ext.version, Some("3.0.0".to_string()));
396 }
397
398 #[cfg(feature = "postgres")]
399 #[test]
400 fn test_create_extension_database_forwards() {
401 use crate::backends::schema::test_utils::MockSchemaEditor;
402
403 let ext = CreateExtension::new("hstore");
404 let editor = MockSchemaEditor::new();
405
406 let sql = ext.database_forwards(&editor);
407 assert_eq!(sql.len(), 1);
408 assert!(sql[0].contains("CREATE EXTENSION IF NOT EXISTS"));
409 assert!(sql[0].contains("\"hstore\""));
410 }
411
412 #[cfg(feature = "postgres")]
413 #[test]
414 fn test_create_extension_with_schema_sql() {
415 use crate::backends::schema::test_utils::MockSchemaEditor;
416
417 let ext = CreateExtension::new("hstore").with_schema("public");
418 let editor = MockSchemaEditor::new();
419
420 let sql = ext.database_forwards(&editor);
421 assert!(sql[0].contains("SCHEMA"));
422 assert!(sql[0].contains("\"public\""));
423 }
424
425 #[cfg(feature = "postgres")]
426 #[test]
427 fn test_drop_extension() {
428 use crate::backends::schema::test_utils::MockSchemaEditor;
429
430 let drop = DropExtension::new("hstore");
431 let editor = MockSchemaEditor::new();
432
433 let sql = drop.database_forwards(&editor);
434 assert_eq!(sql.len(), 1);
435 assert!(sql[0].contains("DROP EXTENSION IF EXISTS"));
436 assert!(sql[0].contains("\"hstore\""));
437 }
438
439 #[cfg(feature = "postgres")]
440 #[test]
441 fn test_create_collation() {
442 use crate::backends::schema::test_utils::MockSchemaEditor;
443
444 let collation = CreateCollation::new("german", "de_DE");
445 let editor = MockSchemaEditor::new();
446
447 let sql = collation.database_forwards(&editor);
448 assert_eq!(sql.len(), 1);
449 assert!(sql[0].contains("CREATE COLLATION IF NOT EXISTS"));
450 assert!(sql[0].contains("\"german\""));
451 assert!(sql[0].contains("de_DE"));
452 }
453
454 #[test]
455 fn test_extension_helpers() {
456 let hstore = extensions::hstore();
457 assert_eq!(hstore.name, "hstore");
458
459 let pg_trgm = extensions::pg_trgm();
460 assert_eq!(pg_trgm.name, "pg_trgm");
461
462 let postgis = extensions::postgis();
463 assert_eq!(postgis.name, "postgis");
464 }
465}