Skip to main content

drizzle_types/postgres/ddl/
sql.rs

1//! SQL generation for PostgreSQL DDL types
2//!
3//! This module provides SQL generation methods for DDL types, enabling
4//! unified SQL output from both compile-time and runtime schema definitions.
5
6use crate::alloc_prelude::*;
7
8use super::{
9    CheckConstraint, Column, Enum, ForeignKey, Generated, GeneratedType, Identity, IdentityType,
10    Index, IndexColumnDef, Policy, PrimaryKey, Sequence, Table, UniqueConstraint, View,
11};
12
13// =============================================================================
14// Table SQL Generation
15// =============================================================================
16
17/// A complete table definition with all related entities for SQL generation
18#[derive(Clone, Debug)]
19pub struct TableSql<'a> {
20    pub table: &'a Table,
21    pub columns: &'a [Column],
22    pub primary_key: Option<&'a PrimaryKey>,
23    pub foreign_keys: &'a [ForeignKey],
24    pub unique_constraints: &'a [UniqueConstraint],
25    pub check_constraints: &'a [CheckConstraint],
26    pub indexes: &'a [Index],
27    pub policies: &'a [Policy],
28}
29
30impl<'a> TableSql<'a> {
31    /// Create a new TableSql for SQL generation
32    pub fn new(table: &'a Table) -> Self {
33        Self {
34            table,
35            columns: &[],
36            primary_key: None,
37            foreign_keys: &[],
38            unique_constraints: &[],
39            check_constraints: &[],
40            indexes: &[],
41            policies: &[],
42        }
43    }
44
45    /// Set columns
46    pub fn columns(mut self, columns: &'a [Column]) -> Self {
47        self.columns = columns;
48        self
49    }
50
51    /// Set primary key
52    pub fn primary_key(mut self, pk: Option<&'a PrimaryKey>) -> Self {
53        self.primary_key = pk;
54        self
55    }
56
57    /// Set foreign keys
58    pub fn foreign_keys(mut self, fks: &'a [ForeignKey]) -> Self {
59        self.foreign_keys = fks;
60        self
61    }
62
63    /// Set unique constraints
64    pub fn unique_constraints(mut self, uniques: &'a [UniqueConstraint]) -> Self {
65        self.unique_constraints = uniques;
66        self
67    }
68
69    /// Set check constraints
70    pub fn check_constraints(mut self, checks: &'a [CheckConstraint]) -> Self {
71        self.check_constraints = checks;
72        self
73    }
74
75    /// Set indexes
76    pub fn indexes(mut self, indexes: &'a [Index]) -> Self {
77        self.indexes = indexes;
78        self
79    }
80
81    /// Set policies
82    pub fn policies(mut self, policies: &'a [Policy]) -> Self {
83        self.policies = policies;
84        self
85    }
86
87    fn schema_prefix(&self) -> String {
88        if self.table.schema() != "public" {
89            format!("\"{}\".", self.table.schema())
90        } else {
91            String::new()
92        }
93    }
94
95    /// Generate CREATE TABLE SQL
96    pub fn create_table_sql(&self) -> String {
97        let schema_prefix = self.schema_prefix();
98        let mut sql = format!(
99            "CREATE TABLE {}\"{}\" (\n",
100            schema_prefix,
101            self.table.name()
102        );
103
104        let mut lines = Vec::new();
105
106        // Column definitions
107        for column in self.columns {
108            lines.push(format!("\t{}", column.to_column_sql()));
109        }
110
111        // Primary key
112        if let Some(pk) = &self.primary_key {
113            let cols = pk
114                .columns
115                .iter()
116                .map(|c| format!("\"{}\"", c))
117                .collect::<Vec<_>>()
118                .join(", ");
119            if pk.name_explicit {
120                lines.push(format!(
121                    "\tCONSTRAINT \"{}\" PRIMARY KEY({})",
122                    pk.name(),
123                    cols
124                ));
125            } else {
126                lines.push(format!("\tPRIMARY KEY({})", cols));
127            }
128        }
129
130        // Foreign keys
131        for fk in self.foreign_keys {
132            lines.push(format!("\t{}", fk.to_constraint_sql()));
133        }
134
135        // Unique constraints
136        for unique in self.unique_constraints {
137            let cols = unique
138                .columns
139                .iter()
140                .map(|c| format!("\"{}\"", c))
141                .collect::<Vec<_>>()
142                .join(", ");
143            lines.push(format!(
144                "\tCONSTRAINT \"{}\" UNIQUE({})",
145                unique.name(),
146                cols
147            ));
148        }
149
150        // Check constraints
151        for check in self.check_constraints {
152            lines.push(format!(
153                "\tCONSTRAINT \"{}\" CHECK ({})",
154                check.name(),
155                &check.value
156            ));
157        }
158
159        sql.push_str(&lines.join(",\n"));
160        sql.push_str("\n);");
161
162        sql
163    }
164
165    /// Generate DROP TABLE SQL
166    pub fn drop_table_sql(&self) -> String {
167        let schema_prefix = self.schema_prefix();
168        format!("DROP TABLE {}\"{}\";", schema_prefix, self.table.name())
169    }
170
171    /// Generate all related indexes
172    pub fn create_indexes_sql(&self) -> Vec<String> {
173        self.indexes.iter().map(|i| i.create_index_sql()).collect()
174    }
175
176    /// Generate RLS enable statement if needed
177    pub fn enable_rls_sql(&self) -> Option<String> {
178        if self.table.is_rls_enabled.unwrap_or(false) {
179            let schema_prefix = self.schema_prefix();
180            Some(format!(
181                "ALTER TABLE {}\"{}\" ENABLE ROW LEVEL SECURITY;",
182                schema_prefix,
183                self.table.name()
184            ))
185        } else {
186            None
187        }
188    }
189
190    /// Generate all policies
191    pub fn create_policies_sql(&self) -> Vec<String> {
192        self.policies
193            .iter()
194            .map(|p| p.create_policy_sql())
195            .collect()
196    }
197}
198
199// =============================================================================
200// Column SQL Generation
201// =============================================================================
202
203impl Column {
204    /// Generate the column definition SQL (without leading/trailing punctuation)
205    pub fn to_column_sql(&self) -> String {
206        let mut sql = format!("\"{}\" {}", self.name(), self.sql_type());
207
208        // Handle identity columns
209        if let Some(identity) = &self.identity {
210            sql.push_str(&identity.to_sql());
211        }
212
213        // Handle generated columns
214        if let Some(generated) = &self.generated {
215            sql.push_str(&generated.to_sql());
216        }
217
218        // Default value (skip if identity or generated - PostgreSQL doesn't allow both)
219        if self.identity.is_none()
220            && self.generated.is_none()
221            && let Some(default) = self.default.as_ref()
222        {
223            sql.push_str(&format!(" DEFAULT {}", default));
224        }
225
226        // NOT NULL
227        if self.not_null {
228            sql.push_str(" NOT NULL");
229        }
230
231        sql
232    }
233
234    /// Generate ADD COLUMN SQL
235    pub fn add_column_sql(&self) -> String {
236        let schema_prefix = if self.schema() != "public" {
237            format!("\"{}\".", self.schema())
238        } else {
239            String::new()
240        };
241        format!(
242            "ALTER TABLE {}\"{}\" ADD COLUMN {};",
243            schema_prefix,
244            self.table(),
245            self.to_column_sql()
246        )
247    }
248
249    /// Generate DROP COLUMN SQL
250    pub fn drop_column_sql(&self) -> String {
251        let schema_prefix = if self.schema() != "public" {
252            format!("\"{}\".", self.schema())
253        } else {
254            String::new()
255        };
256        format!(
257            "ALTER TABLE {}\"{}\" DROP COLUMN \"{}\";",
258            schema_prefix,
259            self.table(),
260            self.name()
261        )
262    }
263}
264
265// =============================================================================
266// Identity Column SQL
267// =============================================================================
268
269impl Identity {
270    /// Generate the GENERATED AS IDENTITY clause
271    pub fn to_sql(&self) -> String {
272        let identity_type = match self.type_ {
273            IdentityType::Always => "ALWAYS",
274            IdentityType::ByDefault => "BY DEFAULT",
275        };
276
277        let mut sql = format!(" GENERATED {} AS IDENTITY", identity_type);
278
279        // Add sequence options if any are specified
280        let mut options = Vec::new();
281
282        if let Some(increment) = self.increment.as_ref() {
283            options.push(format!("INCREMENT BY {}", increment));
284        }
285        if let Some(min) = self.min_value.as_ref() {
286            options.push(format!("MINVALUE {}", min));
287        }
288        if let Some(max) = self.max_value.as_ref() {
289            options.push(format!("MAXVALUE {}", max));
290        }
291        if let Some(start) = self.start_with.as_ref() {
292            options.push(format!("START WITH {}", start));
293        }
294        if let Some(cache) = self.cache {
295            options.push(format!("CACHE {}", cache));
296        }
297        if self.cycle.unwrap_or(false) {
298            options.push("CYCLE".to_string());
299        }
300
301        if !options.is_empty() {
302            sql.push_str(&format!(" ({})", options.join(" ")));
303        }
304
305        sql
306    }
307}
308
309// =============================================================================
310// Generated Column SQL
311// =============================================================================
312
313impl Generated {
314    /// Generate the GENERATED clause SQL
315    pub fn to_sql(&self) -> String {
316        let gen_type = match self.gen_type {
317            GeneratedType::Stored => "STORED",
318        };
319        format!(" GENERATED ALWAYS AS ({}) {}", self.expression, gen_type)
320    }
321}
322
323// =============================================================================
324// Foreign Key SQL Generation
325// =============================================================================
326
327impl ForeignKey {
328    /// Generate the CONSTRAINT ... FOREIGN KEY clause SQL
329    pub fn to_constraint_sql(&self) -> String {
330        let from_cols = self
331            .columns
332            .iter()
333            .map(|c| format!("\"{}\"", c))
334            .collect::<Vec<_>>()
335            .join(", ");
336
337        let to_cols = self
338            .columns_to
339            .iter()
340            .map(|c| format!("\"{}\"", c))
341            .collect::<Vec<_>>()
342            .join(", ");
343
344        let to_schema_prefix = if self.schema_to() != "public" {
345            format!("\"{}\".", self.schema_to())
346        } else {
347            String::new()
348        };
349
350        let mut sql = format!(
351            "CONSTRAINT \"{}\" FOREIGN KEY ({}) REFERENCES {}\"{}\"({})",
352            self.name(),
353            from_cols,
354            to_schema_prefix,
355            self.table_to(),
356            to_cols
357        );
358
359        if let Some(on_update) = self.on_update.as_ref()
360            && on_update != "NO ACTION"
361        {
362            sql.push_str(&format!(" ON UPDATE {}", on_update.to_uppercase()));
363        }
364
365        if let Some(on_delete) = self.on_delete.as_ref()
366            && on_delete != "NO ACTION"
367        {
368            sql.push_str(&format!(" ON DELETE {}", on_delete.to_uppercase()));
369        }
370
371        sql
372    }
373
374    /// Generate ADD FOREIGN KEY SQL
375    pub fn add_fk_sql(&self) -> String {
376        let schema_prefix = if self.schema() != "public" {
377            format!("\"{}\".", self.schema())
378        } else {
379            String::new()
380        };
381        format!(
382            "ALTER TABLE {}\"{}\" ADD {};",
383            schema_prefix,
384            self.table(),
385            self.to_constraint_sql()
386        )
387    }
388
389    /// Generate DROP FOREIGN KEY SQL
390    pub fn drop_fk_sql(&self) -> String {
391        let schema_prefix = if self.schema() != "public" {
392            format!("\"{}\".", self.schema())
393        } else {
394            String::new()
395        };
396        format!(
397            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
398            schema_prefix,
399            self.table(),
400            self.name()
401        )
402    }
403}
404
405// =============================================================================
406// Index SQL Generation
407// =============================================================================
408
409impl Index {
410    /// Generate CREATE INDEX SQL
411    pub fn create_index_sql(&self) -> String {
412        let unique = if self.is_unique { "UNIQUE " } else { "" };
413
414        let concurrently = if self.concurrently {
415            "CONCURRENTLY "
416        } else {
417            ""
418        };
419
420        let schema_prefix = if self.schema() != "public" {
421            format!("\"{}\".", self.schema())
422        } else {
423            String::new()
424        };
425
426        let columns = self
427            .columns
428            .iter()
429            .map(|c| c.to_sql())
430            .collect::<Vec<_>>()
431            .join(", ");
432
433        let using = self
434            .method
435            .as_ref()
436            .map(|m| format!(" USING {}", m))
437            .unwrap_or_default();
438
439        let mut sql = format!(
440            "CREATE {}{}INDEX \"{}\" ON {}\"{}\"{}({});",
441            unique,
442            concurrently,
443            self.name(),
444            schema_prefix,
445            self.table(),
446            using,
447            columns
448        );
449
450        if let Some(where_clause) = self.where_clause.as_ref() {
451            // Remove trailing semicolon to add WHERE
452            sql.pop();
453            sql.push_str(&format!(" WHERE {};", where_clause));
454        }
455
456        sql
457    }
458
459    /// Generate DROP INDEX SQL
460    pub fn drop_index_sql(&self) -> String {
461        let schema_prefix = if self.schema() != "public" {
462            format!("\"{}\".", self.schema())
463        } else {
464            String::new()
465        };
466        format!("DROP INDEX {}\"{}\";", schema_prefix, self.name())
467    }
468}
469
470impl IndexColumnDef {
471    /// Generate the column reference for an index
472    pub fn to_sql(&self) -> String {
473        let mut sql = if self.is_expression {
474            format!("({})", self.value)
475        } else {
476            format!("\"{}\"", self.value)
477        };
478
479        if let Some(op) = self.opclass.as_ref() {
480            sql.push_str(&format!(" {}", op));
481        }
482
483        if !self.asc {
484            sql.push_str(" DESC");
485        }
486
487        if self.nulls_first {
488            sql.push_str(" NULLS FIRST");
489        }
490
491        sql
492    }
493}
494
495// =============================================================================
496// Enum SQL Generation
497// =============================================================================
498
499impl Enum {
500    /// Generate CREATE TYPE ... AS ENUM SQL
501    pub fn create_enum_sql(&self) -> String {
502        let schema_prefix = if self.schema() != "public" {
503            format!("\"{}\".", self.schema())
504        } else {
505            String::new()
506        };
507        let values = self
508            .values
509            .iter()
510            .map(|v| format!("'{}'", v))
511            .collect::<Vec<_>>()
512            .join(", ");
513        format!(
514            "CREATE TYPE {}\"{}\" AS ENUM ({});",
515            schema_prefix,
516            self.name(),
517            values
518        )
519    }
520
521    /// Generate DROP TYPE SQL
522    pub fn drop_enum_sql(&self) -> String {
523        let schema_prefix = if self.schema() != "public" {
524            format!("\"{}\".", self.schema())
525        } else {
526            String::new()
527        };
528        format!("DROP TYPE {}\"{}\";", schema_prefix, self.name())
529    }
530
531    /// Generate ALTER TYPE ... ADD VALUE SQL
532    pub fn add_value_sql(&self, value: &str, before: Option<&str>) -> String {
533        let schema_prefix = if self.schema() != "public" {
534            format!("\"{}\".", self.schema())
535        } else {
536            String::new()
537        };
538        if let Some(before_value) = before {
539            format!(
540                "ALTER TYPE {}\"{}\" ADD VALUE '{}' BEFORE '{}';",
541                schema_prefix,
542                self.name(),
543                value,
544                before_value
545            )
546        } else {
547            format!(
548                "ALTER TYPE {}\"{}\" ADD VALUE '{}';",
549                schema_prefix,
550                self.name(),
551                value
552            )
553        }
554    }
555}
556
557// =============================================================================
558// Sequence SQL Generation
559// =============================================================================
560
561impl Sequence {
562    /// Generate CREATE SEQUENCE SQL
563    pub fn create_sequence_sql(&self) -> String {
564        let schema_prefix = if self.schema() != "public" {
565            format!("\"{}\".", self.schema())
566        } else {
567            String::new()
568        };
569
570        let mut sql = format!("CREATE SEQUENCE {}\"{}\"", schema_prefix, self.name());
571
572        if let Some(inc) = self.increment_by.as_ref() {
573            sql.push_str(&format!(" INCREMENT BY {}", inc));
574        }
575        if let Some(min) = self.min_value.as_ref() {
576            sql.push_str(&format!(" MINVALUE {}", min));
577        }
578        if let Some(max) = self.max_value.as_ref() {
579            sql.push_str(&format!(" MAXVALUE {}", max));
580        }
581        if let Some(start) = self.start_with.as_ref() {
582            sql.push_str(&format!(" START WITH {}", start));
583        }
584        if let Some(cache) = self.cache_size {
585            sql.push_str(&format!(" CACHE {}", cache));
586        }
587        if self.cycle.unwrap_or(false) {
588            sql.push_str(" CYCLE");
589        }
590
591        sql.push(';');
592        sql
593    }
594
595    /// Generate DROP SEQUENCE SQL
596    pub fn drop_sequence_sql(&self) -> String {
597        let schema_prefix = if self.schema() != "public" {
598            format!("\"{}\".", self.schema())
599        } else {
600            String::new()
601        };
602        format!("DROP SEQUENCE {}\"{}\";", schema_prefix, self.name())
603    }
604}
605
606// =============================================================================
607// View SQL Generation
608// =============================================================================
609
610impl View {
611    /// Generate CREATE VIEW SQL
612    pub fn create_view_sql(&self) -> String {
613        let schema_prefix = if self.schema() != "public" {
614            format!("\"{}\".", self.schema())
615        } else {
616            String::new()
617        };
618
619        let materialized = if self.materialized {
620            "MATERIALIZED "
621        } else {
622            ""
623        };
624
625        let Some(def) = self.definition.as_ref() else {
626            return format!(
627                "-- {}View {}\"{}\" has no definition",
628                materialized,
629                schema_prefix,
630                self.name()
631            );
632        };
633
634        use core::fmt::Write;
635
636        let mut sql = String::with_capacity(def.len() + 64);
637        let _ = write!(
638            sql,
639            "CREATE {}VIEW {}\"{}\"",
640            materialized,
641            schema_prefix,
642            self.name(),
643        );
644
645        if let Some(using) = self.using.as_ref() {
646            let _ = write!(sql, " USING {}", using);
647        }
648
649        let mut check_option_clause = None;
650        if let Some(with_opts) = self.with.as_ref() {
651            let mut options = String::new();
652            let mut has_option = false;
653
654            macro_rules! push_option {
655                ($name:expr, $value:expr) => {{
656                    if has_option {
657                        options.push_str(", ");
658                    } else {
659                        has_option = true;
660                    }
661                    let _ = write!(options, "{} = {}", $name, $value);
662                }};
663            }
664
665            if let Some(check_option) = with_opts.check_option.as_deref() {
666                check_option_clause = Some(check_option.to_ascii_uppercase());
667            }
668
669            if let Some(value) = with_opts.security_barrier {
670                push_option!("security_barrier", value);
671            }
672            if let Some(value) = with_opts.security_invoker {
673                push_option!("security_invoker", value);
674            }
675            if let Some(value) = with_opts.fillfactor {
676                push_option!("fillfactor", value);
677            }
678            if let Some(value) = with_opts.toast_tuple_target {
679                push_option!("toast_tuple_target", value);
680            }
681            if let Some(value) = with_opts.parallel_workers {
682                push_option!("parallel_workers", value);
683            }
684            if let Some(value) = with_opts.autovacuum_enabled {
685                push_option!("autovacuum_enabled", value);
686            }
687            if let Some(value) = with_opts.vacuum_index_cleanup.as_ref() {
688                push_option!("vacuum_index_cleanup", value);
689            }
690            if let Some(value) = with_opts.vacuum_truncate {
691                push_option!("vacuum_truncate", value);
692            }
693            if let Some(value) = with_opts.autovacuum_vacuum_threshold {
694                push_option!("autovacuum_vacuum_threshold", value);
695            }
696            if let Some(value) = with_opts.autovacuum_vacuum_scale_factor {
697                push_option!("autovacuum_vacuum_scale_factor", value);
698            }
699            if let Some(value) = with_opts.autovacuum_vacuum_cost_delay {
700                push_option!("autovacuum_vacuum_cost_delay", value);
701            }
702            if let Some(value) = with_opts.autovacuum_vacuum_cost_limit {
703                push_option!("autovacuum_vacuum_cost_limit", value);
704            }
705            if let Some(value) = with_opts.autovacuum_freeze_min_age {
706                push_option!("autovacuum_freeze_min_age", value);
707            }
708            if let Some(value) = with_opts.autovacuum_freeze_max_age {
709                push_option!("autovacuum_freeze_max_age", value);
710            }
711            if let Some(value) = with_opts.autovacuum_freeze_table_age {
712                push_option!("autovacuum_freeze_table_age", value);
713            }
714            if let Some(value) = with_opts.autovacuum_multixact_freeze_min_age {
715                push_option!("autovacuum_multixact_freeze_min_age", value);
716            }
717            if let Some(value) = with_opts.autovacuum_multixact_freeze_max_age {
718                push_option!("autovacuum_multixact_freeze_max_age", value);
719            }
720            if let Some(value) = with_opts.autovacuum_multixact_freeze_table_age {
721                push_option!("autovacuum_multixact_freeze_table_age", value);
722            }
723            if let Some(value) = with_opts.log_autovacuum_min_duration {
724                push_option!("log_autovacuum_min_duration", value);
725            }
726            if let Some(value) = with_opts.user_catalog_table {
727                push_option!("user_catalog_table", value);
728            }
729
730            if has_option {
731                let _ = write!(sql, " WITH ({})", options);
732            }
733        }
734
735        if let Some(tablespace) = self.tablespace.as_ref() {
736            let _ = write!(sql, " TABLESPACE \"{}\"", tablespace);
737        }
738
739        sql.push_str(" AS ");
740        sql.push_str(def);
741
742        if let Some(check_option) = check_option_clause {
743            let _ = write!(sql, " WITH {} CHECK OPTION", check_option);
744        }
745
746        if self.materialized && matches!(self.with_no_data, Some(true)) {
747            sql.push_str(" WITH NO DATA");
748        }
749
750        sql.push(';');
751        sql
752    }
753
754    /// Generate DROP VIEW SQL
755    pub fn drop_view_sql(&self) -> String {
756        let schema_prefix = if self.schema() != "public" {
757            format!("\"{}\".", self.schema())
758        } else {
759            String::new()
760        };
761        let materialized = if self.materialized {
762            "MATERIALIZED "
763        } else {
764            ""
765        };
766        format!(
767            "DROP {}VIEW {}\"{}\";",
768            materialized,
769            schema_prefix,
770            self.name()
771        )
772    }
773}
774
775// =============================================================================
776// Policy SQL Generation
777// =============================================================================
778
779impl Policy {
780    /// Generate CREATE POLICY SQL
781    pub fn create_policy_sql(&self) -> String {
782        let schema_prefix = if self.schema() != "public" {
783            format!("\"{}\".", self.schema())
784        } else {
785            String::new()
786        };
787
788        let mut sql = format!(
789            "CREATE POLICY \"{}\" ON {}\"{}\"",
790            self.name(),
791            schema_prefix,
792            self.table()
793        );
794
795        if let Some(r#for) = self.for_clause.as_ref() {
796            sql.push_str(&format!(" FOR {}", r#for.to_uppercase()));
797        }
798
799        if let Some(to) = self.to.as_ref()
800            && !to.is_empty()
801        {
802            let to_roles = to
803                .iter()
804                .map(|r| {
805                    if *r == "public" {
806                        "PUBLIC".to_string()
807                    } else {
808                        format!("\"{}\"", r)
809                    }
810                })
811                .collect::<Vec<_>>()
812                .join(", ");
813            sql.push_str(&format!(" TO {}", to_roles));
814        }
815
816        if let Some(using) = self.using.as_ref() {
817            sql.push_str(&format!(" USING ({})", using));
818        }
819
820        if let Some(with_check) = self.with_check.as_ref() {
821            sql.push_str(&format!(" WITH CHECK ({})", with_check));
822        }
823
824        sql.push(';');
825        sql
826    }
827
828    /// Generate DROP POLICY SQL
829    pub fn drop_policy_sql(&self) -> String {
830        let schema_prefix = if self.schema() != "public" {
831            format!("\"{}\".", self.schema())
832        } else {
833            String::new()
834        };
835        format!(
836            "DROP POLICY \"{}\" ON {}\"{}\";",
837            self.name(),
838            schema_prefix,
839            self.table()
840        )
841    }
842}
843
844// =============================================================================
845// Table-level utilities
846// =============================================================================
847
848impl Table {
849    /// Generate DROP TABLE SQL
850    pub fn drop_table_sql(&self) -> String {
851        let schema_prefix = if self.schema() != "public" {
852            format!("\"{}\".", self.schema())
853        } else {
854            String::new()
855        };
856        format!("DROP TABLE {}\"{}\";", schema_prefix, self.name())
857    }
858
859    /// Generate RENAME TABLE SQL
860    pub fn rename_table_sql(&self, new_name: &str) -> String {
861        let schema_prefix = if self.schema() != "public" {
862            format!("\"{}\".", self.schema())
863        } else {
864            String::new()
865        };
866        format!(
867            "ALTER TABLE {}\"{}\" RENAME TO \"{}\";",
868            schema_prefix,
869            self.name(),
870            new_name
871        )
872    }
873}
874
875// =============================================================================
876// Primary Key SQL Generation
877// =============================================================================
878
879impl PrimaryKey {
880    /// Generate the PRIMARY KEY constraint clause
881    pub fn to_constraint_sql(&self) -> String {
882        let cols = self
883            .columns
884            .iter()
885            .map(|c| format!("\"{}\"", c))
886            .collect::<Vec<_>>()
887            .join(", ");
888
889        format!("CONSTRAINT \"{}\" PRIMARY KEY({})", self.name(), cols)
890    }
891
892    /// Generate ADD PRIMARY KEY SQL
893    pub fn add_pk_sql(&self) -> String {
894        let schema_prefix = if self.schema() != "public" {
895            format!("\"{}\".", self.schema())
896        } else {
897            String::new()
898        };
899        format!(
900            "ALTER TABLE {}\"{}\" ADD {};",
901            schema_prefix,
902            self.table(),
903            self.to_constraint_sql()
904        )
905    }
906
907    /// Generate DROP PRIMARY KEY SQL
908    pub fn drop_pk_sql(&self) -> String {
909        let schema_prefix = if self.schema() != "public" {
910            format!("\"{}\".", self.schema())
911        } else {
912            String::new()
913        };
914        format!(
915            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
916            schema_prefix,
917            self.table(),
918            self.name()
919        )
920    }
921}
922
923// =============================================================================
924// Unique Constraint SQL Generation
925// =============================================================================
926
927impl UniqueConstraint {
928    /// Generate the UNIQUE constraint clause
929    pub fn to_constraint_sql(&self) -> String {
930        let cols = self
931            .columns
932            .iter()
933            .map(|c| format!("\"{}\"", c))
934            .collect::<Vec<_>>()
935            .join(", ");
936
937        format!("CONSTRAINT \"{}\" UNIQUE({})", self.name(), cols)
938    }
939
940    /// Generate ADD UNIQUE SQL
941    pub fn add_unique_sql(&self) -> String {
942        let schema_prefix = if self.schema() != "public" {
943            format!("\"{}\".", self.schema())
944        } else {
945            String::new()
946        };
947        format!(
948            "ALTER TABLE {}\"{}\" ADD {};",
949            schema_prefix,
950            self.table(),
951            self.to_constraint_sql()
952        )
953    }
954
955    /// Generate DROP UNIQUE SQL
956    pub fn drop_unique_sql(&self) -> String {
957        let schema_prefix = if self.schema() != "public" {
958            format!("\"{}\".", self.schema())
959        } else {
960            String::new()
961        };
962        format!(
963            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
964            schema_prefix,
965            self.table(),
966            self.name()
967        )
968    }
969}
970
971// =============================================================================
972// Check Constraint SQL Generation
973// =============================================================================
974
975impl CheckConstraint {
976    /// Generate the CHECK constraint clause
977    pub fn to_constraint_sql(&self) -> String {
978        format!("CONSTRAINT \"{}\" CHECK ({})", self.name(), &self.value)
979    }
980
981    /// Generate ADD CHECK SQL
982    pub fn add_check_sql(&self) -> String {
983        let schema_prefix = if self.schema() != "public" {
984            format!("\"{}\".", self.schema())
985        } else {
986            String::new()
987        };
988        format!(
989            "ALTER TABLE {}\"{}\" ADD {};",
990            schema_prefix,
991            self.table(),
992            self.to_constraint_sql()
993        )
994    }
995
996    /// Generate DROP CHECK SQL
997    pub fn drop_check_sql(&self) -> String {
998        let schema_prefix = if self.schema() != "public" {
999            format!("\"{}\".", self.schema())
1000        } else {
1001            String::new()
1002        };
1003        format!(
1004            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
1005            schema_prefix,
1006            self.table(),
1007            self.name()
1008        )
1009    }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015    use crate::postgres::ddl::{ColumnDef, PrimaryKeyDef, TableDef};
1016    use std::borrow::Cow;
1017
1018    #[test]
1019    fn test_simple_create_table() {
1020        let table = TableDef::new("public", "users").into_table();
1021        let columns = [
1022            ColumnDef::new("public", "users", "id", "SERIAL")
1023                .not_null()
1024                .into_column(),
1025            ColumnDef::new("public", "users", "name", "TEXT")
1026                .not_null()
1027                .into_column(),
1028            ColumnDef::new("public", "users", "email", "TEXT").into_column(),
1029        ];
1030        const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
1031        let pk = PrimaryKeyDef::new("public", "users", "users_pkey")
1032            .columns(PK_COLS)
1033            .into_primary_key();
1034
1035        let sql = TableSql::new(&table)
1036            .columns(&columns)
1037            .primary_key(Some(&pk))
1038            .create_table_sql();
1039
1040        assert!(sql.contains("CREATE TABLE \"users\""));
1041        assert!(sql.contains("\"id\" SERIAL NOT NULL"));
1042        assert!(sql.contains("\"name\" TEXT NOT NULL"));
1043        assert!(sql.contains("\"email\" TEXT"));
1044    }
1045
1046    #[test]
1047    fn test_table_with_schema() {
1048        let table = TableDef::new("myschema", "users").into_table();
1049        let sql = TableSql::new(&table).create_table_sql();
1050        assert!(sql.contains("\"myschema\".\"users\""));
1051    }
1052}