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

  1. Rewrite the filter/rule SELECT to reference exactly one table in FROM; use WHERE clauses instead of JOINs to filter rows
  2. If cross-table transformation is needed, replicate the base table and derive the target via a view or post-copy transformation
  3. 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

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


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/c6f528c85e97bd89. Report an issue: GitHub.