vitessio/vitess · error

unexpected: %v

Error message

unexpected: %v

What it means

buildTablePlan parses the source query of a vreplication rule and requires it to be a plain SELECT statement, since vdiff builds source/target selects from it. If the parsed statement is any other AST node type (INSERT, UPDATE, SET, etc.), it fails with `unexpected: <sql>`.

Source

Thrown at go/vt/vttablet/tabletmanager/vdiff/table_plan.go:83

	table          *tabletmanagerdatapb.TableDefinition
	orderBy        sqlparser.OrderBy
	aggregates     []*engine.AggregateParams
	WorkflowConfig **vttablet.VReplicationConfig
}

func (td *tableDiffer) buildTablePlan(dbClient binlogplayer.DBClient, dbName string, collationEnv *collations.Environment) (*tablePlan, error) {
	tp := &tablePlan{
		table:          td.table,
		dbName:         dbName,
		WorkflowConfig: td.wd.WorkflowConfig,
	}
	statement, err := td.wd.ct.vde.parser.Parse(td.sourceQuery)
	if err != nil {
		return nil, err
	}
	sel, ok := statement.(*sqlparser.Select)
	if !ok {
		return nil, fmt.Errorf("unexpected: %v", sqlparser.String(statement))
	}

	sourceSelect := &sqlparser.Select{}
	targetSelect := &sqlparser.Select{}
	// Aggregates is the list of Aggregate functions, if any.
	var aggregates []*engine.AggregateParams
	for _, selExpr := range sel.GetColumns() {
		switch selExpr := selExpr.(type) {
		case *sqlparser.StarExpr:
			// If it's a '*' expression, expand column list from the schema.
			for _, fld := range tp.table.Fields {
				aliased := &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI(fld.Name)}}
				sourceSelect.AddSelectExpr(aliased)
				targetSelect.AddSelectExpr(aliased)
			}
		case *sqlparser.AliasedExpr:
			var targetCol *sqlparser.ColName
			if selExpr.As.IsEmpty() {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Inspect the workflow's filter rules and fix the offending rule to be a single valid SELECT statement
  2. If the rule was meant to be a keyrange/table filter, use the supported forms (table name, keyrange string, or SELECT) instead of arbitrary SQL
  3. Re-create the workflow (MoveTables/Migrate) with correct filter rules rather than editing stored SQL by hand

Example fix

// before (invalid filter rule)
rule.Filter = "update t set a=1"
// after
rule.Filter = "select * from t"
Defensive patterns

Strategy: validation

Validate before calling

stmt, err := parser.Parse(rule.Filter)
if err != nil { return err }
if _, ok := stmt.(*sqlparser.Select); !ok {
  return fmt.Errorf("filter rule must be a SELECT: %s", rule.Filter)
}

Type guard

func isSelectStatement(sqlText string) bool {
  stmt, err := sqlparser.Parse(sqlText)
  if err != nil { return false }
  _, ok := stmt.(*sqlparser.Select)
  return ok
}

Try / catch

td, err := buildTablePlan(...)
if err != nil {
  if strings.Contains(err.Error(), "unexpected:") {
    log.Errorf("filter rule for %s is not a SELECT; fix the rule", tableName)
  }
  return err
}

Prevention

When it happens

Trigger: A binlog filter rule's Filter value, when parsed by sqlparser, is not a *sqlparser.Select — e.g. the rule was written as a full non-select statement or contains multiple/invalid statements.

Common situations: Hand-edited vreplication filter rules (e.g. in the _vt.vreplication workflow column or MoveTables rules) that accidentally contain non-SELECT text; copying a rule from another tool; version differences in the filter stored for legacy workflows.

Related errors


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