54 lines
1.4 KiB
Go
54 lines
1.4 KiB
Go
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
|
||
|
|
}
|