vitessio/vitess · error

one or two tables must be specified

Error message

one or two tables must be specified

What it means

During CreateLookupVindex, prepareCreate validates the "tables" entry of the Vindex spec. A lookup vindex backfill can only operate against one table (single-table vindex) or two tables (source plus lookup target), so any other count — zero, or more than two — is rejected with this error.

Source

Thrown at go/vt/vtctl/workflow/lookup_vindex.go:130

		for _, colVindex := range table.ColumnVindexes {
			columnVindexByName[colVindex.Name] = colVindex
		}
	}

	for vindexName, vindex := range specs.Vindexes {
		vInfo, err := lv.validateAndGetVindexInfo(vindexName, vindex, specs.Tables)
		if err != nil {
			return nil, nil, nil, nil, err
		}

		if vindex.Owner == "" {
			// If the vindex is unowned, we need to search for source table
			// using a loop iterating over specs.Tables. And, as we have
			// already validated if all vindexes are owned in case of multiple
			// vindexes, so this case should be possible only when we are
			// backfilling a single vindex. So, this approach can be used.
			if len(specs.Tables) < 1 || len(specs.Tables) > 2 {
				return nil, nil, nil, nil, errors.New("one or two tables must be specified")
			}
			vInfo.sourceTable, vInfo.sourceTableName, err = getSourceTable(specs.Tables, vInfo.targetTableName, vInfo.fromCols)
			if err != nil {
				return nil, nil, nil, nil, err
			}
			if vInfo.sourceTable == nil || vInfo.sourceTableName == "" {
				return nil, nil, nil, nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "source table not found for vindex %s", vInfo.name)
			}
		} else {
			var ok bool
			if vInfo.sourceTable, ok = specs.Tables[vindex.Owner]; !ok {
				return nil, nil, nil, nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "table owner not found for vindex %s", vInfo.name)
			}
			vInfo.sourceTableName = vindex.Owner
		}

		if len(specs.Vindexes) == 1 {
			sourceVindexColumns, err = validateSourceTableAndGetVindexColumns(vInfo, vindex, keyspace)

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Restructure the vschema so each CreateLookupVindex call's spec lists exactly one (or two) tables
  2. Issue one CreateLookupVindex per table for a lookup vindex shared by multiple tables
  3. Validate the vschema JSON (table count per vindex spec) before invoking the workflow

Example fix

// before (vschema spec)
"tables": {"t1": {}, "t2": {}, "t3": {}}
// after
"tables": {"t1": {"column_vindexes": [{"column": "c", "name": "lv"}]}}  // one table per call
Defensive patterns

Strategy: validation

Validate before calling

if len(spec.Tables) < 1 || len(spec.Tables) > 2 {
    return fmt.Errorf("lookup vindex spec must reference one or two tables, got %d", len(spec.Tables))
}

Type guard

func validTableCount(n int) bool { return n >= 1 && n <= 2 }

Try / catch

if _, _, _, _, err := prepareCreate(ctx, env, ks, params, spec); err != nil {
    if strings.Contains(err.Error(), "one or two tables must be specified") {
        return fmt.Errorf("split the vschema spec per table: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling workflow.CreateLookupVindex where the parsed vschema spec's Tables slice has length 0 or > 2, typically a lookup vindex definition whose tables map references three or more tables, or omits tables entirely at this stage.

Common situations: Authoring a complex vschema with a shared lookup vindex across many tables and trying to Materialize/Create it without splitting per table; malformed JSON in the vschema spec.

Related errors


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