vitessio/vitess · error

unsupported distinct expression usage: %v

Error message

unsupported distinct expression usage: %v

What it means

Aggregate functions with DISTINCT (e.g. `COUNT(DISTINCT x)`) are not supported by the vstreamer plan builder because distinct aggregation cannot be streamed incrementally. Only non-distinct aggregates — specifically `COUNT(*)` — are allowed.

Source

Thrown at go/vt/vttablet/tabletmanager/vreplication/table_plan_builder.go:492

		cexpr.references[as.String()] = true
		return cexpr, nil
	}
	if expr, ok := aliased.Expr.(*sqlparser.FuncExpr); ok {
		switch fname := expr.Name.Lowered(); fname {
		case "keyspace_id":
			if len(expr.Exprs) != 0 {
				return nil, fmt.Errorf("unsupported multiple keyspace_id expressions: %v", sqlparser.String(expr))
			}

			tpb.sendSelect.AddSelectExpr(&sqlparser.AliasedExpr{Expr: aliased.Expr})
			// The vstreamer responds with "keyspace_id" as the field name for this request.
			cexpr.expr = &sqlparser.ColName{Name: sqlparser.NewIdentifierCI("keyspace_id")}
			return cexpr, nil
		}
	}
	if expr, ok := aliased.Expr.(sqlparser.AggrFunc); ok {
		if sqlparser.IsDistinct(expr) {
			return nil, fmt.Errorf("unsupported distinct expression usage: %v", sqlparser.String(expr))
		}
		switch fname := expr.AggrName(); fname {
		case "count":
			if _, ok := expr.(*sqlparser.CountStar); !ok {
				return nil, fmt.Errorf("only count(*) is supported: %v", sqlparser.String(expr))
			}
			cexpr.operation = opCount
			return cexpr, nil
		case "sum":
			if len(expr.GetArgs()) != 1 {
				return nil, fmt.Errorf("unsupported multiple columns in sum clause: %v", sqlparser.String(expr))
			}
			innerCol, ok := expr.GetArg().(*sqlparser.ColName)
			if !ok {
				return nil, fmt.Errorf("unsupported non-column name in sum clause: %v", sqlparser.String(expr))
			}
			if !innerCol.Qualifier.IsEmpty() {
				return nil, fmt.Errorf("unsupported qualifier for column: %v", sqlparser.String(innerCol))

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the DISTINCT keyword from the aggregate
  2. Use plain `COUNT(*)` if counting is the goal — it is the only supported aggregate form
  3. Perform distinct aggregation outside the replication pipeline, e.g. in a downstream query

Example fix

// before
SELECT COUNT(DISTINCT uid) AS c FROM t
// after
SELECT COUNT(*) AS c FROM t
Defensive patterns

Strategy: validation

Validate before calling

for _, e := range sel.SelectExprs {
    ae, ok := e.(*sqlparser.AliasedExpr)
    if !ok { continue }
    if ag, ok := ae.Expr.(sqlparser.AggrFunc); ok {
        if sqlparser.IsDistinct(ag) {
            return fmt.Errorf("DISTINCT aggregates unsupported: %v", sqlparser.String(ae))
        }
    }
}

Type guard

func isSupportedAggregate(e sqlparser.Expr) bool {
    cs, ok := e.(*sqlparser.CountStar)
    return ok && cs != nil
}

Try / catch

if err != nil && strings.Contains(err.Error(), "distinct expression") {
    return fmt.Errorf("remove DISTINCT from aggregates in filter select list: %w", err)
}

Prevention

When it happens

Trigger: A filter select list contains `SUM(DISTINCT col)`, `COUNT(DISTINCT col)`, `AVG(DISTINCT col)`, etc.

Common situations: Porting analytical queries into replication filter rules; users expecting full aggregate support in MoveTables filters.

Related errors


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