169 lines
4.0 KiB
Go
169 lines
4.0 KiB
Go
package schema
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"aijianzhan/platform/internal/blueprint"
|
||
)
|
||
|
||
// BuildPostgresDDL 仅拼接白名单标识符与固定类型映射。
|
||
func BuildPostgresDDL(bp *blueprint.Blueprint) ([]string, error) {
|
||
if bp.Storage.SchemaName == "" {
|
||
return nil, fmt.Errorf("schema_name empty")
|
||
}
|
||
schema := bp.Storage.SchemaName
|
||
stmts := []string{
|
||
fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", quoteIdent(schema)),
|
||
}
|
||
|
||
for _, e := range bp.Entities {
|
||
cols := make([]string, 0, len(e.Fields)+4)
|
||
hasTenant := false
|
||
hasOrgUnit := false
|
||
hasCreatedAt := false
|
||
hasUpdatedAt := false
|
||
hasCreatedBy := false
|
||
|
||
for _, f := range e.Fields {
|
||
sqlType, err := mapType(f)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var col string
|
||
switch {
|
||
case f.Name == e.PrimaryKey && f.Type == "bigint":
|
||
col = fmt.Sprintf("%s BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY", quoteIdent(f.Name))
|
||
case f.Name == e.PrimaryKey && f.Type == "int":
|
||
col = fmt.Sprintf("%s INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY", quoteIdent(f.Name))
|
||
default:
|
||
nullSQL := "NULL"
|
||
if !blueprint.BoolOr(f.Nullable, true) {
|
||
nullSQL = "NOT NULL"
|
||
}
|
||
col = fmt.Sprintf("%s %s %s", quoteIdent(f.Name), sqlType, nullSQL)
|
||
if f.Name == e.PrimaryKey {
|
||
col += " PRIMARY KEY"
|
||
} else if f.Unique {
|
||
col += " UNIQUE"
|
||
}
|
||
if f.Type == "enum" && len(f.EnumValues) > 0 {
|
||
// 枚举值仅作 UI 提示,不写死 CHECK,避免导入新值被拒
|
||
}
|
||
}
|
||
cols = append(cols, col)
|
||
switch f.Name {
|
||
case "tenant_id":
|
||
hasTenant = true
|
||
case "org_unit_id":
|
||
hasOrgUnit = true
|
||
case "created_at":
|
||
hasCreatedAt = true
|
||
case "updated_at":
|
||
hasUpdatedAt = true
|
||
case "created_by":
|
||
hasCreatedBy = true
|
||
}
|
||
}
|
||
|
||
// 系统强制列
|
||
if !hasTenant {
|
||
cols = append(cols, "tenant_id BIGINT NOT NULL")
|
||
}
|
||
if !hasOrgUnit {
|
||
cols = append(cols, "org_unit_id BIGINT")
|
||
}
|
||
if !hasCreatedBy {
|
||
cols = append(cols, "created_by BIGINT")
|
||
}
|
||
if !hasCreatedAt {
|
||
cols = append(cols, "created_at TIMESTAMPTZ NOT NULL DEFAULT now()")
|
||
}
|
||
if !hasUpdatedAt {
|
||
cols = append(cols, "updated_at TIMESTAMPTZ NOT NULL DEFAULT now()")
|
||
}
|
||
|
||
create := fmt.Sprintf(
|
||
"CREATE TABLE IF NOT EXISTS %s.%s (\n %s\n)",
|
||
quoteIdent(schema),
|
||
quoteIdent(e.Table),
|
||
strings.Join(cols, ",\n "),
|
||
)
|
||
stmts = append(stmts, create)
|
||
|
||
for _, idx := range e.Indexes {
|
||
unique := ""
|
||
if idx.Unique {
|
||
unique = "UNIQUE "
|
||
}
|
||
colsQuoted := make([]string, 0, len(idx.Columns))
|
||
for _, c := range idx.Columns {
|
||
colsQuoted = append(colsQuoted, quoteIdent(c))
|
||
}
|
||
stmts = append(stmts, fmt.Sprintf(
|
||
"CREATE %sINDEX IF NOT EXISTS %s ON %s.%s (%s)",
|
||
unique,
|
||
quoteIdent(idx.Name),
|
||
quoteIdent(schema),
|
||
quoteIdent(e.Table),
|
||
strings.Join(colsQuoted, ", "),
|
||
))
|
||
}
|
||
}
|
||
return stmts, nil
|
||
}
|
||
|
||
func mapType(f blueprint.Field) (string, error) {
|
||
switch f.Type {
|
||
case "string":
|
||
n := f.MaxLength
|
||
if n <= 0 {
|
||
n = 255
|
||
}
|
||
return fmt.Sprintf("VARCHAR(%d)", n), nil
|
||
case "text":
|
||
return "TEXT", nil
|
||
case "int":
|
||
return "INTEGER", nil
|
||
case "bigint":
|
||
return "BIGINT", nil
|
||
case "decimal":
|
||
p, s := f.Precision, f.Scale
|
||
if p <= 0 {
|
||
p = 18
|
||
}
|
||
if s < 0 {
|
||
s = 2
|
||
}
|
||
return fmt.Sprintf("NUMERIC(%d,%d)", p, s), nil
|
||
case "boolean":
|
||
return "BOOLEAN", nil
|
||
case "date":
|
||
return "DATE", nil
|
||
case "datetime":
|
||
return "TIMESTAMPTZ", nil
|
||
case "enum":
|
||
return "VARCHAR(64)", nil
|
||
case "json":
|
||
return "JSONB", nil
|
||
case "file_ref":
|
||
return "VARCHAR(512)", nil
|
||
default:
|
||
return "", fmt.Errorf("unknown field type: %s", f.Type)
|
||
}
|
||
}
|
||
|
||
func enumCheck(col string, values []string) string {
|
||
quoted := make([]string, 0, len(values))
|
||
for _, v := range values {
|
||
quoted = append(quoted, "'"+strings.ReplaceAll(v, "'", "''")+"'")
|
||
}
|
||
return fmt.Sprintf("CHECK (%s IN (%s))", quoteIdent(col), strings.Join(quoted, ", "))
|
||
}
|
||
|
||
func quoteIdent(name string) string {
|
||
// 调用方已白名单校验;仍用双引号包裹防止关键字冲突
|
||
return `"` + strings.ReplaceAll(name, `"`, ``) + `"`
|
||
}
|