1use crate::alloc_prelude::*;
7use core::fmt::Write;
8
9use super::{
10 CheckConstraint, Column, Enum, ForeignKey, Generated, GeneratedType, Identity, IdentityType,
11 Index, IndexColumn, IndexColumnDef, Policy, PrimaryKey, Sequence, Table, UniqueConstraint,
12 View,
13};
14
15fn quote_ident(ident: &str) -> String {
16 format!("\"{}\"", ident.replace('"', "\"\""))
17}
18
19fn quote_literal(value: &str) -> String {
20 format!("'{}'", value.replace('\'', "''"))
21}
22
23fn schema_prefix(schema: &str) -> String {
24 if schema == "public" {
25 String::new()
26 } else {
27 format!("{}.", quote_ident(schema))
28 }
29}
30
31fn qualified_name(schema: &str, name: &str) -> String {
32 format!("{}{}", schema_prefix(schema), quote_ident(name))
33}
34
35fn quote_qualified_name(name: &str) -> String {
38 name.split('.')
39 .map(quote_ident)
40 .collect::<Vec<_>>()
41 .join(".")
42}
43
44fn type_name_needs_quoting(base: &str) -> bool {
51 !base.contains(' ')
52 && !base
53 .chars()
54 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
55}
56
57fn index_column_sql(column: &IndexColumn) -> String {
58 let mut sql = if column.is_expression {
59 format!("({})", column.value)
60 } else {
61 quote_ident(&column.value)
62 };
63
64 if let Some(op) = column.opclass.as_ref() {
65 let _ = write!(sql, " {op}");
66 }
67
68 if !column.asc {
69 sql.push_str(" DESC");
70 }
71
72 if column.nulls_first {
73 sql.push_str(" NULLS FIRST");
74 }
75
76 sql
77}
78
79#[derive(Clone, Debug)]
85pub struct TableSql<'a> {
86 pub table: &'a Table,
87 pub columns: &'a [Column],
88 pub primary_key: Option<&'a PrimaryKey>,
89 pub foreign_keys: &'a [ForeignKey],
90 pub unique_constraints: &'a [UniqueConstraint],
91 pub check_constraints: &'a [CheckConstraint],
92 pub indexes: &'a [Index],
93 pub policies: &'a [Policy],
94}
95
96impl<'a> TableSql<'a> {
97 #[must_use]
99 pub const fn new(table: &'a Table) -> Self {
100 Self {
101 table,
102 columns: &[],
103 primary_key: None,
104 foreign_keys: &[],
105 unique_constraints: &[],
106 check_constraints: &[],
107 indexes: &[],
108 policies: &[],
109 }
110 }
111
112 #[must_use]
114 pub const fn columns(mut self, columns: &'a [Column]) -> Self {
115 self.columns = columns;
116 self
117 }
118
119 #[must_use]
121 pub const fn primary_key(mut self, pk: Option<&'a PrimaryKey>) -> Self {
122 self.primary_key = pk;
123 self
124 }
125
126 #[must_use]
128 pub const fn foreign_keys(mut self, fks: &'a [ForeignKey]) -> Self {
129 self.foreign_keys = fks;
130 self
131 }
132
133 #[must_use]
135 pub const fn unique_constraints(mut self, uniques: &'a [UniqueConstraint]) -> Self {
136 self.unique_constraints = uniques;
137 self
138 }
139
140 #[must_use]
142 pub const fn check_constraints(mut self, checks: &'a [CheckConstraint]) -> Self {
143 self.check_constraints = checks;
144 self
145 }
146
147 #[must_use]
149 pub const fn indexes(mut self, indexes: &'a [Index]) -> Self {
150 self.indexes = indexes;
151 self
152 }
153
154 #[must_use]
156 pub const fn policies(mut self, policies: &'a [Policy]) -> Self {
157 self.policies = policies;
158 self
159 }
160
161 #[must_use]
163 pub fn create_table_sql(&self) -> String {
164 let table_kind = if self.table.is_temporary.unwrap_or(false) {
165 "TEMPORARY "
166 } else if self.table.is_unlogged.unwrap_or(false) {
167 "UNLOGGED "
168 } else {
169 ""
170 };
171 let mut sql = format!(
172 "CREATE {}TABLE {} (\n",
173 table_kind,
174 qualified_name(self.table.schema(), self.table.name())
175 );
176
177 let mut lines = Vec::new();
178
179 for column in self.columns {
181 lines.push(format!("\t{}", column.to_column_sql()));
182 }
183
184 if let Some(pk) = &self.primary_key {
186 let cols = pk
187 .columns
188 .iter()
189 .map(|c| quote_ident(c))
190 .collect::<Vec<_>>()
191 .join(", ");
192 if pk.name_explicit {
193 lines.push(format!(
194 "\tCONSTRAINT {} PRIMARY KEY({})",
195 quote_ident(pk.name()),
196 cols
197 ));
198 } else {
199 lines.push(format!("\tPRIMARY KEY({cols})"));
200 }
201 }
202
203 for fk in self.foreign_keys {
205 lines.push(format!("\t{}", fk.to_constraint_sql()));
206 }
207
208 for unique in self.unique_constraints {
210 lines.push(format!("\t{}", unique.to_constraint_sql()));
211 }
212
213 for check in self.check_constraints {
215 lines.push(format!(
216 "\tCONSTRAINT {} CHECK ({})",
217 quote_ident(check.name()),
218 check.value
219 ));
220 }
221
222 sql.push_str(&lines.join(",\n"));
223 sql.push('\n');
224 sql.push(')');
225
226 if let Some(inherits) = self.table.inherits.as_ref() {
227 let _ = write!(sql, " INHERITS ({})", quote_qualified_name(inherits));
228 }
229
230 if let Some(tablespace) = self.table.tablespace.as_ref() {
231 let _ = write!(sql, " TABLESPACE {}", quote_ident(tablespace));
232 }
233
234 sql.push(';');
235
236 sql
237 }
238
239 #[must_use]
241 pub fn drop_table_sql(&self) -> String {
242 format!(
243 "DROP TABLE {};",
244 qualified_name(self.table.schema(), self.table.name())
245 )
246 }
247
248 #[must_use]
250 pub fn create_indexes_sql(&self) -> Vec<String> {
251 self.indexes
252 .iter()
253 .map(super::index::Index::create_index_sql)
254 .collect()
255 }
256
257 #[must_use]
259 pub fn create_comments_sql(&self) -> Vec<String> {
260 let mut comments = Vec::new();
261 let table_name = qualified_name(self.table.schema(), self.table.name());
262
263 if let Some(comment) = self.table.comment.as_ref() {
264 comments.push(format!(
265 "COMMENT ON TABLE {} IS {};",
266 table_name,
267 quote_literal(comment)
268 ));
269 }
270
271 for column in self.columns {
272 if let Some(comment) = column.comment.as_ref() {
273 comments.push(format!(
274 "COMMENT ON COLUMN {}.{} IS {};",
275 table_name,
276 quote_ident(column.name()),
277 quote_literal(comment)
278 ));
279 }
280 }
281
282 comments
283 }
284
285 #[must_use]
287 pub fn enable_rls_sql(&self) -> Option<String> {
288 if self.table.is_rls_enabled.unwrap_or(false) {
289 Some(format!(
290 "ALTER TABLE {} ENABLE ROW LEVEL SECURITY;",
291 qualified_name(self.table.schema(), self.table.name())
292 ))
293 } else {
294 None
295 }
296 }
297
298 #[must_use]
300 pub fn create_policies_sql(&self) -> Vec<String> {
301 self.policies
302 .iter()
303 .map(super::policy::Policy::create_policy_sql)
304 .collect()
305 }
306}
307
308impl Column {
313 #[must_use]
323 pub fn type_sql(&self) -> String {
324 let mut sql = match self.type_schema.as_deref() {
325 Some(type_schema) => {
326 let raw = self.sql_type();
327 let (base, params) = raw
328 .find('(')
329 .map_or((raw, ""), |idx| (raw[..idx].trim_end(), &raw[idx..]));
330 if type_schema == "public" {
331 if type_name_needs_quoting(base) {
332 format!("{}{}", quote_ident(base), params)
333 } else {
334 raw.to_string()
335 }
336 } else {
337 format!(
338 "{}.{}{}",
339 quote_ident(type_schema),
340 quote_ident(base),
341 params
342 )
343 }
344 }
345 None => self.sql_type().to_string(),
346 };
347
348 if let Some(dimensions) = self.dimensions
349 && dimensions > 0
350 {
351 for _ in 0..dimensions {
352 sql.push_str("[]");
353 }
354 }
355
356 sql
357 }
358
359 #[must_use]
361 pub fn to_column_sql(&self) -> String {
362 let mut sql = format!("{} {}", quote_ident(self.name()), self.type_sql());
363
364 if let Some(collate) = self.collate.as_ref() {
368 let _ = write!(sql, " COLLATE {}", quote_ident(collate));
369 }
370
371 if let Some(identity) = &self.identity {
373 sql.push_str(&identity.to_sql());
374 }
375
376 if let Some(generated) = &self.generated {
378 sql.push_str(&generated.to_sql());
379 }
380
381 if self.identity.is_none()
383 && self.generated.is_none()
384 && let Some(default) = self.default.as_ref()
385 {
386 let _ = write!(sql, " DEFAULT {default}");
387 }
388
389 if self.not_null {
391 sql.push_str(" NOT NULL");
392 }
393
394 sql
395 }
396
397 #[must_use]
399 pub fn add_column_sql(&self) -> String {
400 format!(
401 "ALTER TABLE {} ADD COLUMN {};",
402 qualified_name(self.schema(), self.table()),
403 self.to_column_sql()
404 )
405 }
406
407 #[must_use]
409 pub fn drop_column_sql(&self) -> String {
410 format!(
411 "ALTER TABLE {} DROP COLUMN {};",
412 qualified_name(self.schema(), self.table()),
413 quote_ident(self.name())
414 )
415 }
416}
417
418impl Identity {
423 #[must_use]
425 pub fn to_sql(&self) -> String {
426 let identity_type = match self.type_ {
427 IdentityType::Always => "ALWAYS",
428 IdentityType::ByDefault => "BY DEFAULT",
429 };
430
431 let mut sql = format!(" GENERATED {identity_type} AS IDENTITY");
432
433 let mut options = Vec::new();
435
436 if let Some(increment) = self.increment.as_ref() {
437 options.push(format!("INCREMENT BY {increment}"));
438 }
439 if let Some(min) = self.min_value.as_ref() {
440 options.push(format!("MINVALUE {min}"));
441 }
442 if let Some(max) = self.max_value.as_ref() {
443 options.push(format!("MAXVALUE {max}"));
444 }
445 if let Some(start) = self.start_with.as_ref() {
446 options.push(format!("START WITH {start}"));
447 }
448 if let Some(cache) = self.cache {
449 options.push(format!("CACHE {cache}"));
450 }
451 if self.cycle.unwrap_or(false) {
452 options.push("CYCLE".to_string());
453 }
454
455 if !options.is_empty() {
456 let _ = write!(sql, " ({})", options.join(" "));
457 }
458
459 sql
460 }
461}
462
463impl Generated {
468 #[must_use]
470 pub fn to_sql(&self) -> String {
471 let gen_type = match self.gen_type {
472 GeneratedType::Stored => "STORED",
473 GeneratedType::Virtual => "VIRTUAL",
474 };
475 format!(" GENERATED ALWAYS AS ({}) {}", self.expression, gen_type)
476 }
477}
478
479impl ForeignKey {
484 #[must_use]
486 pub fn to_constraint_sql(&self) -> String {
487 let from_cols = self
488 .columns
489 .iter()
490 .map(|c| quote_ident(c))
491 .collect::<Vec<_>>()
492 .join(", ");
493
494 let to_cols = self
495 .columns_to
496 .iter()
497 .map(|c| quote_ident(c))
498 .collect::<Vec<_>>()
499 .join(", ");
500
501 let mut sql = format!(
502 "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({})",
503 quote_ident(self.name()),
504 from_cols,
505 qualified_name(self.schema_to(), self.table_to()),
506 to_cols
507 );
508
509 if let Some(on_delete) = self.on_delete.as_ref()
510 && !on_delete.eq_ignore_ascii_case("NO ACTION")
511 {
512 let _ = write!(sql, " ON DELETE {}", on_delete.to_uppercase());
513 }
514
515 if let Some(on_update) = self.on_update.as_ref()
516 && !on_update.eq_ignore_ascii_case("NO ACTION")
517 {
518 let _ = write!(sql, " ON UPDATE {}", on_update.to_uppercase());
519 }
520
521 if self.deferrable || self.initially_deferred {
522 sql.push_str(" DEFERRABLE");
523 if self.initially_deferred {
524 sql.push_str(" INITIALLY DEFERRED");
525 }
526 }
527
528 sql
529 }
530
531 #[must_use]
533 pub fn add_fk_sql(&self) -> String {
534 format!(
535 "ALTER TABLE {} ADD {};",
536 qualified_name(self.schema(), self.table()),
537 self.to_constraint_sql()
538 )
539 }
540
541 #[must_use]
543 pub fn drop_fk_sql(&self) -> String {
544 format!(
545 "ALTER TABLE {} DROP CONSTRAINT {};",
546 qualified_name(self.schema(), self.table()),
547 quote_ident(self.name())
548 )
549 }
550}
551
552impl Index {
557 #[must_use]
559 pub fn create_index_sql(&self) -> String {
560 let unique = if self.is_unique { "UNIQUE " } else { "" };
561
562 let concurrently = if self.concurrently {
563 "CONCURRENTLY "
564 } else {
565 ""
566 };
567
568 let columns = self
569 .columns
570 .iter()
571 .map(index_column_sql)
572 .collect::<Vec<_>>()
573 .join(", ");
574
575 let using = self
576 .method
577 .as_ref()
578 .map(|m| format!(" USING {m}"))
579 .unwrap_or_default();
580
581 let mut sql = format!(
582 "CREATE {}INDEX {}{} ON {}{}({})",
583 unique,
584 concurrently,
585 quote_ident(self.name()),
586 qualified_name(self.schema(), self.table()),
587 using,
588 columns
589 );
590
591 if let Some(with) = self.with.as_ref() {
592 let _ = write!(sql, " WITH ({with})");
593 }
594
595 if let Some(where_clause) = self.where_clause.as_ref() {
596 let _ = write!(sql, " WHERE {where_clause}");
597 }
598
599 sql.push(';');
600 sql
601 }
602
603 #[must_use]
605 pub fn drop_index_sql(&self) -> String {
606 format!("DROP INDEX {};", qualified_name(self.schema(), self.name()))
607 }
608}
609
610impl IndexColumnDef {
611 #[must_use]
613 pub fn to_sql(&self) -> String {
614 let mut sql = if self.is_expression {
615 format!("({})", self.value)
616 } else {
617 quote_ident(self.value)
618 };
619
620 if let Some(op) = self.opclass.as_ref() {
621 let _ = write!(sql, " {op}");
622 }
623
624 if !self.asc {
625 sql.push_str(" DESC");
626 }
627
628 if self.nulls_first {
629 sql.push_str(" NULLS FIRST");
630 }
631
632 sql
633 }
634}
635
636impl Enum {
641 #[must_use]
643 pub fn create_enum_sql(&self) -> String {
644 let values = self
645 .values
646 .iter()
647 .map(|v| quote_literal(v))
648 .collect::<Vec<_>>()
649 .join(", ");
650 format!(
651 "CREATE TYPE {} AS ENUM ({});",
652 qualified_name(self.schema(), self.name()),
653 values
654 )
655 }
656
657 #[must_use]
659 pub fn drop_enum_sql(&self) -> String {
660 format!("DROP TYPE {};", qualified_name(self.schema(), self.name()))
661 }
662
663 #[must_use]
665 pub fn add_value_sql(&self, value: &str, before: Option<&str>) -> String {
666 before.map_or_else(
667 || {
668 format!(
669 "ALTER TYPE {} ADD VALUE {};",
670 qualified_name(self.schema(), self.name()),
671 quote_literal(value)
672 )
673 },
674 |before_value| {
675 format!(
676 "ALTER TYPE {} ADD VALUE {} BEFORE {};",
677 qualified_name(self.schema(), self.name()),
678 quote_literal(value),
679 quote_literal(before_value)
680 )
681 },
682 )
683 }
684}
685
686impl Sequence {
691 #[must_use]
693 pub fn create_sequence_sql(&self) -> String {
694 let mut sql = format!(
695 "CREATE SEQUENCE {}",
696 qualified_name(self.schema(), self.name())
697 );
698
699 if let Some(inc) = self.increment_by.as_ref() {
700 let _ = write!(sql, " INCREMENT BY {inc}");
701 }
702 if let Some(min) = self.min_value.as_ref() {
703 let _ = write!(sql, " MINVALUE {min}");
704 }
705 if let Some(max) = self.max_value.as_ref() {
706 let _ = write!(sql, " MAXVALUE {max}");
707 }
708 if let Some(start) = self.start_with.as_ref() {
709 let _ = write!(sql, " START WITH {start}");
710 }
711 if let Some(cache) = self.cache_size {
712 let _ = write!(sql, " CACHE {cache}");
713 }
714 if self.cycle.unwrap_or(false) {
715 sql.push_str(" CYCLE");
716 }
717
718 sql.push(';');
719 sql
720 }
721
722 #[must_use]
724 pub fn drop_sequence_sql(&self) -> String {
725 format!(
726 "DROP SEQUENCE {};",
727 qualified_name(self.schema(), self.name())
728 )
729 }
730}
731
732fn append_view_with_options(
739 sql: &mut String,
740 with_opts: &super::view::ViewWithOption,
741) -> Option<String> {
742 let mut options = String::new();
743 let mut has_option = false;
744
745 let mut push_option = |name: &str, value: &dyn core::fmt::Display| {
746 if has_option {
747 options.push_str(", ");
748 } else {
749 has_option = true;
750 }
751 let _ = write!(options, "{name} = {value}");
752 };
753
754 if let Some(value) = with_opts.security_barrier {
755 push_option("security_barrier", &value);
756 }
757 if let Some(value) = with_opts.security_invoker {
758 push_option("security_invoker", &value);
759 }
760 if let Some(value) = with_opts.fillfactor {
761 push_option("fillfactor", &value);
762 }
763 if let Some(value) = with_opts.toast_tuple_target {
764 push_option("toast_tuple_target", &value);
765 }
766 if let Some(value) = with_opts.parallel_workers {
767 push_option("parallel_workers", &value);
768 }
769 if let Some(value) = with_opts.autovacuum_enabled {
770 push_option("autovacuum_enabled", &value);
771 }
772 if let Some(value) = with_opts.vacuum_index_cleanup.as_ref() {
773 push_option("vacuum_index_cleanup", value);
774 }
775 if let Some(value) = with_opts.vacuum_truncate {
776 push_option("vacuum_truncate", &value);
777 }
778 if let Some(value) = with_opts.autovacuum_vacuum_threshold {
779 push_option("autovacuum_vacuum_threshold", &value);
780 }
781 if let Some(value) = with_opts.autovacuum_vacuum_scale_factor {
782 push_option("autovacuum_vacuum_scale_factor", &value);
783 }
784 if let Some(value) = with_opts.autovacuum_vacuum_cost_delay {
785 push_option("autovacuum_vacuum_cost_delay", &value);
786 }
787 if let Some(value) = with_opts.autovacuum_vacuum_cost_limit {
788 push_option("autovacuum_vacuum_cost_limit", &value);
789 }
790 if let Some(value) = with_opts.autovacuum_freeze_min_age {
791 push_option("autovacuum_freeze_min_age", &value);
792 }
793 if let Some(value) = with_opts.autovacuum_freeze_max_age {
794 push_option("autovacuum_freeze_max_age", &value);
795 }
796 if let Some(value) = with_opts.autovacuum_freeze_table_age {
797 push_option("autovacuum_freeze_table_age", &value);
798 }
799 if let Some(value) = with_opts.autovacuum_multixact_freeze_min_age {
800 push_option("autovacuum_multixact_freeze_min_age", &value);
801 }
802 if let Some(value) = with_opts.autovacuum_multixact_freeze_max_age {
803 push_option("autovacuum_multixact_freeze_max_age", &value);
804 }
805 if let Some(value) = with_opts.autovacuum_multixact_freeze_table_age {
806 push_option("autovacuum_multixact_freeze_table_age", &value);
807 }
808 if let Some(value) = with_opts.log_autovacuum_min_duration {
809 push_option("log_autovacuum_min_duration", &value);
810 }
811 if let Some(value) = with_opts.user_catalog_table {
812 push_option("user_catalog_table", &value);
813 }
814
815 if has_option {
816 let _ = write!(sql, " WITH ({options})");
817 }
818
819 with_opts
820 .check_option
821 .as_deref()
822 .map(str::to_ascii_uppercase)
823}
824
825impl View {
826 #[must_use]
828 pub fn create_view_sql(&self) -> String {
829 let materialized = if self.materialized {
830 "MATERIALIZED "
831 } else {
832 ""
833 };
834
835 let Some(def) = self.definition.as_ref() else {
836 return format!(
837 "-- {}View {} has no definition",
838 materialized,
839 qualified_name(self.schema(), self.name())
840 );
841 };
842
843 let mut sql = String::with_capacity(def.len() + 64);
844 let _ = write!(
845 sql,
846 "CREATE {}VIEW {}",
847 materialized,
848 qualified_name(self.schema(), self.name()),
849 );
850
851 if let Some(using) = self.using.as_ref() {
852 let _ = write!(sql, " USING {using}");
853 }
854
855 let check_option_clause = self
856 .with
857 .as_ref()
858 .and_then(|with_opts| append_view_with_options(&mut sql, with_opts));
859
860 if let Some(tablespace) = self.tablespace.as_ref() {
861 let _ = write!(sql, " TABLESPACE {}", quote_ident(tablespace));
862 }
863
864 sql.push_str(" AS ");
865 sql.push_str(def);
866
867 if let Some(check_option) = check_option_clause {
868 let _ = write!(sql, " WITH {check_option} CHECK OPTION");
869 }
870
871 if self.materialized && matches!(self.with_no_data, Some(true)) {
872 sql.push_str(" WITH NO DATA");
873 }
874
875 sql.push(';');
876 sql
877 }
878
879 #[must_use]
881 pub fn drop_view_sql(&self) -> String {
882 let materialized = if self.materialized {
883 "MATERIALIZED "
884 } else {
885 ""
886 };
887 format!(
888 "DROP {}VIEW {};",
889 materialized,
890 qualified_name(self.schema(), self.name())
891 )
892 }
893}
894
895impl Policy {
900 #[must_use]
902 pub fn create_policy_sql(&self) -> String {
903 let mut sql = format!(
904 "CREATE POLICY {} ON {}",
905 quote_ident(self.name()),
906 qualified_name(self.schema(), self.table())
907 );
908
909 let as_clause = self.as_clause.as_deref().unwrap_or("PERMISSIVE");
910 let _ = write!(sql, " AS {}", as_clause.to_uppercase());
911
912 if let Some(r#for) = self.for_clause.as_ref() {
913 let _ = write!(sql, " FOR {}", r#for.to_uppercase());
914 }
915
916 if let Some(to) = self.to.as_ref()
917 && !to.is_empty()
918 {
919 let to_roles = to
920 .iter()
921 .map(|r| {
922 if r.eq_ignore_ascii_case("public") {
923 "PUBLIC".to_string()
924 } else {
925 quote_ident(r)
926 }
927 })
928 .collect::<Vec<_>>()
929 .join(", ");
930 let _ = write!(sql, " TO {to_roles}");
931 }
932
933 if let Some(using) = self.using.as_ref() {
934 let _ = write!(sql, " USING ({using})");
935 }
936
937 if let Some(with_check) = self.with_check.as_ref() {
938 let _ = write!(sql, " WITH CHECK ({with_check})");
939 }
940
941 sql.push(';');
942 sql
943 }
944
945 #[must_use]
947 pub fn drop_policy_sql(&self) -> String {
948 format!(
949 "DROP POLICY {} ON {};",
950 quote_ident(self.name()),
951 qualified_name(self.schema(), self.table())
952 )
953 }
954}
955
956impl Table {
961 #[must_use]
963 pub fn drop_table_sql(&self) -> String {
964 format!("DROP TABLE {};", qualified_name(self.schema(), self.name()))
965 }
966
967 #[must_use]
969 pub fn rename_table_sql(&self, new_name: &str) -> String {
970 format!(
971 "ALTER TABLE {} RENAME TO {};",
972 qualified_name(self.schema(), self.name()),
973 quote_ident(new_name)
974 )
975 }
976}
977
978impl PrimaryKey {
983 #[must_use]
985 pub fn to_constraint_sql(&self) -> String {
986 let cols = self
987 .columns
988 .iter()
989 .map(|c| quote_ident(c))
990 .collect::<Vec<_>>()
991 .join(", ");
992
993 format!(
994 "CONSTRAINT {} PRIMARY KEY({})",
995 quote_ident(self.name()),
996 cols
997 )
998 }
999
1000 #[must_use]
1002 pub fn add_pk_sql(&self) -> String {
1003 format!(
1004 "ALTER TABLE {} ADD {};",
1005 qualified_name(self.schema(), self.table()),
1006 self.to_constraint_sql()
1007 )
1008 }
1009
1010 #[must_use]
1012 pub fn drop_pk_sql(&self) -> String {
1013 format!(
1014 "ALTER TABLE {} DROP CONSTRAINT {};",
1015 qualified_name(self.schema(), self.table()),
1016 quote_ident(self.name())
1017 )
1018 }
1019}
1020
1021impl UniqueConstraint {
1026 fn constraint_sql(&self, space_before_columns: bool) -> String {
1027 let cols = self
1028 .columns
1029 .iter()
1030 .map(|c| quote_ident(c))
1031 .collect::<Vec<_>>()
1032 .join(", ");
1033
1034 let separator = if space_before_columns { " " } else { "" };
1035 let nulls_not_distinct = if self.nulls_not_distinct {
1036 " NULLS NOT DISTINCT"
1038 } else {
1039 ""
1040 };
1041 let mut sql = format!(
1042 "CONSTRAINT {} UNIQUE{}{}({})",
1043 quote_ident(self.name()),
1044 nulls_not_distinct,
1045 separator,
1046 cols
1047 );
1048 if self.deferrable || self.initially_deferred {
1049 sql.push_str(" DEFERRABLE");
1050 if self.initially_deferred {
1051 sql.push_str(" INITIALLY DEFERRED");
1052 }
1053 }
1054 sql
1055 }
1056
1057 #[must_use]
1059 pub fn to_constraint_sql(&self) -> String {
1060 self.constraint_sql(false)
1061 }
1062
1063 #[must_use]
1065 pub fn add_unique_sql(&self) -> String {
1066 format!(
1067 "ALTER TABLE {} ADD {};",
1068 qualified_name(self.schema(), self.table()),
1069 self.constraint_sql(true)
1070 )
1071 }
1072
1073 #[must_use]
1075 pub fn drop_unique_sql(&self) -> String {
1076 format!(
1077 "ALTER TABLE {} DROP CONSTRAINT {};",
1078 qualified_name(self.schema(), self.table()),
1079 quote_ident(self.name())
1080 )
1081 }
1082}
1083
1084impl CheckConstraint {
1089 #[must_use]
1091 pub fn to_constraint_sql(&self) -> String {
1092 format!(
1093 "CONSTRAINT {} CHECK ({})",
1094 quote_ident(self.name()),
1095 self.value
1096 )
1097 }
1098
1099 #[must_use]
1101 pub fn add_check_sql(&self) -> String {
1102 format!(
1103 "ALTER TABLE {} ADD {};",
1104 qualified_name(self.schema(), self.table()),
1105 self.to_constraint_sql()
1106 )
1107 }
1108
1109 #[must_use]
1111 pub fn drop_check_sql(&self) -> String {
1112 format!(
1113 "ALTER TABLE {} DROP CONSTRAINT {};",
1114 qualified_name(self.schema(), self.table()),
1115 quote_ident(self.name())
1116 )
1117 }
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122 use super::*;
1123 use crate::postgres::ddl::{ColumnDef, PrimaryKeyDef, TableDef};
1124 use std::borrow::Cow;
1125
1126 #[test]
1127 fn test_simple_create_table() {
1128 let table = TableDef::new("public", "users").into_table();
1129 let columns = [
1130 ColumnDef::new("public", "users", "id", "SERIAL")
1131 .not_null()
1132 .into_column(),
1133 ColumnDef::new("public", "users", "name", "TEXT")
1134 .not_null()
1135 .into_column(),
1136 ColumnDef::new("public", "users", "email", "TEXT").into_column(),
1137 ];
1138 const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
1139 let pk = PrimaryKeyDef::new("public", "users", "users_pkey")
1140 .columns(PK_COLS)
1141 .into_primary_key();
1142
1143 let sql = TableSql::new(&table)
1144 .columns(&columns)
1145 .primary_key(Some(&pk))
1146 .create_table_sql();
1147
1148 assert!(sql.contains("CREATE TABLE \"users\""));
1149 assert!(sql.contains("\"id\" SERIAL NOT NULL"));
1150 assert!(sql.contains("\"name\" TEXT NOT NULL"));
1151 assert!(sql.contains("\"email\" TEXT"));
1152 }
1153
1154 #[test]
1155 fn test_table_with_schema() {
1156 let table = TableDef::new("myschema", "users").into_table();
1157 let sql = TableSql::new(&table).create_table_sql();
1158 assert!(sql.contains("\"myschema\".\"users\""));
1159 }
1160
1161 #[test]
1162 fn test_unique_concurrently_index_word_order() {
1163 let mut index = Index::new(
1164 "public",
1165 "users",
1166 "users_email_idx",
1167 vec![IndexColumn::new("email")],
1168 )
1169 .unique();
1170 index.concurrently = true;
1171
1172 assert_eq!(
1173 index.create_index_sql(),
1174 "CREATE UNIQUE INDEX CONCURRENTLY \"users_email_idx\" ON \"users\"(\"email\");"
1175 );
1176 }
1177
1178 #[test]
1179 fn test_policy_uses_explicit_as_and_public_role() {
1180 let mut policy = Policy::new("public", "users", "users_policy");
1181 policy.to = Some(vec![Cow::Borrowed("public")]);
1182
1183 assert_eq!(
1184 policy.create_policy_sql(),
1185 "CREATE POLICY \"users_policy\" ON \"users\" AS PERMISSIVE TO PUBLIC;"
1186 );
1187 }
1188}