{"record":{"id":"6c550a1a2311a624","repo":"vitessio/vitess","slug":"expression-needs-an-alias-v","errorCode":null,"errorMessage":"expression needs an alias: %v","messagePattern":"expression needs an alias: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vttablet/tabletmanager/vdiff/table_plan.go","lineNumber":105,"sourceCode":"\ttargetSelect := &sqlparser.Select{}\n\t// Aggregates is the list of Aggregate functions, if any.\n\tvar aggregates []*engine.AggregateParams\n\tfor _, selExpr := range sel.GetColumns() {\n\t\tswitch selExpr := selExpr.(type) {\n\t\tcase *sqlparser.StarExpr:\n\t\t\t// If it's a '*' expression, expand column list from the schema.\n\t\t\tfor _, fld := range tp.table.Fields {\n\t\t\t\taliased := &sqlparser.AliasedExpr{Expr: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI(fld.Name)}}\n\t\t\t\tsourceSelect.AddSelectExpr(aliased)\n\t\t\t\ttargetSelect.AddSelectExpr(aliased)\n\t\t\t}\n\t\tcase *sqlparser.AliasedExpr:\n\t\t\tvar targetCol *sqlparser.ColName\n\t\t\tif selExpr.As.IsEmpty() {\n\t\t\t\tif colAs, ok := selExpr.Expr.(*sqlparser.ColName); ok {\n\t\t\t\t\ttargetCol = colAs\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, fmt.Errorf(\"expression needs an alias: %v\", sqlparser.String(selExpr))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttargetCol = &sqlparser.ColName{Name: selExpr.As}\n\t\t\t}\n\t\t\t// If the input was \"select a as b\", then source will use \"a\" and target will use \"b\".\n\t\t\tsourceSelect.AddSelectExpr(selExpr)\n\t\t\ttargetSelect.AddSelectExpr(&sqlparser.AliasedExpr{Expr: targetCol})\n\n\t\t\t// Check if it's an aggregate expression\n\t\t\tif expr, ok := selExpr.Expr.(sqlparser.AggrFunc); ok {\n\t\t\t\tswitch fname := expr.AggrName(); fname {\n\t\t\t\tcase \"count\", \"sum\":\n\t\t\t\t\t// this will only work as long as aggregates can be pushed down to tablets\n\t\t\t\t\t// this won't work: \"select count(*) from (select id from t limit 1)\"\n\t\t\t\t\t// since vreplication only handles simple tables (no joins/derived tables) this is fine for now\n\t\t\t\t\t// but will need to be revisited when we add such support to vreplication\n\t\t\t\t\taggregates = append(aggregates, engine.NewAggregateParam(\n\t\t\t\t\t\t/*opcode*/ opcode.AggregateSum,","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vttablet/tabletmanager/vdiff/table_plan.go#L87-L123","documentation":"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`.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add an explicit alias: wrap every non-column expression with `AS <name>` in the workflow's select/filter rule","Make the alias match the actual column name on the target table so compareCols validation passes","Use plain column selects in rules wherever possible, restricting expressions (like convert_tz) to aliased ones"],"exampleFix":"// before\nselect convert_tz(ts, 'UTC', 'US/Eastern') from t\n// after\nselect convert_tz(ts, 'UTC', 'US/Eastern') as ts from t","handlingStrategy":"validation","validationCode":"// validate all select expressions are alias-safe before running vdiff\nfor _, se := range selectExprs {\n  ae := se.(*sqlparser.AliasedExpr)\n  if ae.As.IsEmpty() {\n    if _, ok := ae.Expr.(*sqlparser.ColName); !ok {\n      return fmt.Errorf(\"add AS alias to: %s\", sqlparser.String(ae))\n    }\n  }\n}","typeGuard":"func hasRequiredAlias(e sqlparser.SelectExpr) bool {\n  ae, ok := e.(*sqlparser.AliasedExpr)\n  if !ok { return false }\n  if !ae.As.IsEmpty() { return true }\n  _, isCol := ae.Expr.(*sqlparser.ColName)\n  return isCol\n}","tryCatchPattern":"tp, err := buildTablePlan(ctx, dbClient, dbName, collationEnv)\nif err != nil {\n  if strings.Contains(err.Error(), \"needs an alias\") {\n    return fmt.Errorf(\"fix filter rule: %w\", err)\n  }\n  return err\n}","preventionTips":["Alias every non-column expression in select lists","Match aliases to actual target column names","Prefer plain column selects in filter rules"],"tags":["vdiff","sqlparser","missing-alias"],"backgroundTag":"expression-needs-alias","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}