package main import ( "fmt" "regexp" "strings" ) // SQL identifiers in this migrator are always static allowlisted names or // programmer-supplied column paths — never end-user free text. Still quote // and validate before interpolating into DDL/DML to fail closed on mistakes. var sqlIdentSegment = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) func quoteMySQLIdent(ident string) (string, error) { if !sqlIdentSegment.MatchString(ident) { return "", fmt.Errorf("invalid MySQL identifier %q", ident) } return "`" + strings.ReplaceAll(ident, "`", "``") + "`", nil } // quoteMySQLIdentPath quotes dotted paths such as company_id or cf.company_id. func quoteMySQLIdentPath(path string) (string, error) { path = strings.TrimSpace(path) if path == "" { return "", fmt.Errorf("empty MySQL identifier path") } parts := strings.Split(path, ".") out := make([]string, len(parts)) for i, part := range parts { q, err := quoteMySQLIdent(part) if err != nil { return "", err } out[i] = q } return strings.Join(out, "."), nil } func mustQuoteMySQLIdent(ident string) string { q, err := quoteMySQLIdent(ident) if err != nil { panic(err) } return q } func quotePGIdent(ident string) (string, error) { if !sqlIdentSegment.MatchString(ident) { return "", fmt.Errorf("invalid Postgres identifier %q", ident) } return `"` + strings.ReplaceAll(ident, `"`, `""`) + `"`, nil }