vitessio/vitess · error
more than one target for source table %s: %s and %s
Error message
more than one target for source table %s: %s and %s
What it means
buildReplicatorPlan enforces a 1:1 mapping between a source table (the SendRule.Match key) and a target table. When a second tablePlan resolves to the same source table match key, planning fails rather than silently merging or double-streaming rows to two targets.
Source
Thrown at go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go:175
return nil, err
}
if rule == nil {
continue
}
colInfos, ok := colInfoMap[tableName]
if !ok {
return nil, fmt.Errorf("table %s not found in schema", tableName)
}
tablePlan, err := buildTablePlan(tableName, rule, colInfos, lastpk, stats, source, collationEnv, parser, vr.workflowConfig)
if err != nil {
return nil, vterrors.Wrapf(err, "failed to build table replication plan for %s table", tableName)
}
if tablePlan == nil {
// Table was excluded.
continue
}
if dup, ok := plan.TablePlans[tablePlan.SendRule.Match]; ok {
return nil, fmt.Errorf("more than one target for source table %s: %s and %s", tablePlan.SendRule.Match, dup.TargetName, tableName)
}
plan.VStreamFilter.Rules = append(plan.VStreamFilter.Rules, tablePlan.SendRule)
plan.TargetTables[tableName] = tablePlan
plan.TablePlans[tablePlan.SendRule.Match] = tablePlan
}
return plan, nil
}
// MatchTable is similar to tableMatches and buildPlan defined in vstreamer/planbuilder.go.
func MatchTable(tableName string, filter *binlogdatapb.Filter) (*binlogdatapb.Rule, error) {
for _, rule := range filter.Rules {
switch {
case strings.HasPrefix(rule.Match, "/"):
expr := strings.Trim(rule.Match, "/")
result, err := regexp.MatchString(expr, tableName)
if err != nil {
return nil, err
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Review the Filter rules and remove/merge overlapping matches
- Make wildcard rules more specific so they don't overlap exact matches
- Order rules so the intended one wins and delete the redundant rule (first explicit rule should be the only one matching each table)
Example fix
// before
"rules": [{"match": "t*", "targetTable": "t"}, {"match": "t1", "targetTable": "t1"}]
// after
"rules": [{"match": "t1", "targetTable": "t1"}, {"match": "t2", "targetTable": "t2"}] Defensive patterns
Strategy: validation
Validate before calling
seen := map[string]string{}
for _, r := range filter.Rules {
if prev, dup := seen[r.Match]; dup {
return fmt.Errorf("rule %q duplicates match already handled by %q", r.TargetTable, prev)
}
seen[r.Match] = r.TargetTable
} Type guard
func hasOverlappingRules(rules []*Rule) bool {
matches := map[string]bool{}
for _, r := range rules {
if matches[r.Match] { return true }
matches[r.Match] = true
}
return false
} Prevention
- Audit Filter rules for overlapping regex patterns before MoveTables
- Prefer explicit per-table rules over broad wildcards
- Remove stale catch-all rules when adding specific ones
When it happens
Trigger: Two filter rules whose `match` patterns resolve to the same source table, e.g. one exact rule `t1` plus a wildcard `t*` that also matches t1, both producing a plan for the same SendRule.Match.
Common situations: Overlapping regex rules in a MoveTables Filter; adding a specific-table rule without removing an existing catch-all rule; copy-phase rules built by initTablesForCopy colliding with user-supplied rules.
Related errors
- table %s not found in schema
- rule %v does not have a select expression in vreplication
- unexpected rule in vreplication: %v
- invalid expression: %v
- failed to find column name for convert using expression: %v,
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/f331aa775e14ccc3.
Report an issue: GitHub.