vitessio/vitess · error

expression needs an alias: %v

Error message

expression needs an alias: %v

What it means

Each target select expression in a vdiff plan must resolve to a column name. When an AliasedExpr has no alias, it is only accepted if the expression is a bare ColName; otherwise vdiff cannot name the target column and throws `expression needs an alias`.

Source

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

	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() {
				if colAs, ok := selExpr.Expr.(*sqlparser.ColName); ok {
					targetCol = colAs
				} else {
					return nil, fmt.Errorf("expression needs an alias: %v", sqlparser.String(selExpr))
				}
			} else {
				targetCol = &sqlparser.ColName{Name: selExpr.As}
			}
			// If the input was "select a as b", then source will use "a" and target will use "b".
			sourceSelect.AddSelectExpr(selExpr)
			targetSelect.AddSelectExpr(&sqlparser.AliasedExpr{Expr: targetCol})

			// Check if it's an aggregate expression
			if expr, ok := selExpr.Expr.(sqlparser.AggrFunc); ok {
				switch fname := expr.AggrName(); fname {
				case "count", "sum":
					// this will only work as long as aggregates can be pushed down to tablets
					// this won't work: "select count(*) from (select id from t limit 1)"
					// since vreplication only handles simple tables (no joins/derived tables) this is fine for now
					// but will need to be revisited when we add such support to vreplication
					aggregates = append(aggregates, engine.NewAggregateParam(
						/*opcode*/ opcode.AggregateSum,

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Add an explicit alias: wrap every non-column expression with `AS <name>` in the workflow's select/filter rule
  2. Make the alias match the actual column name on the target table so compareCols validation passes
  3. Use plain column selects in rules wherever possible, restricting expressions (like convert_tz) to aliased ones

Example fix

// before
select convert_tz(ts, 'UTC', 'US/Eastern') from t
// after
select convert_tz(ts, 'UTC', 'US/Eastern') as ts from t
Defensive patterns

Strategy: validation

Validate before calling

// validate all select expressions are alias-safe before running vdiff
for _, se := range selectExprs {
  ae := se.(*sqlparser.AliasedExpr)
  if ae.As.IsEmpty() {
    if _, ok := ae.Expr.(*sqlparser.ColName); !ok {
      return fmt.Errorf("add AS alias to: %s", sqlparser.String(ae))
    }
  }
}

Type guard

func hasRequiredAlias(e sqlparser.SelectExpr) bool {
  ae, ok := e.(*sqlparser.AliasedExpr)
  if !ok { return false }
  if !ae.As.IsEmpty() { return true }
  _, isCol := ae.Expr.(*sqlparser.ColName)
  return isCol
}

Try / catch

tp, err := buildTablePlan(ctx, dbClient, dbName, collationEnv)
if err != nil {
  if strings.Contains(err.Error(), "needs an alias") {
    return fmt.Errorf("fix filter rule: %w", err)
  }
  return err
}

Prevention

When it happens

Trigger: A select list entry like `select convert_tz(ts,...) from t` or `select a+b from t` with no `AS alias`, parsed by buildTablePlan, where the expression is not a simple column reference.

Common situations: Custom filter rules using computed columns without aliases; convert_tz style transformations added for datetime handling but left unaliased; generated rules edited to add expressions.

Related errors


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