1use crate::alloc_prelude::*;
11use core::fmt::Write;
12
13use super::{
14 CheckConstraint, Column, Enum, ForeignKey, Generated, GeneratedType, Identity, IdentityType,
15 Index, IndexColumn, IndexColumnDef, Policy, PrimaryKey, Sequence, Table, UniqueConstraint,
16 View,
17};
18
19fn quote_ident(ident: &str) -> String {
20 format!("\"{}\"", ident.replace('"', "\"\""))
21}
22
23fn quote_literal(value: &str) -> String {
24 format!("'{}'", value.replace('\'', "''"))
25}
26
27fn schema_prefix(schema: &str) -> String {
28 if schema == "public" {
29 String::new()
30 } else {
31 format!("{}.", quote_ident(schema))
32 }
33}
34
35fn qualified_name(schema: &str, name: &str) -> String {
36 format!("{}{}", schema_prefix(schema), quote_ident(name))
37}
38
39fn quote_qualified_name(name: &str) -> String {
42 name.split('.')
43 .map(quote_ident)
44 .collect::<Vec<_>>()
45 .join(".")
46}
47
48fn type_name_needs_quoting(base: &str) -> bool {
55 !base.contains(' ')
56 && !base
57 .chars()
58 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
59}
60
61fn index_column_sql(column: &IndexColumn) -> String {
62 let mut sql = if column.is_expression {
63 format!("({})", column.value)
64 } else {
65 quote_ident(&column.value)
66 };
67
68 if let Some(op) = column.opclass.as_ref() {
69 let _ = write!(sql, " {op}");
70 }
71
72 sql.push_str(nulls_order_sql(column.asc, column.nulls_first));
73
74 sql
75}
76
77const fn nulls_order_sql(asc: bool, nulls_first: bool) -> &'static str {
80 match (asc, nulls_first) {
81 (true, false) => "",
82 (true, true) => " NULLS FIRST",
83 (false, true) => " DESC",
84 (false, false) => " DESC NULLS LAST",
85 }
86}
87
88#[derive(Clone, Debug)]
112pub struct TableSql<'a> {
113 pub table: &'a Table,
115 pub columns: &'a [Column],
117 pub primary_key: Option<&'a PrimaryKey>,
119 pub foreign_keys: &'a [ForeignKey],
121 pub unique_constraints: &'a [UniqueConstraint],
123 pub check_constraints: &'a [CheckConstraint],
125 pub indexes: &'a [Index],
127 pub policies: &'a [Policy],
129}
130
131impl<'a> TableSql<'a> {
132 #[must_use]
135 pub const fn new(table: &'a Table) -> Self {
136 Self {
137 table,
138 columns: &[],
139 primary_key: None,
140 foreign_keys: &[],
141 unique_constraints: &[],
142 check_constraints: &[],
143 indexes: &[],
144 policies: &[],
145 }
146 }
147
148 #[must_use]
150 pub const fn columns(mut self, columns: &'a [Column]) -> Self {
151 self.columns = columns;
152 self
153 }
154
155 #[must_use]
157 pub const fn primary_key(mut self, pk: Option<&'a PrimaryKey>) -> Self {
158 self.primary_key = pk;
159 self
160 }
161
162 #[must_use]
164 pub const fn foreign_keys(mut self, fks: &'a [ForeignKey]) -> Self {
165 self.foreign_keys = fks;
166 self
167 }
168
169 #[must_use]
171 pub const fn unique_constraints(mut self, uniques: &'a [UniqueConstraint]) -> Self {
172 self.unique_constraints = uniques;
173 self
174 }
175
176 #[must_use]
178 pub const fn check_constraints(mut self, checks: &'a [CheckConstraint]) -> Self {
179 self.check_constraints = checks;
180 self
181 }
182
183 #[must_use]
185 pub const fn indexes(mut self, indexes: &'a [Index]) -> Self {
186 self.indexes = indexes;
187 self
188 }
189
190 #[must_use]
192 pub const fn policies(mut self, policies: &'a [Policy]) -> Self {
193 self.policies = policies;
194 self
195 }
196
197 #[must_use]
205 pub fn create_table_sql(&self) -> String {
206 let table_kind = if self.table.is_temporary.unwrap_or(false) {
207 "TEMPORARY "
208 } else if self.table.is_unlogged.unwrap_or(false) {
209 "UNLOGGED "
210 } else {
211 ""
212 };
213 let mut sql = format!(
214 "CREATE {}TABLE {} (\n",
215 table_kind,
216 qualified_name(self.table.schema(), self.table.name())
217 );
218
219 let mut lines = Vec::new();
220
221 for column in self.columns {
223 lines.push(format!("\t{}", column.to_column_sql()));
224 }
225
226 if let Some(pk) = &self.primary_key {
228 let cols = pk
229 .columns
230 .iter()
231 .map(|c| quote_ident(c))
232 .collect::<Vec<_>>()
233 .join(", ");
234 if pk.name_explicit {
235 lines.push(format!(
236 "\tCONSTRAINT {} PRIMARY KEY({})",
237 quote_ident(pk.name()),
238 cols
239 ));
240 } else {
241 lines.push(format!("\tPRIMARY KEY({cols})"));
242 }
243 }
244
245 for fk in self.foreign_keys {
247 lines.push(format!("\t{}", fk.to_constraint_sql()));
248 }
249
250 for unique in self.unique_constraints {
252 lines.push(format!("\t{}", unique.to_constraint_sql()));
253 }
254
255 for check in self.check_constraints {
257 lines.push(format!(
258 "\tCONSTRAINT {} CHECK ({})",
259 quote_ident(check.name()),
260 check.value
261 ));
262 }
263
264 sql.push_str(&lines.join(",\n"));
265 sql.push('\n');
266 sql.push(')');
267
268 if let Some(inherits) = self.table.inherits.as_ref() {
269 let _ = write!(sql, " INHERITS ({})", quote_qualified_name(inherits));
270 }
271
272 if let Some(tablespace) = self.table.tablespace.as_ref() {
273 let _ = write!(sql, " TABLESPACE {}", quote_ident(tablespace));
274 }
275
276 sql.push(';');
277
278 sql
279 }
280
281 #[must_use]
283 pub fn drop_table_sql(&self) -> String {
284 format!(
285 "DROP TABLE {};",
286 qualified_name(self.table.schema(), self.table.name())
287 )
288 }
289
290 #[must_use]
292 pub fn create_indexes_sql(&self) -> Vec<String> {
293 self.indexes
294 .iter()
295 .map(super::index::Index::create_index_sql)
296 .collect()
297 }
298
299 #[must_use]
302 pub fn create_comments_sql(&self) -> Vec<String> {
303 let mut comments = Vec::new();
304 let table_name = qualified_name(self.table.schema(), self.table.name());
305
306 if let Some(comment) = self.table.comment.as_ref() {
307 comments.push(format!(
308 "COMMENT ON TABLE {} IS {};",
309 table_name,
310 quote_literal(comment)
311 ));
312 }
313
314 for column in self.columns {
315 if let Some(comment) = column.comment.as_ref() {
316 comments.push(format!(
317 "COMMENT ON COLUMN {}.{} IS {};",
318 table_name,
319 quote_ident(column.name()),
320 quote_literal(comment)
321 ));
322 }
323 }
324
325 comments
326 }
327
328 #[must_use]
331 pub fn enable_rls_sql(&self) -> Option<String> {
332 if self.table.is_rls_enabled.unwrap_or(false) {
333 Some(format!(
334 "ALTER TABLE {} ENABLE ROW LEVEL SECURITY;",
335 qualified_name(self.table.schema(), self.table.name())
336 ))
337 } else {
338 None
339 }
340 }
341
342 #[must_use]
344 pub fn create_policies_sql(&self) -> Vec<String> {
345 self.policies
346 .iter()
347 .map(super::policy::Policy::create_policy_sql)
348 .collect()
349 }
350}
351
352impl Column {
357 #[must_use]
367 pub fn type_sql(&self) -> String {
368 let mut sql = match self.type_schema.as_deref() {
369 Some(type_schema) => {
370 let raw = self.sql_type();
371 let (base, params) = raw
372 .find('(')
373 .map_or((raw, ""), |idx| (raw[..idx].trim_end(), &raw[idx..]));
374 if type_schema == "public" {
375 if type_name_needs_quoting(base) {
376 format!("{}{}", quote_ident(base), params)
377 } else {
378 raw.to_string()
379 }
380 } else {
381 format!(
382 "{}.{}{}",
383 quote_ident(type_schema),
384 quote_ident(base),
385 params
386 )
387 }
388 }
389 None => self.sql_type().to_string(),
390 };
391
392 if let Some(dimensions) = self.dimensions
393 && dimensions > 0
394 {
395 for _ in 0..dimensions {
396 sql.push_str("[]");
397 }
398 }
399
400 sql
401 }
402
403 #[must_use]
409 pub fn to_column_sql(&self) -> String {
410 let mut sql = format!("{} {}", quote_ident(self.name()), self.type_sql());
411
412 if let Some(collate) = self.collate.as_ref() {
416 let _ = write!(sql, " COLLATE {}", quote_ident(collate));
417 }
418
419 if let Some(identity) = &self.identity {
421 sql.push_str(&identity.to_sql());
422 }
423
424 if let Some(generated) = &self.generated {
426 sql.push_str(&generated.to_sql());
427 }
428
429 if self.identity.is_none()
431 && self.generated.is_none()
432 && let Some(default) = self.default.as_ref()
433 {
434 let _ = write!(sql, " DEFAULT {default}");
435 }
436
437 if self.not_null {
439 sql.push_str(" NOT NULL");
440 }
441
442 sql
443 }
444
445 #[must_use]
447 pub fn add_column_sql(&self) -> String {
448 format!(
449 "ALTER TABLE {} ADD COLUMN {};",
450 qualified_name(self.schema(), self.table()),
451 self.to_column_sql()
452 )
453 }
454
455 #[must_use]
457 pub fn drop_column_sql(&self) -> String {
458 format!(
459 "ALTER TABLE {} DROP COLUMN {};",
460 qualified_name(self.schema(), self.table()),
461 quote_ident(self.name())
462 )
463 }
464}
465
466impl Identity {
471 #[must_use]
475 pub fn to_sql(&self) -> String {
476 let identity_type = match self.type_ {
477 IdentityType::Always => "ALWAYS",
478 IdentityType::ByDefault => "BY DEFAULT",
479 };
480
481 let mut sql = format!(" GENERATED {identity_type} AS IDENTITY");
482
483 let mut options = Vec::new();
485
486 if let Some(increment) = self.increment.as_ref() {
487 options.push(format!("INCREMENT BY {increment}"));
488 }
489 if let Some(min) = self.min_value.as_ref() {
490 options.push(format!("MINVALUE {min}"));
491 }
492 if let Some(max) = self.max_value.as_ref() {
493 options.push(format!("MAXVALUE {max}"));
494 }
495 if let Some(start) = self.start_with.as_ref() {
496 options.push(format!("START WITH {start}"));
497 }
498 if let Some(cache) = self.cache {
499 options.push(format!("CACHE {cache}"));
500 }
501 if self.cycle.unwrap_or(false) {
502 options.push("CYCLE".to_string());
503 }
504
505 if !options.is_empty() {
506 let _ = write!(sql, " ({})", options.join(" "));
507 }
508
509 sql
510 }
511}
512
513impl Generated {
518 #[must_use]
521 pub fn to_sql(&self) -> String {
522 let gen_type = match self.gen_type {
523 GeneratedType::Stored => "STORED",
524 GeneratedType::Virtual => "VIRTUAL",
525 };
526 format!(" GENERATED ALWAYS AS ({}) {}", self.expression, gen_type)
527 }
528}
529
530impl ForeignKey {
535 #[must_use]
539 pub fn to_constraint_sql(&self) -> String {
540 let from_cols = self
541 .columns
542 .iter()
543 .map(|c| quote_ident(c))
544 .collect::<Vec<_>>()
545 .join(", ");
546
547 let to_cols = self
548 .columns_to
549 .iter()
550 .map(|c| quote_ident(c))
551 .collect::<Vec<_>>()
552 .join(", ");
553
554 let mut sql = format!(
555 "CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({})",
556 quote_ident(self.name()),
557 from_cols,
558 qualified_name(self.schema_to(), self.table_to()),
559 to_cols
560 );
561
562 if let Some(on_delete) = self.on_delete.as_ref()
563 && !on_delete.eq_ignore_ascii_case("NO ACTION")
564 {
565 let _ = write!(sql, " ON DELETE {}", on_delete.to_uppercase());
566 }
567
568 if let Some(on_update) = self.on_update.as_ref()
569 && !on_update.eq_ignore_ascii_case("NO ACTION")
570 {
571 let _ = write!(sql, " ON UPDATE {}", on_update.to_uppercase());
572 }
573
574 if self.deferrable || self.initially_deferred {
575 sql.push_str(" DEFERRABLE");
576 if self.initially_deferred {
577 sql.push_str(" INITIALLY DEFERRED");
578 }
579 }
580
581 sql
582 }
583
584 #[must_use]
586 pub fn add_fk_sql(&self) -> String {
587 format!(
588 "ALTER TABLE {} ADD {};",
589 qualified_name(self.schema(), self.table()),
590 self.to_constraint_sql()
591 )
592 }
593
594 #[must_use]
596 pub fn drop_fk_sql(&self) -> String {
597 format!(
598 "ALTER TABLE {} DROP CONSTRAINT {};",
599 qualified_name(self.schema(), self.table()),
600 quote_ident(self.name())
601 )
602 }
603}
604
605impl Index {
610 #[must_use]
613 pub fn create_index_sql(&self) -> String {
614 let unique = if self.is_unique { "UNIQUE " } else { "" };
615
616 let concurrently = if self.concurrently {
617 "CONCURRENTLY "
618 } else {
619 ""
620 };
621
622 let columns = self
623 .columns
624 .iter()
625 .map(index_column_sql)
626 .collect::<Vec<_>>()
627 .join(", ");
628
629 let using = self
630 .method
631 .as_ref()
632 .map(|m| format!(" USING {m}"))
633 .unwrap_or_default();
634
635 let mut sql = format!(
636 "CREATE {}INDEX {}{} ON {}{}({})",
637 unique,
638 concurrently,
639 quote_ident(self.name()),
640 qualified_name(self.schema(), self.table()),
641 using,
642 columns
643 );
644
645 if let Some(with) = self.with.as_ref() {
646 let _ = write!(sql, " WITH ({with})");
647 }
648
649 if let Some(where_clause) = self.where_clause.as_ref() {
650 let _ = write!(sql, " WHERE {where_clause}");
651 }
652
653 sql.push(';');
654 sql
655 }
656
657 #[must_use]
659 pub fn drop_index_sql(&self) -> String {
660 format!("DROP INDEX {};", qualified_name(self.schema(), self.name()))
661 }
662}
663
664impl IndexColumnDef {
665 #[must_use]
668 pub fn to_sql(&self) -> String {
669 let mut sql = if self.is_expression {
670 format!("({})", self.value)
671 } else {
672 quote_ident(self.value)
673 };
674
675 if let Some(op) = self.opclass.as_ref() {
676 let _ = write!(sql, " {op}");
677 }
678
679 sql.push_str(nulls_order_sql(self.asc, self.nulls_first));
680
681 sql
682 }
683}
684
685impl Enum {
690 #[must_use]
692 pub fn create_enum_sql(&self) -> String {
693 let values = self
694 .values
695 .iter()
696 .map(|v| quote_literal(v))
697 .collect::<Vec<_>>()
698 .join(", ");
699 format!(
700 "CREATE TYPE {} AS ENUM ({});",
701 qualified_name(self.schema(), self.name()),
702 values
703 )
704 }
705
706 #[must_use]
708 pub fn drop_enum_sql(&self) -> String {
709 format!("DROP TYPE {};", qualified_name(self.schema(), self.name()))
710 }
711
712 #[must_use]
714 pub fn add_value_sql(&self, value: &str, before: Option<&str>) -> String {
715 before.map_or_else(
716 || {
717 format!(
718 "ALTER TYPE {} ADD VALUE {};",
719 qualified_name(self.schema(), self.name()),
720 quote_literal(value)
721 )
722 },
723 |before_value| {
724 format!(
725 "ALTER TYPE {} ADD VALUE {} BEFORE {};",
726 qualified_name(self.schema(), self.name()),
727 quote_literal(value),
728 quote_literal(before_value)
729 )
730 },
731 )
732 }
733}
734
735impl Sequence {
740 #[must_use]
742 pub fn create_sequence_sql(&self) -> String {
743 let mut sql = format!(
744 "CREATE SEQUENCE {}",
745 qualified_name(self.schema(), self.name())
746 );
747
748 if let Some(inc) = self.increment_by.as_ref() {
749 let _ = write!(sql, " INCREMENT BY {inc}");
750 }
751 if let Some(min) = self.min_value.as_ref() {
752 let _ = write!(sql, " MINVALUE {min}");
753 }
754 if let Some(max) = self.max_value.as_ref() {
755 let _ = write!(sql, " MAXVALUE {max}");
756 }
757 if let Some(start) = self.start_with.as_ref() {
758 let _ = write!(sql, " START WITH {start}");
759 }
760 if let Some(cache) = self.cache_size {
761 let _ = write!(sql, " CACHE {cache}");
762 }
763 if self.cycle.unwrap_or(false) {
764 sql.push_str(" CYCLE");
765 }
766
767 sql.push(';');
768 sql
769 }
770
771 #[must_use]
773 pub fn drop_sequence_sql(&self) -> String {
774 format!(
775 "DROP SEQUENCE {};",
776 qualified_name(self.schema(), self.name())
777 )
778 }
779}
780
781fn append_view_with_options(
788 sql: &mut String,
789 with_opts: &super::view::ViewWithOption,
790) -> Option<String> {
791 let mut options = String::new();
792 let mut has_option = false;
793
794 let mut push_option = |name: &str, value: &dyn core::fmt::Display| {
795 if has_option {
796 options.push_str(", ");
797 } else {
798 has_option = true;
799 }
800 let _ = write!(options, "{name} = {value}");
801 };
802
803 if let Some(value) = with_opts.security_barrier {
804 push_option("security_barrier", &value);
805 }
806 if let Some(value) = with_opts.security_invoker {
807 push_option("security_invoker", &value);
808 }
809 if let Some(value) = with_opts.fillfactor {
810 push_option("fillfactor", &value);
811 }
812 if let Some(value) = with_opts.toast_tuple_target {
813 push_option("toast_tuple_target", &value);
814 }
815 if let Some(value) = with_opts.parallel_workers {
816 push_option("parallel_workers", &value);
817 }
818 if let Some(value) = with_opts.autovacuum_enabled {
819 push_option("autovacuum_enabled", &value);
820 }
821 if let Some(value) = with_opts.vacuum_index_cleanup.as_ref() {
822 push_option("vacuum_index_cleanup", value);
823 }
824 if let Some(value) = with_opts.vacuum_truncate {
825 push_option("vacuum_truncate", &value);
826 }
827 if let Some(value) = with_opts.autovacuum_vacuum_threshold {
828 push_option("autovacuum_vacuum_threshold", &value);
829 }
830 if let Some(value) = with_opts.autovacuum_vacuum_scale_factor {
831 push_option("autovacuum_vacuum_scale_factor", &value);
832 }
833 if let Some(value) = with_opts.autovacuum_vacuum_cost_delay {
834 push_option("autovacuum_vacuum_cost_delay", &value);
835 }
836 if let Some(value) = with_opts.autovacuum_vacuum_cost_limit {
837 push_option("autovacuum_vacuum_cost_limit", &value);
838 }
839 if let Some(value) = with_opts.autovacuum_freeze_min_age {
840 push_option("autovacuum_freeze_min_age", &value);
841 }
842 if let Some(value) = with_opts.autovacuum_freeze_max_age {
843 push_option("autovacuum_freeze_max_age", &value);
844 }
845 if let Some(value) = with_opts.autovacuum_freeze_table_age {
846 push_option("autovacuum_freeze_table_age", &value);
847 }
848 if let Some(value) = with_opts.autovacuum_multixact_freeze_min_age {
849 push_option("autovacuum_multixact_freeze_min_age", &value);
850 }
851 if let Some(value) = with_opts.autovacuum_multixact_freeze_max_age {
852 push_option("autovacuum_multixact_freeze_max_age", &value);
853 }
854 if let Some(value) = with_opts.autovacuum_multixact_freeze_table_age {
855 push_option("autovacuum_multixact_freeze_table_age", &value);
856 }
857 if let Some(value) = with_opts.log_autovacuum_min_duration {
858 push_option("log_autovacuum_min_duration", &value);
859 }
860 if let Some(value) = with_opts.user_catalog_table {
861 push_option("user_catalog_table", &value);
862 }
863
864 if has_option {
865 let _ = write!(sql, " WITH ({options})");
866 }
867
868 with_opts
869 .check_option
870 .as_deref()
871 .map(str::to_ascii_uppercase)
872}
873
874impl View {
875 #[must_use]
878 pub fn create_view_sql(&self) -> String {
879 let materialized = if self.materialized {
880 "MATERIALIZED "
881 } else {
882 ""
883 };
884
885 let Some(def) = self.definition.as_ref() else {
886 return format!(
887 "-- {}View {} has no definition",
888 materialized,
889 qualified_name(self.schema(), self.name())
890 );
891 };
892
893 let mut sql = String::with_capacity(def.len() + 64);
894 let _ = write!(
895 sql,
896 "CREATE {}VIEW {}",
897 materialized,
898 qualified_name(self.schema(), self.name()),
899 );
900
901 if let Some(using) = self.using.as_ref() {
902 let _ = write!(sql, " USING {using}");
903 }
904
905 let check_option_clause = self
906 .with
907 .as_ref()
908 .and_then(|with_opts| append_view_with_options(&mut sql, with_opts));
909
910 if let Some(tablespace) = self.tablespace.as_ref() {
911 let _ = write!(sql, " TABLESPACE {}", quote_ident(tablespace));
912 }
913
914 sql.push_str(" AS ");
915 sql.push_str(def);
916
917 if let Some(check_option) = check_option_clause {
918 let _ = write!(sql, " WITH {check_option} CHECK OPTION");
919 }
920
921 if self.materialized && matches!(self.with_no_data, Some(true)) {
922 sql.push_str(" WITH NO DATA");
923 }
924
925 sql.push(';');
926 sql
927 }
928
929 #[must_use]
931 pub fn drop_view_sql(&self) -> String {
932 let materialized = if self.materialized {
933 "MATERIALIZED "
934 } else {
935 ""
936 };
937 format!(
938 "DROP {}VIEW {};",
939 materialized,
940 qualified_name(self.schema(), self.name())
941 )
942 }
943}
944
945impl Policy {
950 #[must_use]
953 pub fn create_policy_sql(&self) -> String {
954 let mut sql = format!(
955 "CREATE POLICY {} ON {}",
956 quote_ident(self.name()),
957 qualified_name(self.schema(), self.table())
958 );
959
960 let as_clause = self.as_clause.as_deref().unwrap_or("PERMISSIVE");
961 let _ = write!(sql, " AS {}", as_clause.to_uppercase());
962
963 if let Some(r#for) = self.for_clause.as_ref() {
964 let _ = write!(sql, " FOR {}", r#for.to_uppercase());
965 }
966
967 if let Some(to) = self.to.as_ref()
968 && !to.is_empty()
969 {
970 let to_roles = to
971 .iter()
972 .map(|r| {
973 if ["public", "current_user", "current_role", "session_user"]
977 .iter()
978 .any(|keyword| r.eq_ignore_ascii_case(keyword))
979 {
980 r.to_ascii_uppercase()
981 } else {
982 quote_ident(r)
983 }
984 })
985 .collect::<Vec<_>>()
986 .join(", ");
987 let _ = write!(sql, " TO {to_roles}");
988 }
989
990 if let Some(using) = self.using.as_ref() {
991 let _ = write!(sql, " USING ({using})");
992 }
993
994 if let Some(with_check) = self.with_check.as_ref() {
995 let _ = write!(sql, " WITH CHECK ({with_check})");
996 }
997
998 sql.push(';');
999 sql
1000 }
1001
1002 #[must_use]
1004 pub fn drop_policy_sql(&self) -> String {
1005 format!(
1006 "DROP POLICY {} ON {};",
1007 quote_ident(self.name()),
1008 qualified_name(self.schema(), self.table())
1009 )
1010 }
1011}
1012
1013impl Table {
1018 #[must_use]
1020 pub fn drop_table_sql(&self) -> String {
1021 format!("DROP TABLE {};", qualified_name(self.schema(), self.name()))
1022 }
1023
1024 #[must_use]
1026 pub fn rename_table_sql(&self, new_name: &str) -> String {
1027 format!(
1028 "ALTER TABLE {} RENAME TO {};",
1029 qualified_name(self.schema(), self.name()),
1030 quote_ident(new_name)
1031 )
1032 }
1033}
1034
1035impl PrimaryKey {
1040 #[must_use]
1042 pub fn to_constraint_sql(&self) -> String {
1043 let cols = self
1044 .columns
1045 .iter()
1046 .map(|c| quote_ident(c))
1047 .collect::<Vec<_>>()
1048 .join(", ");
1049
1050 format!(
1051 "CONSTRAINT {} PRIMARY KEY({})",
1052 quote_ident(self.name()),
1053 cols
1054 )
1055 }
1056
1057 #[must_use]
1059 pub fn add_pk_sql(&self) -> String {
1060 format!(
1061 "ALTER TABLE {} ADD {};",
1062 qualified_name(self.schema(), self.table()),
1063 self.to_constraint_sql()
1064 )
1065 }
1066
1067 #[must_use]
1069 pub fn drop_pk_sql(&self) -> String {
1070 format!(
1071 "ALTER TABLE {} DROP CONSTRAINT {};",
1072 qualified_name(self.schema(), self.table()),
1073 quote_ident(self.name())
1074 )
1075 }
1076}
1077
1078impl UniqueConstraint {
1083 fn constraint_sql(&self, space_before_columns: bool) -> String {
1084 let cols = self
1085 .columns
1086 .iter()
1087 .map(|c| quote_ident(c))
1088 .collect::<Vec<_>>()
1089 .join(", ");
1090
1091 let separator = if space_before_columns { " " } else { "" };
1092 let nulls_not_distinct = if self.nulls_not_distinct {
1093 " NULLS NOT DISTINCT"
1095 } else {
1096 ""
1097 };
1098 let mut sql = format!(
1099 "CONSTRAINT {} UNIQUE{}{}({})",
1100 quote_ident(self.name()),
1101 nulls_not_distinct,
1102 separator,
1103 cols
1104 );
1105 if self.deferrable || self.initially_deferred {
1106 sql.push_str(" DEFERRABLE");
1107 if self.initially_deferred {
1108 sql.push_str(" INITIALLY DEFERRED");
1109 }
1110 }
1111 sql
1112 }
1113
1114 #[must_use]
1117 pub fn to_constraint_sql(&self) -> String {
1118 self.constraint_sql(false)
1119 }
1120
1121 #[must_use]
1123 pub fn add_unique_sql(&self) -> String {
1124 format!(
1125 "ALTER TABLE {} ADD {};",
1126 qualified_name(self.schema(), self.table()),
1127 self.constraint_sql(true)
1128 )
1129 }
1130
1131 #[must_use]
1133 pub fn drop_unique_sql(&self) -> String {
1134 format!(
1135 "ALTER TABLE {} DROP CONSTRAINT {};",
1136 qualified_name(self.schema(), self.table()),
1137 quote_ident(self.name())
1138 )
1139 }
1140}
1141
1142impl CheckConstraint {
1147 #[must_use]
1149 pub fn to_constraint_sql(&self) -> String {
1150 format!(
1151 "CONSTRAINT {} CHECK ({})",
1152 quote_ident(self.name()),
1153 self.value
1154 )
1155 }
1156
1157 #[must_use]
1159 pub fn add_check_sql(&self) -> String {
1160 format!(
1161 "ALTER TABLE {} ADD {};",
1162 qualified_name(self.schema(), self.table()),
1163 self.to_constraint_sql()
1164 )
1165 }
1166
1167 #[must_use]
1169 pub fn drop_check_sql(&self) -> String {
1170 format!(
1171 "ALTER TABLE {} DROP CONSTRAINT {};",
1172 qualified_name(self.schema(), self.table()),
1173 quote_ident(self.name())
1174 )
1175 }
1176}
1177
1178#[cfg(test)]
1179mod tests {
1180 use super::*;
1181 use crate::postgres::ddl::{ColumnDef, PrimaryKeyDef, TableDef};
1182 use std::borrow::Cow;
1183
1184 #[test]
1185 fn test_simple_create_table() {
1186 let table = TableDef::new("public", "users").into_table();
1187 let columns = [
1188 ColumnDef::new("public", "users", "id", "SERIAL")
1189 .not_null()
1190 .into_column(),
1191 ColumnDef::new("public", "users", "name", "TEXT")
1192 .not_null()
1193 .into_column(),
1194 ColumnDef::new("public", "users", "email", "TEXT").into_column(),
1195 ];
1196 const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
1197 let pk = PrimaryKeyDef::new("public", "users", "users_pkey")
1198 .columns(PK_COLS)
1199 .into_primary_key();
1200
1201 let sql = TableSql::new(&table)
1202 .columns(&columns)
1203 .primary_key(Some(&pk))
1204 .create_table_sql();
1205
1206 assert!(sql.contains("CREATE TABLE \"users\""));
1207 assert!(sql.contains("\"id\" SERIAL NOT NULL"));
1208 assert!(sql.contains("\"name\" TEXT NOT NULL"));
1209 assert!(sql.contains("\"email\" TEXT"));
1210 }
1211
1212 #[test]
1213 fn test_table_with_schema() {
1214 let table = TableDef::new("myschema", "users").into_table();
1215 let sql = TableSql::new(&table).create_table_sql();
1216 assert!(sql.contains("\"myschema\".\"users\""));
1217 }
1218
1219 #[test]
1220 fn test_unique_concurrently_index_word_order() {
1221 let mut index = Index::new(
1222 "public",
1223 "users",
1224 "users_email_idx",
1225 vec![IndexColumn::new("email")],
1226 )
1227 .unique();
1228 index.concurrently = true;
1229
1230 assert_eq!(
1231 index.create_index_sql(),
1232 "CREATE UNIQUE INDEX CONCURRENTLY \"users_email_idx\" ON \"users\"(\"email\");"
1233 );
1234 }
1235
1236 #[test]
1237 fn test_policy_uses_explicit_as_and_public_role() {
1238 let mut policy = Policy::new("public", "users", "users_policy");
1239 policy.to = Some(vec![Cow::Borrowed("public")]);
1240
1241 assert_eq!(
1242 policy.create_policy_sql(),
1243 "CREATE POLICY \"users_policy\" ON \"users\" AS PERMISSIVE TO PUBLIC;"
1244 );
1245 }
1246
1247 #[test]
1248 fn test_policy_role_keywords_are_not_quoted() {
1249 let mut policy = Policy::new("public", "users", "own_rows");
1250 policy.to = Some(vec![
1251 Cow::Borrowed("current_user"),
1252 Cow::Borrowed("session_user"),
1253 Cow::Borrowed("CURRENT_ROLE"),
1254 Cow::Borrowed("app_user"),
1255 ]);
1256
1257 assert_eq!(
1258 policy.create_policy_sql(),
1259 "CREATE POLICY \"own_rows\" ON \"users\" AS PERMISSIVE TO CURRENT_USER, SESSION_USER, CURRENT_ROLE, \"app_user\";"
1260 );
1261 }
1262}