vitessio/vitess · error

unsupported from source (%T)

Error message

unsupported from source (%T)

What it means

After confirming the FROM element is an AliasedTableExpr, the builder extracts the table name via sqlparser.GetTableName. If the underlying expression is not a plain TableName (e.g. a subquery inside parentheses), the name is empty and the builder rejects the plan since it cannot identify the source table to stream from.

Solutions

  1. Replace the derived table with the actual base table name in FROM
  2. Create and populate a materialized table if intermediate processing is required
  3. Keep table-to-table replication plans limited to plain `SELECT ... FROM <table>` shapes

Example fix

// before
FROM (SELECT * FROM sales) AS s
// after
FROM sales
Defensive patterns

Strategy: validation

Validate before calling

node, ok := sel.From[0].(*sqlparser.AliasedTableExpr)
if !ok { return errors.New("not a table expr") }
if sqlparser.GetTableName(node.Expr).IsEmpty() {
    return errors.New("FROM must name a real table, not a derived table")
}

Type guard

func fromNamesRealTable(sel *sqlparser.Select) bool {
    node, ok := sel.From[0].(*sqlparser.AliasedTableExpr)
    return ok && !sqlparser.GetTableName(node.Expr).IsEmpty()
}

Try / catch

plan, err := buildTablePlan(...)
if err != nil && strings.Contains(err.Error(), "unsupported from source") {
    log.Warn("derived-table FROM is not vstreamable; rewrite to base table")
}

Prevention

When it happens

Trigger: FROM contains a parenthesized subquery such as `FROM (SELECT * FROM t) AS x`, so GetTableName returns an empty TableName.

Common situations: Auto-generated or hand-written filter SELECTs using derived tables; migration tooling translating complex queries into vreplication plans.

Related errors


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

Appendix: source

Thrown at go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go:413

	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
		}
		tpb.colExprs = append(tpb.colExprs, cexpr)
	}
	return nil
}

func (tpb *tablePlanBuilder) analyzeExpr(selExpr sqlparser.SelectExpr) (*colExpr, error) {
	aliased, ok := selExpr.(*sqlparser.AliasedExpr)
	if !ok {

View on GitHub (pinned to 01a25a7d17)