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
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ go build -o erdn ./cmd/erdn
```
erdn render <schema.erdn> [--out <file.svg>]
erdn validate <schema.erdn>
erdn sql <schema.erdn> [--dbms <mysql|postgresql|mssql|oracle|sqlite>] [--out <file.sql>]
```

### `render`
Expand All @@ -75,6 +76,37 @@ erdn validate schema.erdn
# OK
```

### `sql`

Generates SQL DDL (`CREATE TABLE`, indexes, and foreign key constraints) from the schema. Use `--dbms` to target a specific database engine (default: `mysql`).

| DBMS | Flag value |
|------|-----------|
| MySQL | `mysql` |
| PostgreSQL | `postgresql` |
| Microsoft SQL Server | `mssql` |
| Oracle Database | `oracle` |
| SQLite | `sqlite` |

```sh
# Output written to schema.sql by default (MySQL)
erdn sql schema.erdn

# Target PostgreSQL
erdn sql schema.erdn --dbms postgresql

# Specify a custom output path
erdn sql schema.erdn --dbms mssql --out migrations/001_init.sql
```

The generated SQL includes:

- `CREATE TABLE` statements with DBMS-appropriate types and constraints.
- `PRIMARY KEY` constraints.
- `AUTO_INCREMENT` / `IDENTITY(1,1)` / `GENERATED ALWAYS AS IDENTITY` for auto-increment columns.
- `CREATE INDEX` statements for `indexed` columns.
- `ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY` statements derived from `link` declarations (inline `FOREIGN KEY` for SQLite).

---

## Syntax
Expand Down
50 changes: 47 additions & 3 deletions cmd/erdn/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/headercat/erdn-lang/internal/parser"
"github.com/headercat/erdn-lang/internal/render"
"github.com/headercat/erdn-lang/internal/semantic"
"github.com/headercat/erdn-lang/internal/sqlexport"
)

func main() {
Expand All @@ -25,6 +26,8 @@ func main() {
runRender(os.Args[2:])
case "validate":
runValidate(os.Args[2:])
case "sql":
runSQL(os.Args[2:])
default:
fmt.Fprintf(os.Stderr, "unknown subcommand: %s\n", os.Args[1])
printUsage()
Expand All @@ -33,11 +36,13 @@ func main() {
}

func printUsage() {
fmt.Fprintln(os.Stderr, `erdn - erdn-lang schema toolchain
fmt.Fprintf(os.Stderr, `erdn - erdn-lang schema toolchain

Usage:
erdn render <schema.erdn> [--out <file>]
erdn validate <schema.erdn>`)
erdn render <schema.erdn> [--out <file>]
erdn validate <schema.erdn>
erdn sql <schema.erdn> [--dbms <%s>] [--out <file>]
`, strings.Join(sqlexport.SupportedDBMS, "|"))
}

// loadAndValidate parses and semantically validates a schema file.
Expand Down Expand Up @@ -122,3 +127,42 @@ func runValidate(args []string) {
}
fmt.Println("OK")
}

func runSQL(args []string) {
fs := flag.NewFlagSet("sql", flag.ExitOnError)
dbmsFlag := fs.String("dbms", string(sqlexport.DBMSMySQL),
"target DBMS ("+strings.Join(sqlexport.SupportedDBMS, ", ")+")")
outFlag := fs.String("out", "", "output file path (default: <schema>.sql)")
if err := fs.Parse(args); err != nil {
os.Exit(1)
}
if fs.NArg() < 1 {
fmt.Fprintln(os.Stderr, "sql: missing schema file")
os.Exit(1)
}
if !sqlexport.ValidDBMS(*dbmsFlag) {
fmt.Fprintf(os.Stderr, "sql: unknown DBMS %q; supported: %s\n",
*dbmsFlag, strings.Join(sqlexport.SupportedDBMS, ", "))
os.Exit(1)
}

schemaFile := fs.Arg(0)
prog, err := loadAndValidate(schemaFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

outPath := *outFlag
if outPath == "" {
base := strings.TrimSuffix(schemaFile, filepath.Ext(schemaFile))
outPath = base + ".sql"
}

sql := sqlexport.Generate(prog, sqlexport.DBMS(*dbmsFlag))
if err := os.WriteFile(outPath, []byte(sql), 0644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("exported %s\n", outPath)
}
Loading
Loading