feat(writer): 🎉 Add support for unique constraints in schema generation
* Implement unique constraint handling in GenerateSchemaStatements * Add writeUniqueConstraints method for generating SQL statements * Create unit test for unique constraints in writer_test.go
This commit is contained in:
@@ -295,6 +295,31 @@ func (w *Writer) GenerateSchemaStatements(schema *models.Schema) ([]string, erro
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5.5: Unique constraints
|
||||
for _, table := range schema.Tables {
|
||||
for _, constraint := range table.Constraints {
|
||||
if constraint.Type != models.UniqueConstraint {
|
||||
continue
|
||||
}
|
||||
|
||||
// Wrap in DO block to check for existing constraint
|
||||
stmt := fmt.Sprintf("DO $$\nBEGIN\n"+
|
||||
" IF NOT EXISTS (\n"+
|
||||
" SELECT 1 FROM information_schema.table_constraints\n"+
|
||||
" WHERE table_schema = '%s'\n"+
|
||||
" AND table_name = '%s'\n"+
|
||||
" AND constraint_name = '%s'\n"+
|
||||
" ) THEN\n"+
|
||||
" ALTER TABLE %s.%s ADD CONSTRAINT %s UNIQUE (%s);\n"+
|
||||
" END IF;\n"+
|
||||
"END;\n$$",
|
||||
schema.Name, table.Name, constraint.Name,
|
||||
schema.SQLName(), table.SQLName(), constraint.Name,
|
||||
strings.Join(constraint.Columns, ", "))
|
||||
statements = append(statements, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 6: Foreign keys
|
||||
for _, table := range schema.Tables {
|
||||
for _, constraint := range table.Constraints {
|
||||
@@ -542,6 +567,11 @@ func (w *Writer) WriteSchema(schema *models.Schema) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Phase 5.5: Create unique constraints (priority 185)
|
||||
if err := w.writeUniqueConstraints(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Phase 6: Create foreign key constraints (priority 195)
|
||||
if err := w.writeForeignKeys(schema); err != nil {
|
||||
return err
|
||||
@@ -865,6 +895,55 @@ func (w *Writer) writeIndexes(schema *models.Schema) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeUniqueConstraints generates ALTER TABLE statements for unique constraints
|
||||
func (w *Writer) writeUniqueConstraints(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Unique constraints for schema: %s\n", schema.Name)
|
||||
|
||||
for _, table := range schema.Tables {
|
||||
// Sort constraints by name for consistent output
|
||||
constraintNames := make([]string, 0, len(table.Constraints))
|
||||
for name, constraint := range table.Constraints {
|
||||
if constraint.Type == models.UniqueConstraint {
|
||||
constraintNames = append(constraintNames, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(constraintNames)
|
||||
|
||||
for _, name := range constraintNames {
|
||||
constraint := table.Constraints[name]
|
||||
|
||||
// Build column list
|
||||
columnExprs := make([]string, 0, len(constraint.Columns))
|
||||
for _, colName := range constraint.Columns {
|
||||
if col, ok := table.Columns[colName]; ok {
|
||||
columnExprs = append(columnExprs, col.SQLName())
|
||||
}
|
||||
}
|
||||
|
||||
if len(columnExprs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Wrap in DO block to check for existing constraint
|
||||
fmt.Fprintf(w.writer, "DO $$\n")
|
||||
fmt.Fprintf(w.writer, "BEGIN\n")
|
||||
fmt.Fprintf(w.writer, " IF NOT EXISTS (\n")
|
||||
fmt.Fprintf(w.writer, " SELECT 1 FROM information_schema.table_constraints\n")
|
||||
fmt.Fprintf(w.writer, " WHERE table_schema = '%s'\n", schema.Name)
|
||||
fmt.Fprintf(w.writer, " AND table_name = '%s'\n", table.Name)
|
||||
fmt.Fprintf(w.writer, " AND constraint_name = '%s'\n", constraint.Name)
|
||||
fmt.Fprintf(w.writer, " ) THEN\n")
|
||||
fmt.Fprintf(w.writer, " ALTER TABLE %s.%s ADD CONSTRAINT %s UNIQUE (%s);\n",
|
||||
schema.SQLName(), table.SQLName(), constraint.Name, strings.Join(columnExprs, ", "))
|
||||
fmt.Fprintf(w.writer, " END IF;\n")
|
||||
fmt.Fprintf(w.writer, "END;\n")
|
||||
fmt.Fprintf(w.writer, "$$;\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeForeignKeys generates ALTER TABLE statements for foreign keys
|
||||
func (w *Writer) writeForeignKeys(schema *models.Schema) error {
|
||||
fmt.Fprintf(w.writer, "-- Foreign keys for schema: %s\n", schema.Name)
|
||||
|
||||
@@ -164,6 +164,76 @@ func TestWriteForeignKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteUniqueConstraints(t *testing.T) {
|
||||
// Create a test database with unique constraints
|
||||
db := models.InitDatabase("testdb")
|
||||
schema := models.InitSchema("public")
|
||||
|
||||
// Create table with unique constraints
|
||||
table := models.InitTable("users", "public")
|
||||
|
||||
// Add columns
|
||||
emailCol := models.InitColumn("email", "users", "public")
|
||||
emailCol.Type = "varchar(255)"
|
||||
emailCol.NotNull = true
|
||||
table.Columns["email"] = emailCol
|
||||
|
||||
guidCol := models.InitColumn("guid", "users", "public")
|
||||
guidCol.Type = "uuid"
|
||||
guidCol.NotNull = true
|
||||
table.Columns["guid"] = guidCol
|
||||
|
||||
// Add unique constraints
|
||||
emailConstraint := &models.Constraint{
|
||||
Name: "uq_email",
|
||||
Type: models.UniqueConstraint,
|
||||
Schema: "public",
|
||||
Table: "users",
|
||||
Columns: []string{"email"},
|
||||
}
|
||||
table.Constraints["uq_email"] = emailConstraint
|
||||
|
||||
guidConstraint := &models.Constraint{
|
||||
Name: "uq_guid",
|
||||
Type: models.UniqueConstraint,
|
||||
Schema: "public",
|
||||
Table: "users",
|
||||
Columns: []string{"guid"},
|
||||
}
|
||||
table.Constraints["uq_guid"] = guidConstraint
|
||||
|
||||
schema.Tables = append(schema.Tables, table)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
// Create writer with output to buffer
|
||||
var buf bytes.Buffer
|
||||
options := &writers.WriterOptions{}
|
||||
writer := NewWriter(options)
|
||||
writer.writer = &buf
|
||||
|
||||
// Write the database
|
||||
err := writer.WriteDatabase(db)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteDatabase failed: %v", err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
|
||||
// Print output for debugging
|
||||
t.Logf("Generated SQL:\n%s", output)
|
||||
|
||||
// Verify unique constraints are present
|
||||
if !strings.Contains(output, "-- Unique constraints for schema: public") {
|
||||
t.Errorf("Output missing unique constraints header")
|
||||
}
|
||||
if !strings.Contains(output, "ADD CONSTRAINT uq_email UNIQUE (email)") {
|
||||
t.Errorf("Output missing uq_email unique constraint\nFull output:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(output, "ADD CONSTRAINT uq_guid UNIQUE (guid)") {
|
||||
t.Errorf("Output missing uq_guid unique constraint\nFull output:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteTable(t *testing.T) {
|
||||
// Create a single table
|
||||
table := models.InitTable("products", "public")
|
||||
|
||||
Reference in New Issue
Block a user