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 }