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        let mut sql = format!(
635            "CREATE {}VIEW {}\"{}\"",
636            materialized,
637            schema_prefix,
638            self.name(),
639        );
640
641        if let Some(using) = self.using.as_ref() {
642            sql.push_str(&format!(" USING {}", using));
643        }
644
645        let mut check_option_clause = None;
646        if let Some(with_opts) = self.with.as_ref() {
647            let mut options = Vec::new();
648
649            if let Some(check_option) = with_opts.check_option.as_ref() {
650                check_option_clause = Some(check_option.as_ref().to_uppercase());
651            }
652
653            if let Some(value) = with_opts.security_barrier {
654                options.push(format!("security_barrier = {}", value));
655            }
656            if let Some(value) = with_opts.security_invoker {
657                options.push(format!("security_invoker = {}", value));
658            }
659            if let Some(value) = with_opts.fillfactor {
660                options.push(format!("fillfactor = {}", value));
661            }
662            if let Some(value) = with_opts.toast_tuple_target {
663                options.push(format!("toast_tuple_target = {}", value));
664            }
665            if let Some(value) = with_opts.parallel_workers {
666                options.push(format!("parallel_workers = {}", value));
667            }
668            if let Some(value) = with_opts.autovacuum_enabled {
669                options.push(format!("autovacuum_enabled = {}", value));
670            }
671            if let Some(value) = with_opts.vacuum_index_cleanup.as_ref() {
672                options.push(format!("vacuum_index_cleanup = {}", value));
673            }
674            if let Some(value) = with_opts.vacuum_truncate {
675                options.push(format!("vacuum_truncate = {}", value));
676            }
677            if let Some(value) = with_opts.autovacuum_vacuum_threshold {
678                options.push(format!("autovacuum_vacuum_threshold = {}", value));
679            }
680            if let Some(value) = with_opts.autovacuum_vacuum_scale_factor {
681                options.push(format!("autovacuum_vacuum_scale_factor = {}", value));
682            }
683            if let Some(value) = with_opts.autovacuum_vacuum_cost_delay {
684                options.push(format!("autovacuum_vacuum_cost_delay = {}", value));
685            }
686            if let Some(value) = with_opts.autovacuum_vacuum_cost_limit {
687                options.push(format!("autovacuum_vacuum_cost_limit = {}", value));
688            }
689            if let Some(value) = with_opts.autovacuum_freeze_min_age {
690                options.push(format!("autovacuum_freeze_min_age = {}", value));
691            }
692            if let Some(value) = with_opts.autovacuum_freeze_max_age {
693                options.push(format!("autovacuum_freeze_max_age = {}", value));
694            }
695            if let Some(value) = with_opts.autovacuum_freeze_table_age {
696                options.push(format!("autovacuum_freeze_table_age = {}", value));
697            }
698            if let Some(value) = with_opts.autovacuum_multixact_freeze_min_age {
699                options.push(format!("autovacuum_multixact_freeze_min_age = {}", value));
700            }
701            if let Some(value) = with_opts.autovacuum_multixact_freeze_max_age {
702                options.push(format!("autovacuum_multixact_freeze_max_age = {}", value));
703            }
704            if let Some(value) = with_opts.autovacuum_multixact_freeze_table_age {
705                options.push(format!("autovacuum_multixact_freeze_table_age = {}", value));
706            }
707            if let Some(value) = with_opts.log_autovacuum_min_duration {
708                options.push(format!("log_autovacuum_min_duration = {}", value));
709            }
710            if let Some(value) = with_opts.user_catalog_table {
711                options.push(format!("user_catalog_table = {}", value));
712            }
713
714            if !options.is_empty() {
715                sql.push_str(&format!(" WITH ({})", options.join(", ")));
716            }
717        }
718
719        sql.push_str(" AS ");
720        sql.push_str(def);
721
722        if let Some(check_option) = check_option_clause {
723            sql.push_str(&format!(" WITH {} CHECK OPTION", check_option));
724        }
725
726        if self.materialized && matches!(self.with_no_data, Some(true)) {
727            sql.push_str(" WITH NO DATA");
728        }
729
730        if let Some(tablespace) = self.tablespace.as_ref() {
731            sql.push_str(&format!(" TABLESPACE \"{}\"", tablespace));
732        }
733
734        sql.push(';');
735        sql
736    }
737
738    /// Generate DROP VIEW SQL
739    pub fn drop_view_sql(&self) -> String {
740        let schema_prefix = if self.schema() != "public" {
741            format!("\"{}\".", self.schema())
742        } else {
743            String::new()
744        };
745        let materialized = if self.materialized {
746            "MATERIALIZED "
747        } else {
748            ""
749        };
750        format!(
751            "DROP {}VIEW {}\"{}\";",
752            materialized,
753            schema_prefix,
754            self.name()
755        )
756    }
757}
758
759// =============================================================================
760// Policy SQL Generation
761// =============================================================================
762
763impl Policy {
764    /// Generate CREATE POLICY SQL
765    pub fn create_policy_sql(&self) -> String {
766        let schema_prefix = if self.schema() != "public" {
767            format!("\"{}\".", self.schema())
768        } else {
769            String::new()
770        };
771
772        let mut sql = format!(
773            "CREATE POLICY \"{}\" ON {}\"{}\"",
774            self.name(),
775            schema_prefix,
776            self.table()
777        );
778
779        if let Some(r#for) = self.for_clause.as_ref() {
780            sql.push_str(&format!(" FOR {}", r#for.to_uppercase()));
781        }
782
783        if let Some(to) = self.to.as_ref()
784            && !to.is_empty()
785        {
786            let to_roles = to
787                .iter()
788                .map(|r| {
789                    if *r == "public" {
790                        "PUBLIC".to_string()
791                    } else {
792                        format!("\"{}\"", r)
793                    }
794                })
795                .collect::<Vec<_>>()
796                .join(", ");
797            sql.push_str(&format!(" TO {}", to_roles));
798        }
799
800        if let Some(using) = self.using.as_ref() {
801            sql.push_str(&format!(" USING ({})", using));
802        }
803
804        if let Some(with_check) = self.with_check.as_ref() {
805            sql.push_str(&format!(" WITH CHECK ({})", with_check));
806        }
807
808        sql.push(';');
809        sql
810    }
811
812    /// Generate DROP POLICY SQL
813    pub fn drop_policy_sql(&self) -> String {
814        let schema_prefix = if self.schema() != "public" {
815            format!("\"{}\".", self.schema())
816        } else {
817            String::new()
818        };
819        format!(
820            "DROP POLICY \"{}\" ON {}\"{}\";",
821            self.name(),
822            schema_prefix,
823            self.table()
824        )
825    }
826}
827
828// =============================================================================
829// Table-level utilities
830// =============================================================================
831
832impl Table {
833    /// Generate DROP TABLE SQL
834    pub fn drop_table_sql(&self) -> String {
835        let schema_prefix = if self.schema() != "public" {
836            format!("\"{}\".", self.schema())
837        } else {
838            String::new()
839        };
840        format!("DROP TABLE {}\"{}\";", schema_prefix, self.name())
841    }
842
843    /// Generate RENAME TABLE SQL
844    pub fn rename_table_sql(&self, new_name: &str) -> String {
845        let schema_prefix = if self.schema() != "public" {
846            format!("\"{}\".", self.schema())
847        } else {
848            String::new()
849        };
850        format!(
851            "ALTER TABLE {}\"{}\" RENAME TO \"{}\";",
852            schema_prefix,
853            self.name(),
854            new_name
855        )
856    }
857}
858
859// =============================================================================
860// Primary Key SQL Generation
861// =============================================================================
862
863impl PrimaryKey {
864    /// Generate the PRIMARY KEY constraint clause
865    pub fn to_constraint_sql(&self) -> String {
866        let cols = self
867            .columns
868            .iter()
869            .map(|c| format!("\"{}\"", c))
870            .collect::<Vec<_>>()
871            .join(", ");
872
873        format!("CONSTRAINT \"{}\" PRIMARY KEY({})", self.name(), cols)
874    }
875
876    /// Generate ADD PRIMARY KEY SQL
877    pub fn add_pk_sql(&self) -> String {
878        let schema_prefix = if self.schema() != "public" {
879            format!("\"{}\".", self.schema())
880        } else {
881            String::new()
882        };
883        format!(
884            "ALTER TABLE {}\"{}\" ADD {};",
885            schema_prefix,
886            self.table(),
887            self.to_constraint_sql()
888        )
889    }
890
891    /// Generate DROP PRIMARY KEY SQL
892    pub fn drop_pk_sql(&self) -> String {
893        let schema_prefix = if self.schema() != "public" {
894            format!("\"{}\".", self.schema())
895        } else {
896            String::new()
897        };
898        format!(
899            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
900            schema_prefix,
901            self.table(),
902            self.name()
903        )
904    }
905}
906
907// =============================================================================
908// Unique Constraint SQL Generation
909// =============================================================================
910
911impl UniqueConstraint {
912    /// Generate the UNIQUE constraint clause
913    pub fn to_constraint_sql(&self) -> String {
914        let cols = self
915            .columns
916            .iter()
917            .map(|c| format!("\"{}\"", c))
918            .collect::<Vec<_>>()
919            .join(", ");
920
921        format!("CONSTRAINT \"{}\" UNIQUE({})", self.name(), cols)
922    }
923
924    /// Generate ADD UNIQUE SQL
925    pub fn add_unique_sql(&self) -> String {
926        let schema_prefix = if self.schema() != "public" {
927            format!("\"{}\".", self.schema())
928        } else {
929            String::new()
930        };
931        format!(
932            "ALTER TABLE {}\"{}\" ADD {};",
933            schema_prefix,
934            self.table(),
935            self.to_constraint_sql()
936        )
937    }
938
939    /// Generate DROP UNIQUE SQL
940    pub fn drop_unique_sql(&self) -> String {
941        let schema_prefix = if self.schema() != "public" {
942            format!("\"{}\".", self.schema())
943        } else {
944            String::new()
945        };
946        format!(
947            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
948            schema_prefix,
949            self.table(),
950            self.name()
951        )
952    }
953}
954
955// =============================================================================
956// Check Constraint SQL Generation
957// =============================================================================
958
959impl CheckConstraint {
960    /// Generate the CHECK constraint clause
961    pub fn to_constraint_sql(&self) -> String {
962        format!("CONSTRAINT \"{}\" CHECK ({})", self.name(), &self.value)
963    }
964
965    /// Generate ADD CHECK SQL
966    pub fn add_check_sql(&self) -> String {
967        let schema_prefix = if self.schema() != "public" {
968            format!("\"{}\".", self.schema())
969        } else {
970            String::new()
971        };
972        format!(
973            "ALTER TABLE {}\"{}\" ADD {};",
974            schema_prefix,
975            self.table(),
976            self.to_constraint_sql()
977        )
978    }
979
980    /// Generate DROP CHECK SQL
981    pub fn drop_check_sql(&self) -> String {
982        let schema_prefix = if self.schema() != "public" {
983            format!("\"{}\".", self.schema())
984        } else {
985            String::new()
986        };
987        format!(
988            "ALTER TABLE {}\"{}\" DROP CONSTRAINT \"{}\";",
989            schema_prefix,
990            self.table(),
991            self.name()
992        )
993    }
994}
995
996#[cfg(test)]
997mod tests {
998    use super::*;
999    use crate::postgres::ddl::{ColumnDef, PrimaryKeyDef, TableDef};
1000    use std::borrow::Cow;
1001
1002    #[test]
1003    fn test_simple_create_table() {
1004        let table = TableDef::new("public", "users").into_table();
1005        let columns = [
1006            ColumnDef::new("public", "users", "id", "SERIAL")
1007                .not_null()
1008                .into_column(),
1009            ColumnDef::new("public", "users", "name", "TEXT")
1010                .not_null()
1011                .into_column(),
1012            ColumnDef::new("public", "users", "email", "TEXT").into_column(),
1013        ];
1014        const PK_COLS: &[Cow<'static, str>] = &[Cow::Borrowed("id")];
1015        let pk = PrimaryKeyDef::new("public", "users", "users_pkey")
1016            .columns(PK_COLS)
1017            .into_primary_key();
1018
1019        let sql = TableSql::new(&table)
1020            .columns(&columns)
1021            .primary_key(Some(&pk))
1022            .create_table_sql();
1023
1024        assert!(sql.contains("CREATE TABLE \"users\""));
1025        assert!(sql.contains("\"id\" SERIAL NOT NULL"));
1026        assert!(sql.contains("\"name\" TEXT NOT NULL"));
1027        assert!(sql.contains("\"email\" TEXT"));
1028    }
1029
1030    #[test]
1031    fn test_table_with_schema() {
1032        let table = TableDef::new("myschema", "users").into_table();
1033        let sql = TableSql::new(&table).create_table_sql();
1034        assert!(sql.contains("\"myschema\".\"users\""));
1035    }
1036}