ConfigProvider, PolicyExecutor, DBStatsProvider, scheduler и safety; зависимость robfig/cron/v3. Co-authored-by: Cursor <[email protected]>
73 lines
1.7 KiB
Go
73 lines
1.7 KiB
Go
package maintenance
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
DefaultBatchRows = 10000
|
|
MaxBatchRows = 100000
|
|
DefaultStatementTimeoutSec = 30
|
|
)
|
|
|
|
var (
|
|
blockedTableNames = map[string]struct{}{
|
|
"schema_migrations": {},
|
|
"tenant": {},
|
|
"maintenance_policy": {},
|
|
"maintenance_policy_config_audit": {},
|
|
}
|
|
|
|
sqlForbidden = regexp.MustCompile(`(?i)(;|--|/\*|\b(drop|truncate|insert|update|alter|create|grant|revoke|copy)\b)`)
|
|
)
|
|
|
|
// ValidateTableName ensures table is a safe identifier and not blocked.
|
|
func ValidateTableName(name string) error {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" || !isSafeIdent(name) {
|
|
return fmt.Errorf("maintenance: invalid table name")
|
|
}
|
|
if _, blocked := blockedTableNames[strings.ToLower(name)]; blocked {
|
|
return fmt.Errorf("maintenance: table %q is not allowed", name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateCondition ensures the WHERE fragment is safe for parameterized cleanup.
|
|
func ValidateCondition(condition string) error {
|
|
c := strings.TrimSpace(condition)
|
|
if c == "" {
|
|
return nil
|
|
}
|
|
if sqlForbidden.MatchString(c) {
|
|
return fmt.Errorf("maintenance: unsafe condition")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NormalizeBatchLimit clamps delete batch size.
|
|
func NormalizeBatchLimit(maxRows *int) int {
|
|
if maxRows == nil || *maxRows <= 0 {
|
|
return DefaultBatchRows
|
|
}
|
|
if *maxRows > MaxBatchRows {
|
|
return MaxBatchRows
|
|
}
|
|
return *maxRows
|
|
}
|
|
|
|
func isSafeIdent(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
for _, r := range name {
|
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|