vitessio/vitess · error

unexpected: %+v

Error message

unexpected: %+v

What it means

genRowDiff builds the per-row diff query and expects the parsed statement to be a *sqlparser.Select. If the parsed diff statement is any other AST node type (INSERT, UNION, etc.), it fails fast with this error, including the stringified statement.

Source

Thrown at go/vt/vttablet/tabletmanager/vdiff/report.go:84

	// LosslessValues is set when the sample contains all of the row's column
	// values without truncation, meaning it can be used to prove that two
	// rows are identical during extra-row reconciliation. The marker is
	// deliberately affirmative: samples that are lossy (only-pks, truncated
	// values) -- or that were persisted by an older binary and reloaded on
	// resume -- lack it and are excluded from reconciliation.
	LosslessValues bool `json:"LosslessValues,omitempty"`
}

func (td *tableDiffer) genRowDiff(queryStmt string, row []sqltypes.Value, opts *tabletmanagerdatapb.VDiffReportOptions) (*RowDiff, error) {
	rd := &RowDiff{}
	rd.Row = make(map[string]string)
	statement, err := td.wd.ct.vde.parser.Parse(queryStmt)
	if err != nil {
		return nil, err
	}
	sel, ok := statement.(*sqlparser.Select)
	if !ok {
		return nil, fmt.Errorf("unexpected: %+v", sqlparser.String(statement))
	}

	if opts.GetDebugQuery() {
		rd.Query = td.genDebugQueryDiff(sel, row, opts.GetOnlyPks())
	}

	truncated := false
	addVal := func(index int, truncateAt int) error {
		buf := sqlparser.NewTrackedBuffer(nil)
		sel.SelectExprs.Exprs[index].Format(buf)
		col := buf.String()
		// Let's truncate if it's really worth it to avoid losing
		// value for a few chars.
		if truncateAt > 0 && row[index].Len() >= truncateAt+len(truncatedNotation)+20 {
			truncated = true
			if row[index].IsBinary() {
				rb, err := row[index].ToBytes()
				if err != nil { // Should never happen

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Inspect the diff query in the vdiff table state (debug output / _vt.vdiff_table) and check it is a plain SELECT.
  2. Fix the workflow's filter/table settings so the source query is a simple SELECT.
  3. Recreate the vdiff after correcting the rule: delete, then VDiff create.

Example fix

// before (filter rule)
filter: "select * from t1 union select * from t2"
// after
filter: "select * from t1" // plain SELECT only
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

func isSelectStatement(stmt sqlparser.Statement) bool {
  _, ok := stmt.(*sqlparser.Select)
  return ok
}

Try / catch

if err := runDiff(); err != nil && strings.Contains(err.Error(), "unexpected:") {
  // inspect the offending statement printed after 'unexpected:' and fix the filter rule
}

Prevention

When it happens

Trigger: A vdiff table diff query string, constructed from the workflow's filter rules/table settings, parses to a non-Select statement — typically a malformed or non-SELECT filter rule feeding the table_diff pipeline.

Common situations: A MoveTables/Reshard vreplication filter rule that isn't a plain SELECT; manual edits to _vt.vdiff_table or workflow options; a parser version change reinterpreting a rule differently.

Related errors


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