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 happenView on GitHub (pinned to 01a25a7d17)
Solutions
- Inspect the diff query in the vdiff table state (debug output / _vt.vdiff_table) and check it is a plain SELECT.
- Fix the workflow's filter/table settings so the source query is a simple SELECT.
- 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
- Keep vreplication filter rules as simple SELECTs.
- Test filter rules with a parser round-trip before creating the workflow.
- Don't hand-edit _vt.vdiff_table query columns.
- Pin consistent Vitess versions so parser behavior matches.
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
- error getting select: %s
- found target SelectExpr which was neither ColName nor FuncEx
- unexpected: %v
- expression needs an alias: %v
- table expression is complex
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/ae6979024be32832.
Report an issue: GitHub.