Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 38 additions & 30 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ var (
modelContent = ""
)

//nolint:cyclop
//nolint:cyclop,gocognit
func generateMigrationStatements(
ctx context.Context,
config *configuration.Config,
Expand All @@ -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
Comment on lines +516 to +520
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not copy the file without read/write?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How? There is no os.Copy right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io.Copy, but I guess in doesn't really matter given the amount of data we're handling here.

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))
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down
File renamed without changes.
52 changes: 52 additions & 0 deletions example/foo.gen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed example/foo.png
Binary file not shown.
10 changes: 0 additions & 10 deletions example/migrations/001_init.up.sql
Original file line number Diff line number Diff line change
@@ -1,10 +0,0 @@
-- ** Database generated with pgModeler (PostgreSQL Database Modeler).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file still exists but its empty because there are no diffs.

-- ** pgModeler version: 1.2.2
-- ** PostgreSQL version: 18.0
-- ** Project Site: pgmodeler.io
-- ** Model Author: ---

SET search_path TO pg_catalog,public;
-- ddl-end --


3 changes: 3 additions & 0 deletions example/trek.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ db_name: bar
roles:
- name: alice
- name: bob
output:
sql: {}
svg: {}
Comment on lines +7 to +8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the example it would be nice to have one with an explicit path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that, but the example is generated via trek init which doesn't have env vars to set these.. I don't think adding env vars there makes sense. I think instead we should improve documentation in general. And/or maybe the example dir shouldn't be generated?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah maybe get rid of the example, since it's empty anyway it doesn't have that much value and it isn't even useful as a starting point, since we have trek init.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets pick that up in a separate story/PR; to improve docs in general.

44 changes: 44 additions & 0 deletions internal/configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand All @@ -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"))
Expand Down Expand Up @@ -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)
}
Expand Down
30 changes: 28 additions & 2 deletions internal/pgmodeler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
3 changes: 3 additions & 0 deletions internal/templates/trek.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ model_name: {{.model_name}}
db_name: {{.db_name}}
roles:{{range .roleNames}}
- name: {{.}}{{end}}
output:
sql: {}
svg: {}
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trek.yaml template now includes output configuration for sql and svg but not png. Consider documenting this behavior or adding a comment in the template to explain that png can also be configured if needed. This would help users understand all available output options.

Suggested change
svg: {}
svg: {}
# Note: PNG output can also be configured here if needed, for example:
# png: {}

Copilot uses AI. Check for mistakes.
10 changes: 0 additions & 10 deletions tests/output/migrations/001_init.up.sql
Original file line number Diff line number Diff line change
@@ -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 --


Loading
Loading