package schema import ( "context" "database/sql" "fmt" "strings" "aijianzhan/platform/internal/blueprint" ) // BuildPostgresAlterDDL 对比蓝图与已有表,生成 ADD COLUMN IF NOT EXISTS(Z11a)。 // db 可为 nil:则只返回空(调用方应先 CREATE TABLE)。 func BuildPostgresAlterDDL(ctx context.Context, db *sql.DB, bp *blueprint.Blueprint) ([]string, error) { if bp == nil || bp.Storage.SchemaName == "" { return nil, fmt.Errorf("schema_name empty") } if db == nil { return nil, nil } schemaName := bp.Storage.SchemaName var stmts []string for _, e := range bp.Entities { existing, err := listTableColumns(ctx, db, schemaName, e.Table) if err != nil { return nil, err } if existing == nil { // 表尚不存在:由 CREATE TABLE IF NOT EXISTS 处理 continue } for _, f := range e.Fields { if _, ok := existing[f.Name]; ok { continue } sqlType, err := mapType(f) if err != nil { return nil, err } // 存量表补列一律可空,避免非空约束导致迁库失败 stmts = append(stmts, fmt.Sprintf( `ALTER TABLE %s.%s ADD COLUMN IF NOT EXISTS %s %s`, quoteIdent(schemaName), quoteIdent(e.Table), quoteIdent(f.Name), sqlType, )) } } return stmts, nil } func listTableColumns(ctx context.Context, db *sql.DB, schemaName, table string) (map[string]struct{}, error) { const q = ` SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2` rows, err := db.QueryContext(ctx, q, schemaName, table) if err != nil { return nil, err } defer rows.Close() out := map[string]struct{}{} found := false for rows.Next() { found = true var name string if err := rows.Scan(&name); err != nil { return nil, err } out[name] = struct{}{} } if err := rows.Err(); err != nil { return nil, err } if !found { return nil, nil } return out, nil } // MergeDDL 将 alter 语句追加到 create ddl 之后。 func MergeDDL(create, alter []string) []string { if len(alter) == 0 { return create } out := make([]string, 0, len(create)+len(alter)) out = append(out, create...) out = append(out, alter...) return out } // Quote for tests func normalizeSchemaTable(s string) string { return strings.TrimSpace(s) }