vitessio/vitess · error

unsupported construct: %s

Error message

unsupported construct: %s

What it means

buildControllerPlan parses a SQL statement issued against the vreplication engine and only supports Update, Insert, Delete and Select statements. Any other construct (DDL, SET, transactions, SHOW, etc.) hits the default branch and returns `unsupported construct: <SQL>`.

Source

Thrown at go/vt/vttablet/tabletmanager/vreplication/controller_plan.go:157

// buildControllerPlan parses the input query and returns an appropriate plan.
func buildControllerPlan(query string, parser *sqlparser.Parser) (*controllerPlan, error) {
	stmt, err := parser.Parse(query)
	if err != nil {
		return nil, err
	}
	var plan *controllerPlan
	switch stmt := stmt.(type) {
	case *sqlparser.Insert:
		plan, err = buildInsertPlan(stmt)
	case *sqlparser.Update:
		plan, err = buildUpdatePlan(stmt)
	case *sqlparser.Delete:
		plan, err = buildDeletePlan(stmt)
	case *sqlparser.Select:
		plan, err = buildSelectPlan(stmt)
	default:
		return nil, fmt.Errorf("unsupported construct: %s", sqlparser.String(stmt))
	}
	if err != nil {
		return nil, err
	}
	plan.query = query
	return plan, nil
}

func buildInsertPlan(ins *sqlparser.Insert) (*controllerPlan, error) {
	// This should never happen.
	if ins == nil {
		return nil, errors.New("BUG: invalid nil INSERT statement found when building VReplication plan")
	}
	tableName, err := ins.Table.TableName()
	if err != nil {
		return nil, err
	}
	if tableName.Qualifier.String() != sidecar.GetName() && tableName.Qualifier.String() != sidecar.DefaultName {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Rewrite the statement as one of UPDATE/INSERT/DELETE/SELECT against _vt.vreplication or _vt.resharding_journal.
  2. Use vtctldclient workflow commands instead of raw DDL to mutate vreplication state.
  3. For DDL on the sidecar table, run it directly on the tablet's MySQL, not through the vreplication engine.
  4. If a legitimate new statement type should be supported, file/see an upstream issue to add a plan builder branch.

Example fix

// before
exec("ALTER TABLE _vt.vreplication ADD COLUMN foo INT")
// after
// run DDL directly on tablet MySQL, or use:
exec("update _vt.vreplication set state='Stopped' where id=1")
Defensive patterns

Strategy: validation

Validate before calling

// Only send DML to the vreplication engine:
stmt := strings.TrimSpace(query)
lower := strings.ToLower(stmt)
if !(strings.HasPrefix(lower, "select") || strings.HasPrefix(lower, "insert") ||
     strings.HasPrefix(lower, "update") || strings.HasPrefix(lower, "delete")) {
    return fmt.Errorf("vexec only supports SELECT/INSERT/UPDATE/DELETE, got: %s", stmt)
}

Try / catch

plan, err := buildControllerPlan(stmt, query, sidecar)
if err != nil {
    if strings.Contains(err.Error(), "unsupported construct") {
        // fall back to running the statement directly on tablet MySQL
    }
    return err
}

Prevention

When it happens

Trigger: Executing a non-DML statement through the vreplication engine's exec path (the code that runs _vt.vreplication queries), e.g. `ALTER TABLE _vt.vreplication ...`, `SET ...`, `BEGIN`, or `TRUNCATE` passed to vexec / UpdateVReplicationWorkflow style APIs.

Common situations: Operators running ad-hoc SQL on _vt.vreplication via vtctldclient VExec with DDL; tooling or scripts sending unsupported statements; newer parser statement types (e.g. source-specific syntax) not mapped to a plan builder.

Related errors


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