vitessio/vitess · error
unsupported multi-table usage
Error message
unsupported multi-table usage
What it means
In vreplication's table plan builder, analyzeSelectFrom only supports copying from exactly one table per SELECT. This error is thrown when the source query's FROM clause contains more than one table expression, meaning a join or comma-separated multi-table select was used where a single-table select is required.
Source
Thrown at go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go:405
}
func analyzeSelectFrom(query string, parser *sqlparser.Parser) (sel *sqlparser.Select, from string, err error) {
statement, err := parser.Parse(query)
if err != nil {
return nil, "", err
}
sel, ok := statement.(*sqlparser.Select)
if !ok {
return nil, "", errors.New("unsupported non-select statement")
}
if sel.Distinct {
return nil, "", errors.New("unsupported distinct clause")
}
if len(sel.From) == 0 {
return nil, "", errors.New("unsupported select from dual")
}
if len(sel.From) > 1 {
return nil, "", errors.New("unsupported multi-table usage")
}
node, ok := sel.From[0].(*sqlparser.AliasedTableExpr)
if !ok {
return nil, "", fmt.Errorf("unsupported from expression (%T)", sel.From[0])
}
fromTable := sqlparser.GetTableName(node.Expr)
if fromTable.IsEmpty() {
return nil, "", fmt.Errorf("unsupported from source (%T)", node.Expr)
}
return sel, fromTable.String(), nil
}
func (tpb *tablePlanBuilder) analyzeExprs(selExprs []sqlparser.SelectExpr) error {
for _, selExpr := range selExprs {
cexpr, err := tpb.analyzeExpr(selExpr)
if err != nil {
return err
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Rewrite the filter/rule SELECT to reference exactly one table in FROM; use WHERE clauses instead of JOINs to filter rows
- If cross-table transformation is needed, replicate the base table and derive the target via a view or post-copy transformation
- Use a table-level filter (one table per rule) instead of a join-based select
Example fix
// before
filter: { rules: [{ match: 't', filter: 'select * from t join u on t.id=u.id' }] }
// after
filter: { rules: [{ match: 't', filter: 'select t.* from t where exists (select 1 from u where u.id=t.id)' }] } Defensive patterns
Strategy: validation
Validate before calling
// Before configuring a vreplication filter rule, ensure the select references one table:
stmt, err := sqlparser.Parse(filterSQL)
if err != nil { return err }
sel, ok := stmt.(*sqlparser.Select)
if !ok { return fmt.Errorf("expected SELECT") }
if len(sel.From) > 1 { return fmt.Errorf("filter must reference exactly one table, got %d", len(sel.From)) } Prevention
- One table per filter rule — never JOIN in filter selects
- Use WHERE/subquery filtering instead of joins
- Test filter rules against a scratch workflow before production MoveTables
When it happens
Trigger: analyzeSelectFrom (called by buildTablePlan) encounters sel.From with length > 1, i.e. a vreplication filter/rule was compiled against a query with multiple tables in FROM (joins or comma lists).
Common situations: A MoveTables/VReplication filter rule uses a select with a JOIN to filter or transform rows; hand-written filter rules that reference multiple tables; misconfigured keyrange/table filters.
Related errors
- ErrUnsupportedQuery
- value out of range
- both atomic copy and partial mode cannot be specified for th
- invalid workflow
- multiple source keyspaces for a single workflow
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/c6f528c85e97bd89.
Report an issue: GitHub.