Skip to content

Bug Report: VStream Schema Version Tracking Fails on Dropped ENUM Column #20175

@mattlord

Description

@mattlord

Overview of the Issue

If you ALTER a table to DROP an ENUM column, a vstream can fail to process older binlog events even when schema version tracking (--track-schema-versions) is enabled.

Reproduction Steps

git checkout main && make build

cat <<'EOF'>/tmp/bug-test-case-diff.txt
diff --git a/examples/common/scripts/vttablet-up.sh b/examples/common/scripts/vttablet-up.sh
index 8ccd8bbea1..5d273df6ed 100755
--- a/examples/common/scripts/vttablet-up.sh
+++ b/examples/common/scripts/vttablet-up.sh
@@ -55,6 +55,7 @@ vttablet \
    --heartbeat-on-demand-duration=5s \
    --pprof-http \
    --log-format text \
+   ${VTTABLET_EXTRA_FLAGS:-} \
    >$VTDATAROOT/$tablet_dir/vttablet.out 2>&1 &
 
 # Block waiting for the tablet to be listening
diff --git a/examples/local/vstream_client_enum_repro.go b/examples/local/vstream_client_enum_repro.go
new file mode 100644
index 0000000000..4d671fc74b
--- /dev/null
+++ b/examples/local/vstream_client_enum_repro.go
@@ -0,0 +1,116 @@
+/*
+Copyright 2026 The Vitess Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"time"
+
+	_ "vitess.io/vitess/go/vt/vtctl/grpcvtctlclient"
+	_ "vitess.io/vitess/go/vt/vtgate/grpcvtgateconn"
+	"vitess.io/vitess/go/vt/vtgate/vtgateconn"
+
+	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
+	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
+	vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
+)
+
+// vstream_client_enum_repro reproduces the upstream Vitess bug where
+// vstream cannot decode an ENUM column whose column has been dropped
+// between the cursor position and the live schema.
+//
+// Two modes (selected by the VSTREAM_CURSOR_GTID env var):
+//
+//  1. Capture: when VSTREAM_CURSOR_GTID is unset, the client streams
+//     from the current position, waits for the first VGTID event, prints it,
+//     and exits. The printed VGTID is the resume point to use for mode 2.
+//
+//  2. Resume: when VSTREAM_CURSOR_GTID is set (format: "MySQL56/<uuid>:1-N"),
+//     the client streams from that position. After the ALTER TABLE
+//     DROP COLUMN, vstream replays the older INSERTs, hits the empty
+//     ColumnType in addEnumAndSetMappingstoPlan, and fails with
+//     "enum or set column pricing_scheme does not have valid string values".
+func main() {
+	cursorGTID := os.Getenv("VSTREAM_CURSOR_GTID")
+	startGTID := cursorGTID
+	if startGTID == "" {
+		startGTID = "current"
+	}
+
+	ctx := context.Background()
+	vgtid := &binlogdatapb.VGtid{
+		ShardGtids: []*binlogdatapb.ShardGtid{{
+			Keyspace: "commerce",
+			Shard:    "0",
+			Gtid:     startGTID,
+		}},
+	}
+	filter := &binlogdatapb.Filter{
+		Rules: []*binlogdatapb.Rule{{
+			Match:  "enum_repro",
+			Filter: "select * from enum_repro",
+		}},
+		FieldEventMode: binlogdatapb.Filter_ERR_ON_MISMATCH,
+	}
+
+	conn, err := vtgateconn.Dial(ctx, "localhost:15991")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer conn.Close()
+
+	flags := &vtgatepb.VStreamFlags{}
+	reader, err := conn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	captureMode := cursorGTID == ""
+	if captureMode {
+		fmt.Println("CAPTURE MODE: waiting for first VGTID event...")
+	} else {
+		fmt.Printf("RESUME MODE: streaming from %s\n", cursorGTID)
+	}
+
+	for {
+		evs, err := reader.Recv()
+		switch err {
+		case nil:
+			for _, ev := range evs {
+				if captureMode && ev.Type == binlogdatapb.VEventType_VGTID {
+					for _, sg := range ev.Vgtid.ShardGtids {
+						fmt.Printf("CAPTURED VGTID: keyspace=%s shard=%s gtid=%s\n",
+							sg.Keyspace, sg.Shard, sg.Gtid)
+						fmt.Printf("export VSTREAM_CURSOR_GTID='%s'\n", sg.Gtid)
+					}
+					return
+				}
+				fmt.Printf("%v\n", ev)
+			}
+		case io.EOF:
+			fmt.Printf("stream ended\n")
+			return
+		default:
+			fmt.Printf("%s:: remote error: %v\n", time.Now(), err)
+			return
+		}
+	}
+}
EOF

git apply /tmp/bug-test-case-diff.txt

cd examples/local
source ../common/env.sh

export VTTABLET_EXTRA_FLAGS='--track-schema-versions --schema-version-max-age-seconds=7776000' SKIP_VTADMIN=1

./101_initial_cluster.sh

command mysql --no-defaults -h 127.0.0.1 -P 15306 --binary-as-hex=false <<'SQL'
DROP TABLE IF EXISTS enum_repro;
CREATE TABLE enum_repro (
  id bigint NOT NULL,
  pricing_scheme enum('free', 'standard', 'enterprise') NOT NULL,
  note varchar(128) NOT NULL,
  PRIMARY KEY (id)
);
SQL

until command mysql --no-defaults -h 127.0.0.1 -P 15306 --binary-as-hex=false commerce:0 -Nse \
  "select count(*) from _vt.schema_version where ddl like '%enum_repro%'" | grep -q '^[1-9]'; do
  sleep 1
done

unset VSTREAM_CURSOR_GTID
go run ./vstream_client_enum_repro.go

Cut and paste the printed export VSTREAM_CURSOR_GTID='...' line.

command mysql --no-defaults -h 127.0.0.1 -P 15306 --binary-as-hex=false <<'SQL'
INSERT INTO enum_repro(id, pricing_scheme, note) VALUES
  (1, 'free', 'before drop'),
  (2, 'enterprise', 'before drop');

ALTER TABLE enum_repro DROP COLUMN pricing_scheme;
SQL

go run ./vstream_client_enum_repro.go

The Final Results

go run ./vstream_client_enum_repro.go
RESUME MODE: streaming from MySQL56/5cb3b178-571c-11f1-9925-d8830bab3b24:1-44
type:BEGIN  timestamp:1779591484  current_time:1779591553275772000  keyspace:"commerce"  shard:"0"  commit_parent:44  sequence_number:45  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:45"
type:VGTID  vgtid:{shard_gtids:{keyspace:"commerce"  shard:"0"  gtid:"MySQL56/5cb3b178-571c-11f1-9925-d8830bab3b24:1-45"}}  keyspace:"commerce"  shard:"0"
type:COMMIT  timestamp:1779591484  current_time:1779591553275782000  keyspace:"commerce"  shard:"0"  commit_parent:44  sequence_number:45  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:45"
type:BEGIN  timestamp:1779591485  current_time:1779591553275792000  keyspace:"commerce"  shard:"0"  commit_parent:45  sequence_number:46  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:46"
type:VGTID  vgtid:{shard_gtids:{keyspace:"commerce"  shard:"0"  gtid:"MySQL56/5cb3b178-571c-11f1-9925-d8830bab3b24:1-46"}}  keyspace:"commerce"  shard:"0"
type:COMMIT  timestamp:1779591485  current_time:1779591553275797000  keyspace:"commerce"  shard:"0"  commit_parent:45  sequence_number:46  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:46"
type:BEGIN  timestamp:1779591486  current_time:1779591553275806000  keyspace:"commerce"  shard:"0"  commit_parent:46  sequence_number:47  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:47"
type:VGTID  vgtid:{shard_gtids:{keyspace:"commerce"  shard:"0"  gtid:"MySQL56/5cb3b178-571c-11f1-9925-d8830bab3b24:1-47"}}  keyspace:"commerce"  shard:"0"
type:COMMIT  timestamp:1779591486  current_time:1779591553275813000  keyspace:"commerce"  shard:"0"  commit_parent:46  sequence_number:47  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:47"
type:BEGIN  timestamp:1779591487  current_time:1779591553275821000  keyspace:"commerce"  shard:"0"  commit_parent:47  sequence_number:48  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:48"
type:VGTID  vgtid:{shard_gtids:{keyspace:"commerce"  shard:"0"  gtid:"MySQL56/5cb3b178-571c-11f1-9925-d8830bab3b24:1-48"}}  keyspace:"commerce"  shard:"0"
type:COMMIT  timestamp:1779591487  current_time:1779591553275828000  keyspace:"commerce"  shard:"0"  commit_parent:47  sequence_number:48  event_gtid:"5cb3b178-571c-11f1-9925-d8830bab3b24:48"
2026-05-24 02:59:13.287359 +0000 UTC m=+0.059699876:: remote error: Code: UNKNOWN
rpc error: code = Unknown desc = error starting stream from shard GTID keyspace:"commerce" shard:"0" gtid:"MySQL56/5cb3b178-571c-11f1-9925-d8830bab3b24:1-48": persistent error in vstream for commerce/0 on tablet zone1-0000000100; giving up: target: commerce.0.primary: vttablet: rpc error: code = Unknown desc = stream (at source tablet) error @ (including the GTID we failed to process) 5cb3b178-571c-11f1-9925-d8830bab3b24:1-49: enum or set column pricing_scheme does not have valid string values:
failed to build ENUM and SET column integer to string mappings
failed to build table replication plan for table enum_repro

Binary Version

❯ vtgate --version
vtgate version Version: 25.0.0-SNAPSHOT (Git revision c08871ee9a103424a22c5bd5919572e45be66377 branch 'main') built on Fri May 15 19:51:50 UTC 2026 by matt@pslord.local using go1.26.3 darwin/arm64

Operating System and Environment details

N/A

Log Fragments

Metadata

Metadata

Assignees

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions