vitessio/vitess · error

rule %v does not have a select expression in vreplication

Error message

rule %v does not have a select expression in vreplication

What it means

After classifying a rule's table, templatizeRule inspects rule.Filter to templatize its keyrange or SELECT expression. If the filter is an empty string there is no select expression to work with, so the rule cannot be migrated and this error is thrown. A vreplication rule must carry either a keyrange or a select expression as its filter.

Source

Thrown at go/vt/vtctl/workflow/stream_migrator.go:1124

	return shardedStreams, nil
}

// templatizeRule replaces keyrange values with {{.}}.
// This can then be used by go's template package to substitute other keyrange values.
func (sm *StreamMigrator) templatizeRule(ctx context.Context, rule *binlogdatapb.Rule) (StreamType, error) {
	vtable, ok := sm.ts.SourceKeyspaceSchema().Tables[rule.Match]
	if !ok && !schema.IsInternalOperationTableName(rule.Match) {
		return StreamTypeUnknown, fmt.Errorf("table %v not found in vschema", rule.Match)
	}

	if vtable != nil && vtable.Type == vindexes.TypeReference {
		return StreamTypeReference, nil
	}

	switch {
	case rule.Filter == "":
		return StreamTypeUnknown, fmt.Errorf("rule %v does not have a select expression in vreplication", rule)
	case key.IsValidKeyRange(rule.Filter):
		rule.Filter = "{{.}}"
		return StreamTypeSharded, nil
	case rule.Filter == vreplication.ExcludeStr:
		return StreamTypeUnknown, fmt.Errorf("unexpected rule in vreplication: %v", rule)
	default:
		if err := sm.templatizeKeyRange(ctx, rule); err != nil {
			return StreamTypeUnknown, err
		}

		return StreamTypeSharded, nil
	}
}

func (sm *StreamMigrator) templatizeKeyRange(ctx context.Context, rule *binlogdatapb.Rule) error {
	statement, err := sm.parser.Parse(rule.Filter)
	if err != nil {
		return err

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Inspect the stream's filter (`select id, workflow, source from _vt.vreplication`) and fix the rule so each has a valid keyrange or SELECT filter.
  2. Cancel and recreate the workflow using MoveTables/Materialize so rules are generated correctly.
  3. If rows were hand-edited or written by external tooling, stop doing so — always create/modify streams via vtctldclient.
  4. Check the Vitess version that created the rows for format changes between versions.

Example fix

// before: {match: "user", filter: ""}
// after: {match: "user", filter: "select * from user where in_keyrange(-40)"} — or recreate the workflow
Defensive patterns

Strategy: validation

Validate before calling

-- Each rule must have a non-empty filter (keyrange or SELECT)
SELECT id, workflow FROM _vt.vreplication
WHERE JSON_EXTRACT(source, '$.filter.rules[*].filter') IS NULL
   OR JSON_SEARCH(source, 'one', '') IS NOT NULL;

Type guard

func ruleHasFilter(r *binlogdatapb.Rule) bool { return r != nil && r.Filter != "" }

Prevention

When it happens

Trigger: templatize -> templatizeRule encountering a Rule whose Filter field is "" — e.g. a manually inserted/edited _vt.vreplication row or a filter rule constructed programmatically without a filter, then a stream migration (BuildStreamMigrator/StopStreams) runs.

Common situations: Hand-edited _vt.vreplication rows; custom tooling that wrote filter rules with an empty filter; workflow created by an older Vitess version writing rules in a format the current code rejects; corrupted binlog source proto.

Related errors


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