vitessio/vitess · error

unsupported multiple keyspace_id expressions: %v

Error message

unsupported multiple keyspace_id expressions: %v

What it means

The special keyspace_id() function in a filter select list must be called with zero arguments — the vstreamer itself supplies the row's keyspace id. Any argument count other than 0 (typically 1) is rejected with this error.

Source

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

		}, aliased.Expr)
		if err != nil {
			return nil, fmt.Errorf("failed to find column name for convert using expression: %v, %v", sqlparser.String(aliased.Expr), err)
		}
		selExpr := &sqlparser.ConvertUsingExpr{
			Type: "utf8mb4",
			Expr: &sqlparser.ColName{Name: colName},
		}
		cexpr.expr = expr
		cexpr.operation = opExpr
		tpb.sendSelect.AddSelectExpr(&sqlparser.AliasedExpr{Expr: selExpr, As: as})
		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

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Change the call to bare `keyspace_id()` with no arguments
  2. If you need the value of a vindex column, select the column itself instead
  3. Re-run the workflow after fixing the select expression

Example fix

// before
SELECT keyspace_id(user_id) AS keyspace_id FROM t
// after
SELECT keyspace_id() AS keyspace_id FROM t
Defensive patterns

Strategy: validation

Validate before calling

for _, e := range sel.SelectExprs {
    fe, ok := e.(*sqlparser.FuncExpr)
    if !ok || fe.Name.Lowered() != "keyspace_id" { continue }
    if len(fe.Exprs) != 0 {
        return errors.New("keyspace_id() takes no arguments")
    }
}

Type guard

func isBareKeyspaceID(e sqlparser.SelectExpr) bool {
    ae, ok := e.(*sqlparser.AliasedExpr)
    if !ok { return false }
    fe, ok := ae.Expr.(*sqlparser.FuncExpr)
    return ok && fe.Name.Lowered() == "keyspace_id" && len(fe.Exprs) == 0
}

Try / catch

if err != nil && strings.Contains(err.Error(), "keyspace_id") {
    return fmt.Errorf("use keyspace_id() with no arguments: %w", err)
}

Prevention

When it happens

Trigger: A filter rule contains `keyspace_id(some_col)` or `keyspace_id(1)` instead of the bare `keyspace_id()`.

Common situations: Users assuming keyspace_id() takes a column argument; generators emitting vindex-style calls; confusion with similar functions in other systems.

Related errors


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