diff --git a/cmd/generate.go b/cmd/generate.go
index 368d6da..ad0f001 100644
--- a/cmd/generate.go
+++ b/cmd/generate.go
@@ -487,7 +487,7 @@ var (
modelContent = ""
)
-//nolint:cyclop
+//nolint:cyclop,gocognit
func generateMigrationStatements(
ctx context.Context,
config *configuration.Config,
@@ -501,25 +501,41 @@ func generateMigrationStatements(
) (string, error) {
log.Println("Generating migration statements")
- err := internal.PgModelerExportToFile(
- ctx,
- filepath.Join(wd, fmt.Sprintf("%s.dbm", config.ModelName)),
- filepath.Join(wd, fmt.Sprintf("%s.sql", config.ModelName)),
- )
+ dbmPath := filepath.Join(wd, fmt.Sprintf("%s.dbm", config.ModelName))
+ // Generate SQL file in tmpDir for internal use during migration generation
+ tmpSQLPath := filepath.Join(tmpDir, fmt.Sprintf("%s.sql", config.ModelName))
+
+ err := internal.PgmodelerExportSQL(ctx, dbmPath, tmpSQLPath)
if err != nil {
return "", fmt.Errorf("failed to export model: %w", err)
}
- go func() {
- err = internal.PgModelerExportToPng(
- ctx,
- filepath.Join(wd, fmt.Sprintf("%s.dbm", config.ModelName)),
- filepath.Join(wd, fmt.Sprintf("%s.png", config.ModelName)),
- )
+ // Copy SQL to output path if enabled
+ if sqlPath := config.GetOutputPath("sql"); sqlPath != "" {
+ var sqlContent []byte
+ sqlContent, err = os.ReadFile(tmpSQLPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to read sql file: %w", err)
+ }
+ err = os.WriteFile(filepath.Join(wd, sqlPath), sqlContent, 0o644) //nolint:gosec
+ if err != nil {
+ return "", fmt.Errorf("failed to write sql output file: %w", err)
+ }
+ }
+
+ if pngPath := config.GetOutputPath("png"); pngPath != "" {
+ err = internal.PgmodelerExportPNG(ctx, dbmPath, filepath.Join(wd, pngPath))
+ if err != nil {
+ return "", fmt.Errorf("failed to export png: %w", err)
+ }
+ }
+
+ if svgPath := config.GetOutputPath("svg"); svgPath != "" {
+ err = internal.PgmodelerExportSVG(ctx, dbmPath, filepath.Join(wd, svgPath))
if err != nil {
- log.Printf("Failed to export png: %v\n", err)
+ return "", fmt.Errorf("failed to export svg: %w", err)
}
- }()
+ }
for _, role := range config.Roles {
_, err = targetConn.Exec(ctx, fmt.Sprintf("CREATE ROLE %q WITH LOGIN;", role.Name))
@@ -528,28 +544,20 @@ func generateMigrationStatements(
}
}
- err = executeTargetSQL(ctx, config, wd, targetConn)
+ err = executeTargetSQL(ctx, tmpSQLPath, targetConn)
if err != nil {
return "", fmt.Errorf("failed to execute target sql: %w", err)
}
- if initial {
- // If we are developing the schema initially, there will be no diffs,
- // and we want to copy over the schema file to the initial migration file
- var input []byte
- input, err = os.ReadFile(filepath.Join(wd, fmt.Sprintf("%s.sql", config.ModelName)))
+ // Apply existing migrations to the migrate database (skip if no migrations exist yet)
+ if !initial {
+ err = executeMigrateSQL(migrationsDir, migrateConn)
if err != nil {
- return "", fmt.Errorf("failed to read sql file: %w", err)
+ return "", fmt.Errorf("failed to execute migrate sql: %w", err)
}
-
- return string(input), nil
- }
-
- err = executeMigrateSQL(migrationsDir, migrateConn)
- if err != nil {
- return "", fmt.Errorf("failed to execute migrate sql: %w", err)
}
+ // Generate diff between migrate database (with existing migrations) and target database (with full schema)
statements, err := internal.Diff(
ctx,
postgresConn,
@@ -595,8 +603,8 @@ func executeMigrateSQL(migrationsDir string, migrateConn *pgx.Conn) error {
return nil
}
-func executeTargetSQL(ctx context.Context, config *configuration.Config, wd string, targetConn *pgx.Conn) error {
- targetSQL, err := os.ReadFile(filepath.Join(wd, fmt.Sprintf("%s.sql", config.ModelName)))
+func executeTargetSQL(ctx context.Context, sqlPath string, targetConn *pgx.Conn) error {
+ targetSQL, err := os.ReadFile(sqlPath)
if err != nil {
return fmt.Errorf("failed to read target sql: %w", err)
}
diff --git a/example/foo.sql b/example/foo.gen.sql
similarity index 100%
rename from example/foo.sql
rename to example/foo.gen.sql
diff --git a/example/foo.gen.svg b/example/foo.gen.svg
new file mode 100644
index 0000000..9690861
--- /dev/null
+++ b/example/foo.gen.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/example/foo.png b/example/foo.png
deleted file mode 100644
index 54c13c7..0000000
Binary files a/example/foo.png and /dev/null differ
diff --git a/example/migrations/001_init.up.sql b/example/migrations/001_init.up.sql
index f46bd93..e69de29 100644
--- a/example/migrations/001_init.up.sql
+++ b/example/migrations/001_init.up.sql
@@ -1,10 +0,0 @@
--- ** Database generated with pgModeler (PostgreSQL Database Modeler).
--- ** pgModeler version: 1.2.2
--- ** PostgreSQL version: 18.0
--- ** Project Site: pgmodeler.io
--- ** Model Author: ---
-
-SET search_path TO pg_catalog,public;
--- ddl-end --
-
-
diff --git a/example/trek.yaml b/example/trek.yaml
index 22a1c9d..96b7bc2 100644
--- a/example/trek.yaml
+++ b/example/trek.yaml
@@ -3,3 +3,6 @@ db_name: bar
roles:
- name: alice
- name: bob
+output:
+ sql: {}
+ svg: {}
diff --git a/internal/configuration/config.go b/internal/configuration/config.go
index 394b60b..54eea68 100644
--- a/internal/configuration/config.go
+++ b/internal/configuration/config.go
@@ -24,7 +24,9 @@ type Config struct {
//nolint:tagliatelle
Roles []Role `yaml:"roles"`
Templates []Template `yaml:"templates"`
+ Output *Output `yaml:"output"`
}
+
type Role struct {
Name string `yaml:"name"`
}
@@ -34,6 +36,16 @@ type Template struct {
Content string `yaml:"content"`
}
+type OutputFile struct {
+ Path string `yaml:"path"`
+}
+
+type Output struct {
+ SQL *OutputFile `yaml:"sql"`
+ PNG *OutputFile `yaml:"png"`
+ SVG *OutputFile `yaml:"svg"`
+}
+
func ReadConfig(wd string) (*Config, error) {
var config *Config
file, err := os.ReadFile(filepath.Join(wd, "trek.yaml"))
@@ -85,6 +97,38 @@ func (c *Config) validate() (problems []string) {
return problems
}
+// GetOutputPath returns the output path for the given type if enabled, or empty string if not.
+// The outputType must be one of: "sql", "png", "svg". Panics if an invalid outputType is provided.
+func (c *Config) GetOutputPath(outputType string) string {
+ if c.Output == nil {
+ return ""
+ }
+
+ var outputFile *OutputFile
+
+ switch outputType {
+ case "sql":
+ outputFile = c.Output.SQL
+ case "png":
+ outputFile = c.Output.PNG
+ case "svg":
+ outputFile = c.Output.SVG
+ default:
+ panic(fmt.Sprintf("invalid output type: %q", outputType))
+ }
+
+ if outputFile == nil {
+ return ""
+ }
+
+ if outputFile.Path != "" {
+ return outputFile.Path
+ }
+
+ // Default path: {model_name}.gen.{ext}
+ return fmt.Sprintf("%s.gen.%s", c.ModelName, outputType)
+}
+
func ValidateIdentifier(identifier string) bool {
return regexpValidIdentifier.MatchString(identifier)
}
diff --git a/internal/pgmodeler.go b/internal/pgmodeler.go
index e70cd7f..db2f3b8 100644
--- a/internal/pgmodeler.go
+++ b/internal/pgmodeler.go
@@ -7,7 +7,7 @@ import (
"os/exec"
)
-func PgModelerExportToFile(ctx context.Context, input, output string) error {
+func PgmodelerExportSQL(ctx context.Context, input, output string) error {
//nolint:gosec
err := os.WriteFile(output, []byte{}, 0o644)
if err != nil {
@@ -35,7 +35,7 @@ func PgModelerExportToFile(ctx context.Context, input, output string) error {
return nil
}
-func PgModelerExportToPng(ctx context.Context, input, output string) error {
+func PgmodelerExportPNG(ctx context.Context, input, output string) error {
//nolint:gosec
err := os.WriteFile(output, []byte{}, 0o644)
if err != nil {
@@ -60,3 +60,29 @@ func PgModelerExportToPng(ctx context.Context, input, output string) error {
return nil
}
+
+func PgmodelerExportSVG(ctx context.Context, input, output string) error {
+ //nolint:gosec
+ err := os.WriteFile(output, []byte{}, 0o644)
+ if err != nil {
+ return fmt.Errorf("failed to create output svg: %w", err)
+ }
+ //nolint:gosec
+ cmdPgModeler := exec.CommandContext(
+ ctx,
+ "pgmodeler-cli",
+ "--input",
+ input,
+ "--export-to-svg",
+ "--output",
+ output,
+ )
+ cmdPgModeler.Stderr = os.Stderr
+
+ out, err := cmdPgModeler.Output()
+ if err != nil {
+ return fmt.Errorf("failed to run pgmodeler: %w %s", err, string(out))
+ }
+
+ return nil
+}
diff --git a/internal/templates/trek.yaml.tmpl b/internal/templates/trek.yaml.tmpl
index 8e59a24..a9ac737 100755
--- a/internal/templates/trek.yaml.tmpl
+++ b/internal/templates/trek.yaml.tmpl
@@ -2,3 +2,6 @@ model_name: {{.model_name}}
db_name: {{.db_name}}
roles:{{range .roleNames}}
- name: {{.}}{{end}}
+output:
+ sql: {}
+ svg: {}
diff --git a/tests/output/migrations/001_init.up.sql b/tests/output/migrations/001_init.up.sql
index f46bd93..e69de29 100644
--- a/tests/output/migrations/001_init.up.sql
+++ b/tests/output/migrations/001_init.up.sql
@@ -1,10 +0,0 @@
--- ** Database generated with pgModeler (PostgreSQL Database Modeler).
--- ** pgModeler version: 1.2.2
--- ** PostgreSQL version: 18.0
--- ** Project Site: pgmodeler.io
--- ** Model Author: ---
-
-SET search_path TO pg_catalog,public;
--- ddl-end --
-
-
diff --git a/tests/output/santas_warehouse.sql b/tests/output/santas_warehouse.gen.sql
similarity index 100%
rename from tests/output/santas_warehouse.sql
rename to tests/output/santas_warehouse.gen.sql
diff --git a/tests/output/santas_warehouse.gen.svg b/tests/output/santas_warehouse.gen.svg
new file mode 100644
index 0000000..8f3448c
--- /dev/null
+++ b/tests/output/santas_warehouse.gen.svg
@@ -0,0 +1,1408 @@
+
+
diff --git a/tests/output/santas_warehouse.png b/tests/output/santas_warehouse.png
deleted file mode 100644
index 4cd0644..0000000
Binary files a/tests/output/santas_warehouse.png and /dev/null differ
diff --git a/tests/output/trek.yaml b/tests/output/trek.yaml
index 2d3f4b9..0c0c874 100644
--- a/tests/output/trek.yaml
+++ b/tests/output/trek.yaml
@@ -3,3 +3,6 @@ db_name: north_pole
roles:
- name: santa
- name: worker
+output:
+ sql: {}
+ svg: {}