Files
2026-07-31 10:31:17 +08:00

169 lines
4.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, `"`, ``) + `"`
}