Skip to content
Merged
63 changes: 35 additions & 28 deletions go/vt/proto/binlogdata/binlogdata.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions go/vt/vtgate/endtoend/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ create table t1(
primary key(id1)
) Engine=InnoDB;

create table t1_copy_basic(
id1 bigint,
id2 bigint,
primary key(id1)
) Engine=InnoDB;

create table t1_copy_resume(
id1 bigint,
id2 bigint,
Expand Down Expand Up @@ -140,6 +146,12 @@ create table t1_sharded(
Name: "t1_id2_vdx",
}},
},
"t1_copy_basic": {
ColumnVindexes: []*vschemapb.ColumnVindex{{
Column: "id1",
Name: "hash",
}},
},
"t1_copy_resume": {
ColumnVindexes: []*vschemapb.ColumnVindex{{
Column: "id1",
Expand Down
37 changes: 31 additions & 6 deletions go/vt/vtgate/endtoend/vstream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func TestVStreamCopyBasic(t *testing.T) {
gconn, conn, mconn, closeConnections := initialize(ctx, t)
defer closeConnections()

_, err := conn.ExecuteFetch("insert into t1(id1,id2) values(1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8)", 1, false)
_, err := conn.ExecuteFetch("insert into t1_copy_basic(id1,id2) values(1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8)", 1, false)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To pass the End-to-End Test, I prepared the exclusive table named t1_copy_basic. 9e4b5d8

The problem sharing t1 table with the other test cases (e.g. TestConsistentLookupMultiInsertIgnore) running concurrently is not introduced by this PR. Although the number of the received events matches by chance, content of the events vary depending on the concurrent tests in the first place.

(base) ❯ git branch | grep main
* main

# The events are expected.
(base) ❯ go test -v -count=1 -run "^TestVStreamCopyBasic$" vitess.io/vitess/go/vt/vtgate/endtoend --alsologtostderr
...
===START===
Event 0; type:BEGIN keyspace:"ks" shard:"-80"
Event 1; type:FIELD field_event:{table_name:"ks.t1" fields:{name:"id1" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_-80" org_name:"id1" column_length:20 charset:63 flags:53251} fields:{name:"id2" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_-80" org_name:"id2" column_length:20 charset:63 flags:32768} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"
Event 2; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}} shard_gtids:{keyspace:"ks" shard:"80-" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}}} keyspace:"ks" shard:"-80"
Event 3; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"55"}} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"
Event 4; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:1 values:"5"}}}} shard_gtids:{keyspace:"ks" shard:"80-" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}}} keyspace:"ks" shard:"-80"
Event 5; type:COMMIT keyspace:"ks" shard:"-80"
Event 6; type:BEGIN keyspace:"ks" shard:"80-"
Event 7; type:FIELD field_event:{table_name:"ks.t1" fields:{name:"id1" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_80-" org_name:"id1" column_length:20 charset:63 flags:53251} fields:{name:"id2" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_80-" org_name:"id2" column_length:20 charset:63 flags:32768} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 8; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:1 values:"5"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}}} keyspace:"ks" shard:"80-"
Event 9; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"66"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 10; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"77"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 11; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"88"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 12; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:1 values:"5"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:1 values:"8"}}}}} keyspace:"ks" shard:"80-"
Event 13; type:COMMIT keyspace:"ks" shard:"80-"
Event 14; type:BEGIN keyspace:"ks" shard:"80-"
Event 15; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:1 values:"5"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/82178ac0-6666-11ed-b225-e84ab9c3a7f8:1-75"}} keyspace:"ks" shard:"80-"
Event 16; type:COMMIT keyspace:"ks" shard:"80-"
===END===
    vstream_test.go:222: TestVStreamCopyBasic was successful
--- PASS: TestVStreamCopyBasic (0.01s)
PASS
# Apparently, Event 6, Event 13, and Event 14 are not expected.
## I guess that these rows are inserted at https://github.com/vitessio/vitess/blob/4c33b2b5a90dc200e6cc649681cbace2520725bd/go/vt/vtgate/endtoend/lookup_test.go#L292.
$ go test -v -count=1 vitess.io/vitess/go/vt/vtgate/endtoend --alsologtostderr
...
===START===
Event 0; type:BEGIN keyspace:"ks" shard:"80-"
Event 1; type:FIELD field_event:{table_name:"ks.t1" fields:{name:"id1" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_80-" org_name:"id1" column_length:20 charset:63 flags:53251} fields:{name:"id2" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_80-" org_name:"id2" column_length:20 charset:63 flags:32768} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 2; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/2ac38034-6667-11ed-a179-dd231a1931a8:1-148" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}}} keyspace:"ks" shard:"80-"
Event 3; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"66"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 4; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"77"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 5; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"88"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 6; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:2 lengths:2 values:"5060"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"
Event 7; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/2ac38034-6667-11ed-a179-dd231a1931a8:1-148" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:2 values:"50"}}}}} keyspace:"ks" shard:"80-"
Event 8; type:COMMIT keyspace:"ks" shard:"80-"
Event 9; type:BEGIN keyspace:"ks" shard:"-80"
Event 10; type:FIELD field_event:{table_name:"ks.t1" fields:{name:"id1" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_-80" org_name:"id1" column_length:20 charset:63 flags:53251} fields:{name:"id2" type:INT64 table:"t1" org_table:"t1" database:"vt_ks_-80" org_name:"id2" column_length:20 charset:63 flags:32768} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"
Event 11; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/2ac38034-6667-11ed-a179-dd231a1931a8:1-148" table_p_ks:{table_name:"t1" lastpk:{fields:{name:"id1" type:INT32} rows:{lengths:1 values:"4"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/2ac38034-6667-11ed-a179-dd231a1931a8:1-148" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:2 values:"50"}}}}} keyspace:"ks" shard:"-80"
Event 12; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:1 lengths:1 values:"55"}} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"
Event 13; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:2 lengths:2 values:"1020"}} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"
Event 14; type:ROW row_event:{table_name:"ks.t1" row_changes:{after:{lengths:2 lengths:2 values:"3040"}} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"
Event 15; type:VGTID vgtid:{shard_gtids:{keyspace:"ks" shard:"-80" gtid:"MySQL56/2ac38034-6667-11ed-a179-dd231a1931a8:1-148" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:2 values:"30"}}}} shard_gtids:{keyspace:"ks" shard:"80-" gtid:"MySQL56/2ac38034-6667-11ed-a179-dd231a1931a8:1-148" table_p_ks:{table_name:"t1" lastpk:{rows:{lengths:2 values:"50"}}}}} keyspace:"ks" shard:"-80"
Event 16; type:COMMIT keyspace:"ks" shard:"-80"
===END===
    vstream_test.go:222: TestVStreamCopyBasic was successful
--- PASS: TestVStreamCopyBasic (0.01s)

Since this discrepancy caused the test failure in my PR, I fixed it.

if err != nil {
t.Fatal(err)
}
Expand All @@ -180,7 +180,7 @@ func TestVStreamCopyBasic(t *testing.T) {
}
qr := sqltypes.ResultToProto3(&lastPK)
tablePKs := []*binlogdatapb.TableLastPK{{
TableName: "t1",
TableName: "t1_copy_basic",
Lastpk: qr,
}}
var shardGtids []*binlogdatapb.ShardGtid
Expand All @@ -200,8 +200,8 @@ func TestVStreamCopyBasic(t *testing.T) {
vgtid.ShardGtids = shardGtids
filter := &binlogdatapb.Filter{
Rules: []*binlogdatapb.Rule{{
Match: "t1",
Filter: "select * from t1",
Match: "t1_copy_basic",
Filter: "select * from t1_copy_basic",
}},
}
flags := &vtgatepb.VStreamFlags{}
Expand All @@ -210,19 +210,44 @@ func TestVStreamCopyBasic(t *testing.T) {
if err != nil {
t.Fatal(err)
}
numExpectedEvents := 2 /* num shards */ * (7 /* begin/field/vgtid:pos/2 rowevents avg/vgitd: lastpk/commit) */ + 3 /* begin/vgtid/commit for completed table */)
numExpectedEvents := 2 /* num shards */ *(7 /* begin/field/vgtid:pos/2 rowevents avg/vgitd: lastpk/commit) */ +3 /* begin/vgtid/commit for completed table */ +1 /* copy operation completed */) + 1 /* fully copy operation completed */
expectedCompletedEvents := []string{
`type:COPY_COMPLETED keyspace:"ks" shard:"-80"`,
`type:COPY_COMPLETED keyspace:"ks" shard:"80-"`,
`type:COPY_COMPLETED`,
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this PR change, the above three events are added to the expected events.

require.NotNil(t, reader)
var evs []*binlogdatapb.VEvent
var completedEvs []*binlogdatapb.VEvent
for {
e, err := reader.Recv()
switch err {
case nil:
evs = append(evs, e...)

for _, ev := range e {
if ev.Type == binlogdatapb.VEventType_COPY_COMPLETED {
completedEvs = append(completedEvs, ev)
}
}

printEvents(evs) // for debugging ci failures

if len(evs) == numExpectedEvents {
// The arrival order of COPY_COMPLETED events with keyspace/shard is not constant.
// On the other hand, the last event should always be a fully COPY_COMPLETED event.
// That's why the sort.Slice doesn't have to handle the last element in completedEvs.
sort.Slice(completedEvs[:len(completedEvs)-1], func(i, j int) bool {
return completedEvs[i].GetShard() < completedEvs[j].GetShard()
})
for i, ev := range completedEvs {
require.Regexp(t, expectedCompletedEvents[i], ev.String())
}
t.Logf("TestVStreamCopyBasic was successful")
return
} else if numExpectedEvents < len(evs) {
t.Fatalf("len(events)=%v are not expected\n", len(evs))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, once len(evs) gets greater than numExpectedEvents, End-to-End Test job failed due to the timeout while showing no specific errors. With this error message, it's hard to narrow down the cause.

ok  	vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/endtoend	0.130s
Error: The action has timed out.

5825b27 makes the same test fail faster and shows more straightforward error messages.

--- FAIL: TestVStreamCopyBasic (0.02s)
    vstream_test.go:244: len(events)=24 are not expected
...
FAIL	vitess.io/vitess/go/vt/vtgate/endtoend	77.838s

}
printEvents(evs) // for debugging ci failures
case io.EOF:
log.Infof("stream ended\n")
cancel()
Expand Down
1 change: 1 addition & 0 deletions go/vt/vtgate/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,7 @@ func (e *Executor) startVStream(ctx context.Context, rss []*srvtopo.ResolvedShar
vsm: vsm,
eventCh: make(chan []*binlogdatapb.VEvent),
ts: ts,
copyCompletedShard: make(map[string]struct{}),
}
_ = vs.stream(ctx)
return nil
Expand Down
39 changes: 39 additions & 0 deletions go/vt/vtgate/vstream_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ type vstream struct {
// the timestamp of the most recent event, keyed by streamId. streamId is of the form <keyspace>.<shard>
timestamps map[string]int64

// the shard map tracking the copy completion, keyed by streamId. streamId is of the form <keyspace>.<shard>
copyCompletedShard map[string]struct{}

vsm *vstreamManager

eventCh chan []*binlogdatapb.VEvent
Expand Down Expand Up @@ -152,6 +155,7 @@ func (vsm *vstreamManager) VStream(ctx context.Context, tabletType topodatapb.Ta
eventCh: make(chan []*binlogdatapb.VEvent),
heartbeatInterval: flags.GetHeartbeatInterval(),
ts: ts,
copyCompletedShard: make(map[string]struct{}),
}
return vs.stream(ctx)
}
Expand Down Expand Up @@ -544,6 +548,22 @@ func (vs *vstream) streamFromTablet(ctx context.Context, sgtid *binlogdatapb.Sha
return err
}

if err := vs.sendAll(ctx, sgtid, eventss); err != nil {
return err
}
eventss = nil
sendevents = nil
case binlogdatapb.VEventType_COPY_COMPLETED:
sendevents = append(sendevents, event)
if fullyCopied, doneEvent := vs.isCopyFullyCompleted(ctx, sgtid, event); fullyCopied {
sendevents = append(sendevents, doneEvent)
}
eventss = append(eventss, sendevents)

if err := vs.alignStreams(ctx, event, sgtid.Keyspace, sgtid.Shard); err != nil {
return err
}

if err := vs.sendAll(ctx, sgtid, eventss); err != nil {
return err
}
Expand Down Expand Up @@ -676,6 +696,25 @@ func (vs *vstream) sendAll(ctx context.Context, sgtid *binlogdatapb.ShardGtid, e
return nil
}

// isCopyFullyCompleted returns true if all stream has received a copy_completed event.
// If true, it will also return a new copy_completed event that needs to be sent.
// This new event represents the completion of all the copy operations.
func (vs *vstream) isCopyFullyCompleted(ctx context.Context, sgtid *binlogdatapb.ShardGtid, event *binlogdatapb.VEvent) (bool, *binlogdatapb.VEvent) {
vs.mu.Lock()
defer vs.mu.Unlock()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function generates an additional event while holding a lock for vs.vgtid.ShardGtids.
The same lock also protects the concurrent access to vs.copyCompletedShard.


vs.copyCompletedShard[fmt.Sprintf("%s/%s", event.Keyspace, event.Shard)] = struct{}{}

for _, shard := range vs.vgtid.ShardGtids {
if _, ok := vs.copyCompletedShard[fmt.Sprintf("%s/%s", shard.Keyspace, shard.Shard)]; !ok {
return false, nil
}
}
return true, &binlogdatapb.VEvent{
Type: binlogdatapb.VEventType_COPY_COMPLETED,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This event has neither keyspace nor shard.
It represents the completion of data copy wherever the data is.

}
}

func (vs *vstream) getError() error {
vs.errMu.Lock()
defer vs.errMu.Unlock()
Expand Down
15 changes: 14 additions & 1 deletion go/vt/vttablet/tabletserver/vstreamer/uvstreamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,9 @@ func (uvs *uvstreamer) Stream() error {
uvs.vse.errorCounts.Add("Copy", 1)
return err
}
uvs.sendTestEvent("Copy Done")
if err := uvs.allCopyComplete(); err != nil {
return err
}
}
vs := newVStreamer(uvs.ctx, uvs.cp, uvs.se, mysql.EncodePosition(uvs.pos), mysql.EncodePosition(uvs.stopPos),
uvs.filter, uvs.getVSchema(), uvs.send, "replicate", uvs.vse)
Expand Down Expand Up @@ -456,6 +458,17 @@ func (uvs *uvstreamer) setCopyState(tableName string, qr *querypb.QueryResult) {
uvs.plans[tableName].tablePK.Lastpk = qr
}

func (uvs *uvstreamer) allCopyComplete() error {
ev := &binlogdatapb.VEvent{
Type: binlogdatapb.VEventType_COPY_COMPLETED,
}

if err := uvs.send([]*binlogdatapb.VEvent{ev}); err != nil {
return err
}
return nil
}

// dummy event sent only in test mode
func (uvs *uvstreamer) sendTestEvent(msg string) {
if !uvstreamerTestMode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func TestVStreamCopyCompleteFlow(t *testing.T) {

}

callbacks["OTHER.*Copy Done"] = func() {
callbacks["COPY_COMPLETED"] = func() {
log.Info("Copy done, inserting events to stream")
insertRow(t, "t1", 1, numInitialRows+4)
insertRow(t, "t2", 2, numInitialRows+3)
Expand All @@ -252,7 +252,7 @@ commit;"
}

numCopyEvents := 3 /*t1,t2,t3*/ * (numInitialRows + 1 /*FieldEvent*/ + 1 /*LastPKEvent*/ + 1 /*TestEvent: Copy Start*/ + 2 /*begin,commit*/ + 3 /* LastPK Completed*/)
numCopyEvents += 2 /* GTID + Test event after all copy is done */
numCopyEvents += 2 /* GTID + Event after all copy is done */
numCatchupEvents := 3 * 5 /* 2 t1, 1 t2 : BEGIN+FIELD+ROW+GTID+COMMIT */
numFastForwardEvents := 5 /*t1:FIELD+ROW*/
numMisc := 1 /* t2 insert during t1 catchup that comes in t2 copy */
Expand Down Expand Up @@ -539,7 +539,7 @@ var expectedEvents = []string{
"type:BEGIN",
"type:LASTPK last_p_k_event:{table_last_p_k:{table_name:\"t3\"} completed:true}",
"type:COMMIT",
"type:OTHER gtid:\"Copy Done\"",
"type:COPY_COMPLETED",
"type:BEGIN",
"type:FIELD field_event:{table_name:\"t1\" fields:{name:\"id11\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id11\" column_length:11 charset:63 column_type:\"int(11)\"} fields:{name:\"id12\" type:INT32 table:\"t1\" org_table:\"t1\" database:\"vttest\" org_name:\"id12\" column_length:11 charset:63 column_type:\"int(11)\"}}",
"type:ROW row_event:{table_name:\"t1\" row_changes:{after:{lengths:2 lengths:3 values:\"14140\"}}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ func TestVStreamCopySimpleFlow(t *testing.T) {
testcases := []testcase{
{
input: []string{},
output: [][]string{t1FieldEvent, {"gtid"}, t1Events, {"begin", "lastpk", "commit"}, t2FieldEvent, t2Events, {"begin", "lastpk", "commit"}},
output: [][]string{t1FieldEvent, {"gtid"}, t1Events, {"begin", "lastpk", "commit"}, t2FieldEvent, t2Events, {"begin", "lastpk", "commit"}, {"copy_completed"}},
},

{
Expand Down Expand Up @@ -2178,6 +2178,10 @@ func expectLog(ctx context.Context, t *testing.T, input any, ch <-chan []*binlog
if evs[i].Type != binlogdatapb.VEventType_DDL {
t.Fatalf("%v (%d): event: %v, want ddl", input, i, evs[i])
}
case "copy_completed":
if evs[i].Type != binlogdatapb.VEventType_COPY_COMPLETED {
t.Fatalf("%v (%d): event: %v, want copy_completed", input, i, evs[i])
}
default:
evs[i].Timestamp = 0
if evs[i].Type == binlogdatapb.VEventType_FIELD {
Expand Down
4 changes: 4 additions & 0 deletions proto/binlogdata.proto
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ enum VEventType {
VERSION = 17;
LASTPK = 18;
SAVEPOINT = 19;
// COPY_COMPLETED is sent when VTGate's VStream copy operation is done.
// If a client experiences some disruptions before receiving the event,
// the client should restart the copy operation.
COPY_COMPLETED = 20;
}

// RowChange represents one row change.
Expand Down
3 changes: 2 additions & 1 deletion web/vtadmin/src/proto/vtadmin.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading