vitessio/vitess · error

VT09018

VT09018

Error message

cannot add '%s' expression to a table/vindex

What it means

VT09018 is raised when the planner tries to add a non-column expression to an operator that only supports plain column references. addColumn for a table/vindex operator asserts that the expression being added is a *sqlparser.ColName; anything else (function calls, arithmetic, literals, subqueries) cannot be materialized by a table/vindex source, so the planner panics with the expression string embedded.

Source

Thrown at go/vt/vtgate/planbuilder/operators/table.go:126

func (to *Table) GetColNames() []*sqlparser.ColName {
	return to.Columns
}

func (to *Table) AddCol(col *sqlparser.ColName) {
	to.Columns = append(to.Columns, col)
}

func (to *Table) TablesUsed(in []string) []string {
	if to.QTable == nil || to.VTable == nil || sqlparser.SystemSchema(to.QTable.Table.Qualifier.String()) {
		return in
	}
	return append(in, QualifiedString(to.VTable.Keyspace, to.VTable.Name.String()))
}

func addColumn(ctx *plancontext.PlanningContext, op ColNameColumns, e sqlparser.Expr) int {
	col, ok := e.(*sqlparser.ColName)
	if !ok {
		panic(vterrors.VT09018(fmt.Sprintf("cannot add '%s' expression to a table/vindex", sqlparser.String(e))))
	}
	sqlparser.RemoveKeyspaceInCol(col)
	cols := op.GetColNames()
	colAsExpr := func(c *sqlparser.ColName) sqlparser.Expr { return c }
	if offset, found := canReuseColumn(ctx, cols, e, colAsExpr); found {
		return offset
	}
	offset := len(cols)
	op.AddCol(col)
	return offset
}

func (to *Table) ShortDescription() string {
	if to.QTable == nil {
		return "dual"
	}
	var tbl string
	if to.VTable != nil {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Rewrite the query so the non-column expression is computed in a derived table: SELECT e.val FROM (SELECT func(x) AS val ... ) e — keeping the table operator's columns simple.
  2. Check the expression in the panic message ('cannot add %s ...') and simplify it, e.g. avoid complex expressions directly on the table's projected columns.
  3. If the query is standard SQL and should be supported, report it as a planner bug with the minimized query and Vitess version.

Example fix

// before: pushing a function expression to the table operator
SELECT SHA1(col) FROM t

// after: wrap in a derived table so only a column reaches the table
SELECT d.h FROM (SELECT SHA1(col) AS h FROM t) AS d
Defensive patterns

Strategy: validation

Validate before calling

if _, ok := expr.(*sqlparser.ColName); !ok {
    // only plain columns can be added to a table/vindex; handle at an outer projection
    return errors.New("non-column expression cannot be pushed to table")
}

Type guard

func isPlainColumn(e sqlparser.Expr) bool {
    _, ok := e.(*sqlparser.ColName)
    return ok
}

Try / catch

// Panics are internal invariants; prefer pre-validation. If catching in a planner boundary:
defer func() {
    if r := recover(); r != nil {
        vte, ok := r.(error)
        if ok && vterrors.Code(vte) == vtrpcpb.Code_VT09018 { // unwrap/handle
            _ = vte
        }
    }
}()

Prevention

When it happens

Trigger: The planner's addColumn path receives an expression that is not a simple column name while planning column output for a Table/Vindex operator — e.g. a SELECT item or pushed expression like `SELECT func(x) FROM t` reaching the table operator's AddColumn via addColumn instead of being evaluated in an enclosing projection.

Common situations: Queries with computed expressions or function calls in the select list that the planner expected to be handled by an outer projection but were pushed to the table/vindex; usually indicates a planner limitation or bug with the specific SQL shape rather than user misconfiguration.

Related errors


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