From 88d70331d571fa97676428711f23e17f55461e31 Mon Sep 17 00:00:00 2001 From: Manan Gupta <35839558+GuptaManan100@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:10:03 +0530 Subject: [PATCH 01/34] Allow cross cell promotion in PRS (#16461) Signed-off-by: Manan Gupta --- changelog/21.0/21.0.0/summary.md | 6 + go/cmd/vtctldclient/command/reparents.go | 3 + .../reparent/plannedreparent/reparent_test.go | 34 +- go/test/endtoend/reparent/utils/utils.go | 11 +- go/vt/proto/vtctldata/vtctldata.pb.go | 1691 +++++++++-------- go/vt/proto/vtctldata/vtctldata_vtproto.pb.go | 34 + go/vt/vtctl/grpcvtctldserver/server.go | 9 +- go/vt/vtctl/reparent.go | 10 +- .../vtctl/reparentutil/planned_reparenter.go | 11 +- go/vt/vtctl/reparentutil/util.go | 22 +- go/vt/vtctl/reparentutil/util_test.go | 89 +- proto/vtctldata.proto | 2 + web/vtadmin/src/proto/vtadmin.d.ts | 6 + web/vtadmin/src/proto/vtadmin.js | 23 + 14 files changed, 1056 insertions(+), 895 deletions(-) diff --git a/changelog/21.0/21.0.0/summary.md b/changelog/21.0/21.0.0/summary.md index 0d046ae9d75..a29e2d286ec 100644 --- a/changelog/21.0/21.0.0/summary.md +++ b/changelog/21.0/21.0.0/summary.md @@ -11,6 +11,7 @@ - **[Traffic Mirroring](#traffic-mirroring)** - **[New VTGate Shutdown Behavior](#new-vtgate-shutdown-behavior)** - **[Tablet Throttler: Multi-Metric support](#tablet-throttler)** + - **[Allow Cross Cell Promotion in PRS](#allow-cross-cell)** ## Major Changes @@ -96,3 +97,8 @@ Each metric has a factory threshold which can be overridden by the `UpdateThrott The throttler also supports the catch-all `"all"` app name, and it is thus possible to assign metrics to _all_ apps. Explicit app to metric assignments will override the catch-all configuration. Metrics are assigned a default _scope_, which could be `self` (isolated to the tablet) or `shard` (max, aka _worst_ value among shard tablets). It is further possible to require a different scope for each metric. + +### Allow Cross Cell Promotion in PRS +Up until now if the users wanted to promote a replica in a different cell than the current primary using `PlannedReparentShard`, they had to specify the new primary with the `--new-primary` flag. + +We have now added a new flag `--allow-cross-cell-promotion` that lets `PlannedReparentShard` choose a primary in a different cell even if no new primary is provided explicitly. diff --git a/go/cmd/vtctldclient/command/reparents.go b/go/cmd/vtctldclient/command/reparents.go index 17b87eaba4f..8888c5a48cd 100644 --- a/go/cmd/vtctldclient/command/reparents.go +++ b/go/cmd/vtctldclient/command/reparents.go @@ -187,6 +187,7 @@ var plannedReparentShardOptions = struct { AvoidPrimaryAliasStr string WaitReplicasTimeout time.Duration TolerableReplicationLag time.Duration + AllowCrossCellPromotion bool }{} func commandPlannedReparentShard(cmd *cobra.Command, args []string) error { @@ -223,6 +224,7 @@ func commandPlannedReparentShard(cmd *cobra.Command, args []string) error { AvoidPrimary: avoidPrimaryAlias, WaitReplicasTimeout: protoutil.DurationToProto(plannedReparentShardOptions.WaitReplicasTimeout), TolerableReplicationLag: protoutil.DurationToProto(plannedReparentShardOptions.TolerableReplicationLag), + AllowCrossCellPromotion: plannedReparentShardOptions.AllowCrossCellPromotion, }) if err != nil { return err @@ -297,6 +299,7 @@ func init() { PlannedReparentShard.Flags().DurationVar(&plannedReparentShardOptions.TolerableReplicationLag, "tolerable-replication-lag", 0, "Amount of replication lag that is considered acceptable for a tablet to be eligible for promotion when Vitess makes the choice of a new primary.") PlannedReparentShard.Flags().StringVar(&plannedReparentShardOptions.NewPrimaryAliasStr, "new-primary", "", "Alias of a tablet that should be the new primary.") PlannedReparentShard.Flags().StringVar(&plannedReparentShardOptions.AvoidPrimaryAliasStr, "avoid-primary", "", "Alias of a tablet that should not be the primary; i.e. \"reparent to any other tablet if this one is the primary\".") + PlannedReparentShard.Flags().BoolVar(&plannedReparentShardOptions.AllowCrossCellPromotion, "allow-cross-cell-promotion", false, "Allow cross cell promotion") Root.AddCommand(PlannedReparentShard) Root.AddCommand(ReparentTablet) diff --git a/go/test/endtoend/reparent/plannedreparent/reparent_test.go b/go/test/endtoend/reparent/plannedreparent/reparent_test.go index ccfd2eee239..fb625b691f5 100644 --- a/go/test/endtoend/reparent/plannedreparent/reparent_test.go +++ b/go/test/endtoend/reparent/plannedreparent/reparent_test.go @@ -125,14 +125,8 @@ func TestReparentReplicaOffline(t *testing.T) { require.Error(t, err) // Assert that PRS failed - if clusterInstance.VtctlMajorVersion <= 17 { - assert.True(t, utils.SetReplicationSourceFailed(tablets[3], out)) - utils.CheckPrimaryTablet(t, clusterInstance, tablets[1]) - } else { - assert.Contains(t, out, "rpc error: code = DeadlineExceeded desc") - utils.CheckPrimaryTablet(t, clusterInstance, tablets[0]) - } - + assert.Contains(t, out, "rpc error: code = DeadlineExceeded desc") + utils.CheckPrimaryTablet(t, clusterInstance, tablets[0]) } func TestReparentAvoid(t *testing.T) { @@ -155,20 +149,32 @@ func TestReparentAvoid(t *testing.T) { require.NoError(t, err) utils.ValidateTopology(t, clusterInstance, false) - // tablets[1 is in the same cell and tablets[3] is in a different cell, so we must land on tablets[1 + // tablets[1] is in the same cell and tablets[3] is in a different cell, so we must land on tablets[1] utils.CheckPrimaryTablet(t, clusterInstance, tablets[1]) // If we kill the tablet in the same cell as primary then reparent --avoid_tablet will fail. utils.StopTablet(t, tablets[0], true) out, err := utils.PrsAvoid(t, clusterInstance, tablets[1]) require.Error(t, err) - if clusterInstance.VtctlMajorVersion <= 17 { - assert.Contains(t, out, "cannot find a tablet to reparent to in the same cell as the current primary") - } else { - assert.Contains(t, out, "rpc error: code = DeadlineExceeded desc = latest balancer error") - } + assert.Contains(t, out, "rpc error: code = DeadlineExceeded desc = latest balancer error") utils.ValidateTopology(t, clusterInstance, false) utils.CheckPrimaryTablet(t, clusterInstance, tablets[1]) + + t.Run("Allow cross cell promotion", func(t *testing.T) { + if clusterInstance.VtctlMajorVersion <= 20 { + t.Skip("Allow Cross Cell Promotion was added in v21") + } + utils.DeleteTablet(t, clusterInstance, tablets[0]) + // Perform a graceful reparent operation and verify it fails because we have no replicas in the same cell as the primary. + out, err = utils.PrsAvoid(t, clusterInstance, tablets[1]) + require.Error(t, err) + assert.Contains(t, out, "is not in the same cell as the previous primary") + + // If we run PRS with allow cross cell promotion then it should succeed and should promote the replica in another cell. + _, err = utils.PrsAvoid(t, clusterInstance, tablets[1], "--allow-cross-cell-promotion") + require.NoError(t, err) + utils.CheckPrimaryTablet(t, clusterInstance, tablets[3]) + }) } func TestReparentFromOutside(t *testing.T) { diff --git a/go/test/endtoend/reparent/utils/utils.go b/go/test/endtoend/reparent/utils/utils.go index 5038352d721..0d3eddc0464 100644 --- a/go/test/endtoend/reparent/utils/utils.go +++ b/go/test/endtoend/reparent/utils/utils.go @@ -293,17 +293,17 @@ func execute(t *testing.T, conn *mysql.Conn, query string) *sqltypes.Result { // region ers, prs // Prs runs PRS -func Prs(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet) (string, error) { - return PrsWithTimeout(t, clusterInstance, tab, false, "", "") +func Prs(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet, extraArgs ...string) (string, error) { + return PrsWithTimeout(t, clusterInstance, tab, false, "", "", extraArgs...) } // PrsAvoid runs PRS -func PrsAvoid(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet) (string, error) { - return PrsWithTimeout(t, clusterInstance, tab, true, "", "") +func PrsAvoid(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet, extraArgs ...string) (string, error) { + return PrsWithTimeout(t, clusterInstance, tab, true, "", "", extraArgs...) } // PrsWithTimeout runs PRS -func PrsWithTimeout(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet, avoid bool, actionTimeout, waitTimeout string) (string, error) { +func PrsWithTimeout(t *testing.T, clusterInstance *cluster.LocalProcessCluster, tab *cluster.Vttablet, avoid bool, actionTimeout, waitTimeout string, extraArgs ...string) (string, error) { args := []string{ "PlannedReparentShard", fmt.Sprintf("%s/%s", KeyspaceName, ShardName)} @@ -319,6 +319,7 @@ func PrsWithTimeout(t *testing.T, clusterInstance *cluster.LocalProcessCluster, args = append(args, "--new-primary") } args = append(args, tab.Alias) + args = append(args, extraArgs...) out, err := clusterInstance.VtctldClientProcess.ExecuteCommandWithOutput(args...) return out, err } diff --git a/go/vt/proto/vtctldata/vtctldata.pb.go b/go/vt/proto/vtctldata/vtctldata.pb.go index a79acd29dab..51a94298a40 100644 --- a/go/vt/proto/vtctldata/vtctldata.pb.go +++ b/go/vt/proto/vtctldata/vtctldata.pb.go @@ -9668,6 +9668,8 @@ type PlannedReparentShardRequest struct { // acceptable for a tablet to be eligible for promotion when Vitess makes the choice of a new primary. // A value of 0 indicates that Vitess shouldn't consider the replication lag at all. TolerableReplicationLag *vttime.Duration `protobuf:"bytes,6,opt,name=tolerable_replication_lag,json=tolerableReplicationLag,proto3" json:"tolerable_replication_lag,omitempty"` + // AllowCrossCellPromotion allows cross cell promotion, + AllowCrossCellPromotion bool `protobuf:"varint,7,opt,name=allow_cross_cell_promotion,json=allowCrossCellPromotion,proto3" json:"allow_cross_cell_promotion,omitempty"` } func (x *PlannedReparentShardRequest) Reset() { @@ -9744,6 +9746,13 @@ func (x *PlannedReparentShardRequest) GetTolerableReplicationLag() *vttime.Durat return nil } +func (x *PlannedReparentShardRequest) GetAllowCrossCellPromotion() bool { + if x != nil { + return x.AllowCrossCellPromotion + } + return false +} + type PlannedReparentShardResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -18159,7 +18168,7 @@ var file_vtctldata_proto_rawDesc = []byte{ 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x14, 0x0a, 0x12, 0x50, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd7, 0x02, 0x0a, 0x1b, 0x50, 0x6c, 0x61, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x94, 0x03, 0x0a, 0x1b, 0x50, 0x6c, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, @@ -18181,538 +18190,576 @@ var file_vtctldata_proto_rawDesc = []byte{ 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x17, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, - 0x61, 0x67, 0x22, 0xba, 0x01, 0x0a, 0x1c, 0x50, 0x6c, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, - 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x40, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, - 0x64, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x64, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, - 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, - 0x74, 0x0a, 0x1b, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, - 0x6c, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, - 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, - 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x50, 0x61, - 0x72, 0x74, 0x69, 0x61, 0x6c, 0x22, 0x1e, 0x0a, 0x1c, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x0a, 0x1a, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x47, 0x72, 0x61, 0x70, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x13, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x64, 0x0a, 0x1a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x73, 0x5f, 0x70, 0x61, - 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, - 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, - 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, - 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x4f, 0x0a, - 0x13, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, - 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, - 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, - 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x16, - 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa9, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x6c, 0x6f, 0x61, - 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, 0x61, 0x69, 0x74, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x22, 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x18, 0x52, - 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x53, 0x68, 0x61, 0x72, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x61, 0x69, - 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x77, 0x61, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, - 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, - 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x43, 0x0a, 0x19, 0x52, 0x65, 0x6c, - 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, - 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x5b, - 0x0a, 0x13, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x7f, 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4b, 0x65, 0x79, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x61, 0x67, 0x12, 0x3b, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x72, 0x6f, 0x73, + 0x73, 0x5f, 0x63, 0x65, 0x6c, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x72, 0x6f, + 0x73, 0x73, 0x43, 0x65, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0xba, 0x01, 0x0a, 0x1c, 0x50, 0x6c, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x63, 0x65, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x6c, 0x6c, - 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, - 0x69, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, - 0x73, 0x69, 0x76, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4b, 0x65, - 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x9b, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x68, 0x61, - 0x72, 0x64, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x61, - 0x72, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x6c, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x12, 0x14, 0x0a, 0x05, - 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, - 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, - 0x22, 0x19, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, - 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x15, 0x52, - 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x74, 0x22, 0x7b, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, + 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, + 0x72, 0x64, 0x12, 0x40, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x64, 0x5f, 0x70, + 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, + 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x64, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x74, 0x0a, 0x1b, + 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x47, + 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x23, 0x0a, + 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x61, 0x6c, 0x22, 0x1e, 0x0a, 0x1c, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x32, 0x0a, 0x1a, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x56, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x13, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x64, + 0x0a, 0x1a, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x79, + 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, + 0x65, 0x6c, 0x6c, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x69, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x15, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x4f, 0x0a, 0x13, 0x52, 0x65, + 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x52, + 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xa9, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, 0x61, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, + 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x20, 0x0a, + 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, + 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x26, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x6c, 0x6f, + 0x61, 0x64, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, + 0x61, 0x69, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x43, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x5b, 0x0a, 0x13, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x68, 0x61, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x7f, 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, - 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, - 0x2f, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x22, 0x8f, 0x04, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x68, 0x61, 0x72, 0x64, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x68, 0x61, 0x72, 0x64, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, - 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, - 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x6c, 0x0a, 0x1b, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x2c, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x19, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x6b, 0x69, 0x70, - 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0e, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x43, 0x6f, - 0x70, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x6e, 0x5f, 0x64, 0x64, 0x6c, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x6f, 0x6e, 0x44, 0x64, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, - 0x70, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x41, 0x66, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x70, - 0x79, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x61, 0x72, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x12, 0x64, 0x65, 0x66, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4b, - 0x65, 0x79, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x22, 0x82, 0x02, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x72, - 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2d, 0x0a, 0x0b, 0x62, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, - 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x0a, 0x62, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x70, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x6f, 0x50, 0x6f, 0x73, 0x12, 0x17, - 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x3e, 0x0a, 0x14, 0x72, 0x65, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x52, 0x12, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x6f, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xad, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, - 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, - 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x12, 0x24, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x4d, 0x0a, 0x1b, 0x52, 0x65, 0x74, 0x72, 0x79, - 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x6c, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x12, 0x14, 0x0a, + 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, + 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x9b, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, + 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x22, 0x19, 0x0a, + 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x43, 0x65, 0x6c, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x22, 0x7b, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x07, + 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x8f, 0x04, + 0x0a, 0x14, 0x52, 0x65, 0x73, 0x68, 0x61, 0x72, 0x64, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, + 0x72, 0x64, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x68, + 0x61, 0x72, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x37, + 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x6c, 0x0a, 0x1b, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x19, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0e, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x43, 0x6f, 0x70, 0x79, 0x12, + 0x15, 0x0a, 0x06, 0x6f, 0x6e, 0x5f, 0x64, 0x64, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6f, 0x6e, 0x44, 0x64, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x61, + 0x66, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x41, 0x66, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x30, + 0x0a, 0x14, 0x64, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, + 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x64, 0x65, + 0x66, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x4b, 0x65, 0x79, 0x73, + 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x22, + 0x82, 0x02, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2d, 0x0a, 0x0b, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x0a, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x74, 0x6f, 0x5f, 0x70, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x6f, 0x50, 0x6f, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x64, + 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, + 0x79, 0x52, 0x75, 0x6e, 0x12, 0x3e, 0x0a, 0x14, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, + 0x74, 0x6f, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x52, 0x12, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x6f, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x22, 0xad, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, + 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1a, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x24, + 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x6c, 0x6f, 0x67, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x22, 0x4d, 0x0a, 0x1b, 0x52, 0x65, 0x74, 0x72, 0x79, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, + 0x75, 0x69, 0x64, 0x22, 0xdd, 0x01, 0x0a, 0x1c, 0x52, 0x65, 0x74, 0x72, 0x79, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x72, 0x6f, 0x77, 0x73, 0x5f, 0x61, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4d, 0x69, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x6f, + 0x77, 0x73, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, + 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x72, 0x6f, 0x77, 0x73, 0x41, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x46, 0x0a, 0x18, 0x52, + 0x6f, 0x77, 0x73, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x68, 0x61, + 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x51, 0x0a, 0x15, 0x52, 0x75, 0x6e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x75, 0x6e, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x6d, 0x0a, 0x22, 0x53, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x44, + 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0xdd, 0x01, 0x0a, 0x1c, 0x52, 0x65, 0x74, 0x72, 0x79, - 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x16, 0x72, 0x6f, 0x77, 0x73, 0x5f, - 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4d, - 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x52, 0x6f, 0x77, 0x73, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, - 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x72, 0x6f, 0x77, 0x73, 0x41, - 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x46, - 0x0a, 0x18, 0x52, 0x6f, 0x77, 0x73, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x79, - 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x51, 0x0a, 0x15, 0x52, 0x75, 0x6e, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x75, 0x6e, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x22, 0x53, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x44, 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x79, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x64, 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x22, 0x55, 0x0a, 0x23, 0x53, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x44, 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6b, 0x65, 0x79, + 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, + 0x75, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, + 0x55, 0x0a, 0x23, 0x53, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x44, 0x75, + 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x5e, 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x4b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, + 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x51, 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x4b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x5e, 0x0a, 0x1e, 0x53, 0x65, 0x74, - 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, - 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, - 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x4a, 0x04, 0x08, - 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x51, 0x0a, 0x1f, 0x53, 0x65, 0x74, - 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, - 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x72, 0x0a, 0x1f, - 0x53, 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x49, 0x73, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, - 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, - 0x22, 0x49, 0x0a, 0x20, 0x53, 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x49, 0x73, 0x50, 0x72, - 0x69, 0x6d, 0x61, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, - 0x68, 0x61, 0x72, 0x64, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x8e, 0x02, 0x0a, 0x1c, - 0x53, 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x72, 0x0a, 0x1f, 0x53, 0x65, 0x74, + 0x53, 0x68, 0x61, 0x72, 0x64, 0x49, 0x73, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x35, - 0x0a, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, - 0x65, 0x6e, 0x69, 0x65, 0x64, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, - 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x46, 0x0a, 0x1d, - 0x53, 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, - 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, - 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x22, 0x6a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x57, 0x72, 0x69, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, - 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x57, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x1a, 0x53, 0x68, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x22, 0x1d, 0x0a, 0x1b, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x62, 0x0a, 0x1a, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x22, 0x54, 0x0a, 0x1b, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x78, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, - 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x54, 0x0a, 0x20, 0x53, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x22, 0x49, 0x0a, + 0x20, 0x53, 0x65, 0x74, 0x53, 0x68, 0x61, 0x72, 0x64, 0x49, 0x73, 0x50, 0x72, 0x69, 0x6d, 0x61, + 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x25, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, + 0x64, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x8e, 0x02, 0x0a, 0x1c, 0x53, 0x65, 0x74, + 0x53, 0x68, 0x61, 0x72, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x35, 0x0a, 0x0b, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x6e, 0x69, + 0x65, 0x64, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0c, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, + 0x15, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x46, 0x0a, 0x1d, 0x53, 0x65, 0x74, + 0x53, 0x68, 0x61, 0x72, 0x64, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x73, 0x68, + 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x22, 0x6a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x57, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x15, 0x0a, + 0x13, 0x53, 0x65, 0x74, 0x57, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x1a, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, + 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, + 0x1d, 0x0a, 0x1b, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x62, + 0x0a, 0x1a, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x46, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x63, 0x65, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, + 0x6c, 0x6c, 0x22, 0x54, 0x0a, 0x1b, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x35, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, + 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x54, 0x0a, 0x20, 0x53, 0x68, 0x61, 0x72, + 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0xaa, + 0x03, 0x0a, 0x21, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x14, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x22, 0xaa, 0x03, 0x0a, 0x21, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x78, 0x0a, 0x14, 0x72, 0x65, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, - 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x72, 0x65, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x72, 0x65, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x5a, + 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, + 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x1a, 0x5f, 0x0a, 0x18, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, - 0x73, 0x12, 0x5a, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, - 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x1a, 0x5f, 0x0a, - 0x18, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4e, - 0x0a, 0x0e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8b, - 0x01, 0x0a, 0x1d, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, - 0x72, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, - 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, - 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x20, 0x0a, 0x1e, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4e, 0x0a, 0x0e, 0x54, + 0x61, 0x62, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8b, 0x01, 0x0a, 0x1d, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7c, - 0x0a, 0x12, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, - 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, - 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, - 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2c, - 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, - 0x53, 0x6c, 0x65, 0x65, 0x70, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0xf0, 0x01, 0x0a, 0x15, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, - 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, - 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x75, 0x69, - 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x12, 0x2f, 0x0a, - 0x09, 0x6b, 0x65, 0x79, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x22, 0x3f, 0x0a, 0x16, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x68, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x25, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, - 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x5e, 0x0a, 0x18, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x20, 0x0a, 0x1e, 0x53, 0x68, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7c, 0x0a, 0x12, 0x53, + 0x6c, 0x65, 0x65, 0x70, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0b, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2c, 0x0a, 0x08, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x6c, 0x65, + 0x65, 0x70, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0xf0, 0x01, 0x0a, 0x15, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, + 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x27, + 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x09, 0x6b, 0x65, + 0x79, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x73, 0x22, 0x3f, 0x0a, 0x16, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, + 0x72, 0x64, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, + 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, + 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x05, 0x73, + 0x68, 0x61, 0x72, 0x64, 0x22, 0x5e, 0x0a, 0x18, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, + 0x61, 0x72, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, + 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x03, 0x75, 0x69, 0x64, 0x22, 0x42, 0x0a, 0x19, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, + 0x61, 0x72, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x25, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, + 0x64, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x53, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x1a, 0x0a, + 0x18, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x0a, 0x16, 0x53, 0x74, 0x6f, + 0x70, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x19, 0x0a, + 0x17, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x0a, 0x21, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, + 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x22, 0xc6, 0x01, 0x0a, + 0x22, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x6c, + 0x79, 0x52, 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0x42, 0x0a, 0x19, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, - 0x68, 0x61, 0x72, 0x64, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x53, 0x0a, 0x17, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, - 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, - 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, - 0x22, 0x1a, 0x0a, 0x18, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x0a, 0x16, - 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, - 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, - 0x69, 0x61, 0x73, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, - 0x22, 0x19, 0x0a, 0x17, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x0a, 0x21, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x52, - 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x22, - 0xc6, 0x01, 0x0a, 0x22, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x36, 0x0a, 0x0b, 0x6e, 0x65, 0x77, 0x5f, - 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, - 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0a, 0x6e, 0x65, 0x77, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x12, 0x36, 0x0a, 0x0b, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0a, 0x6f, 0x6c, - 0x64, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x5c, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x63, 0x65, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x63, 0x65, - 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x5d, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x43, 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x63, 0x65, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x63, 0x65, 0x6c, - 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x64, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, - 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x5f, 0x61, 0x6c, - 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, - 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, - 0x0a, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x65, 0x0a, 0x18, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x63, - 0x65, 0x6c, 0x6c, 0x73, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x65, 0x6c, 0x6c, - 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0a, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x22, 0x34, 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x69, 0x6e, - 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x22, 0xfb, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x62, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x1a, 0x69, 0x0a, 0x16, 0x52, + 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x36, 0x0a, 0x0b, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, + 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x52, 0x0a, 0x6e, 0x65, 0x77, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x36, 0x0a, + 0x0b, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0a, 0x6f, 0x6c, 0x64, 0x50, 0x72, + 0x69, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x5c, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x63, 0x65, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x63, 0x65, 0x6c, 0x6c, 0x49, + 0x6e, 0x66, 0x6f, 0x22, 0x5d, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x6c, + 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x63, 0x65, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x43, 0x65, 0x6c, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x63, 0x65, 0x6c, 0x6c, 0x49, 0x6e, + 0x66, 0x6f, 0x22, 0x64, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x65, 0x6c, 0x6c, + 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x0a, 0x63, 0x65, + 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x65, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x63, 0x65, 0x6c, 0x6c, + 0x73, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x52, 0x0a, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, + 0x34, 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x73, 0x22, 0xfb, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x12, 0x62, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x5f, + 0x62, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, - 0x22, 0xfc, 0x01, 0x0a, 0x18, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x61, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x37, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, - 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x63, 0x0a, 0x13, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0xd8, 0x01, 0x0a, 0x1d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x25, 0x0a, - 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x76, 0x69, 0x65, 0x77, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x56, 0x69, 0x65, 0x77, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, - 0x70, 0x5f, 0x6e, 0x6f, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x4e, 0x6f, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, - 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x76, 0x73, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, - 0x75, 0x64, 0x65, 0x56, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0x88, 0x02, 0x0a, 0x1e, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, 0x65, 0x79, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x67, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x3d, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, - 0x1a, 0x63, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, - 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, - 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6b, 0x0a, 0x14, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, - 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x73, 0x22, 0x31, 0x0a, 0x15, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x3c, 0x0a, 0x1e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x22, 0x8a, 0x02, 0x0a, 0x1f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x12, 0x68, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, - 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x76, 0x74, - 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, + 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x1a, 0x69, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0b, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x22, 0xfc, 0x01, + 0x0a, 0x18, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x12, 0x61, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x5f, + 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, + 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, + 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, + 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x63, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd8, 0x01, 0x0a, + 0x1d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x76, 0x69, 0x65, + 0x77, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x56, 0x69, 0x65, 0x77, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x6e, + 0x6f, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x4e, 0x6f, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x27, + 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x56, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0x88, 0x02, 0x0a, 0x1e, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x12, 0x67, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x5f, + 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, + 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x63, 0x0a, + 0x13, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x6b, 0x0a, 0x14, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0b, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x73, 0x22, + 0x31, 0x0a, 0x15, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x22, 0x3c, 0x0a, 0x1e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x22, 0x8a, 0x02, 0x0a, 0x1f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x68, + 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, + 0x72, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, + 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x63, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, + 0x1b, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x38, + 0x0a, 0x1c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x16, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x56, 0x69, + 0x65, 0x77, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, + 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x60, 0x0a, 0x10, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x63, 0x0a, 0x13, 0x52, @@ -18722,340 +18769,306 @@ var file_vtctldata_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x4f, 0x0a, 0x1b, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x22, 0x38, 0x0a, 0x1c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x16, - 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x76, 0x69, 0x65, - 0x77, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, - 0x65, 0x56, 0x69, 0x65, 0x77, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x65, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x60, 0x0a, 0x10, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x56, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, 0x1a, 0x63, - 0x0a, 0x13, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x79, 0x53, 0x68, 0x61, 0x72, 0x64, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x88, 0x07, 0x0a, 0x12, 0x56, 0x44, 0x69, 0x66, 0x66, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, - 0x75, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x65, - 0x6c, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, - 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x73, 0x12, 0x6c, 0x0a, 0x1b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x19, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x55, - 0x0a, 0x1e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x1b, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x61, 0x69, - 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x65, 0x62, 0x75, - 0x67, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x09, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, - 0x5f, 0x6b, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x6c, 0x79, 0x50, - 0x4b, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x38, 0x0a, 0x19, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x72, 0x6f, - 0x77, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x45, 0x78, 0x74, 0x72, 0x61, 0x52, 0x6f, 0x77, - 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, - 0x69, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x77, 0x61, 0x69, 0x74, 0x12, 0x42, - 0x0a, 0x14, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, - 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, - 0x77, 0x61, 0x69, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, - 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x74, 0x72, - 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x12, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6d, - 0x61, 0x78, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x61, 0x78, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, - 0x12, 0x3c, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x6d, - 0x61, 0x78, 0x44, 0x69, 0x66, 0x66, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x29, - 0x0a, 0x13, 0x56, 0x44, 0x69, 0x66, 0x66, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, 0x49, 0x44, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x22, 0x6b, 0x0a, 0x12, 0x56, 0x44, 0x69, - 0x66, 0x66, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x72, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x61, 0x72, 0x67, 0x22, 0x15, 0x0a, 0x13, 0x56, 0x44, 0x69, 0x66, 0x66, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, - 0x12, 0x56, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, - 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0x15, 0x0a, 0x13, - 0x56, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x69, 0x0a, 0x10, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, 0x77, + 0x22, 0x88, 0x07, 0x0a, 0x12, 0x56, 0x44, 0x69, 0x66, 0x66, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x61, 0x72, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x72, 0x67, 0x22, 0xd7, - 0x01, 0x0a, 0x11, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x10, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, - 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x44, 0x69, 0x66, 0x66, - 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x73, 0x1a, 0x64, 0x0a, 0x14, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x56, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6b, 0x0a, 0x10, 0x56, 0x44, 0x69, 0x66, - 0x66, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x65, 0x6c, 0x6c, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x65, + 0x6c, 0x6c, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x65, + 0x6c, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x43, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, + 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, + 0x6c, 0x0a, 0x1b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x52, 0x19, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x55, 0x0a, 0x1e, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x1b, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, + 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x61, 0x69, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x09, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, 0x5f, 0x6b, 0x73, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x6c, 0x79, 0x50, 0x4b, 0x73, 0x12, + 0x2c, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x38, 0x0a, + 0x19, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x5f, + 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x15, 0x6d, 0x61, 0x78, 0x45, 0x78, 0x74, 0x72, 0x61, 0x52, 0x6f, 0x77, 0x73, 0x54, 0x6f, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x77, 0x61, 0x69, 0x74, 0x12, 0x42, 0x0a, 0x14, 0x77, + 0x61, 0x69, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x77, 0x61, 0x69, + 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, + 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x74, 0x72, 0x79, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x72, 0x6f, + 0x77, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x3c, 0x0a, + 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x44, + 0x69, 0x66, 0x66, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x29, 0x0a, 0x13, 0x56, + 0x44, 0x69, 0x66, 0x66, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x22, 0x6b, 0x0a, 0x12, 0x56, 0x44, 0x69, 0x66, 0x66, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0x13, 0x0a, 0x11, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x74, - 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x15, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1b, 0x0a, 0x09, - 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x6b, 0x65, 0x65, 0x70, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x12, 0x6b, 0x65, 0x65, - 0x70, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x69, - 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x22, - 0xd1, 0x01, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, - 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x55, 0x0a, 0x0a, - 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, - 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, - 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x22, 0x67, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x22, 0xe6, 0x07, 0x0a, - 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x10, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x35, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x72, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x61, 0x72, 0x67, 0x22, 0x15, 0x0a, 0x13, 0x56, 0x44, 0x69, 0x66, 0x66, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x12, 0x56, 0x44, + 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x56, 0x44, 0x69, + 0x66, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x69, 0x0a, 0x10, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x72, 0x67, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x72, 0x67, 0x22, 0xd7, 0x01, 0x0a, 0x11, + 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5c, 0x0a, 0x10, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x76, 0x74, + 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x68, 0x6f, + 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x1a, + 0x64, 0x0a, 0x14, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x44, 0x69, + 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6b, 0x0a, 0x10, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x74, + 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, + 0x69, 0x64, 0x22, 0x13, 0x0a, 0x11, 0x56, 0x44, 0x69, 0x66, 0x66, 0x53, 0x74, 0x6f, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x6b, 0x65, 0x65, + 0x70, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6b, 0x65, + 0x65, 0x70, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x12, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x10, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, + 0x75, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x22, 0xd1, 0x01, 0x0a, + 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x12, 0x46, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x55, 0x0a, 0x0a, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, + 0x22, 0x67, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x22, 0xe6, 0x07, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x43, - 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x33, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x10, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, + 0x70, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, + 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x70, 0x79, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x76, + 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x12, + 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x1a, 0xe8, 0x01, 0x0a, 0x0e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, + 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x77, 0x73, 0x5f, + 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x72, 0x6f, + 0x77, 0x73, 0x43, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x77, 0x73, + 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x6f, + 0x77, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x6f, 0x77, 0x73, 0x5f, + 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, + 0x52, 0x0e, 0x72, 0x6f, 0x77, 0x73, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x70, + 0x69, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x1a, + 0xbc, 0x01, 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x68, + 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x1a, 0x5c, + 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x12, 0x4c, + 0x0a, 0x07, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x32, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x66, 0x66, - 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a, 0xe8, 0x01, 0x0a, 0x0e, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, - 0x77, 0x73, 0x5f, 0x63, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x72, 0x6f, 0x77, 0x73, 0x43, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x6f, 0x77, 0x73, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x72, 0x6f, 0x77, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x6f, - 0x77, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x02, 0x52, 0x0e, 0x72, 0x6f, 0x77, 0x73, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, - 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x70, - 0x69, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, - 0x43, 0x6f, 0x70, 0x69, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x79, 0x74, 0x65, 0x73, - 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x02, 0x52, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, - 0x67, 0x65, 0x1a, 0xbc, 0x01, 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x53, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, - 0x6f, 0x1a, 0x5c, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x73, 0x12, 0x4c, 0x0a, 0x07, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x07, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x1a, - 0x73, 0x0a, 0x13, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, + 0x73, 0x65, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x07, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x1a, 0x73, 0x0a, 0x13, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x70, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x1a, 0x6f, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x43, 0x6f, 0x70, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x6f, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x76, 0x74, 0x63, - 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x68, - 0x61, 0x72, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xef, 0x03, 0x0a, 0x1c, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x14, - 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, - 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, - 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x4f, 0x0a, - 0x1b, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x18, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x61, 0x67, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x3c, - 0x0a, 0x1a, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x18, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x07, 0x74, 0x69, - 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, - 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, - 0x3e, 0x0a, 0x1b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x1d, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, - 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x72, 0x79, - 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0d, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x22, 0x90, 0x01, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, - 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, - 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x34, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0d, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0xd1, 0x01, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x07, 0x64, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x74, 0x63, - 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x1a, 0x55, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x2d, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4d, - 0x69, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x51, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6d, - 0x69, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x69, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x0b, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x75, 0x6c, 0x65, 0x73, 0x22, 0xa9, 0x01, 0x0a, 0x1c, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x37, 0x0a, - 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, - 0x22, 0x7f, 0x0a, 0x1d, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4d, 0x69, 0x72, 0x72, - 0x6f, 0x72, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x2a, 0x4a, 0x0a, 0x15, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, - 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x4f, 0x56, 0x45, 0x54, 0x41, - 0x42, 0x4c, 0x45, 0x53, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, - 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x02, 0x2a, 0x38, 0x0a, - 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x08, - 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x53, 0x43, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x45, 0x53, 0x43, 0x45, - 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x42, 0x28, 0x5a, 0x26, 0x76, 0x69, 0x74, 0x65, 0x73, - 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, - 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, - 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0xef, 0x03, 0x0a, 0x1c, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, + 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x14, 0x0a, 0x05, 0x63, + 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, + 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x1b, 0x6d, 0x61, + 0x78, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x61, + 0x67, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x18, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4c, 0x61, 0x67, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, 0x72, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x18, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x52, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x3e, 0x0a, 0x1b, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x19, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x68, 0x61, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x68, + 0x61, 0x72, 0x64, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x1d, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, + 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0d, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x90, + 0x01, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x0d, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0xd1, 0x01, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x55, + 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x06, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, + 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x51, + 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x69, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x52, + 0x75, 0x6c, 0x65, 0x73, 0x52, 0x0b, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x75, 0x6c, 0x65, + 0x73, 0x22, 0xa9, 0x01, 0x0a, 0x1c, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4d, 0x69, + 0x72, 0x72, 0x6f, 0x72, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, + 0x32, 0x14, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x22, 0x7f, 0x0a, + 0x1d, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x54, + 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x4a, + 0x0a, 0x15, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, + 0x4d, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x4f, 0x56, 0x45, 0x54, 0x41, 0x42, 0x4c, 0x45, + 0x53, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x4c, 0x4f, 0x4f, + 0x4b, 0x55, 0x50, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x02, 0x2a, 0x38, 0x0a, 0x0d, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x08, 0x0a, 0x04, 0x4e, + 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x53, 0x43, 0x45, 0x4e, 0x44, 0x49, + 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x45, 0x53, 0x43, 0x45, 0x4e, 0x44, 0x49, + 0x4e, 0x47, 0x10, 0x02, 0x42, 0x28, 0x5a, 0x26, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, + 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x74, 0x63, 0x74, 0x6c, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/vt/proto/vtctldata/vtctldata_vtproto.pb.go b/go/vt/proto/vtctldata/vtctldata_vtproto.pb.go index 7e38623a907..9532622dc98 100644 --- a/go/vt/proto/vtctldata/vtctldata_vtproto.pb.go +++ b/go/vt/proto/vtctldata/vtctldata_vtproto.pb.go @@ -3484,6 +3484,7 @@ func (m *PlannedReparentShardRequest) CloneVT() *PlannedReparentShardRequest { AvoidPrimary: m.AvoidPrimary.CloneVT(), WaitReplicasTimeout: m.WaitReplicasTimeout.CloneVT(), TolerableReplicationLag: m.TolerableReplicationLag.CloneVT(), + AllowCrossCellPromotion: m.AllowCrossCellPromotion, } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -15087,6 +15088,16 @@ func (m *PlannedReparentShardRequest) MarshalToSizedBufferVT(dAtA []byte) (int, i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.AllowCrossCellPromotion { + i-- + if m.AllowCrossCellPromotion { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } if m.TolerableReplicationLag != nil { size, err := m.TolerableReplicationLag.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -24414,6 +24425,9 @@ func (m *PlannedReparentShardRequest) SizeVT() (n int) { l = m.TolerableReplicationLag.SizeVT() n += 1 + l + sov(uint64(l)) } + if m.AllowCrossCellPromotion { + n += 2 + } n += len(m.unknownFields) return n } @@ -49626,6 +49640,26 @@ func (m *PlannedReparentShardRequest) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowCrossCellPromotion", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowCrossCellPromotion = bool(v != 0) default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/go/vt/vtctl/grpcvtctldserver/server.go b/go/vt/vtctl/grpcvtctldserver/server.go index 0ab76e6b523..d036cb0e8dd 100644 --- a/go/vt/vtctl/grpcvtctldserver/server.go +++ b/go/vt/vtctl/grpcvtctldserver/server.go @@ -2976,10 +2976,11 @@ func (s *VtctldServer) PlannedReparentShard(ctx context.Context, req *vtctldatap req.Keyspace, req.Shard, reparentutil.PlannedReparentOptions{ - AvoidPrimaryAlias: req.AvoidPrimary, - NewPrimaryAlias: req.NewPrimary, - WaitReplicasTimeout: waitReplicasTimeout, - TolerableReplLag: tolerableReplLag, + AvoidPrimaryAlias: req.AvoidPrimary, + NewPrimaryAlias: req.NewPrimary, + WaitReplicasTimeout: waitReplicasTimeout, + TolerableReplLag: tolerableReplLag, + AllowCrossCellPromotion: req.AllowCrossCellPromotion, }, ) diff --git a/go/vt/vtctl/reparent.go b/go/vt/vtctl/reparent.go index 4498228d9c7..192d19ed7ee 100644 --- a/go/vt/vtctl/reparent.go +++ b/go/vt/vtctl/reparent.go @@ -119,6 +119,7 @@ func commandPlannedReparentShard(ctx context.Context, wr *wrangler.Wrangler, sub keyspaceShard := subFlags.String("keyspace_shard", "", "keyspace/shard of the shard that needs to be reparented") newPrimary := subFlags.String("new_primary", "", "alias of a tablet that should be the new primary") avoidTablet := subFlags.String("avoid_tablet", "", "alias of a tablet that should not be the primary, i.e. reparent to any other tablet if this one is the primary") + allowCrossCellPromotion := subFlags.Bool("allow-cross-cell-promotion", false, "allow cross cell promotions") if err := subFlags.Parse(args); err != nil { return err @@ -153,10 +154,11 @@ func commandPlannedReparentShard(ctx context.Context, wr *wrangler.Wrangler, sub } return wr.PlannedReparentShard(ctx, keyspace, shard, reparentutil.PlannedReparentOptions{ - NewPrimaryAlias: newPrimaryAlias, - AvoidPrimaryAlias: avoidTabletAlias, - WaitReplicasTimeout: *waitReplicasTimeout, - TolerableReplLag: *tolerableReplicationLag, + NewPrimaryAlias: newPrimaryAlias, + AvoidPrimaryAlias: avoidTabletAlias, + WaitReplicasTimeout: *waitReplicasTimeout, + TolerableReplLag: *tolerableReplicationLag, + AllowCrossCellPromotion: *allowCrossCellPromotion, }) } diff --git a/go/vt/vtctl/reparentutil/planned_reparenter.go b/go/vt/vtctl/reparentutil/planned_reparenter.go index e09761ea982..0852583bec9 100644 --- a/go/vt/vtctl/reparentutil/planned_reparenter.go +++ b/go/vt/vtctl/reparentutil/planned_reparenter.go @@ -59,10 +59,11 @@ type PlannedReparenter struct { // operations. Options are passed by value, so it is safe for callers to mutate // resue options structs for multiple calls. type PlannedReparentOptions struct { - NewPrimaryAlias *topodatapb.TabletAlias - AvoidPrimaryAlias *topodatapb.TabletAlias - WaitReplicasTimeout time.Duration - TolerableReplLag time.Duration + NewPrimaryAlias *topodatapb.TabletAlias + AvoidPrimaryAlias *topodatapb.TabletAlias + WaitReplicasTimeout time.Duration + TolerableReplLag time.Duration + AllowCrossCellPromotion bool // Private options managed internally. We use value-passing semantics to // set these options inside a PlannedReparent without leaking these details @@ -181,7 +182,7 @@ func (pr *PlannedReparenter) preflightChecks( } event.DispatchUpdate(ev, "electing a primary candidate") - opts.NewPrimaryAlias, err = ElectNewPrimary(ctx, pr.tmc, &ev.ShardInfo, tabletMap, innodbBufferPoolData, opts.NewPrimaryAlias, opts.AvoidPrimaryAlias, opts.WaitReplicasTimeout, opts.TolerableReplLag, opts.durability, pr.logger) + opts.NewPrimaryAlias, err = ElectNewPrimary(ctx, pr.tmc, &ev.ShardInfo, tabletMap, innodbBufferPoolData, opts, pr.logger) if err != nil { return true, err } diff --git a/go/vt/vtctl/reparentutil/util.go b/go/vt/vtctl/reparentutil/util.go index ea7a9f7262c..fd701f8c69b 100644 --- a/go/vt/vtctl/reparentutil/util.go +++ b/go/vt/vtctl/reparentutil/util.go @@ -69,11 +69,7 @@ func ElectNewPrimary( shardInfo *topo.ShardInfo, tabletMap map[string]*topo.TabletInfo, innodbBufferPoolData map[string]int, - newPrimaryAlias *topodatapb.TabletAlias, - avoidPrimaryAlias *topodatapb.TabletAlias, - waitReplicasTimeout time.Duration, - tolerableReplLag time.Duration, - durability Durabler, + opts *PlannedReparentOptions, // (TODO:@ajm188) it's a little gross we need to pass this, maybe embed in the context? logger logutil.Logger, ) (*topodatapb.TabletAlias, error) { @@ -98,16 +94,16 @@ func ElectNewPrimary( reasonsToInvalidate := strings.Builder{} for _, tablet := range tabletMap { switch { - case newPrimaryAlias != nil: + case opts.NewPrimaryAlias != nil: // If newPrimaryAlias is provided, then that is the only valid tablet, even if it is not of type replica or in a different cell. - if !topoproto.TabletAliasEqual(tablet.Alias, newPrimaryAlias) { + if !topoproto.TabletAliasEqual(tablet.Alias, opts.NewPrimaryAlias) { reasonsToInvalidate.WriteString(fmt.Sprintf("\n%v does not match the new primary alias provided", topoproto.TabletAliasString(tablet.Alias))) continue } - case primaryCell != "" && tablet.Alias.Cell != primaryCell: + case !opts.AllowCrossCellPromotion && primaryCell != "" && tablet.Alias.Cell != primaryCell: reasonsToInvalidate.WriteString(fmt.Sprintf("\n%v is not in the same cell as the previous primary", topoproto.TabletAliasString(tablet.Alias))) continue - case avoidPrimaryAlias != nil && topoproto.TabletAliasEqual(tablet.Alias, avoidPrimaryAlias): + case opts.AvoidPrimaryAlias != nil && topoproto.TabletAliasEqual(tablet.Alias, opts.AvoidPrimaryAlias): reasonsToInvalidate.WriteString(fmt.Sprintf("\n%v matches the primary alias to avoid", topoproto.TabletAliasString(tablet.Alias))) continue case tablet.Tablet.Type != topodatapb.TabletType_REPLICA: @@ -122,7 +118,7 @@ func ElectNewPrimary( // then we don't need to find the position of the said tablet for sorting. // We can just return the tablet quickly. // This check isn't required, but it saves us an RPC call that is otherwise unnecessary. - if len(candidates) == 1 && tolerableReplLag == 0 { + if len(candidates) == 1 && opts.TolerableReplLag == 0 { return candidates[0].Alias, nil } @@ -130,10 +126,10 @@ func ElectNewPrimary( tb := tablet errorGroup.Go(func() error { // find and store the positions for the tablet - pos, replLag, err := findPositionAndLagForTablet(groupCtx, tb, logger, tmc, waitReplicasTimeout) + pos, replLag, err := findPositionAndLagForTablet(groupCtx, tb, logger, tmc, opts.WaitReplicasTimeout) mu.Lock() defer mu.Unlock() - if err == nil && (tolerableReplLag == 0 || tolerableReplLag >= replLag) { + if err == nil && (opts.TolerableReplLag == 0 || opts.TolerableReplLag >= replLag) { validTablets = append(validTablets, tb) tabletPositions = append(tabletPositions, pos) innodbBufferPool = append(innodbBufferPool, innodbBufferPoolData[topoproto.TabletAliasString(tb.Alias)]) @@ -155,7 +151,7 @@ func ElectNewPrimary( } // sort the tablets for finding the best primary - err = sortTabletsForReparent(validTablets, tabletPositions, innodbBufferPool, durability) + err = sortTabletsForReparent(validTablets, tabletPositions, innodbBufferPool, opts.durability) if err != nil { return nil, err } diff --git a/go/vt/vtctl/reparentutil/util_test.go b/go/vt/vtctl/reparentutil/util_test.go index d42ae76f337..f4e9092fc3f 100644 --- a/go/vt/vtctl/reparentutil/util_test.go +++ b/go/vt/vtctl/reparentutil/util_test.go @@ -67,16 +67,17 @@ func TestElectNewPrimary(t *testing.T) { ctx := context.Background() logger := logutil.NewMemoryLogger() tests := []struct { - name string - tmc *chooseNewPrimaryTestTMClient - shardInfo *topo.ShardInfo - tabletMap map[string]*topo.TabletInfo - innodbBufferPoolData map[string]int - newPrimaryAlias *topodatapb.TabletAlias - avoidPrimaryAlias *topodatapb.TabletAlias - tolerableReplLag time.Duration - expected *topodatapb.TabletAlias - errContains []string + name string + tmc *chooseNewPrimaryTestTMClient + shardInfo *topo.ShardInfo + tabletMap map[string]*topo.TabletInfo + innodbBufferPoolData map[string]int + newPrimaryAlias *topodatapb.TabletAlias + avoidPrimaryAlias *topodatapb.TabletAlias + tolerableReplLag time.Duration + allowCrossCellPromotion bool + expected *topodatapb.TabletAlias + errContains []string }{ { name: "found a replica", @@ -716,6 +717,64 @@ func TestElectNewPrimary(t *testing.T) { `zone1-0000000102 is not in the same cell as the previous primary`, }, }, + { + name: "no replicas in primary cell but cross cell allowed", + tmc: &chooseNewPrimaryTestTMClient{ + // zone1-101 is behind zone1-102 + replicationStatuses: map[string]*replicationdatapb.Status{ + "zone1-0000000101": { + Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", + }, + "zone1-0000000102": { + Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", + }, + }, + }, + allowCrossCellPromotion: true, + shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ + PrimaryAlias: &topodatapb.TabletAlias{ + Cell: "zone2", + Uid: 200, + }, + }, nil), + tabletMap: map[string]*topo.TabletInfo{ + "primary": { + Tablet: &topodatapb.Tablet{ + Alias: &topodatapb.TabletAlias{ + Cell: "zone2", + Uid: 200, + }, + Type: topodatapb.TabletType_PRIMARY, + }, + }, + "replica1": { + Tablet: &topodatapb.Tablet{ + Alias: &topodatapb.TabletAlias{ + Cell: "zone1", + Uid: 101, + }, + Type: topodatapb.TabletType_REPLICA, + }, + }, + "replica2": { + Tablet: &topodatapb.Tablet{ + Alias: &topodatapb.TabletAlias{ + Cell: "zone1", + Uid: 102, + }, + Type: topodatapb.TabletType_REPLICA, + }, + }, + }, + avoidPrimaryAlias: &topodatapb.TabletAlias{ + Cell: "zone1", + Uid: 0, + }, + expected: &topodatapb.TabletAlias{ + Cell: "zone1", + Uid: 102, + }, + }, { name: "only available tablet is AvoidPrimary", tmc: &chooseNewPrimaryTestTMClient{ @@ -794,7 +853,15 @@ zone1-0000000100 is not a replica`, t.Run(tt.name, func(t *testing.T) { t.Parallel() - actual, err := ElectNewPrimary(ctx, tt.tmc, tt.shardInfo, tt.tabletMap, tt.innodbBufferPoolData, tt.newPrimaryAlias, tt.avoidPrimaryAlias, time.Millisecond*50, tt.tolerableReplLag, durability, logger) + options := &PlannedReparentOptions{ + NewPrimaryAlias: tt.newPrimaryAlias, + AvoidPrimaryAlias: tt.avoidPrimaryAlias, + TolerableReplLag: tt.tolerableReplLag, + durability: durability, + AllowCrossCellPromotion: tt.allowCrossCellPromotion, + WaitReplicasTimeout: time.Millisecond * 50, + } + actual, err := ElectNewPrimary(ctx, tt.tmc, tt.shardInfo, tt.tabletMap, tt.innodbBufferPoolData, options, logger) if len(tt.errContains) > 0 { for _, errC := range tt.errContains { assert.ErrorContains(t, err, errC) diff --git a/proto/vtctldata.proto b/proto/vtctldata.proto index 317e8706584..869e50a23df 100644 --- a/proto/vtctldata.proto +++ b/proto/vtctldata.proto @@ -1357,6 +1357,8 @@ message PlannedReparentShardRequest { // acceptable for a tablet to be eligible for promotion when Vitess makes the choice of a new primary. // A value of 0 indicates that Vitess shouldn't consider the replication lag at all. vttime.Duration tolerable_replication_lag = 6; + // AllowCrossCellPromotion allows cross cell promotion, + bool allow_cross_cell_promotion = 7; } message PlannedReparentShardResponse { diff --git a/web/vtadmin/src/proto/vtadmin.d.ts b/web/vtadmin/src/proto/vtadmin.d.ts index bf371914dca..de8496005f7 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -63057,6 +63057,9 @@ export namespace vtctldata { /** PlannedReparentShardRequest tolerable_replication_lag */ tolerable_replication_lag?: (vttime.IDuration|null); + + /** PlannedReparentShardRequest allow_cross_cell_promotion */ + allow_cross_cell_promotion?: (boolean|null); } /** Represents a PlannedReparentShardRequest. */ @@ -63086,6 +63089,9 @@ export namespace vtctldata { /** PlannedReparentShardRequest tolerable_replication_lag. */ public tolerable_replication_lag?: (vttime.IDuration|null); + /** PlannedReparentShardRequest allow_cross_cell_promotion. */ + public allow_cross_cell_promotion: boolean; + /** * Creates a new PlannedReparentShardRequest instance using the specified properties. * @param [properties] Properties to set diff --git a/web/vtadmin/src/proto/vtadmin.js b/web/vtadmin/src/proto/vtadmin.js index d3b9f6ead0e..3843e89005b 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -154782,6 +154782,7 @@ export const vtctldata = $root.vtctldata = (() => { * @property {topodata.ITabletAlias|null} [avoid_primary] PlannedReparentShardRequest avoid_primary * @property {vttime.IDuration|null} [wait_replicas_timeout] PlannedReparentShardRequest wait_replicas_timeout * @property {vttime.IDuration|null} [tolerable_replication_lag] PlannedReparentShardRequest tolerable_replication_lag + * @property {boolean|null} [allow_cross_cell_promotion] PlannedReparentShardRequest allow_cross_cell_promotion */ /** @@ -154847,6 +154848,14 @@ export const vtctldata = $root.vtctldata = (() => { */ PlannedReparentShardRequest.prototype.tolerable_replication_lag = null; + /** + * PlannedReparentShardRequest allow_cross_cell_promotion. + * @member {boolean} allow_cross_cell_promotion + * @memberof vtctldata.PlannedReparentShardRequest + * @instance + */ + PlannedReparentShardRequest.prototype.allow_cross_cell_promotion = false; + /** * Creates a new PlannedReparentShardRequest instance using the specified properties. * @function create @@ -154883,6 +154892,8 @@ export const vtctldata = $root.vtctldata = (() => { $root.vttime.Duration.encode(message.wait_replicas_timeout, writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim(); if (message.tolerable_replication_lag != null && Object.hasOwnProperty.call(message, "tolerable_replication_lag")) $root.vttime.Duration.encode(message.tolerable_replication_lag, writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); + if (message.allow_cross_cell_promotion != null && Object.hasOwnProperty.call(message, "allow_cross_cell_promotion")) + writer.uint32(/* id 7, wireType 0 =*/56).bool(message.allow_cross_cell_promotion); return writer; }; @@ -154941,6 +154952,10 @@ export const vtctldata = $root.vtctldata = (() => { message.tolerable_replication_lag = $root.vttime.Duration.decode(reader, reader.uint32()); break; } + case 7: { + message.allow_cross_cell_promotion = reader.bool(); + break; + } default: reader.skipType(tag & 7); break; @@ -155002,6 +155017,9 @@ export const vtctldata = $root.vtctldata = (() => { if (error) return "tolerable_replication_lag." + error; } + if (message.allow_cross_cell_promotion != null && message.hasOwnProperty("allow_cross_cell_promotion")) + if (typeof message.allow_cross_cell_promotion !== "boolean") + return "allow_cross_cell_promotion: boolean expected"; return null; }; @@ -155041,6 +155059,8 @@ export const vtctldata = $root.vtctldata = (() => { throw TypeError(".vtctldata.PlannedReparentShardRequest.tolerable_replication_lag: object expected"); message.tolerable_replication_lag = $root.vttime.Duration.fromObject(object.tolerable_replication_lag); } + if (object.allow_cross_cell_promotion != null) + message.allow_cross_cell_promotion = Boolean(object.allow_cross_cell_promotion); return message; }; @@ -155064,6 +155084,7 @@ export const vtctldata = $root.vtctldata = (() => { object.avoid_primary = null; object.wait_replicas_timeout = null; object.tolerable_replication_lag = null; + object.allow_cross_cell_promotion = false; } if (message.keyspace != null && message.hasOwnProperty("keyspace")) object.keyspace = message.keyspace; @@ -155077,6 +155098,8 @@ export const vtctldata = $root.vtctldata = (() => { object.wait_replicas_timeout = $root.vttime.Duration.toObject(message.wait_replicas_timeout, options); if (message.tolerable_replication_lag != null && message.hasOwnProperty("tolerable_replication_lag")) object.tolerable_replication_lag = $root.vttime.Duration.toObject(message.tolerable_replication_lag, options); + if (message.allow_cross_cell_promotion != null && message.hasOwnProperty("allow_cross_cell_promotion")) + object.allow_cross_cell_promotion = message.allow_cross_cell_promotion; return object; }; From 59408f95e1b3c1b0952deae2b891fcab18e1443e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Taylor?= Date: Thu, 25 Jul 2024 12:49:52 +0200 Subject: [PATCH 02/34] bugfix: don't treat join predicates as filter predicates (#16472) Signed-off-by: Andres Taylor --- .../endtoend/vtgate/queries/misc/misc_test.go | 3 ++ .../planbuilder/operators/apply_join.go | 6 ++- .../planbuilder/operators/route_planning.go | 25 ++++----- .../planbuilder/testdata/from_cases.json | 53 +++++++++++++++++++ 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/go/test/endtoend/vtgate/queries/misc/misc_test.go b/go/test/endtoend/vtgate/queries/misc/misc_test.go index e43171b6701..f003ae3c1b8 100644 --- a/go/test/endtoend/vtgate/queries/misc/misc_test.go +++ b/go/test/endtoend/vtgate/queries/misc/misc_test.go @@ -371,6 +371,9 @@ func TestAliasesInOuterJoinQueries(t *testing.T) { mcmp.ExecWithColumnCompare("select t1.id1 as t0, t1.id1 as t1, tbl.unq_col as col from t1 left outer join tbl on t1.id2 = tbl.nonunq_col order by t1.id2 limit 2 offset 2") mcmp.ExecWithColumnCompare("select t1.id1 as t0, t1.id1 as t1, count(*) as leCount from t1 left outer join tbl on t1.id2 = tbl.nonunq_col group by 1, t1") mcmp.ExecWithColumnCompare("select t.id1, t.id2, derived.unq_col from t1 t join (select id, unq_col, nonunq_col from tbl) as derived on t.id2 = derived.nonunq_col") + if utils.BinaryIsAtLeastAtVersion(21, "vtgate") { + mcmp.ExecWithColumnCompare("select * from t1 t left join tbl on t.id1 = 666 and t.id2 = tbl.id") + } } func TestAlterTableWithView(t *testing.T) { diff --git a/go/vt/vtgate/planbuilder/operators/apply_join.go b/go/vt/vtgate/planbuilder/operators/apply_join.go index cd8f7b94dd5..d87fb529caf 100644 --- a/go/vt/vtgate/planbuilder/operators/apply_join.go +++ b/go/vt/vtgate/planbuilder/operators/apply_join.go @@ -37,8 +37,6 @@ type ( // JoinType is permitted to store only 3 of the possible values // NormalJoinType, StraightJoinType and LeftJoinType. JoinType sqlparser.JoinType - // LeftJoin will be true in the case of an outer join - LeftJoin bool // JoinColumns keeps track of what AST expression is represented in the Columns array JoinColumns *applyJoinColumns @@ -344,6 +342,10 @@ func (aj *ApplyJoin) ShortDescription() string { } firstPart := fmt.Sprintf("on %s columns: %s", fn(aj.JoinPredicates), fn(aj.JoinColumns)) + if aj.JoinType == sqlparser.LeftJoinType { + firstPart = "LEFT JOIN " + firstPart + } + if len(aj.ExtraLHSVars) == 0 { return firstPart } diff --git a/go/vt/vtgate/planbuilder/operators/route_planning.go b/go/vt/vtgate/planbuilder/operators/route_planning.go index 47405e3b935..22db69f287b 100644 --- a/go/vt/vtgate/planbuilder/operators/route_planning.go +++ b/go/vt/vtgate/planbuilder/operators/route_planning.go @@ -305,13 +305,18 @@ func mergeOrJoin(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPredic } join := NewApplyJoin(ctx, Clone(rhs), Clone(lhs), nil, joinType) - newOp := pushJoinPredicates(ctx, joinPredicates, join) - return newOp, Rewrote("logical join to applyJoin, switching side because LIMIT") + for _, pred := range joinPredicates { + join.AddJoinPredicate(ctx, pred) + } + return join, Rewrote("logical join to applyJoin, switching side because LIMIT") } join := NewApplyJoin(ctx, Clone(lhs), Clone(rhs), nil, joinType) - newOp := pushJoinPredicates(ctx, joinPredicates, join) - return newOp, Rewrote("logical join to applyJoin ") + for _, pred := range joinPredicates { + join.AddJoinPredicate(ctx, pred) + } + + return join, Rewrote("logical join to applyJoin ") } func operatorsToRoutes(a, b Operator) (*Route, *Route) { @@ -530,15 +535,3 @@ func hexEqual(a, b *sqlparser.Literal) bool { } return false } - -func pushJoinPredicates(ctx *plancontext.PlanningContext, exprs []sqlparser.Expr, op *ApplyJoin) Operator { - if len(exprs) == 0 { - return op - } - - for _, expr := range exprs { - AddPredicate(ctx, op, expr, true, newFilterSinglePredicate) - } - - return op -} diff --git a/go/vt/vtgate/planbuilder/testdata/from_cases.json b/go/vt/vtgate/planbuilder/testdata/from_cases.json index 50b56b428ec..7b2cda26ff6 100644 --- a/go/vt/vtgate/planbuilder/testdata/from_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/from_cases.json @@ -784,6 +784,59 @@ ] } }, + { + "comment": "Outer join with join predicates that only depend on the inner side", + "query": "select 1 from user left join user_extra on user.foo = 42 and user.bar = user_extra.bar", + "plan": { + "QueryType": "SELECT", + "Original": "select 1 from user left join user_extra on user.foo = 42 and user.bar = user_extra.bar", + "Instructions": { + "OperatorType": "Projection", + "Expressions": [ + "1 as 1" + ], + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "LeftJoin", + "JoinVars": { + "user_bar": 1, + "user_foo": 0 + }, + "TableName": "`user`_user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `user`.foo, `user`.bar from `user` where 1 != 1", + "Query": "select `user`.foo, `user`.bar from `user`", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from user_extra where 1 != 1", + "Query": "select 1 from user_extra where user_extra.bar = :user_bar and :user_foo = 42", + "Table": "user_extra" + } + ] + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + } + }, { "comment": "Parenthesized, single chunk", "query": "select user.col from user join (unsharded as m1 join unsharded as m2)", From 373cf34302def5280be8be47a03062889c04d5ea Mon Sep 17 00:00:00 2001 From: Deepthi Sigireddi Date: Fri, 26 Jul 2024 09:36:41 -0700 Subject: [PATCH 03/34] builtinbackup: log during restore as restore, not as backup (#16483) Signed-off-by: deepthi --- .../endtoend/backup/vtctlbackup/backup_utils.go | 2 +- go/vt/mysqlctl/builtinbackupengine.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/go/test/endtoend/backup/vtctlbackup/backup_utils.go b/go/test/endtoend/backup/vtctlbackup/backup_utils.go index 707c9010b0c..3a0150cc87c 100644 --- a/go/test/endtoend/backup/vtctlbackup/backup_utils.go +++ b/go/test/endtoend/backup/vtctlbackup/backup_utils.go @@ -1032,7 +1032,7 @@ func verifySemiSyncStatus(t *testing.T, vttablet *cluster.Vttablet, expectedStat } func terminateBackup(t *testing.T, alias string) { - stopBackupMsg := "Done taking Backup" + stopBackupMsg := "Completed backing up" if useXtrabackup { stopBackupMsg = "Starting backup with" useXtrabackup = false diff --git a/go/vt/mysqlctl/builtinbackupengine.go b/go/vt/mysqlctl/builtinbackupengine.go index e388912783a..494d765f2a9 100644 --- a/go/vt/mysqlctl/builtinbackupengine.go +++ b/go/vt/mysqlctl/builtinbackupengine.go @@ -766,21 +766,25 @@ func (bp *backupPipe) HashString() string { return hex.EncodeToString(bp.crc32.Sum(nil)) } -func (bp *backupPipe) ReportProgress(period time.Duration, logger logutil.Logger) { +func (bp *backupPipe) ReportProgress(period time.Duration, logger logutil.Logger, restore bool) { + messageStr := "restoring " + if !restore { + messageStr = "backing up " + } tick := time.NewTicker(period) defer tick.Stop() for { select { case <-bp.done: - logger.Infof("Done taking Backup %q", bp.filename) + logger.Infof("Completed %s %q", messageStr, bp.filename) return case <-tick.C: written := float64(atomic.LoadInt64(&bp.nn)) if bp.maxSize == 0 { - logger.Infof("Backup %q: %.02fkb", bp.filename, written/1024.0) + logger.Infof("%s %q: %.02fkb", messageStr, bp.filename, written/1024.0) } else { maxSize := float64(bp.maxSize) - logger.Infof("Backup %q: %.02f%% (%.02f/%.02fkb)", bp.filename, 100.0*written/maxSize, written/1024.0, maxSize/1024.0) + logger.Infof("%s %q: %.02f%% (%.02f/%.02fkb)", messageStr, bp.filename, 100.0*written/maxSize, written/1024.0, maxSize/1024.0) } } } @@ -813,7 +817,7 @@ func (be *BuiltinBackupEngine) backupFile(ctx context.Context, params BackupPara } br := newBackupReader(fe.Name, fi.Size(), timedSource) - go br.ReportProgress(builtinBackupProgress, params.Logger) + go br.ReportProgress(builtinBackupProgress, params.Logger, false /*restore*/) // Open the destination file for writing, and a buffer. params.Logger.Infof("Backing up file: %v", fe.Name) @@ -1078,7 +1082,7 @@ func (be *BuiltinBackupEngine) restoreFile(ctx context.Context, params RestorePa }() br := newBackupReader(name, 0, timedSource) - go br.ReportProgress(builtinBackupProgress, params.Logger) + go br.ReportProgress(builtinBackupProgress, params.Logger, true /*restore*/) var reader io.Reader = br // Open the destination file for writing. From 9aa9ab5a274bb7e441930460bef975a1bd06a9f8 Mon Sep 17 00:00:00 2001 From: Rohit Nayak <57520317+rohit-nayak-ps@users.noreply.github.com> Date: Fri, 26 Jul 2024 22:41:59 +0530 Subject: [PATCH 04/34] VStream API: validate that last PK has fields defined (#16478) Signed-off-by: Rohit Nayak --- .../tabletserver/vstreamer/uvstreamer.go | 6 +++-- .../tabletserver/vstreamer/vstreamer_test.go | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/go/vt/vttablet/tabletserver/vstreamer/uvstreamer.go b/go/vt/vttablet/tabletserver/vstreamer/uvstreamer.go index 2b770c1d4f4..854157b1546 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/uvstreamer.go +++ b/go/vt/vttablet/tabletserver/vstreamer/uvstreamer.go @@ -88,7 +88,7 @@ type uvstreamer struct { config *uvstreamerConfig - vs *vstreamer //last vstreamer created in uvstreamer + vs *vstreamer // last vstreamer created in uvstreamer } type uvstreamerConfig struct { @@ -138,6 +138,9 @@ func (uvs *uvstreamer) buildTablePlan() error { uvs.plans = make(map[string]*tablePlan) tableLastPKs := make(map[string]*binlogdatapb.TableLastPK) for _, tablePK := range uvs.inTablePKs { + if tablePK != nil && tablePK.Lastpk != nil && len(tablePK.Lastpk.Fields) == 0 { + return fmt.Errorf("lastpk for table %s has no fields defined", tablePK.TableName) + } tableLastPKs[tablePK.TableName] = tablePK } tables := uvs.se.GetSchema() @@ -313,7 +316,6 @@ func (uvs *uvstreamer) send2(evs []*binlogdatapb.VEvent) error { } behind := time.Now().UnixNano() - uvs.lastTimestampNs uvs.setReplicationLagSeconds(behind / 1e9) - //log.Infof("sbm set to %d", uvs.ReplicationLagSeconds) var evs2 []*binlogdatapb.VEvent if len(uvs.plans) > 0 { evs2 = uvs.filterEvents(evs) diff --git a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go index 8d0d182790e..4d9f66f1809 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go +++ b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go @@ -398,6 +398,33 @@ func TestMissingTables(t *testing.T) { runCases(t, filter, testcases, startPos, nil) } +// TestVStreamMissingFieldsInLastPK tests that we error out if the lastpk for a table is missing the fields spec. +func TestVStreamMissingFieldsInLastPK(t *testing.T) { + ts := &TestSpec{ + t: t, + ddls: []string{ + "create table t1(id11 int, id12 int, primary key(id11))", + }, + } + ts.Init() + defer ts.Close() + filter := &binlogdatapb.Filter{ + Rules: []*binlogdatapb.Rule{{ + Match: "t1", + Filter: "select * from t1", + }}, + } + var tablePKs []*binlogdatapb.TableLastPK + tablePKs = append(tablePKs, getTablePK("t1", 1)) + for _, tpk := range tablePKs { + tpk.Lastpk.Fields = nil + } + ctx := context.Background() + ch := make(chan []*binlogdatapb.VEvent) + err := vstream(ctx, t, "", tablePKs, filter, ch) + require.ErrorContains(t, err, "lastpk for table t1 has no fields defined") +} + func TestVStreamCopySimpleFlow(t *testing.T) { ts := &TestSpec{ t: t, From e341f239c24b6b1451feb828c01e8c84d0c2c689 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Sun, 28 Jul 2024 10:04:25 +0300 Subject: [PATCH 05/34] Throttler: return app name in check result, synthesize "why throttled" explanation from result (#16416) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- .../onlineddl/vrepl/onlineddl_vrepl_test.go | 2 + go/vt/binlog/binlogplayer/binlog_player.go | 4 +- .../binlog/binlogplayer/binlog_player_test.go | 35 ++ go/vt/proto/binlogdata/binlogdata.pb.go | 374 ++++++++++-------- .../proto/binlogdata/binlogdata_vtproto.pb.go | 126 +++++- .../tabletmanagerdata/tabletmanagerdata.pb.go | 329 +++++++-------- .../tabletmanagerdata_vtproto.pb.go | 88 +++++ .../schema/onlineddl/schema_migrations.sql | 1 + .../schema/vreplication/vreplication.sql | 1 + go/vt/vttablet/onlineddl/executor.go | 12 +- go/vt/vttablet/onlineddl/schema.go | 4 +- go/vt/vttablet/onlineddl/vrepl.go | 1 + go/vt/vttablet/tabletmanager/rpc_throttler.go | 2 + .../tabletmanager/vreplication/vcopier.go | 6 +- .../tabletmanager/vreplication/vplayer.go | 6 +- .../tabletmanager/vreplication/vreplicator.go | 4 +- go/vt/vttablet/tabletserver/gc/tablegc.go | 2 +- go/vt/vttablet/tabletserver/throttle/check.go | 21 +- .../tabletserver/throttle/check_result.go | 34 +- .../throttle/check_result_test.go | 66 ++++ .../vttablet/tabletserver/throttle/client.go | 41 +- .../tabletserver/throttle/throttler.go | 58 +-- .../tabletserver/throttle/throttler_test.go | 343 +++++++++++++--- .../tabletserver/vstreamer/resultstreamer.go | 2 +- .../tabletserver/vstreamer/rowstreamer.go | 4 +- .../tabletserver/vstreamer/vstreamer.go | 17 +- proto/binlogdata.proto | 4 + proto/tabletmanagerdata.proto | 6 + web/vtadmin/src/proto/vtadmin.d.ts | 24 ++ web/vtadmin/src/proto/vtadmin.js | 92 +++++ 30 files changed, 1218 insertions(+), 491 deletions(-) create mode 100644 go/vt/vttablet/tabletserver/throttle/check_result_test.go diff --git a/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go b/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go index 83fe6bea988..70efe4ec8a4 100644 --- a/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go +++ b/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go @@ -397,6 +397,8 @@ func TestSchemaChange(t *testing.T) { assert.GreaterOrEqual(t, lastThrottledTimestamp, startedTimestamp) component := row.AsString("component_throttled", "") assert.Contains(t, []string{throttlerapp.VCopierName.String(), throttlerapp.VPlayerName.String()}, component) + reason := row.AsString("reason_throttled", "") + assert.Contains(t, reason, "is explicitly denied access") // unthrottle onlineddl.UnthrottleAllMigrations(t, &vtParams) diff --git a/go/vt/binlog/binlogplayer/binlog_player.go b/go/vt/binlog/binlogplayer/binlog_player.go index 05685a54d3e..7936a0760c9 100644 --- a/go/vt/binlog/binlogplayer/binlog_player.go +++ b/go/vt/binlog/binlogplayer/binlog_player.go @@ -669,11 +669,11 @@ func GenerateUpdateHeartbeat(uid int32, timeUpdated int64) (string, error) { } // GenerateUpdateTimeThrottled returns a statement to record the latest throttle time in the _vt.vreplication table. -func GenerateUpdateTimeThrottled(uid int32, timeThrottledUnix int64, componentThrottled string) (string, error) { +func GenerateUpdateTimeThrottled(uid int32, timeThrottledUnix int64, componentThrottled string, reasonThrottled string) (string, error) { if timeThrottledUnix == 0 { return "", fmt.Errorf("timeUpdated cannot be zero") } - return fmt.Sprintf("update _vt.vreplication set time_updated=%v, time_throttled=%v, component_throttled='%v' where id=%v", timeThrottledUnix, timeThrottledUnix, componentThrottled, uid), nil + return fmt.Sprintf("update _vt.vreplication set time_updated=%v, time_throttled=%v, component_throttled='%v', reason_throttled=%v where id=%v", timeThrottledUnix, timeThrottledUnix, componentThrottled, encodeString(MessageTruncate(reasonThrottled)), uid), nil } // StartVReplicationUntil returns a statement to start the replication with a stop position. diff --git a/go/vt/binlog/binlogplayer/binlog_player_test.go b/go/vt/binlog/binlogplayer/binlog_player_test.go index 99b0ef496b3..697733a6d18 100644 --- a/go/vt/binlog/binlogplayer/binlog_player_test.go +++ b/go/vt/binlog/binlogplayer/binlog_player_test.go @@ -22,6 +22,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "vitess.io/vitess/go/mysql/replication" "vitess.io/vitess/go/mysql/sqlerror" querypb "vitess.io/vitess/go/vt/proto/query" @@ -454,3 +456,36 @@ func TestReadVReplicationStatus(t *testing.T) { t.Errorf("ReadVReplicationStatus(482821) = %#v, want %#v", got, want) } } + +func TestEncodeString(t *testing.T) { + tcases := []struct { + in, out string + }{ + { + in: "", + out: "''", + }, + { + in: "a", + out: "'a'", + }, + { + in: "here's", + out: "'here\\'s'", + }, + { + in: "online-ddl is denied access due to lag metric value 94.821447 exceeding threshold 5", + out: "'online-ddl is denied access due to lag metric value 94.821447 exceeding threshold 5'", + }, + { + in: "'a','b','c'", + out: "'\\'a\\',\\'b\\',\\'c\\''", + }, + } + for _, tcase := range tcases { + t.Run(tcase.in, func(t *testing.T) { + out := encodeString(tcase.in) + assert.Equal(t, tcase.out, out) + }) + } +} diff --git a/go/vt/proto/binlogdata/binlogdata.pb.go b/go/vt/proto/binlogdata/binlogdata.pb.go index aade1d049f8..8374a4a2733 100644 --- a/go/vt/proto/binlogdata/binlogdata.pb.go +++ b/go/vt/proto/binlogdata/binlogdata.pb.go @@ -1913,6 +1913,8 @@ type VEvent struct { Shard string `protobuf:"bytes,23,opt,name=shard,proto3" json:"shard,omitempty"` // indicate that we are being throttled right now Throttled bool `protobuf:"varint,24,opt,name=throttled,proto3" json:"throttled,omitempty"` + // ThrottledReason is a human readable string that explains why the stream is throttled + ThrottledReason string `protobuf:"bytes,25,opt,name=throttled_reason,json=throttledReason,proto3" json:"throttled_reason,omitempty"` } func (x *VEvent) Reset() { @@ -2045,6 +2047,13 @@ func (x *VEvent) GetThrottled() bool { return false } +func (x *VEvent) GetThrottledReason() string { + if x != nil { + return x.ThrottledReason + } + return "" +} + type MinimalTable struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2397,6 +2406,8 @@ type VStreamRowsResponse struct { Throttled bool `protobuf:"varint,6,opt,name=throttled,proto3" json:"throttled,omitempty"` // Heartbeat indicates that this is a heartbeat message Heartbeat bool `protobuf:"varint,7,opt,name=heartbeat,proto3" json:"heartbeat,omitempty"` + // ThrottledReason is a human readable string that explains why the stream is throttled + ThrottledReason string `protobuf:"bytes,8,opt,name=throttled_reason,json=throttledReason,proto3" json:"throttled_reason,omitempty"` } func (x *VStreamRowsResponse) Reset() { @@ -2480,6 +2491,13 @@ func (x *VStreamRowsResponse) GetHeartbeat() bool { return false } +func (x *VStreamRowsResponse) GetThrottledReason() string { + if x != nil { + return x.ThrottledReason + } + return "" +} + // VStreamTablesRequest is the payload for VStreamTables type VStreamTablesRequest struct { state protoimpl.MessageState @@ -3247,7 +3265,7 @@ var file_binlogdata_proto_rawDesc = []byte{ 0x68, 0x61, 0x72, 0x64, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x22, 0x8b, 0x04, + 0x75, 0x72, 0x63, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x22, 0xb6, 0x04, 0x0a, 0x06, 0x56, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, @@ -3280,183 +3298,189 @@ var file_binlogdata_proto_rawDesc = []byte{ 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x22, 0x8d, 0x01, 0x0a, 0x0c, - 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x70, 0x5f, 0x6b, 0x5f, 0x63, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x70, 0x4b, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0e, 0x70, 0x5f, 0x6b, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x70, 0x4b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x0d, 0x4d, - 0x69, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x30, 0x0a, 0x06, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, - 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x61, - 0x6c, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x22, 0xc7, - 0x02, 0x0a, 0x0e, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x3f, 0x0a, 0x13, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, - 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, - 0x2e, 0x76, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, - 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x45, 0x0a, 0x13, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x5f, - 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, 0x43, 0x61, - 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, - 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x06, - 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, - 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x5f, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x52, 0x09, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x74, + 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x8d, 0x01, 0x0a, 0x0c, 0x4d, 0x69, 0x6e, 0x69, 0x6d, + 0x61, 0x6c, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x70, 0x5f, 0x6b, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x70, 0x4b, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x73, 0x12, 0x23, 0x0a, 0x0e, 0x70, 0x5f, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x4b, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x0d, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x61, + 0x6c, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x30, 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x22, 0xc7, 0x02, 0x0a, 0x0e, 0x56, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x13, + 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, + 0x13, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x2e, 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, + 0x44, 0x52, 0x11, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x70, 0x5f, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, + 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4c, + 0x61, 0x73, 0x74, 0x50, 0x4b, 0x52, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, 0x73, 0x74, + 0x50, 0x4b, 0x73, 0x22, 0x3d, 0x0a, 0x0f, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x56, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x12, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x6f, + 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x13, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x13, 0x69, 0x6d, + 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, + 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, + 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2a, + 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x22, 0xa4, 0x02, 0x0a, 0x13, 0x56, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x28, 0x0a, 0x08, 0x70, 0x6b, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x08, 0x70, 0x6b, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x74, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x67, 0x74, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, + 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x22, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, + 0x6f, 0x77, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, + 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, + 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, + 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x68, 0x65, 0x61, + 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, + 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0f, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x22, 0xc5, 0x01, 0x0a, 0x14, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x13, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x13, 0x69, + 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x2e, 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, + 0x11, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0xde, 0x01, 0x0a, 0x15, 0x56, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x28, 0x0a, 0x08, 0x70, 0x6b, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x08, 0x70, 0x6b, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x74, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x67, 0x74, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, + 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x22, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, + 0x6f, 0x77, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x22, 0x69, 0x0a, 0x0b, 0x4c, 0x61, + 0x73, 0x74, 0x50, 0x4b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0e, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x5f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x50, 0x4b, 0x52, 0x0c, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x4c, 0x61, 0x73, 0x74, 0x50, 0x4b, 0x73, 0x22, 0x3d, 0x0a, 0x0f, 0x56, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x69, - 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x12, 0x56, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, - 0x0a, 0x13, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x65, 0x66, - 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x45, 0x0a, 0x13, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x61, 0x6c, - 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, - 0x72, 0x49, 0x44, 0x52, 0x11, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x43, 0x61, - 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x14, 0x0a, - 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x12, 0x2a, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x18, 0x05, 0x20, + 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x50, 0x4b, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x4c, 0x61, 0x73, 0x74, 0x50, 0x4b, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x58, 0x0a, 0x0b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, + 0x73, 0x74, 0x50, 0x4b, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x22, - 0xf9, 0x01, 0x0a, 0x13, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x28, 0x0a, - 0x08, 0x70, 0x6b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x08, 0x70, - 0x6b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x74, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x74, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x72, - 0x6f, 0x77, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x22, 0x0a, 0x06, 0x6c, - 0x61, 0x73, 0x74, 0x70, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x12, - 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, - 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x14, - 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x13, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, - 0x49, 0x44, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x61, 0x6c, - 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x13, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, - 0x74, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x54, 0x47, 0x61, 0x74, - 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x69, 0x6d, 0x6d, 0x65, 0x64, - 0x69, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x22, 0xde, 0x01, 0x0a, 0x15, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x06, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x73, 0x12, 0x28, 0x0a, 0x08, 0x70, 0x6b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x52, 0x08, 0x70, 0x6b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x67, 0x74, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x74, 0x69, 0x64, - 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, - 0x12, 0x22, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x06, 0x6c, 0x61, - 0x73, 0x74, 0x70, 0x6b, 0x22, 0x69, 0x0a, 0x0b, 0x4c, 0x61, 0x73, 0x74, 0x50, 0x4b, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x70, 0x5f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x69, - 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, - 0x73, 0x74, 0x50, 0x4b, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x50, - 0x4b, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, - 0x58, 0x0a, 0x0b, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x50, 0x4b, 0x12, 0x1d, - 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, - 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x70, 0x6b, 0x22, 0xdc, 0x01, 0x0a, 0x15, 0x56, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x13, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, - 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0f, 0x2e, 0x76, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, - 0x44, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x13, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, - 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, - 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, - 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x72, 0x0a, 0x16, 0x56, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x74, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x74, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, - 0x72, 0x6f, 0x77, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x2a, 0x3e, 0x0a, 0x0b, - 0x4f, 0x6e, 0x44, 0x44, 0x4c, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x49, - 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, - 0x01, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x58, 0x45, 0x43, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x45, - 0x58, 0x45, 0x43, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x10, 0x03, 0x2a, 0x7b, 0x0a, 0x18, - 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x61, 0x74, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x6f, 0x76, - 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x65, 0x10, 0x03, 0x12, 0x0b, 0x0a, - 0x07, 0x52, 0x65, 0x73, 0x68, 0x61, 0x72, 0x64, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x44, 0x4c, 0x10, 0x05, 0x2a, 0x44, 0x0a, 0x1b, 0x56, 0x52, 0x65, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x53, 0x75, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, - 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x10, 0x01, 0x12, - 0x0e, 0x0a, 0x0a, 0x41, 0x74, 0x6f, 0x6d, 0x69, 0x63, 0x43, 0x6f, 0x70, 0x79, 0x10, 0x02, 0x2a, - 0x71, 0x0a, 0x19, 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, - 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x69, - 0x74, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x0b, 0x0a, - 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x4c, 0x61, 0x67, 0x67, 0x69, 0x6e, 0x67, - 0x10, 0x06, 0x2a, 0x8d, 0x02, 0x0a, 0x0a, 0x56, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, - 0x0a, 0x04, 0x47, 0x54, 0x49, 0x44, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x42, 0x45, 0x47, 0x49, - 0x4e, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x03, 0x12, - 0x0c, 0x0a, 0x08, 0x52, 0x4f, 0x4c, 0x4c, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x04, 0x12, 0x07, 0x0a, - 0x03, 0x44, 0x44, 0x4c, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4e, 0x53, 0x45, 0x52, 0x54, - 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x10, 0x07, 0x12, - 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x44, - 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x09, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x45, 0x54, 0x10, 0x0a, - 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x10, 0x0b, 0x12, 0x07, 0x0a, 0x03, 0x52, - 0x4f, 0x57, 0x10, 0x0c, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x10, 0x0d, 0x12, - 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x0e, 0x12, 0x09, - 0x0a, 0x05, 0x56, 0x47, 0x54, 0x49, 0x44, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4a, 0x4f, 0x55, - 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, - 0x4e, 0x10, 0x11, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x41, 0x53, 0x54, 0x50, 0x4b, 0x10, 0x12, 0x12, - 0x0d, 0x0a, 0x09, 0x53, 0x41, 0x56, 0x45, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10, 0x13, 0x12, 0x12, - 0x0a, 0x0e, 0x43, 0x4f, 0x50, 0x59, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, - 0x10, 0x14, 0x2a, 0x27, 0x0a, 0x0d, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x53, 0x10, 0x00, 0x12, - 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x52, 0x44, 0x53, 0x10, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x76, - 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, - 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x69, 0x6e, 0x6c, - 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0xdc, 0x01, 0x0a, 0x15, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x13, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x45, 0x0a, 0x13, 0x69, 0x6d, + 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, + 0x56, 0x54, 0x47, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x44, 0x52, 0x11, + 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x25, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x72, + 0x0a, 0x16, 0x56, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x67, 0x74, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x74, + 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0a, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, + 0x77, 0x73, 0x2a, 0x3e, 0x0a, 0x0b, 0x4f, 0x6e, 0x44, 0x44, 0x4c, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x58, 0x45, 0x43, 0x10, + 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x45, 0x43, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, + 0x10, 0x03, 0x2a, 0x7b, 0x0a, 0x18, 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, + 0x0a, 0x0b, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x10, 0x00, 0x12, + 0x0e, 0x0a, 0x0a, 0x4d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x10, 0x01, 0x12, + 0x15, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, + 0x65, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x68, 0x61, 0x72, 0x64, 0x10, 0x04, + 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x44, 0x4c, 0x10, 0x05, 0x2a, + 0x44, 0x0a, 0x1b, 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x75, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, + 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x74, + 0x69, 0x61, 0x6c, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x74, 0x6f, 0x6d, 0x69, 0x63, 0x43, + 0x6f, 0x70, 0x79, 0x10, 0x02, 0x2a, 0x71, 0x0a, 0x19, 0x56, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, + 0x70, 0x70, 0x65, 0x64, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6f, 0x70, 0x79, 0x69, 0x6e, + 0x67, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x04, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x4c, + 0x61, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x2a, 0x8d, 0x02, 0x0a, 0x0a, 0x56, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x54, 0x49, 0x44, 0x10, 0x01, 0x12, 0x09, + 0x0a, 0x05, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, + 0x4d, 0x49, 0x54, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x4f, 0x4c, 0x4c, 0x42, 0x41, 0x43, + 0x4b, 0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x44, 0x4c, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, + 0x49, 0x4e, 0x53, 0x45, 0x52, 0x54, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x50, 0x4c, + 0x41, 0x43, 0x45, 0x10, 0x07, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, + 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x09, 0x12, 0x07, 0x0a, + 0x03, 0x53, 0x45, 0x54, 0x10, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x10, + 0x0b, 0x12, 0x07, 0x0a, 0x03, 0x52, 0x4f, 0x57, 0x10, 0x0c, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x49, + 0x45, 0x4c, 0x44, 0x10, 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52, 0x54, 0x42, 0x45, + 0x41, 0x54, 0x10, 0x0e, 0x12, 0x09, 0x0a, 0x05, 0x56, 0x47, 0x54, 0x49, 0x44, 0x10, 0x0f, 0x12, + 0x0b, 0x0a, 0x07, 0x4a, 0x4f, 0x55, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, + 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x11, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x41, 0x53, + 0x54, 0x50, 0x4b, 0x10, 0x12, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x41, 0x56, 0x45, 0x50, 0x4f, 0x49, + 0x4e, 0x54, 0x10, 0x13, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4f, 0x50, 0x59, 0x5f, 0x43, 0x4f, 0x4d, + 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x14, 0x2a, 0x27, 0x0a, 0x0d, 0x4d, 0x69, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x41, 0x42, + 0x4c, 0x45, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x52, 0x44, 0x53, 0x10, + 0x01, 0x42, 0x29, 0x5a, 0x27, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, + 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/vt/proto/binlogdata/binlogdata_vtproto.pb.go b/go/vt/proto/binlogdata/binlogdata_vtproto.pb.go index 1332681a976..ea15c40a992 100644 --- a/go/vt/proto/binlogdata/binlogdata_vtproto.pb.go +++ b/go/vt/proto/binlogdata/binlogdata_vtproto.pb.go @@ -512,20 +512,21 @@ func (m *VEvent) CloneVT() *VEvent { return (*VEvent)(nil) } r := &VEvent{ - Type: m.Type, - Timestamp: m.Timestamp, - Gtid: m.Gtid, - Statement: m.Statement, - RowEvent: m.RowEvent.CloneVT(), - FieldEvent: m.FieldEvent.CloneVT(), - Vgtid: m.Vgtid.CloneVT(), - Journal: m.Journal.CloneVT(), - Dml: m.Dml, - CurrentTime: m.CurrentTime, - LastPKEvent: m.LastPKEvent.CloneVT(), - Keyspace: m.Keyspace, - Shard: m.Shard, - Throttled: m.Throttled, + Type: m.Type, + Timestamp: m.Timestamp, + Gtid: m.Gtid, + Statement: m.Statement, + RowEvent: m.RowEvent.CloneVT(), + FieldEvent: m.FieldEvent.CloneVT(), + Vgtid: m.Vgtid.CloneVT(), + Journal: m.Journal.CloneVT(), + Dml: m.Dml, + CurrentTime: m.CurrentTime, + LastPKEvent: m.LastPKEvent.CloneVT(), + Keyspace: m.Keyspace, + Shard: m.Shard, + Throttled: m.Throttled, + ThrottledReason: m.ThrottledReason, } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -671,10 +672,11 @@ func (m *VStreamRowsResponse) CloneVT() *VStreamRowsResponse { return (*VStreamRowsResponse)(nil) } r := &VStreamRowsResponse{ - Gtid: m.Gtid, - Lastpk: m.Lastpk.CloneVT(), - Throttled: m.Throttled, - Heartbeat: m.Heartbeat, + Gtid: m.Gtid, + Lastpk: m.Lastpk.CloneVT(), + Throttled: m.Throttled, + Heartbeat: m.Heartbeat, + ThrottledReason: m.ThrottledReason, } if rhs := m.Fields; rhs != nil { tmpContainer := make([]*query.Field, len(rhs)) @@ -2131,6 +2133,15 @@ func (m *VEvent) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.ThrottledReason) > 0 { + i -= len(m.ThrottledReason) + copy(dAtA[i:], m.ThrottledReason) + i = encodeVarint(dAtA, i, uint64(len(m.ThrottledReason))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xca + } if m.Throttled { i-- if m.Throttled { @@ -2626,6 +2637,13 @@ func (m *VStreamRowsResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.ThrottledReason) > 0 { + i -= len(m.ThrottledReason) + copy(dAtA[i:], m.ThrottledReason) + i = encodeVarint(dAtA, i, uint64(len(m.ThrottledReason))) + i-- + dAtA[i] = 0x42 + } if m.Heartbeat { i-- if m.Heartbeat { @@ -3739,6 +3757,10 @@ func (m *VEvent) SizeVT() (n int) { if m.Throttled { n += 3 } + l = len(m.ThrottledReason) + if l > 0 { + n += 2 + l + sov(uint64(l)) + } n += len(m.unknownFields) return n } @@ -3910,6 +3932,10 @@ func (m *VStreamRowsResponse) SizeVT() (n int) { if m.Heartbeat { n += 2 } + l = len(m.ThrottledReason) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } n += len(m.unknownFields) return n } @@ -7951,6 +7977,38 @@ func (m *VEvent) UnmarshalVT(dAtA []byte) error { } } m.Throttled = bool(v != 0) + case 25: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ThrottledReason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ThrottledReason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -9116,6 +9174,38 @@ func (m *VStreamRowsResponse) UnmarshalVT(dAtA []byte) error { } } m.Heartbeat = bool(v != 0) + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ThrottledReason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ThrottledReason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go b/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go index c0896882735..715e2a2ab36 100644 --- a/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go +++ b/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go @@ -6636,6 +6636,10 @@ type CheckThrottlerResponse struct { // Metrics is a map (metric name -> metric value/error) so that the client has as much // information as possible about all the checked metrics. Metrics map[string]*CheckThrottlerResponse_Metric `protobuf:"bytes,7,rep,name=metrics,proto3" json:"metrics,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // AppName is the name of app that was matched by the throttler + AppName string `protobuf:"bytes,8,opt,name=app_name,json=appName,proto3" json:"app_name,omitempty"` + // Summary is a human readable analysis of the result + Summary string `protobuf:"bytes,9,opt,name=summary,proto3" json:"summary,omitempty"` } func (x *CheckThrottlerResponse) Reset() { @@ -6719,6 +6723,20 @@ func (x *CheckThrottlerResponse) GetMetrics() map[string]*CheckThrottlerResponse return nil } +func (x *CheckThrottlerResponse) GetAppName() string { + if x != nil { + return x.AppName + } + return "" +} + +func (x *CheckThrottlerResponse) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + type GetThrottlerStatusRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -8211,7 +8229,7 @@ var file_tabletmanagerdata_proto_rawDesc = []byte{ 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xc2, 0x04, 0x0a, 0x16, + 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xf7, 0x04, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, @@ -8229,161 +8247,164 @@ var file_tabletmanagerdata_proto_rawDesc = []byte{ 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0xb7, 0x01, 0x0a, 0x06, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x1a, 0x6c, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, - 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe1, 0x0f, - 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, - 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x17, - 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x6f, 0x72, - 0x6d, 0x61, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x6f, - 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x6c, 0x61, 0x67, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, - 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, - 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x3c, 0x0a, 0x1b, - 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x64, - 0x5f, 0x61, 0x73, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x17, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, - 0x64, 0x41, 0x73, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x73, 0x0a, 0x12, 0x61, 0x67, - 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, - 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x67, - 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, - 0x70, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, - 0x6f, 0x6c, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, - 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x10, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, - 0x73, 0x12, 0x67, 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, - 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x67, 0x0a, 0x0e, 0x74, 0x68, - 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x0f, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, + 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x1a, 0xb7, 0x01, + 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x1a, 0x6c, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, + 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0xe1, 0x0f, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, - 0x70, 0x70, 0x73, 0x12, 0x74, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x44, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x41, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x63, - 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x11, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x65, 0x64, 0x12, 0x5e, 0x0a, 0x0b, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x61, - 0x70, 0x70, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, - 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, - 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, - 0x41, 0x70, 0x70, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x1a, 0x80, 0x01, 0x0a, 0x16, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x50, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x43, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x81, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x34, 0x0a, 0x0f, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x41, 0x74, 0x12, - 0x3b, 0x0a, 0x1a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, - 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x17, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x53, 0x69, 0x6e, 0x63, - 0x65, 0x4c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x1a, 0x7c, 0x0a, 0x12, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, - 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5c, 0x0a, 0x12, 0x54, 0x68, - 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6c, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x4c, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, + 0x69, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x69, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, + 0x73, 0x5f, 0x64, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x69, 0x73, 0x44, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x61, + 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x61, 0x67, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, + 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x5f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x73, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4e, 0x61, + 0x6d, 0x65, 0x55, 0x73, 0x65, 0x64, 0x41, 0x73, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, + 0x73, 0x0a, 0x12, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, + 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x11, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x12, 0x70, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, + 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x43, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, + 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x12, 0x67, 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, + 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, + 0x67, 0x0a, 0x0e, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, + 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, + 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, + 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x68, 0x72, 0x6f, 0x74, + 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, 0x12, 0x74, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, + 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, + 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x70, 0x70, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x29, + 0x0a, 0x10, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, + 0x6c, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x5e, 0x0a, 0x0b, 0x72, 0x65, 0x63, + 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, + 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, + 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x72, + 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x80, 0x01, 0x0a, 0x16, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, + 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x68, 0x72, - 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x41, 0x70, 0x70, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, - 0x0a, 0x09, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x12, 0x2b, 0x0a, 0x0a, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x09, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x76, 0x0a, 0x0f, 0x52, 0x65, 0x63, - 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x4d, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, - 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x63, - 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x2a, 0x3e, 0x0a, 0x19, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x07, - 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x4f, 0x52, 0x44, - 0x45, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x03, 0x42, 0x30, 0x5a, 0x2e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, - 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, - 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, + 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x43, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x81, 0x01, + 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x34, + 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x5f, 0x61, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x79, 0x41, 0x74, 0x12, 0x3b, 0x0a, 0x1a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, + 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x79, 0x1a, 0x7c, 0x0a, 0x12, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, + 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, + 0x5c, 0x0a, 0x12, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x52, 0x75, + 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, + 0x16, 0x41, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x09, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, + 0x12, 0x2b, 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x52, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x76, + 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x4d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, + 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x3e, 0x0a, 0x19, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x49, 0x4e, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x42, 0x30, 0x5a, 0x2e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, + 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go b/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go index d48882613d1..eb0b058a56e 100644 --- a/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go +++ b/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go @@ -2529,6 +2529,8 @@ func (m *CheckThrottlerResponse) CloneVT() *CheckThrottlerResponse { Error: m.Error, Message: m.Message, RecentlyChecked: m.RecentlyChecked, + AppName: m.AppName, + Summary: m.Summary, } if rhs := m.Metrics; rhs != nil { tmpContainer := make(map[string]*CheckThrottlerResponse_Metric, len(rhs)) @@ -8833,6 +8835,20 @@ func (m *CheckThrottlerResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Summary) > 0 { + i -= len(m.Summary) + copy(dAtA[i:], m.Summary) + i = encodeVarint(dAtA, i, uint64(len(m.Summary))) + i-- + dAtA[i] = 0x4a + } + if len(m.AppName) > 0 { + i -= len(m.AppName) + copy(dAtA[i:], m.AppName) + i = encodeVarint(dAtA, i, uint64(len(m.AppName))) + i-- + dAtA[i] = 0x42 + } if len(m.Metrics) > 0 { for k := range m.Metrics { v := m.Metrics[k] @@ -11566,6 +11582,14 @@ func (m *CheckThrottlerResponse) SizeVT() (n int) { n += mapEntrySize + 1 + sov(uint64(mapEntrySize)) } } + l = len(m.AppName) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + l = len(m.Summary) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } n += len(m.unknownFields) return n } @@ -25594,6 +25618,70 @@ func (m *CheckThrottlerResponse) UnmarshalVT(dAtA []byte) error { } m.Metrics[mapkey] = mapvalue iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Summary", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Summary = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/go/vt/sidecardb/schema/onlineddl/schema_migrations.sql b/go/vt/sidecardb/schema/onlineddl/schema_migrations.sql index 2926ec76f28..82d0c221f0e 100644 --- a/go/vt/sidecardb/schema/onlineddl/schema_migrations.sql +++ b/go/vt/sidecardb/schema/onlineddl/schema_migrations.sql @@ -63,6 +63,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations `special_plan` text NOT NULL, `last_throttled_timestamp` timestamp NULL DEFAULT NULL, `component_throttled` tinytext NOT NULL, + `reason_throttled` text NOT NULL, `cancelled_timestamp` timestamp NULL DEFAULT NULL, `postpone_launch` tinyint unsigned NOT NULL DEFAULT '0', `stage` text NOT NULL, diff --git a/go/vt/sidecardb/schema/vreplication/vreplication.sql b/go/vt/sidecardb/schema/vreplication/vreplication.sql index 8d2ec41d1a6..293997056fd 100644 --- a/go/vt/sidecardb/schema/vreplication/vreplication.sql +++ b/go/vt/sidecardb/schema/vreplication/vreplication.sql @@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS vreplication `workflow_type` int NOT NULL DEFAULT '0', `time_throttled` bigint NOT NULL DEFAULT '0', `component_throttled` varchar(255) NOT NULL DEFAULT '', + `reason_throttled` varchar(1000) NOT NULL DEFAULT '', `workflow_sub_type` int NOT NULL DEFAULT '0', `defer_secondary_keys` tinyint(1) NOT NULL DEFAULT '0', /* diff --git a/go/vt/vttablet/onlineddl/executor.go b/go/vt/vttablet/onlineddl/executor.go index 1aa75907e88..757caa711b7 100644 --- a/go/vt/vttablet/onlineddl/executor.go +++ b/go/vt/vttablet/onlineddl/executor.go @@ -3640,6 +3640,7 @@ func (e *Executor) readVReplStream(ctx context.Context, uuid string, okIfMissing timeHeartbeat: row.AsInt64("time_heartbeat", 0), timeThrottled: row.AsInt64("time_throttled", 0), componentThrottled: row.AsString("component_throttled", ""), + reasonThrottled: row.AsString("reason_throttled", ""), transactionTimestamp: row.AsInt64("transaction_timestamp", 0), state: binlogdatapb.VReplicationWorkflowState(binlogdatapb.VReplicationWorkflowState_value[row.AsString("state", "")]), message: row.AsString("message", ""), @@ -3872,7 +3873,7 @@ func (e *Executor) reviewRunningMigrations(ctx context.Context) (countRunnning i _ = e.updateMigrationETASecondsByProgress(ctx, uuid) if s.timeThrottled != 0 { // Avoid creating a 0000-00-00 00:00:00 timestamp - _ = e.updateMigrationLastThrottled(ctx, uuid, time.Unix(s.timeThrottled, 0), s.componentThrottled) + _ = e.updateMigrationLastThrottled(ctx, uuid, time.Unix(s.timeThrottled, 0), s.componentThrottled, s.reasonThrottled) } if onlineDDL.StrategySetting().IsInOrderCompletion() { // We will fail an in-order migration if there's _prior_ migrations within the same migration-context @@ -4575,10 +4576,17 @@ func (e *Executor) updateMigrationETASecondsByProgress(ctx context.Context, uuid return err } -func (e *Executor) updateMigrationLastThrottled(ctx context.Context, uuid string, lastThrottledTime time.Time, throttledCompnent string) error { +func (e *Executor) updateMigrationLastThrottled( + ctx context.Context, + uuid string, + lastThrottledTime time.Time, + throttledCompnent string, + reasonThrottled string, +) error { query, err := sqlparser.ParseAndBind(sqlUpdateLastThrottled, sqltypes.StringBindVariable(lastThrottledTime.Format(sqltypes.TimestampFormat)), sqltypes.StringBindVariable(throttledCompnent), + sqltypes.StringBindVariable(reasonThrottled), sqltypes.StringBindVariable(uuid), ) if err != nil { diff --git a/go/vt/vttablet/onlineddl/schema.go b/go/vt/vttablet/onlineddl/schema.go index 7ff1cd220d6..4f65864cbfa 100644 --- a/go/vt/vttablet/onlineddl/schema.go +++ b/go/vt/vttablet/onlineddl/schema.go @@ -249,7 +249,7 @@ const ( migration_uuid=%a ` sqlUpdateLastThrottled = `UPDATE _vt.schema_migrations - SET last_throttled_timestamp=%a, component_throttled=%a + SET last_throttled_timestamp=%a, component_throttled=%a, reason_throttled=%a WHERE migration_uuid=%a ` @@ -435,6 +435,7 @@ const ( last_throttled_timestamp, cancelled_timestamp, component_throttled, + reason_throttled, postpone_launch, postpone_completion, is_immediate_operation, @@ -588,6 +589,7 @@ const ( time_heartbeat, time_throttled, component_throttled, + reason_throttled, state, message, rows_copied diff --git a/go/vt/vttablet/onlineddl/vrepl.go b/go/vt/vttablet/onlineddl/vrepl.go index cde2f276563..42fe33a855f 100644 --- a/go/vt/vttablet/onlineddl/vrepl.go +++ b/go/vt/vttablet/onlineddl/vrepl.go @@ -59,6 +59,7 @@ type VReplStream struct { timeHeartbeat int64 timeThrottled int64 componentThrottled string + reasonThrottled string transactionTimestamp int64 state binlogdatapb.VReplicationWorkflowState message string diff --git a/go/vt/vttablet/tabletmanager/rpc_throttler.go b/go/vt/vttablet/tabletmanager/rpc_throttler.go index 8ec3bb592da..5facbb01229 100644 --- a/go/vt/vttablet/tabletmanager/rpc_throttler.go +++ b/go/vt/vttablet/tabletmanager/rpc_throttler.go @@ -58,6 +58,8 @@ func (tm *TabletManager) CheckThrottler(ctx context.Context, req *tabletmanagerd Threshold: checkResult.Threshold, Message: checkResult.Message, RecentlyChecked: checkResult.RecentlyChecked, + AppName: checkResult.AppName, + Summary: checkResult.Summary(), Metrics: make(map[string]*tabletmanagerdatapb.CheckThrottlerResponse_Metric), } for name, metric := range checkResult.Metrics { diff --git a/go/vt/vttablet/tabletmanager/vreplication/vcopier.go b/go/vt/vttablet/tabletmanager/vreplication/vcopier.go index 9057a55707f..47e3798acd0 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/vcopier.go +++ b/go/vt/vttablet/tabletmanager/vreplication/vcopier.go @@ -456,7 +456,7 @@ func (vc *vcopier) copyTable(ctx context.Context, tableName string, copyState ma default: } if rows.Throttled { - _ = vc.vr.updateTimeThrottled(throttlerapp.RowStreamerName) + _ = vc.vr.updateTimeThrottled(throttlerapp.RowStreamerName, rows.ThrottledReason) return nil } if rows.Heartbeat { @@ -464,10 +464,10 @@ func (vc *vcopier) copyTable(ctx context.Context, tableName string, copyState ma return nil } // verify throttler is happy, otherwise keep looping - if vc.vr.vre.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, throttlerapp.Name(vc.throttlerAppName)) { + if checkResult, ok := vc.vr.vre.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, throttlerapp.Name(vc.throttlerAppName)); ok { break // out of 'for' loop } else { // we're throttled - _ = vc.vr.updateTimeThrottled(throttlerapp.VCopierName) + _ = vc.vr.updateTimeThrottled(throttlerapp.VCopierName, checkResult.Summary()) } } if !copyWorkQueue.isOpen { diff --git a/go/vt/vttablet/tabletmanager/vreplication/vplayer.go b/go/vt/vttablet/tabletmanager/vreplication/vplayer.go index c2eba565524..31e26c30e88 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/vplayer.go +++ b/go/vt/vttablet/tabletmanager/vreplication/vplayer.go @@ -487,8 +487,8 @@ func (vp *vplayer) applyEvents(ctx context.Context, relay *relayLog) error { return ctx.Err() } // Check throttler. - if !vp.vr.vre.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, throttlerapp.Name(vp.throttlerAppName)) { - _ = vp.vr.updateTimeThrottled(throttlerapp.VPlayerName) + if checkResult, ok := vp.vr.vre.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, throttlerapp.Name(vp.throttlerAppName)); !ok { + _ = vp.vr.updateTimeThrottled(throttlerapp.VPlayerName, checkResult.Summary()) continue } @@ -794,7 +794,7 @@ func (vp *vplayer) applyEvent(ctx context.Context, event *binlogdatapb.VEvent, m return io.EOF case binlogdatapb.VEventType_HEARTBEAT: if event.Throttled { - if err := vp.vr.updateTimeThrottled(throttlerapp.VStreamerName); err != nil { + if err := vp.vr.updateTimeThrottled(throttlerapp.VStreamerName, event.ThrottledReason); err != nil { return err } } diff --git a/go/vt/vttablet/tabletmanager/vreplication/vreplicator.go b/go/vt/vttablet/tabletmanager/vreplication/vreplicator.go index abeda52b047..2a4d598c960 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/vreplicator.go +++ b/go/vt/vttablet/tabletmanager/vreplication/vreplicator.go @@ -630,13 +630,13 @@ func (vr *vreplicator) throttlerAppName() string { // tablet throttler over time. It also increments the global throttled count to keep // track of how many times in total vreplication has been throttled across all workflows // (both ones that currently exist and ones that no longer do). -func (vr *vreplicator) updateTimeThrottled(appThrottled throttlerapp.Name) error { +func (vr *vreplicator) updateTimeThrottled(appThrottled throttlerapp.Name, reasonThrottled string) error { appName := appThrottled.String() vr.stats.ThrottledCounts.Add([]string{"tablet", appName}, 1) globalStats.ThrottledCount.Add(1) err := vr.throttleUpdatesRateLimiter.Do(func() error { tm := time.Now().Unix() - update, err := binlogplayer.GenerateUpdateTimeThrottled(vr.id, tm, appName) + update, err := binlogplayer.GenerateUpdateTimeThrottled(vr.id, tm, appName, reasonThrottled) if err != nil { return err } diff --git a/go/vt/vttablet/tabletserver/gc/tablegc.go b/go/vt/vttablet/tabletserver/gc/tablegc.go index f1d64aebea3..4d1714532a3 100644 --- a/go/vt/vttablet/tabletserver/gc/tablegc.go +++ b/go/vt/vttablet/tabletserver/gc/tablegc.go @@ -551,7 +551,7 @@ func (collector *TableGC) purge(ctx context.Context) (tableName string, err erro // cancelled return tableName, err } - if !collector.throttlerClient.ThrottleCheckOKOrWait(ctx) { + if _, ok := collector.throttlerClient.ThrottleCheckOKOrWait(ctx); !ok { continue } // OK, we're clear to go! diff --git a/go/vt/vttablet/tabletserver/throttle/check.go b/go/vt/vttablet/tabletserver/throttle/check.go index e43c4cab043..460d27a5181 100644 --- a/go/vt/vttablet/tabletserver/throttle/check.go +++ b/go/vt/vttablet/tabletserver/throttle/check.go @@ -94,13 +94,13 @@ func (check *ThrottlerCheck) checkAppMetricResult(ctx context.Context, appName s // Handle deprioritized app logic denyApp := false // - metricResult, threshold := check.throttler.AppRequestMetricResult(ctx, appName, metricResultFunc, denyApp) + metricResult, threshold, matchedApp := check.throttler.AppRequestMetricResult(ctx, appName, metricResultFunc, denyApp) if flags.OverrideThreshold > 0 { threshold = flags.OverrideThreshold } value, err := metricResult.Get() if appName == "" { - return NewCheckResult(http.StatusExpectationFailed, value, threshold, fmt.Errorf("no app indicated")) + return NewCheckResult(http.StatusExpectationFailed, value, threshold, "", fmt.Errorf("no app indicated")) } var statusCode int @@ -123,7 +123,7 @@ func (check *ThrottlerCheck) checkAppMetricResult(ctx context.Context, appName s // all good! statusCode = http.StatusOK // 200 } - return NewCheckResult(statusCode, value, threshold, err) + return NewCheckResult(statusCode, value, threshold, matchedApp, err) } // Check is the core function that runs when a user wants to check a metric @@ -136,12 +136,15 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba metricNames = base.MetricNames{check.throttler.metricNameUsedAsDefault()} } metricNames = metricNames.Unique() - applyMetricToCheckResult := func(metric *MetricResult) { + applyMetricToCheckResult := func(metricName base.MetricName, metric *MetricResult) { checkResult.StatusCode = metric.StatusCode checkResult.Value = metric.Value checkResult.Threshold = metric.Threshold checkResult.Error = metric.Error checkResult.Message = metric.Message + checkResult.AppName = metric.AppName + checkResult.Scope = metric.Scope + checkResult.MetricName = metricName.String() } for _, metricName := range metricNames { // Make sure not to modify the given scope. We create a new scope variable to work with. @@ -190,6 +193,7 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba Threshold: metricCheckResult.Threshold, Error: metricCheckResult.Error, Message: metricCheckResult.Message, + AppName: metricCheckResult.AppName, Scope: metricScope.String(), // This reports back the actual scope used for the check } checkResult.Metrics[metricName.String()] = metric @@ -199,17 +203,18 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba // metrics, because a v20 primary would not know how to deal with it, and is not expecting any of those // metrics. // The only metric we ever report back is the default metric, see below. - applyMetricToCheckResult(metric) + applyMetricToCheckResult(metricName, metric) } } - if metric, ok := checkResult.Metrics[check.throttler.metricNameUsedAsDefault().String()]; ok && checkResult.IsOK() { - applyMetricToCheckResult(metric) + metricNameUsedAsDefault := check.throttler.metricNameUsedAsDefault() + if metric, ok := checkResult.Metrics[metricNameUsedAsDefault.String()]; ok && checkResult.IsOK() { + applyMetricToCheckResult(metricNameUsedAsDefault, metric) } if metric, ok := checkResult.Metrics[base.DefaultMetricName.String()]; ok && checkResult.IsOK() { // v20 compatibility: if this v21 server is a replica, reporting to a v20 primary, // then we must supply the v20-flavor check result. // If checkResult is not OK, then we will have populated these fields already by the failing metric. - applyMetricToCheckResult(metric) + applyMetricToCheckResult(base.DefaultMetricName, metric) } go func(statusCode int) { statsThrottlerCheckAnyTotal.Add(1) diff --git a/go/vt/vttablet/tabletserver/throttle/check_result.go b/go/vt/vttablet/tabletserver/throttle/check_result.go index 3c8852e4042..ad32ba33d6f 100644 --- a/go/vt/vttablet/tabletserver/throttle/check_result.go +++ b/go/vt/vttablet/tabletserver/throttle/check_result.go @@ -42,6 +42,7 @@ limitations under the License. package throttle import ( + "fmt" "net/http" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/base" @@ -54,6 +55,7 @@ type MetricResult struct { Threshold float64 `json:"Threshold"` Error error `json:"-"` Message string `json:"Message"` + AppName string `json:"AppName"` } // CheckResult is the result for an app inquiring on a metric. It also exports as JSON via the API @@ -64,15 +66,19 @@ type CheckResult struct { Error error `json:"-"` Message string `json:"Message"` RecentlyChecked bool `json:"RecentlyChecked"` + AppName string `json:"AppName"` + MetricName string `json:"MetricName"` + Scope string `json:"Scope"` Metrics map[string]*MetricResult `json:"Metrics"` // New in multi-metrics support. Will eventually replace the above fields. } // NewCheckResult returns a CheckResult -func NewCheckResult(statusCode int, value float64, threshold float64, err error) *CheckResult { +func NewCheckResult(statusCode int, value float64, threshold float64, appName string, err error) *CheckResult { result := &CheckResult{ StatusCode: statusCode, Value: value, Threshold: threshold, + AppName: appName, Error: err, } if err != nil { @@ -85,14 +91,32 @@ func (c *CheckResult) IsOK() bool { return c.StatusCode == http.StatusOK } +// Summary returns a human-readable summary of the check result +func (c *CheckResult) Summary() string { + switch c.StatusCode { + case http.StatusOK: + return fmt.Sprintf("%s is granted access", c.AppName) + case http.StatusExpectationFailed: + return fmt.Sprintf("%s is explicitly denied access", c.AppName) + case http.StatusInternalServerError: + return fmt.Sprintf("%s is denied access due to unexpected error: %v", c.AppName, c.Error) + case http.StatusTooManyRequests: + return fmt.Sprintf("%s is denied access due to %s/%s metric value %v exceeding threshold %v", c.AppName, c.Scope, c.MetricName, c.Value, c.Threshold) + case http.StatusNotFound: + return fmt.Sprintf("%s is denied access due to unknown or uncollected metric", c.AppName) + case 0: + return "" + default: + return fmt.Sprintf("unknown status code: %v", c.StatusCode) + } +} + // NewErrorCheckResult returns a check result that indicates an error func NewErrorCheckResult(statusCode int, err error) *CheckResult { - return NewCheckResult(statusCode, 0, 0, err) + return NewCheckResult(statusCode, 0, 0, "", err) } // NoSuchMetricCheckResult is a result returns when a metric is unknown var NoSuchMetricCheckResult = NewErrorCheckResult(http.StatusNotFound, base.ErrNoSuchMetric) -var okMetricCheckResult = NewCheckResult(http.StatusOK, 0, 0, nil) - -var invalidCheckTypeCheckResult = NewErrorCheckResult(http.StatusInternalServerError, base.ErrInvalidCheckType) +var okMetricCheckResult = NewCheckResult(http.StatusOK, 0, 0, "", nil) diff --git a/go/vt/vttablet/tabletserver/throttle/check_result_test.go b/go/vt/vttablet/tabletserver/throttle/check_result_test.go new file mode 100644 index 00000000000..f5c984c5943 --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/check_result_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2024 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 throttle + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckResultSummary(t *testing.T) { + tcases := []struct { + checkResult *CheckResult + summary string + }{ + { + checkResult: &CheckResult{}, + summary: "", + }, + { + checkResult: &CheckResult{ + StatusCode: http.StatusOK, + AppName: "test", + }, + summary: "test is granted access", + }, + { + checkResult: &CheckResult{ + StatusCode: http.StatusTooManyRequests, + AppName: "test", + MetricName: "bugginess", + Threshold: 100, + Value: 200, + Scope: "self", + }, + summary: "test is denied access due to self/bugginess metric value 200 exceeding threshold 100", + }, + { + checkResult: &CheckResult{ + StatusCode: http.StatusExpectationFailed, + AppName: "test", + }, + summary: "test is explicitly denied access", + }, + } + for _, tcase := range tcases { + t.Run(tcase.summary, func(t *testing.T) { + assert.Equal(t, tcase.summary, tcase.checkResult.Summary()) + }) + } +} diff --git a/go/vt/vttablet/tabletserver/throttle/client.go b/go/vt/vttablet/tabletserver/throttle/client.go index e8eed627e04..972e63724f9 100644 --- a/go/vt/vttablet/tabletserver/throttle/client.go +++ b/go/vt/vttablet/tabletserver/throttle/client.go @@ -31,8 +31,11 @@ const ( throttleCheckDuration = 250 * time.Millisecond ) -var throttleTicks int64 -var throttleInit sync.Once +var ( + throttleTicks int64 + throttleInit sync.Once + emptyCheckResult = &CheckResult{} +) func initThrottleTicker() { throttleInit.Do(func() { @@ -87,14 +90,14 @@ func (c *Client) clearSuccessfulResultsCache() { // The function caches results for a brief amount of time, hence it's safe and efficient to // be called very frequently. // The function is not thread safe. -func (c *Client) ThrottleCheckOK(ctx context.Context, overrideAppName throttlerapp.Name) (throttleCheckOK bool) { +func (c *Client) ThrottleCheckOK(ctx context.Context, overrideAppName throttlerapp.Name) (checkResult *CheckResult, throttleCheckOK bool) { if c == nil { // no client - return true + return emptyCheckResult, true } if c.throttler == nil { // no throttler - return true + return emptyCheckResult, true } checkApp := c.appName if overrideAppName != "" { @@ -104,20 +107,20 @@ func (c *Client) ThrottleCheckOK(ctx context.Context, overrideAppName throttlera defer c.lastSuccessfulThrottleMu.Unlock() if c.lastSuccessfulThrottle[checkApp.String()] >= atomic.LoadInt64(&throttleTicks) { // if last check was OK just very recently there is no need to check again - return true + return emptyCheckResult, true } // It's time to run a throttler check - checkResult := c.throttler.Check(ctx, checkApp.String(), nil, &c.flags) + checkResult = c.throttler.Check(ctx, checkApp.String(), nil, &c.flags) if checkResult.StatusCode != http.StatusOK { - return false + return checkResult, false } for _, metricResult := range checkResult.Metrics { if metricResult.StatusCode != http.StatusOK { - return false + return checkResult, false } } c.lastSuccessfulThrottle[checkApp.String()] = atomic.LoadInt64(&throttleTicks) - return true + return checkResult, true } @@ -125,22 +128,23 @@ func (c *Client) ThrottleCheckOK(ctx context.Context, overrideAppName throttlera // otherwise it briefly sleeps and returns 'false'. // Non-empty appName overrides the default appName. // The function is not thread safe. -func (c *Client) ThrottleCheckOKOrWaitAppName(ctx context.Context, appName throttlerapp.Name) bool { - if c.ThrottleCheckOK(ctx, appName) { - return true +func (c *Client) ThrottleCheckOKOrWaitAppName(ctx context.Context, appName throttlerapp.Name) (checkResult *CheckResult, throttleCheckOK bool) { + checkResult, throttleCheckOK = c.ThrottleCheckOK(ctx, appName) + if throttleCheckOK { + return checkResult, true } if ctx.Err() != nil { // context expired, skip sleeping - return false + return checkResult, false } time.Sleep(throttleCheckDuration) - return false + return checkResult, false } // ThrottleCheckOKOrWait checks the throttler; if throttler is satisfied, the function returns 'true' immediately, // otherwise it briefly sleeps and returns 'false'. // The function is not thread safe. -func (c *Client) ThrottleCheckOKOrWait(ctx context.Context) bool { +func (c *Client) ThrottleCheckOKOrWait(ctx context.Context) (checkResult *CheckResult, throttleCheckOK bool) { return c.ThrottleCheckOKOrWaitAppName(ctx, "") } @@ -148,7 +152,10 @@ func (c *Client) ThrottleCheckOKOrWait(ctx context.Context) bool { // The function sleeps between throttle checks. // The function is not thread safe. func (c *Client) Throttle(ctx context.Context) { - for !c.ThrottleCheckOKOrWait(ctx) { + for { + if _, ok := c.ThrottleCheckOKOrWait(ctx); ok { + return + } // The function incorporates a bit of sleep so this is not a busy wait. } } diff --git a/go/vt/vttablet/tabletserver/throttle/throttler.go b/go/vt/vttablet/tabletserver/throttle/throttler.go index 73458974dcb..01c4fb3c622 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler.go @@ -206,8 +206,6 @@ type Throttler struct { readSelfThrottleMetrics func(context.Context) base.ThrottleMetrics // overwritten by unit test - httpClient *http.Client - hostCpuCoreCount atomic.Int32 } @@ -263,7 +261,6 @@ func NewThrottler(env tabletenv.Env, srvTopoServer srvtopo.Server, ts *topo.Serv throttler.metricsHealth = cache.New(cache.NoExpiration, 0) throttler.appCheckedMetrics = cache.New(cache.NoExpiration, 0) - throttler.httpClient = base.SetupHTTPClient(2 * activeCollectInterval) throttler.initThrottleTabletTypes() throttler.check = NewThrottlerCheck(throttler) @@ -1378,8 +1375,8 @@ func (throttler *Throttler) UnthrottleApp(appName string) (appThrottle *base.App // IsAppThrottled tells whether some app should be throttled. // Assuming an app is throttled to some extend, it will randomize the result based // on the throttle ratio -func (throttler *Throttler) IsAppThrottled(appName string) bool { - appFound := false +func (throttler *Throttler) IsAppThrottled(appName string) (bool, string) { + appFound := "" isSingleAppNameThrottled := func(singleAppName string) bool { object, found := throttler.throttledApps.Get(singleAppName) if !found { @@ -1392,7 +1389,7 @@ func (throttler *Throttler) IsAppThrottled(appName string) bool { } // From this point on, we consider that this app has some throttling configuration // of any sort. - appFound = true + appFound = singleAppName if appThrottle.Exempt { return false } @@ -1403,32 +1400,32 @@ func (throttler *Throttler) IsAppThrottled(appName string) bool { return false } if isSingleAppNameThrottled(appName) { - return true + return true, appName } for _, singleAppName := range throttlerapp.Name(appName).SplitStrings() { if singleAppName == "" { continue } if isSingleAppNameThrottled(singleAppName) { - return true + return true, singleAppName } } // If app was found then there was some explicit throttle instruction for the app, and the app // passed the test. - if appFound { - return false + if appFound != "" { + return false, appFound } // If the app was not found, ie no specific throttle instruction was found for the app, then // the app should also consider the case where the "all" app is throttled. if isSingleAppNameThrottled(throttlerapp.AllName.String()) { // Means the "all" app is throttled. This is a special case, and it means "all apps are throttled" - return true + return true, throttlerapp.AllName.String() } - return false + return false, appName } // IsAppExempt -func (throttler *Throttler) IsAppExempted(appName string) bool { +func (throttler *Throttler) IsAppExempted(appName string) (bool, string) { isSingleAppNameExempted := func(singleAppName string) bool { if throttlerapp.ExemptFromChecks(appName) { // well known statically exempted apps return true @@ -1448,22 +1445,24 @@ func (throttler *Throttler) IsAppExempted(appName string) bool { return false } if isSingleAppNameExempted(appName) { - return true + return true, appName } for _, singleAppName := range throttlerapp.Name(appName).SplitStrings() { if singleAppName == "" { continue } if isSingleAppNameExempted(singleAppName) { - return true + return true, singleAppName } } - if isSingleAppNameExempted(throttlerapp.AllName.String()) && !throttler.IsAppThrottled(appName) { - return true + if isSingleAppNameExempted(throttlerapp.AllName.String()) { + if throttled, _ := throttler.IsAppThrottled(appName); !throttled { + return true, throttlerapp.AllName.String() + } } - return false + return false, appName } // ThrottledAppsMap returns a (copy) map of currently throttled apps @@ -1517,14 +1516,16 @@ func (throttler *Throttler) metricsHealthSnapshot() base.MetricHealthMap { } // AppRequestMetricResult gets a metric result in the context of a specific app -func (throttler *Throttler) AppRequestMetricResult(ctx context.Context, appName string, metricResultFunc base.MetricResultFunc, denyApp bool) (metricResult base.MetricResult, threshold float64) { +func (throttler *Throttler) AppRequestMetricResult(ctx context.Context, appName string, metricResultFunc base.MetricResultFunc, denyApp bool) (metricResult base.MetricResult, threshold float64, matchedApp string) { if denyApp { - return base.AppDeniedMetric, 0 + return base.AppDeniedMetric, 0, appName } - if throttler.IsAppThrottled(appName) { - return base.AppDeniedMetric, 0 + throttled, matchedApp := throttler.IsAppThrottled(appName) + if throttled { + return base.AppDeniedMetric, 0, matchedApp } - return metricResultFunc() + metricResult, threshold = metricResultFunc() + return metricResult, threshold, matchedApp } // checkScope checks the aggregated value of given store @@ -1532,12 +1533,15 @@ func (throttler *Throttler) checkScope(ctx context.Context, appName string, scop if !throttler.IsRunning() { return okMetricCheckResult } - if throttler.IsAppExempted(appName) { + if exempted, matchedApp := throttler.IsAppExempted(appName); exempted { // Some apps are exempt from checks. They are always responded with OK. This is because those apps are // continuous and do not generate a substantial load. - return okMetricCheckResult + result := okMetricCheckResult + result.AppName = matchedApp + return result } + matchedApp := appName if len(metricNames) == 0 { // No explicit metrics requested. // Get the metric names mappd to the given app @@ -1551,6 +1555,7 @@ func (throttler *Throttler) checkScope(ctx context.Context, appName string, scop case []base.MetricName: metricNames = append(metricNames, val...) } + matchedApp = appToken } } } @@ -1564,17 +1569,20 @@ func (throttler *Throttler) checkScope(ctx context.Context, appName string, scop case []base.MetricName: metricNames = val } + matchedApp = throttlerapp.AllName.String() } } if throttlerapp.VitessName.Equals(appName) { // "vitess" always checks all metrics, irrespective of what is mapped. metricNames = base.KnownMetricNames + matchedApp = appName } if len(metricNames) == 0 { // Nothing mapped? For backwards compatibility and as default, we use the "default" metric. metricNames = base.MetricNames{throttler.metricNameUsedAsDefault()} } checkResult = throttler.check.Check(ctx, appName, scope, metricNames, flags) + checkResult.AppName = matchedApp shouldRequestHeartbeats := !flags.SkipRequestHeartbeats if throttlerapp.VitessName.Equals(appName) { diff --git a/go/vt/vttablet/tabletserver/throttle/throttler_test.go b/go/vt/vttablet/tabletserver/throttle/throttler_test.go index 6363143fd43..508f542478e 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler_test.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler_test.go @@ -418,6 +418,7 @@ func TestApplyThrottlerConfigMetricThresholds(t *testing.T) { assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) t.Run("apply low threshold", func(t *testing.T) { assert.Equal(t, 0.75, throttler.GetMetricsThreshold()) @@ -440,6 +441,7 @@ func TestApplyThrottlerConfigMetricThresholds(t *testing.T) { assert.EqualValues(t, 0.3, checkResult.Value, "unexpected result: %+v", checkResult) // self lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to self/lag metric value") }) t.Run("apply low threshold but high 'lag' override", func(t *testing.T) { throttlerConfig := &topodatapb.ThrottlerConfig{ @@ -463,6 +465,7 @@ func TestApplyThrottlerConfigMetricThresholds(t *testing.T) { assert.EqualValues(t, 0.3, checkResult.Value, "unexpected result: %+v", checkResult) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) }) @@ -520,6 +523,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to shard/lag metric value") }) t.Run("apply high lag threshold", func(t *testing.T) { throttlerConfig.Threshold = 4444.0 @@ -534,6 +538,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) }) t.Run("apply low 'loadavg' threshold", func(t *testing.T) { @@ -548,6 +553,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) }) t.Run("assign 'loadavg' to test app", func(t *testing.T) { @@ -566,6 +572,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 2.718, checkResult.Value) // self loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to self/loadavg metric value") }) }) t.Run("assign 'shard/loadavg' to test app", func(t *testing.T) { @@ -584,6 +591,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 5.1, checkResult.Value) // shard loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to shard/loadavg metric value") }) }) t.Run("assign 'lag,loadavg' to test app", func(t *testing.T) { @@ -601,6 +609,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 2.718, checkResult.Value) // self loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to self/loadavg metric value") }) }) t.Run("assign 'lag,shard/loadavg' to test app", func(t *testing.T) { @@ -618,6 +627,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 5.1, checkResult.Value) // shard loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to shard/loadavg metric value") }) }) t.Run("clear 'loadavg' threshold", func(t *testing.T) { @@ -631,6 +641,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 1, len(checkResult.Metrics), "unexpected metrics: %+v", checkResult.Metrics) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) }) t.Run("assign 'lag,threads_running' to test app", func(t *testing.T) { @@ -648,6 +659,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) }) t.Run("assign 'custom,loadavg' to 'all' app", func(t *testing.T) { @@ -665,6 +677,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 2.718, checkResult.Value) // loadavg self value exceeds threshold assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is denied access due to self/loadavg metric value") }) t.Run("check 'test' after assignment", func(t *testing.T) { // "test" app unaffected by 'all' assignment, because it has @@ -679,6 +692,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) t.Run("'online-ddl' app affected by 'all'", func(t *testing.T) { // "online-ddl" app is affected by 'all' assignment, because it has @@ -692,6 +706,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 2.718, checkResult.Value) // loadavg self value exceeds threshold assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is denied access due to self/loadavg metric value") }) }) t.Run("'vreplication:online-ddl:12345' app affected by 'all'", func(t *testing.T) { @@ -702,6 +717,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 2.718, checkResult.Value) // loadavg self value exceeds threshold assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is denied access due to self/loadavg metric value") }) t.Run("'vreplication:online-ddl:test' app affected by 'test' and not by 'all'", func(t *testing.T) { // "vreplication:online-ddl:test" app is affected by 'test' assignment, because it has @@ -711,6 +727,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) t.Run("deassign metrics from 'all' app", func(t *testing.T) { delete(throttlerConfig.AppCheckedMetrics, throttlerapp.AllName.String()) @@ -725,6 +742,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is granted access") }) t.Run("check 'test' after assignment", func(t *testing.T) { // "test" app unaffected by the entire 'all' assignment, because it has @@ -739,6 +757,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) t.Run("'online-ddl' no longer has 'all' impact", func(t *testing.T) { // "online-ddl" app is affected by 'all' assignment, because it has @@ -752,6 +771,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), throttlerapp.OnlineDDLName.String()+" is granted access") }) }) @@ -768,6 +788,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) assert.Len(t, checkResult.Metrics, 1) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) }) @@ -790,10 +811,26 @@ func TestIsAppThrottled(t *testing.T) { heartbeatWriter: &FakeHeartbeatWriter{}, } t.Run("initial", func(t *testing.T) { - assert.False(t, throttler.IsAppThrottled("app1")) - assert.False(t, throttler.IsAppThrottled("app2")) - assert.False(t, throttler.IsAppThrottled("app3")) - assert.False(t, throttler.IsAppThrottled("app4")) + { + throttled, app := throttler.IsAppThrottled("app1") + assert.False(t, throttled) + assert.Equal(t, "app1", app) + } + { + throttled, app := throttler.IsAppThrottled("app2") + assert.False(t, throttled) + assert.Equal(t, "app2", app) + } + { + throttled, app := throttler.IsAppThrottled("app3") + assert.False(t, throttled) + assert.Equal(t, "app3", app) + } + { + throttled, app := throttler.IsAppThrottled("app4") + assert.False(t, throttled) + assert.Equal(t, "app4", app) + } assert.Equal(t, 0, throttler.throttledApps.ItemCount()) }) @@ -803,11 +840,31 @@ func TestIsAppThrottled(t *testing.T) { throttler.ThrottleApp("app2", time.Now(), DefaultThrottleRatio, false) // instantly expire throttler.ThrottleApp("app3", plusOneHour, DefaultThrottleRatio, false) throttler.ThrottleApp("app4", plusOneHour, 0, false) - assert.False(t, throttler.IsAppThrottled("app1")) // exempted - assert.False(t, throttler.IsAppThrottled("app2")) // expired - assert.True(t, throttler.IsAppThrottled("app3")) - assert.False(t, throttler.IsAppThrottled("app4")) // ratio is zero - assert.False(t, throttler.IsAppThrottled("app_other")) // not specified + { + throttled, app := throttler.IsAppThrottled("app1") + assert.False(t, throttled) // exempted + assert.Equal(t, "app1", app) + } + { + throttled, app := throttler.IsAppThrottled("app2") + assert.False(t, throttled) // expired + assert.Equal(t, "app2", app) + } + { + throttled, app := throttler.IsAppThrottled("app3") + assert.True(t, throttled) + assert.Equal(t, "app3", app) + } + { + throttled, app := throttler.IsAppThrottled("app4") + assert.False(t, throttled) // ratio is zero + assert.Equal(t, "app4", app) + } + { + throttled, app := throttler.IsAppThrottled("app_other") + assert.False(t, throttled) // not specified + assert.Equal(t, "app_other", app) + } assert.Equal(t, 3, throttler.throttledApps.ItemCount()) }) @@ -815,12 +872,36 @@ func TestIsAppThrottled(t *testing.T) { // throttle "all", see how it affects app throttler.ThrottleApp(throttlerapp.AllName.String(), plusOneHour, DefaultThrottleRatio, false) defer throttler.UnthrottleApp(throttlerapp.AllName.String()) - assert.True(t, throttler.IsAppThrottled("all")) // - assert.False(t, throttler.IsAppThrottled("app1")) // exempted - assert.True(t, throttler.IsAppThrottled("app2")) // expired, so falls under "all" - assert.True(t, throttler.IsAppThrottled("app3")) - assert.False(t, throttler.IsAppThrottled("app4")) // ratio is zero, there is a specific instruction for this app, so it doesn't fall under "all" - assert.True(t, throttler.IsAppThrottled("app_other")) // falls under "all" + { + throttled, app := throttler.IsAppThrottled("all") + assert.True(t, throttled) // explicitly throttled + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app1") + assert.False(t, throttled) // exempted + assert.Equal(t, "app1", app) + } + { + throttled, app := throttler.IsAppThrottled("app2") + assert.True(t, throttled) // expired, so falls under "all" + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app3") + assert.True(t, throttled) + assert.Equal(t, "app3", app) + } + { + throttled, app := throttler.IsAppThrottled("app4") + assert.False(t, throttled) // ratio is zero, there is a specific instruction for this app, so it doesn't fall under "all" + assert.Equal(t, "app4", app) + } + { + throttled, app := throttler.IsAppThrottled("app_other") + assert.True(t, throttled) // falls under "all" + assert.Equal(t, "all", app) + } // continuing previous test, we had 3 throttled apps. "all" is a new app being throttled. assert.Equal(t, 4, throttler.throttledApps.ItemCount()) @@ -831,10 +912,27 @@ func TestIsAppThrottled(t *testing.T) { throttler.UnthrottleApp("app2") throttler.UnthrottleApp("app3") throttler.UnthrottleApp("app4") - assert.False(t, throttler.IsAppThrottled("app1")) - assert.False(t, throttler.IsAppThrottled("app2")) - assert.False(t, throttler.IsAppThrottled("app3")) - assert.False(t, throttler.IsAppThrottled("app4")) + + { + throttled, app := throttler.IsAppThrottled("app1") + assert.False(t, throttled) + assert.Equal(t, "app1", app) + } + { + throttled, app := throttler.IsAppThrottled("app2") + assert.False(t, throttled) + assert.Equal(t, "app2", app) + } + { + throttled, app := throttler.IsAppThrottled("app3") + assert.False(t, throttled) + assert.Equal(t, "app3", app) + } + { + throttled, app := throttler.IsAppThrottled("app4") + assert.False(t, throttled) + assert.Equal(t, "app4", app) + } // we've manually unthrottled everything assert.Equal(t, 0, throttler.throttledApps.ItemCount()) @@ -843,12 +941,37 @@ func TestIsAppThrottled(t *testing.T) { // throttle "all", see how it affects app throttler.ThrottleApp(throttlerapp.AllName.String(), plusOneHour, DefaultThrottleRatio, false) defer throttler.UnthrottleApp(throttlerapp.AllName.String()) - assert.True(t, throttler.IsAppThrottled("all")) - assert.True(t, throttler.IsAppThrottled("app1")) - assert.True(t, throttler.IsAppThrottled("app2")) - assert.True(t, throttler.IsAppThrottled("app3")) - assert.True(t, throttler.IsAppThrottled("app4")) - assert.True(t, throttler.IsAppThrottled("app_other")) + + { + throttled, app := throttler.IsAppThrottled("all") + assert.True(t, throttled) // explicitly throttled + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app1") + assert.True(t, throttled) + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app2") + assert.True(t, throttled) + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app3") + assert.True(t, throttled) + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app4") + assert.True(t, throttled) + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app_other") + assert.True(t, throttled) + assert.Equal(t, "all", app) + } // one rule, for "all" app assert.Equal(t, 1, throttler.throttledApps.ItemCount()) @@ -858,43 +981,120 @@ func TestIsAppThrottled(t *testing.T) { throttler.ThrottleApp("app3", plusOneHour, DefaultThrottleRatio, false) throttler.ThrottleApp(throttlerapp.AllName.String(), plusOneHour, DefaultThrottleRatio, true) defer throttler.UnthrottleApp(throttlerapp.AllName.String()) - assert.False(t, throttler.IsAppThrottled("all")) - assert.False(t, throttler.IsAppThrottled("app1")) - assert.False(t, throttler.IsAppThrottled("app2")) - assert.True(t, throttler.IsAppThrottled("app3")) - assert.False(t, throttler.IsAppThrottled("app4")) - assert.False(t, throttler.IsAppThrottled("app_other")) + { + throttled, app := throttler.IsAppThrottled("all") + assert.False(t, throttled) // explicitly throttled + assert.Equal(t, "all", app) + } + { + throttled, app := throttler.IsAppThrottled("app1") + assert.False(t, throttled) + assert.Equal(t, "app1", app) + } + { + throttled, app := throttler.IsAppThrottled("app2") + assert.False(t, throttled) + assert.Equal(t, "app2", app) + } + { + throttled, app := throttler.IsAppThrottled("app3") + assert.True(t, throttled) // explicitly throttled + assert.Equal(t, "app3", app) + } + { + throttled, app := throttler.IsAppThrottled("app4") + assert.False(t, throttled) + assert.Equal(t, "app4", app) + } + { + throttled, app := throttler.IsAppThrottled("app_other") + assert.False(t, throttled) + assert.Equal(t, "app_other", app) + } assert.Equal(t, 2, throttler.throttledApps.ItemCount()) }) } func TestIsAppExempted(t *testing.T) { - + plusOneHour := time.Now().Add(time.Hour) throttler := Throttler{ throttledApps: cache.New(cache.NoExpiration, 0), heartbeatWriter: &FakeHeartbeatWriter{}, } - assert.False(t, throttler.IsAppExempted("app1")) - assert.False(t, throttler.IsAppExempted("app2")) - assert.False(t, throttler.IsAppExempted("app3")) - // - throttler.ThrottleApp("app1", time.Now().Add(time.Hour), DefaultThrottleRatio, true) - throttler.ThrottleApp("app2", time.Now(), DefaultThrottleRatio, true) // instantly expire - assert.True(t, throttler.IsAppExempted("app1")) - assert.True(t, throttler.IsAppExempted("app1:other-tag")) - assert.False(t, throttler.IsAppExempted("app2")) // expired - assert.False(t, throttler.IsAppExempted("app3")) - // - throttler.UnthrottleApp("app1") - throttler.ThrottleApp("app2", time.Now().Add(time.Hour), DefaultThrottleRatio, false) - assert.False(t, throttler.IsAppExempted("app1")) - assert.False(t, throttler.IsAppExempted("app2")) - assert.False(t, throttler.IsAppExempted("app3")) - // - assert.True(t, throttler.IsAppExempted("schema-tracker")) - throttler.UnthrottleApp("schema-tracker") // meaningless. App is statically exempted - assert.True(t, throttler.IsAppExempted("schema-tracker")) + t.Run("initial", func(t *testing.T) { + { + exempted, app := throttler.IsAppExempted("app1") + assert.False(t, exempted) + assert.Equal(t, "app1", app) + } + { + exempted, app := throttler.IsAppExempted("app2") + assert.False(t, exempted) + assert.Equal(t, "app2", app) + } + { + exempted, app := throttler.IsAppExempted("app3") + assert.False(t, exempted) + assert.Equal(t, "app3", app) + } + }) + t.Run("exempt", func(t *testing.T) { + throttler.ThrottleApp("app1", time.Now().Add(time.Hour), DefaultThrottleRatio, true) + throttler.ThrottleApp("app2", time.Now(), DefaultThrottleRatio, true) // instantly expire + { + exempted, app := throttler.IsAppExempted("app1") + assert.True(t, exempted) + assert.Equal(t, "app1", app) + } + { + exempted, app := throttler.IsAppExempted("app1:other-tag") + assert.True(t, exempted) + assert.Equal(t, "app1", app) + } + { + exempted, app := throttler.IsAppExempted("app2") + assert.False(t, exempted) + assert.Equal(t, "app2", app) + } + { + exempted, app := throttler.IsAppExempted("app3") + assert.False(t, exempted) + assert.Equal(t, "app3", app) + } + }) + t.Run("throttle", func(t *testing.T) { + throttler.UnthrottleApp("app1") + throttler.ThrottleApp("app2", time.Now().Add(time.Hour), DefaultThrottleRatio, false) + { + exempted, app := throttler.IsAppExempted("app1") + assert.False(t, exempted) + assert.Equal(t, "app1", app) + } + { + exempted, app := throttler.IsAppExempted("app2") + assert.False(t, exempted) + assert.Equal(t, "app2", app) + } + { + exempted, app := throttler.IsAppExempted("app3") + assert.False(t, exempted) + assert.Equal(t, "app3", app) + } + }) + t.Run("special", func(t *testing.T) { + { + exempted, app := throttler.IsAppExempted("schema-tracker") + assert.True(t, exempted) + assert.Equal(t, "schema-tracker", app) + } + throttler.ThrottleApp("schema-tracker", plusOneHour, 1.0, false) // meaningless. App is statically exempted + { + exempted, app := throttler.IsAppExempted("schema-tracker") + assert.True(t, exempted) + assert.Equal(t, "schema-tracker", app) + } + }) } // TestRefreshInventory tests the behavior of the throttler's RefreshInventory() function, which @@ -1116,8 +1316,10 @@ func TestProbesWhileOperating(t *testing.T) { throttler.refreshInventory(ctx) }) { - checkOK := client.ThrottleCheckOK(ctx, "") + checkResult, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) // we expect threshold exceeded + assert.NotNil(t, checkResult) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to") } }) @@ -1128,7 +1330,7 @@ func TestProbesWhileOperating(t *testing.T) { throttler.refreshInventory(ctx) }) { - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.True(t, checkOK) } }) @@ -1139,8 +1341,10 @@ func TestProbesWhileOperating(t *testing.T) { }) client.clearSuccessfulResultsCache() // ensure we don't read the successful result from the test above { - checkOK := client.ThrottleCheckOK(ctx, "") + checkResult, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) + assert.NotNil(t, checkResult) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to") } }) }) @@ -1205,8 +1409,9 @@ func TestProbesWhileOperating(t *testing.T) { throttler.refreshInventory(ctx) }) { - checkOK := client.ThrottleCheckOK(ctx, "") + checkResult, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) // we expect threshold exceeded + assert.NotNil(t, checkResult) } }) @@ -1217,7 +1422,7 @@ func TestProbesWhileOperating(t *testing.T) { throttler.refreshInventory(ctx) }) { - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) // 0.95 still too low for custom query } }) @@ -1227,7 +1432,7 @@ func TestProbesWhileOperating(t *testing.T) { throttler.refreshInventory(ctx) }) { - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) // 15 still too low for custom query because primary has 17 } }) @@ -1237,7 +1442,7 @@ func TestProbesWhileOperating(t *testing.T) { throttler.refreshInventory(ctx) }) { - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.True(t, checkOK) } }) @@ -1248,7 +1453,7 @@ func TestProbesWhileOperating(t *testing.T) { }) client.clearSuccessfulResultsCache() // ensure we don't read the successful result from the test above { - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) } }) @@ -1502,6 +1707,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Len(t, checkResult.Metrics, 1) }) t.Run("explicit names", func(t *testing.T) { @@ -1513,6 +1719,7 @@ func TestChecks(t *testing.T) { t.Logf("%s: %+v", k, v) } } + assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) assert.EqualValues(t, 0.3, checkResult.Metrics[base.LagMetricName.String()].Value) // self lag value, because flags.Scope is set @@ -1533,6 +1740,7 @@ func TestChecks(t *testing.T) { t.Run("implicit names, always all known", func(t *testing.T) { checkResult := throttler.Check(ctx, throttlerapp.VitessName.String(), nil, flags) // "vitess" app always checks all known metrics: + assert.Equal(t, throttlerapp.VitessName.String(), checkResult.AppName) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) }) t.Run("explicit names, irrelevant, always all known", func(t *testing.T) { @@ -1543,6 +1751,7 @@ func TestChecks(t *testing.T) { checkResult := throttler.Check(ctx, throttlerapp.VitessName.String(), metricNames, flags) require.NotNil(t, checkResult) + assert.Equal(t, throttlerapp.VitessName.String(), checkResult.AppName) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) }) }) @@ -1558,6 +1767,7 @@ func TestChecks(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) + assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Len(t, checkResult.Metrics, 1) }) t.Run("explicit names", func(t *testing.T) { @@ -1566,6 +1776,7 @@ func TestChecks(t *testing.T) { assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) + assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) assert.EqualValues(t, 0.9, checkResult.Metrics[base.LagMetricName.String()].Value) // shard lag value, because flags.Scope is set @@ -1849,7 +2060,7 @@ func TestReplica(t *testing.T) { }) t.Run("client, OK", func(t *testing.T) { client := NewBackgroundClient(throttler, throttlerapp.TestingName, base.UndefinedScope) - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.True(t, checkOK) }) t.Run("client, metrics names mapped, OK", func(t *testing.T) { @@ -1857,7 +2068,7 @@ func TestReplica(t *testing.T) { throttler.appCheckedMetrics.Set(throttlerapp.TestingName.String(), base.MetricNames{base.LagMetricName, base.ThreadsRunningMetricName}, cache.DefaultExpiration) defer throttler.appCheckedMetrics.Delete(throttlerapp.TestingName.String()) client := NewBackgroundClient(throttler, throttlerapp.TestingName, base.UndefinedScope) - checkOK := client.ThrottleCheckOK(ctx, "") + _, checkOK := client.ThrottleCheckOK(ctx, "") assert.True(t, checkOK) }) t.Run("client, metrics names mapped, not OK", func(t *testing.T) { @@ -1865,8 +2076,10 @@ func TestReplica(t *testing.T) { throttler.appCheckedMetrics.Set(throttlerapp.TestingName.String(), base.MetricNames{base.LagMetricName, base.LoadAvgMetricName, base.ThreadsRunningMetricName}, cache.DefaultExpiration) defer throttler.appCheckedMetrics.Delete(throttlerapp.TestingName.String()) client := NewBackgroundClient(throttler, throttlerapp.TestingName, base.UndefinedScope) - checkOK := client.ThrottleCheckOK(ctx, "") + checkResult, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) + assert.NotNil(t, checkResult) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to") }) t.Run("custom query, metrics", func(t *testing.T) { @@ -1900,8 +2113,10 @@ func TestReplica(t *testing.T) { }) t.Run("client, not OK", func(t *testing.T) { client := NewBackgroundClient(throttler, throttlerapp.TestingName, base.SelfScope) - checkOK := client.ThrottleCheckOK(ctx, "") + checkResult, checkOK := client.ThrottleCheckOK(ctx, "") assert.False(t, checkOK) + assert.NotNil(t, checkResult) + assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to") }) }() }) diff --git a/go/vt/vttablet/tabletserver/vstreamer/resultstreamer.go b/go/vt/vttablet/tabletserver/vstreamer/resultstreamer.go index 4632bea672b..b6294cd1939 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/resultstreamer.go +++ b/go/vt/vttablet/tabletserver/vstreamer/resultstreamer.go @@ -109,7 +109,7 @@ func (rs *resultStreamer) Stream() error { } // check throttler. - if !rs.vse.throttlerClient.ThrottleCheckOKOrWaitAppName(rs.ctx, throttlerapp.ResultStreamerName) { + if _, ok := rs.vse.throttlerClient.ThrottleCheckOKOrWaitAppName(rs.ctx, throttlerapp.ResultStreamerName); !ok { logger.Infof("throttled.") continue } diff --git a/go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go b/go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go index bb8ff7af85f..6015590dad7 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go +++ b/go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go @@ -388,9 +388,9 @@ func (rs *rowStreamer) streamQuery(send func(*binlogdatapb.VStreamRowsResponse) } // check throttler. - if !rs.vse.throttlerClient.ThrottleCheckOKOrWaitAppName(rs.ctx, throttlerapp.RowStreamerName) { + if checkResult, ok := rs.vse.throttlerClient.ThrottleCheckOKOrWaitAppName(rs.ctx, throttlerapp.RowStreamerName); !ok { throttleResponseRateLimiter.Do(func() error { - return safeSend(&binlogdatapb.VStreamRowsResponse{Throttled: true}) + return safeSend(&binlogdatapb.VStreamRowsResponse{Throttled: true, ThrottledReason: checkResult.Summary()}) }) logger.Infof("throttled.") continue diff --git a/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go b/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go index 3413c53d811..634c9a5d40c 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go +++ b/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go @@ -287,17 +287,18 @@ func (vs *vstreamer) parseEvents(ctx context.Context, events <-chan mysql.Binlog hbTimer := time.NewTimer(HeartbeatTime) defer hbTimer.Stop() - injectHeartbeat := func(throttled bool) error { + injectHeartbeat := func(throttled bool, throttledReason string) error { now := time.Now().UnixNano() select { case <-ctx.Done(): return vterrors.Errorf(vtrpcpb.Code_CANCELED, "context has expired") default: err := bufferAndTransmit(&binlogdatapb.VEvent{ - Type: binlogdatapb.VEventType_HEARTBEAT, - Timestamp: now / 1e9, - CurrentTime: now, - Throttled: throttled, + Type: binlogdatapb.VEventType_HEARTBEAT, + Timestamp: now / 1e9, + CurrentTime: now, + Throttled: throttled, + ThrottledReason: throttledReason, }) return err } @@ -309,7 +310,7 @@ func (vs *vstreamer) parseEvents(ctx context.Context, events <-chan mysql.Binlog defer throttledHeartbeatsRateLimiter.Stop() for { // check throttler. - if !vs.vse.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, vs.throttlerApp) { + if checkResult, ok := vs.vse.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, vs.throttlerApp); !ok { // make sure to leave if context is cancelled select { case <-ctx.Done(): @@ -318,7 +319,7 @@ func (vs *vstreamer) parseEvents(ctx context.Context, events <-chan mysql.Binlog // do nothing special } throttledHeartbeatsRateLimiter.Do(func() error { - return injectHeartbeat(true) + return injectHeartbeat(true, checkResult.Summary()) }) // we won't process events, until we're no longer throttling logger.Infof("throttled.") @@ -393,7 +394,7 @@ func (vs *vstreamer) parseEvents(ctx context.Context, events <-chan mysql.Binlog case <-ctx.Done(): return nil case <-hbTimer.C: - if err := injectHeartbeat(false); err != nil { + if err := injectHeartbeat(false, ""); err != nil { if err == io.EOF { return nil } diff --git a/proto/binlogdata.proto b/proto/binlogdata.proto index 1e70275e8b5..5f5bbd59c6e 100644 --- a/proto/binlogdata.proto +++ b/proto/binlogdata.proto @@ -458,6 +458,8 @@ message VEvent { string shard = 23; // indicate that we are being throttled right now bool throttled = 24; + // ThrottledReason is a human readable string that explains why the stream is throttled + string throttled_reason = 25; } message MinimalTable { @@ -511,6 +513,8 @@ message VStreamRowsResponse { bool throttled = 6; // Heartbeat indicates that this is a heartbeat message bool heartbeat = 7; + // ThrottledReason is a human readable string that explains why the stream is throttled + string throttled_reason = 8; } diff --git a/proto/tabletmanagerdata.proto b/proto/tabletmanagerdata.proto index ffe4aa29abf..549c6c09782 100644 --- a/proto/tabletmanagerdata.proto +++ b/proto/tabletmanagerdata.proto @@ -771,6 +771,12 @@ message CheckThrottlerResponse { // Metrics is a map (metric name -> metric value/error) so that the client has as much // information as possible about all the checked metrics. map metrics = 7; + + // AppName is the name of app that was matched by the throttler + string app_name = 8; + + // Summary is a human readable analysis of the result + string summary = 9; } message GetThrottlerStatusRequest { diff --git a/web/vtadmin/src/proto/vtadmin.d.ts b/web/vtadmin/src/proto/vtadmin.d.ts index de8496005f7..d69d72daaac 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -30653,6 +30653,12 @@ export namespace tabletmanagerdata { /** CheckThrottlerResponse metrics */ metrics?: ({ [k: string]: tabletmanagerdata.CheckThrottlerResponse.IMetric }|null); + + /** CheckThrottlerResponse app_name */ + app_name?: (string|null); + + /** CheckThrottlerResponse summary */ + summary?: (string|null); } /** Represents a CheckThrottlerResponse. */ @@ -30685,6 +30691,12 @@ export namespace tabletmanagerdata { /** CheckThrottlerResponse metrics. */ public metrics: { [k: string]: tabletmanagerdata.CheckThrottlerResponse.IMetric }; + /** CheckThrottlerResponse app_name. */ + public app_name: string; + + /** CheckThrottlerResponse summary. */ + public summary: string; + /** * Creates a new CheckThrottlerResponse instance using the specified properties. * @param [properties] Properties to set @@ -33809,6 +33821,9 @@ export namespace binlogdata { /** VEvent throttled */ throttled?: (boolean|null); + + /** VEvent throttled_reason */ + throttled_reason?: (string|null); } /** Represents a VEvent. */ @@ -33862,6 +33877,9 @@ export namespace binlogdata { /** VEvent throttled. */ public throttled: boolean; + /** VEvent throttled_reason. */ + public throttled_reason: string; + /** * Creates a new VEvent instance using the specified properties. * @param [properties] Properties to set @@ -34520,6 +34538,9 @@ export namespace binlogdata { /** VStreamRowsResponse heartbeat */ heartbeat?: (boolean|null); + + /** VStreamRowsResponse throttled_reason */ + throttled_reason?: (string|null); } /** Represents a VStreamRowsResponse. */ @@ -34552,6 +34573,9 @@ export namespace binlogdata { /** VStreamRowsResponse heartbeat. */ public heartbeat: boolean; + /** VStreamRowsResponse throttled_reason. */ + public throttled_reason: string; + /** * Creates a new VStreamRowsResponse instance using the specified properties. * @param [properties] Properties to set diff --git a/web/vtadmin/src/proto/vtadmin.js b/web/vtadmin/src/proto/vtadmin.js index 3843e89005b..569d1602c6b 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -71283,6 +71283,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { * @property {string|null} [message] CheckThrottlerResponse message * @property {boolean|null} [recently_checked] CheckThrottlerResponse recently_checked * @property {Object.|null} [metrics] CheckThrottlerResponse metrics + * @property {string|null} [app_name] CheckThrottlerResponse app_name + * @property {string|null} [summary] CheckThrottlerResponse summary */ /** @@ -71357,6 +71359,22 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { */ CheckThrottlerResponse.prototype.metrics = $util.emptyObject; + /** + * CheckThrottlerResponse app_name. + * @member {string} app_name + * @memberof tabletmanagerdata.CheckThrottlerResponse + * @instance + */ + CheckThrottlerResponse.prototype.app_name = ""; + + /** + * CheckThrottlerResponse summary. + * @member {string} summary + * @memberof tabletmanagerdata.CheckThrottlerResponse + * @instance + */ + CheckThrottlerResponse.prototype.summary = ""; + /** * Creates a new CheckThrottlerResponse instance using the specified properties. * @function create @@ -71398,6 +71416,10 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { writer.uint32(/* id 7, wireType 2 =*/58).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]); $root.tabletmanagerdata.CheckThrottlerResponse.Metric.encode(message.metrics[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); } + if (message.app_name != null && Object.hasOwnProperty.call(message, "app_name")) + writer.uint32(/* id 8, wireType 2 =*/66).string(message.app_name); + if (message.summary != null && Object.hasOwnProperty.call(message, "summary")) + writer.uint32(/* id 9, wireType 2 =*/74).string(message.summary); return writer; }; @@ -71479,6 +71501,14 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.metrics[key] = value; break; } + case 8: { + message.app_name = reader.string(); + break; + } + case 9: { + message.summary = reader.string(); + break; + } default: reader.skipType(tag & 7); break; @@ -71542,6 +71572,12 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { return "metrics." + error; } } + if (message.app_name != null && message.hasOwnProperty("app_name")) + if (!$util.isString(message.app_name)) + return "app_name: string expected"; + if (message.summary != null && message.hasOwnProperty("summary")) + if (!$util.isString(message.summary)) + return "summary: string expected"; return null; }; @@ -71579,6 +71615,10 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.metrics[keys[i]] = $root.tabletmanagerdata.CheckThrottlerResponse.Metric.fromObject(object.metrics[keys[i]]); } } + if (object.app_name != null) + message.app_name = String(object.app_name); + if (object.summary != null) + message.summary = String(object.summary); return message; }; @@ -71604,6 +71644,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { object.error = ""; object.message = ""; object.recently_checked = false; + object.app_name = ""; + object.summary = ""; } if (message.status_code != null && message.hasOwnProperty("status_code")) object.status_code = message.status_code; @@ -71623,6 +71665,10 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { for (let j = 0; j < keys2.length; ++j) object.metrics[keys2[j]] = $root.tabletmanagerdata.CheckThrottlerResponse.Metric.toObject(message.metrics[keys2[j]], options); } + if (message.app_name != null && message.hasOwnProperty("app_name")) + object.app_name = message.app_name; + if (message.summary != null && message.hasOwnProperty("summary")) + object.summary = message.summary; return object; }; @@ -79683,6 +79729,7 @@ export const binlogdata = $root.binlogdata = (() => { * @property {string|null} [keyspace] VEvent keyspace * @property {string|null} [shard] VEvent shard * @property {boolean|null} [throttled] VEvent throttled + * @property {string|null} [throttled_reason] VEvent throttled_reason */ /** @@ -79812,6 +79859,14 @@ export const binlogdata = $root.binlogdata = (() => { */ VEvent.prototype.throttled = false; + /** + * VEvent throttled_reason. + * @member {string} throttled_reason + * @memberof binlogdata.VEvent + * @instance + */ + VEvent.prototype.throttled_reason = ""; + /** * Creates a new VEvent instance using the specified properties. * @function create @@ -79864,6 +79919,8 @@ export const binlogdata = $root.binlogdata = (() => { writer.uint32(/* id 23, wireType 2 =*/186).string(message.shard); if (message.throttled != null && Object.hasOwnProperty.call(message, "throttled")) writer.uint32(/* id 24, wireType 0 =*/192).bool(message.throttled); + if (message.throttled_reason != null && Object.hasOwnProperty.call(message, "throttled_reason")) + writer.uint32(/* id 25, wireType 2 =*/202).string(message.throttled_reason); return writer; }; @@ -79954,6 +80011,10 @@ export const binlogdata = $root.binlogdata = (() => { message.throttled = reader.bool(); break; } + case 25: { + message.throttled_reason = reader.string(); + break; + } default: reader.skipType(tag & 7); break; @@ -80065,6 +80126,9 @@ export const binlogdata = $root.binlogdata = (() => { if (message.throttled != null && message.hasOwnProperty("throttled")) if (typeof message.throttled !== "boolean") return "throttled: boolean expected"; + if (message.throttled_reason != null && message.hasOwnProperty("throttled_reason")) + if (!$util.isString(message.throttled_reason)) + return "throttled_reason: string expected"; return null; }; @@ -80227,6 +80291,8 @@ export const binlogdata = $root.binlogdata = (() => { message.shard = String(object.shard); if (object.throttled != null) message.throttled = Boolean(object.throttled); + if (object.throttled_reason != null) + message.throttled_reason = String(object.throttled_reason); return message; }; @@ -80266,6 +80332,7 @@ export const binlogdata = $root.binlogdata = (() => { object.keyspace = ""; object.shard = ""; object.throttled = false; + object.throttled_reason = ""; } if (message.type != null && message.hasOwnProperty("type")) object.type = options.enums === String ? $root.binlogdata.VEventType[message.type] === undefined ? message.type : $root.binlogdata.VEventType[message.type] : message.type; @@ -80301,6 +80368,8 @@ export const binlogdata = $root.binlogdata = (() => { object.shard = message.shard; if (message.throttled != null && message.hasOwnProperty("throttled")) object.throttled = message.throttled; + if (message.throttled_reason != null && message.hasOwnProperty("throttled_reason")) + object.throttled_reason = message.throttled_reason; return object; }; @@ -81801,6 +81870,7 @@ export const binlogdata = $root.binlogdata = (() => { * @property {query.IRow|null} [lastpk] VStreamRowsResponse lastpk * @property {boolean|null} [throttled] VStreamRowsResponse throttled * @property {boolean|null} [heartbeat] VStreamRowsResponse heartbeat + * @property {string|null} [throttled_reason] VStreamRowsResponse throttled_reason */ /** @@ -81877,6 +81947,14 @@ export const binlogdata = $root.binlogdata = (() => { */ VStreamRowsResponse.prototype.heartbeat = false; + /** + * VStreamRowsResponse throttled_reason. + * @member {string} throttled_reason + * @memberof binlogdata.VStreamRowsResponse + * @instance + */ + VStreamRowsResponse.prototype.throttled_reason = ""; + /** * Creates a new VStreamRowsResponse instance using the specified properties. * @function create @@ -81918,6 +81996,8 @@ export const binlogdata = $root.binlogdata = (() => { writer.uint32(/* id 6, wireType 0 =*/48).bool(message.throttled); if (message.heartbeat != null && Object.hasOwnProperty.call(message, "heartbeat")) writer.uint32(/* id 7, wireType 0 =*/56).bool(message.heartbeat); + if (message.throttled_reason != null && Object.hasOwnProperty.call(message, "throttled_reason")) + writer.uint32(/* id 8, wireType 2 =*/66).string(message.throttled_reason); return writer; }; @@ -81986,6 +82066,10 @@ export const binlogdata = $root.binlogdata = (() => { message.heartbeat = reader.bool(); break; } + case 8: { + message.throttled_reason = reader.string(); + break; + } default: reader.skipType(tag & 7); break; @@ -82062,6 +82146,9 @@ export const binlogdata = $root.binlogdata = (() => { if (message.heartbeat != null && message.hasOwnProperty("heartbeat")) if (typeof message.heartbeat !== "boolean") return "heartbeat: boolean expected"; + if (message.throttled_reason != null && message.hasOwnProperty("throttled_reason")) + if (!$util.isString(message.throttled_reason)) + return "throttled_reason: string expected"; return null; }; @@ -82118,6 +82205,8 @@ export const binlogdata = $root.binlogdata = (() => { message.throttled = Boolean(object.throttled); if (object.heartbeat != null) message.heartbeat = Boolean(object.heartbeat); + if (object.throttled_reason != null) + message.throttled_reason = String(object.throttled_reason); return message; }; @@ -82144,6 +82233,7 @@ export const binlogdata = $root.binlogdata = (() => { object.lastpk = null; object.throttled = false; object.heartbeat = false; + object.throttled_reason = ""; } if (message.fields && message.fields.length) { object.fields = []; @@ -82168,6 +82258,8 @@ export const binlogdata = $root.binlogdata = (() => { object.throttled = message.throttled; if (message.heartbeat != null && message.hasOwnProperty("heartbeat")) object.heartbeat = message.heartbeat; + if (message.throttled_reason != null && message.hasOwnProperty("throttled_reason")) + object.throttled_reason = message.throttled_reason; return object; }; From fab907178bca98aa258420cd56ea7b8e4073f764 Mon Sep 17 00:00:00 2001 From: Manan Gupta <35839558+GuptaManan100@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:06:43 +0530 Subject: [PATCH 06/34] Fuzzer testing for 2PC transactions (#16476) Signed-off-by: Manan Gupta --- .../transaction/twopc/fuzzer/fuzzer_test.go | 310 ++++++++++++++++++ .../transaction/twopc/fuzzer/main_test.go | 138 ++++++++ .../transaction/twopc/fuzzer/schema.sql | 14 + .../transaction/twopc/fuzzer/vschema.json | 26 ++ test/config.json | 9 + 5 files changed, 497 insertions(+) create mode 100644 go/test/endtoend/transaction/twopc/fuzzer/fuzzer_test.go create mode 100644 go/test/endtoend/transaction/twopc/fuzzer/main_test.go create mode 100644 go/test/endtoend/transaction/twopc/fuzzer/schema.sql create mode 100644 go/test/endtoend/transaction/twopc/fuzzer/vschema.json diff --git a/go/test/endtoend/transaction/twopc/fuzzer/fuzzer_test.go b/go/test/endtoend/transaction/twopc/fuzzer/fuzzer_test.go new file mode 100644 index 00000000000..ff440164042 --- /dev/null +++ b/go/test/endtoend/transaction/twopc/fuzzer/fuzzer_test.go @@ -0,0 +1,310 @@ +/* +Copyright 2024 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 fuzzer + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/exp/rand" + + "vitess.io/vitess/go/mysql" +) + +var ( + // updateRowBaseVals is the base row values that we use to ensure 1 update on each shard with the same increment. + updateRowBaseVals = [3]int{ + 4, // 4 maps to 0x20 and ends up in the first shard (-40) + 6, // 6 maps to 0x60 and ends up in the second shard (40-80) + 9, // 9 maps to 0x90 and ends up in the third shard (80-) + // We can increment all of these values by multiples of 16 and they'll always be in the same shard. + } + + insertIntoFuzzUpdate = "INSERT INTO twopc_fuzzer_update (id, col) VALUES (%d, %d)" + updateFuzzUpdate = "UPDATE twopc_fuzzer_update SET col = col + %d WHERE id = %d" + insertIntoFuzzInsert = "INSERT INTO twopc_fuzzer_insert (id, updateSet, threadId) VALUES (%d, %d, %d)" + selectFromFuzzUpdate = "SELECT col FROM twopc_fuzzer_update WHERE id = %d" + selectIdFromFuzzInsert = "SELECT threadId FROM twopc_fuzzer_insert WHERE updateSet = %d AND id = %d ORDER BY col" +) + +// TestTwoPCFuzzTest tests 2PC transactions in a fuzzer environment. +// The testing strategy involves running many transactions and checking that they all must be atomic. +// To this end, we have a very unique strategy. We have two sharded tables `twopc_fuzzer_update`, and `twopc_fuzzer_insert` with the following columns. +// - id: This is the sharding column. We use reverse_bits as the sharding vindex because it is easy to reason about where a row will end up. +// - col in `twopc_fuzzer_insert`: An auto-increment column. +// - col in `twopc_fuzzer_update`: This is a bigint value that we will use to increment on updates. +// - updateSet: This column will store which update set the inserts where done for. +// - threadId: It stores the thread id of the fuzzer thread that inserted the row. +// +// The testing strategy is as follows - +// Every transaction will do 2 things - +// - One, it will increment the `col` on 1 row in each of the shards of the `twopc_fuzzer_update` table. +// To do this, we have sets of rows that each map to one shard. We prepopulate this before the test starts. +// These sets are stored in the fuzzer in updateRowsVals. +// - Two, it will insert one row in each of the shards of the `twopc_fuzzer_insert` table and it will also store the update set that it updated the rows off. +// +// We can check that a transaction was atomic by basically checking that the `col` value for all the rows that were updated together should match. +// If any transaction was partially successful, then it would have missed an increment on one of the rows. +// Moreover, the threadIDs of rows for a given update set in the 3 shards should be the same to ensure that conflicting transactions got committed in the same exact order. +func TestTwoPCFuzzTest(t *testing.T) { + testcases := []struct { + name string + threads int + updateSets int + timeForTesting time.Duration + }{ + { + name: "Single Thread - Single Set", + threads: 1, + updateSets: 1, + timeForTesting: 5 * time.Second, + }, + { + name: "Multiple Threads - Single Set", + threads: 2, + updateSets: 1, + timeForTesting: 5 * time.Second, + }, + { + name: "Multiple Threads - Multiple Set", + threads: 15, + updateSets: 15, + timeForTesting: 5 * time.Second, + }, + } + + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + conn, closer := start(t) + defer closer() + fz := newFuzzer(tt.threads, tt.updateSets) + + fz.initialize(t, conn) + // Start the fuzzer. + fz.start(t) + + // Wait for the timeForTesting so that the threads continue to run. + time.Sleep(tt.timeForTesting) + + // Signal the fuzzer to stop. + fz.stop() + + // Verify that all the transactions run were actually atomic and no data issues have occurred. + fz.verifyTransactionsWereAtomic(t) + }) + } +} + +// verifyTransactionsWereAtomic verifies that the invariants of test are held. +// It checks the heuristics to ensure that the transactions run were atomic. +func (fz *fuzzer) verifyTransactionsWereAtomic(t *testing.T) { + conn, err := mysql.Connect(context.Background(), &vtParams) + require.NoError(t, err) + for updateSetIdx, updateSet := range fz.updateRowsVals { + // All the three values of the update set must be equal. + shard1Val := getColValueForIdFromFuzzUpdate(t, conn, updateSet[0]) + shard2Val := getColValueForIdFromFuzzUpdate(t, conn, updateSet[1]) + shard3Val := getColValueForIdFromFuzzUpdate(t, conn, updateSet[2]) + require.EqualValues(t, shard1Val, shard2Val) + require.EqualValues(t, shard3Val, shard2Val) + + // Next we get the IDs from all the three shards for the given update set index. + shard1IDs := getThreadIDsForUpdateSetFromFuzzInsert(t, conn, updateSetIdx, 1) + shard2IDs := getThreadIDsForUpdateSetFromFuzzInsert(t, conn, updateSetIdx, 2) + shard3IDs := getThreadIDsForUpdateSetFromFuzzInsert(t, conn, updateSetIdx, 3) + require.EqualValues(t, shard1IDs, shard2IDs) + require.EqualValues(t, shard3IDs, shard2IDs) + } +} + +// getColValueForIdFromFuzzUpdate gets the col column value for the given id in the twopc_fuzzer_update table. +func getColValueForIdFromFuzzUpdate(t *testing.T, conn *mysql.Conn, id int) uint64 { + res, err := conn.ExecuteFetch(fmt.Sprintf(selectFromFuzzUpdate, id), 1, false) + require.NoError(t, err) + require.Len(t, res.Rows, 1) + require.Len(t, res.Rows[0], 1) + val, err := res.Rows[0][0].ToUint64() + require.NoError(t, err) + return val +} + +// getThreadIDsForUpdateSetFromFuzzInsert gets the thread IDs for the given update set ordered by the col column from the twopc_fuzzer_insert table. +func getThreadIDsForUpdateSetFromFuzzInsert(t *testing.T, conn *mysql.Conn, updateSet int, shard int) []int { + // We will select all the rows for the given update set for the given shard. To get all the rows for the given shard, + // we can use the id column for filtering, since we know that the first shard will have all the values of id as 4, second shard as 6 and the last one as 9. + res, err := conn.ExecuteFetch(fmt.Sprintf(selectIdFromFuzzInsert, updateSet, updateRowBaseVals[shard-1]), 10000, false) + require.NoError(t, err) + var ids []int + for _, row := range res.Rows { + require.Len(t, row, 1) + threadId, err := row[0].ToInt() + require.NoError(t, err) + ids = append(ids, threadId) + } + return ids +} + +// fuzzer runs threads that runs queries against the databases. +// It has parameters that define the way the queries are constructed. +type fuzzer struct { + threads int + updateSets int + + // shouldStop is an internal state variable, that tells the fuzzer + // whether it should stop or not. + shouldStop atomic.Bool + // wg is an internal state variable, that used to know whether the fuzzer threads are running or not. + wg sync.WaitGroup + // updateRowVals are the rows that we use to ensure 1 update on each shard with the same increment. + updateRowsVals [][]int +} + +// newFuzzer creates a new fuzzer struct. +func newFuzzer(threads int, updateSets int) *fuzzer { + fz := &fuzzer{ + threads: threads, + updateSets: updateSets, + wg: sync.WaitGroup{}, + } + // Initially the fuzzer thread is stopped. + fz.shouldStop.Store(true) + return fz +} + +// stop stops the fuzzer and waits for it to finish execution. +func (fz *fuzzer) stop() { + // Mark the thread to be stopped. + fz.shouldStop.Store(true) + // Wait for the fuzzer thread to stop. + fz.wg.Wait() +} + +// start starts running the fuzzer. +func (fz *fuzzer) start(t *testing.T) { + // We mark the fuzzer thread to be running now. + fz.shouldStop.Store(false) + fz.wg.Add(fz.threads) + for i := 0; i < fz.threads; i++ { + go func() { + fz.runFuzzerThread(t, i) + }() + } +} + +// runFuzzerThread is used to run a thread of the fuzzer. +func (fz *fuzzer) runFuzzerThread(t *testing.T, threadId int) { + // Whenever we finish running this thread, we should mark the thread has stopped. + defer func() { + fz.wg.Done() + }() + + for { + // If fuzzer thread is marked to be stopped, then we should exit this go routine. + if fz.shouldStop.Load() == true { + return + } + // Run an atomic transaction + fz.generateAndExecuteTransaction(threadId) + } + +} + +// initialize initializes all the variables that will be needed for running the fuzzer. +// It also creates the rows for the `twopc_fuzzer_update` table. +func (fz *fuzzer) initialize(t *testing.T, conn *mysql.Conn) { + for i := 0; i < fz.updateSets; i++ { + fz.updateRowsVals = append(fz.updateRowsVals, []int{ + updateRowBaseVals[0] + i*16, + updateRowBaseVals[1] + i*16, + updateRowBaseVals[2] + i*16, + }) + } + + for _, updateSet := range fz.updateRowsVals { + for _, id := range updateSet { + _, err := conn.ExecuteFetch(fmt.Sprintf(insertIntoFuzzUpdate, id, 0), 0, false) + require.NoError(t, err) + } + } +} + +// generateAndExecuteTransaction generates the queries of the transaction and then executes them. +func (fz *fuzzer) generateAndExecuteTransaction(threadId int) { + // Create a connection to the vtgate to run transactions. + conn, err := mysql.Connect(context.Background(), &vtParams) + if err != nil { + return + } + defer conn.Close() + // randomly generate an update set to use and the value to increment it by. + updateSetVal := rand.Intn(fz.updateSets) + incrementVal := rand.Int31() + // We have to generate the update queries first. We can run the inserts only after the update queries. + // Otherwise, our check to see that the ids in the twopc_fuzzer_insert table in all the shards are the exact same + // for each update set ordered by the auto increment column will not be true. + // That assertion depends on all the transactions running updates first to ensure that for any given update set, + // no two transactions are running the insert queries. + queries := []string{"begin"} + queries = append(queries, fz.generateUpdateQueries(updateSetVal, incrementVal)...) + queries = append(queries, fz.generateInsertQueries(updateSetVal, threadId)...) + finalCommand := "commit" + for _, query := range queries { + _, err := conn.ExecuteFetch(query, 0, false) + // If any command fails because of deadlocks or timeout or whatever, then we need to rollback the transaction. + if err != nil { + finalCommand = "rollback" + break + } + } + _, _ = conn.ExecuteFetch(finalCommand, 0, false) +} + +// generateUpdateQueries generates the queries to run updates on the twopc_fuzzer_update table. +// It takes the update set index and the value to increment the set by. +func (fz *fuzzer) generateUpdateQueries(updateSet int, incrementVal int32) []string { + var queries []string + for _, id := range fz.updateRowsVals[updateSet] { + queries = append(queries, fmt.Sprintf(updateFuzzUpdate, incrementVal, id)) + } + rand.Shuffle(len(queries), func(i, j int) { + queries[i], queries[j] = queries[j], queries[i] + }) + return queries +} + +// generateInsertQueries generates the queries to run inserts on the twopc_fuzzer_insert table. +// It takes the update set index and the thread id that is generating these inserts. +func (fz *fuzzer) generateInsertQueries(updateSet int, threadId int) []string { + var queries []string + for _, baseVal := range updateRowBaseVals { + // In the twopc_fuzzer_insert table we are going to be inserting the following values - + // - id: We use the updateRowBaseVals to ensure that the 3 insertions happen on 3 different shards. + // This also allows us to read rows any of the shards without shard targeting by just filtering by this column. + // - updateSet: The update set index that these insertions correspond too. + // - threadId: The thread ID of the fuzzer thread that is running the transaction. + queries = append(queries, fmt.Sprintf(insertIntoFuzzInsert, baseVal, updateSet, threadId)) + } + rand.Shuffle(len(queries), func(i, j int) { + queries[i], queries[j] = queries[j], queries[i] + }) + return queries +} diff --git a/go/test/endtoend/transaction/twopc/fuzzer/main_test.go b/go/test/endtoend/transaction/twopc/fuzzer/main_test.go new file mode 100644 index 00000000000..5e1d14d77e4 --- /dev/null +++ b/go/test/endtoend/transaction/twopc/fuzzer/main_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2024 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 fuzzer + +import ( + "context" + _ "embed" + "flag" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/test/endtoend/cluster" +) + +var ( + clusterInstance *cluster.LocalProcessCluster + vtParams mysql.ConnParams + vtgateGrpcAddress string + keyspaceName = "ks" + cell = "zone1" + hostname = "localhost" + sidecarDBName = "vt_ks" + + //go:embed schema.sql + SchemaSQL string + + //go:embed vschema.json + VSchema string +) + +func TestMain(m *testing.M) { + defer cluster.PanicHandler(nil) + flag.Parse() + + exitcode := func() int { + clusterInstance = cluster.NewCluster(cell, hostname) + defer clusterInstance.Teardown() + + // Start topo server + if err := clusterInstance.StartTopo(); err != nil { + return 1 + } + + // Reserve vtGate port in order to pass it to vtTablet + clusterInstance.VtgateGrpcPort = clusterInstance.GetAndReservePort() + + // Set extra args for twopc + clusterInstance.VtGateExtraArgs = append(clusterInstance.VtGateExtraArgs, + "--transaction_mode", "TWOPC", + "--grpc_use_effective_callerid", + ) + clusterInstance.VtTabletExtraArgs = append(clusterInstance.VtTabletExtraArgs, + "--twopc_enable", + "--twopc_abandon_age", "1", + ) + + // Start keyspace + keyspace := &cluster.Keyspace{ + Name: keyspaceName, + SchemaSQL: SchemaSQL, + VSchema: VSchema, + SidecarDBName: sidecarDBName, + } + if err := clusterInstance.StartKeyspace(*keyspace, []string{"-40", "40-80", "80-"}, 0, false); err != nil { + return 1 + } + + // Start Vtgate + if err := clusterInstance.StartVtgate(); err != nil { + return 1 + } + vtParams = clusterInstance.GetVTParams(keyspaceName) + vtgateGrpcAddress = fmt.Sprintf("%s:%d", clusterInstance.Hostname, clusterInstance.VtgateGrpcPort) + + return m.Run() + }() + os.Exit(exitcode) +} + +func start(t *testing.T) (*mysql.Conn, func()) { + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + cleanup(t) + + return conn, func() { + conn.Close() + cleanup(t) + } +} + +func cleanup(t *testing.T) { + cluster.PanicHandler(t) + + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + defer conn.Close() + + clearOutTable(t, conn, "twopc_fuzzer_insert") + clearOutTable(t, conn, "twopc_fuzzer_update") +} + +// clearOutTable deletes everything from a table. Sometimes the table might have more rows than allowed in a single delete query, +// so we have to do the deletions iteratively. +func clearOutTable(t *testing.T, conn *mysql.Conn, tableName string) { + for { + res, err := conn.ExecuteFetch(fmt.Sprintf("SELECT count(*) FROM %v", tableName), 1, false) + require.NoError(t, err) + require.Len(t, res.Rows, 1) + require.Len(t, res.Rows[0], 1) + rowCount, err := res.Rows[0][0].ToInt() + require.NoError(t, err) + if rowCount == 0 { + return + } + _, err = conn.ExecuteFetch(fmt.Sprintf("DELETE FROM %v LIMIT 10000", tableName), 10000, false) + require.NoError(t, err) + } +} diff --git a/go/test/endtoend/transaction/twopc/fuzzer/schema.sql b/go/test/endtoend/transaction/twopc/fuzzer/schema.sql new file mode 100644 index 00000000000..290da808991 --- /dev/null +++ b/go/test/endtoend/transaction/twopc/fuzzer/schema.sql @@ -0,0 +1,14 @@ +create table twopc_fuzzer_update ( + id bigint, + col bigint, + primary key (id) +) Engine=InnoDB; + +create table twopc_fuzzer_insert ( + id bigint, + updateSet bigint, + threadId bigint, + col bigint auto_increment, + key(col), + primary key (id, col) +) Engine=InnoDB; diff --git a/go/test/endtoend/transaction/twopc/fuzzer/vschema.json b/go/test/endtoend/transaction/twopc/fuzzer/vschema.json new file mode 100644 index 00000000000..e3854f8f101 --- /dev/null +++ b/go/test/endtoend/transaction/twopc/fuzzer/vschema.json @@ -0,0 +1,26 @@ +{ + "sharded":true, + "vindexes": { + "reverse_bits": { + "type": "reverse_bits" + } + }, + "tables": { + "twopc_fuzzer_update": { + "column_vindexes": [ + { + "column": "id", + "name": "reverse_bits" + } + ] + }, + "twopc_fuzzer_insert": { + "column_vindexes": [ + { + "column": "id", + "name": "reverse_bits" + } + ] + } + } +} \ No newline at end of file diff --git a/test/config.json b/test/config.json index 1cdf92127ef..49f77e1b7fb 100644 --- a/test/config.json +++ b/test/config.json @@ -842,6 +842,15 @@ "RetryMax": 1, "Tags": [] }, + "vtgate_transaction_twopc_fuzzer": { + "File": "unused.go", + "Args": ["vitess.io/vitess/go/test/endtoend/transaction/twopc/fuzzer"], + "Command": [], + "Manual": false, + "Shard": "vtgate_transaction", + "RetryMax": 1, + "Tags": [] + }, "vtgate_transaction_partial_exec": { "File": "unused.go", "Args": ["vitess.io/vitess/go/test/endtoend/vtgate/partialfailure"], From c86a7315a17eac84d37e8328c49607f0792eef9c Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Tue, 30 Jul 2024 14:01:28 +0530 Subject: [PATCH 07/34] fix: reference table join merge (#16488) Signed-off-by: Harshit Gangal Signed-off-by: Andres Taylor Co-authored-by: Andres Taylor --- .../vtgate/queries/reference/main_test.go | 69 +++---------------- .../queries/reference/reference_test.go | 32 ++++++--- .../vtgate/queries/reference/sschema.sql | 6 ++ .../vtgate/queries/reference/svschema.json | 22 ++++++ .../vtgate/queries/reference/uschema.sql | 17 +++++ .../vtgate/queries/reference/uvschema.json | 6 ++ .../planbuilder/operators/join_merging.go | 33 ++++++++- go/vt/vtgate/planbuilder/operators/joins.go | 5 +- .../planbuilder/testdata/reference_cases.json | 25 +++++++ 9 files changed, 143 insertions(+), 72 deletions(-) create mode 100644 go/test/endtoend/vtgate/queries/reference/sschema.sql create mode 100644 go/test/endtoend/vtgate/queries/reference/svschema.json create mode 100644 go/test/endtoend/vtgate/queries/reference/uschema.sql create mode 100644 go/test/endtoend/vtgate/queries/reference/uvschema.json diff --git a/go/test/endtoend/vtgate/queries/reference/main_test.go b/go/test/endtoend/vtgate/queries/reference/main_test.go index 4c9440ca4ff..c350038bf6e 100644 --- a/go/test/endtoend/vtgate/queries/reference/main_test.go +++ b/go/test/endtoend/vtgate/queries/reference/main_test.go @@ -18,6 +18,7 @@ package reference import ( "context" + _ "embed" "flag" "fmt" "os" @@ -39,68 +40,16 @@ var ( vtParams mysql.ConnParams unshardedKeyspaceName = "uks" - unshardedSQLSchema = ` - CREATE TABLE IF NOT EXISTS zip( - id BIGINT NOT NULL AUTO_INCREMENT, - code5 INT(5) NOT NULL, - PRIMARY KEY(id) - ) ENGINE=InnoDB; + //go:embed uschema.sql + unshardedSQLSchema string + //go:embed uvschema.json + unshardedVSchema string - INSERT INTO zip(id, code5) - VALUES (1, 47107), - (2, 82845), - (3, 11237); - - CREATE TABLE IF NOT EXISTS zip_detail( - id BIGINT NOT NULL AUTO_INCREMENT, - zip_id BIGINT NOT NULL, - discontinued_at DATE, - PRIMARY KEY(id) - ) ENGINE=InnoDB; - - ` - unshardedVSchema = ` - { - "sharded":false, - "tables": { - "zip": {}, - "zip_detail": {} - } - } - ` shardedKeyspaceName = "sks" - shardedSQLSchema = ` - CREATE TABLE IF NOT EXISTS delivery_failure ( - id BIGINT NOT NULL, - zip_detail_id BIGINT NOT NULL, - reason VARCHAR(255), - PRIMARY KEY(id) - ) ENGINE=InnoDB; - ` - shardedVSchema = ` - { - "sharded": true, - "vindexes": { - "hash": { - "type": "hash" - } - }, - "tables": { - "delivery_failure": { - "columnVindexes": [ - { - "column": "id", - "name": "hash" - } - ] - }, - "zip_detail": { - "type": "reference", - "source": "` + unshardedKeyspaceName + `.zip_detail" - } - } - } - ` + //go:embed sschema.sql + shardedSQLSchema string + //go:embed svschema.json + shardedVSchema string ) func TestMain(m *testing.M) { diff --git a/go/test/endtoend/vtgate/queries/reference/reference_test.go b/go/test/endtoend/vtgate/queries/reference/reference_test.go index 0e3096e6064..c9908e91b4b 100644 --- a/go/test/endtoend/vtgate/queries/reference/reference_test.go +++ b/go/test/endtoend/vtgate/queries/reference/reference_test.go @@ -90,14 +90,14 @@ func TestReferenceRouting(t *testing.T) { t, conn, `SELECT t.id FROM ( - SELECT zd.id, zd.zip_id - FROM `+shardedKeyspaceName+`.zip_detail AS zd - WHERE zd.id IN (2) - ORDER BY zd.discontinued_at - LIMIT 1 - ) AS t - LEFT JOIN `+shardedKeyspaceName+`.zip_detail AS t0 ON t.zip_id = t0.zip_id - ORDER BY t.id`, + SELECT zd.id, zd.zip_id + FROM `+shardedKeyspaceName+`.zip_detail AS zd + WHERE zd.id IN (2) + ORDER BY zd.discontinued_at + LIMIT 1 + ) AS t + LEFT JOIN `+shardedKeyspaceName+`.zip_detail AS t0 ON t.zip_id = t0.zip_id + ORDER BY t.id`, `[[INT64(2)]]`, ) }) @@ -156,3 +156,19 @@ func TestReferenceRouting(t *testing.T) { `[[INT64(2)]]`, ) } + +// TestMultiReferenceQuery tests that a query with multiple references with unsharded keyspace and sharded keyspace works with join. +func TestMultiReferenceQuery(t *testing.T) { + utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate") + conn, closer := start(t) + defer closer() + + query := + `select 1 + from delivery_failure df1 + join delivery_failure df2 on df1.id = df2.id + join uks.zip_detail zd1 on df1.zip_detail_id = zd1.zip_id + join uks.zip_detail zd2 on zd1.zip_id = zd2.zip_id` + + utils.Exec(t, conn, query) +} diff --git a/go/test/endtoend/vtgate/queries/reference/sschema.sql b/go/test/endtoend/vtgate/queries/reference/sschema.sql new file mode 100644 index 00000000000..0fcaf63a422 --- /dev/null +++ b/go/test/endtoend/vtgate/queries/reference/sschema.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS delivery_failure ( + id BIGINT NOT NULL, + zip_detail_id BIGINT NOT NULL, + reason VARCHAR(255), + PRIMARY KEY(id) +) ENGINE=InnoDB; \ No newline at end of file diff --git a/go/test/endtoend/vtgate/queries/reference/svschema.json b/go/test/endtoend/vtgate/queries/reference/svschema.json new file mode 100644 index 00000000000..815e0e8d21c --- /dev/null +++ b/go/test/endtoend/vtgate/queries/reference/svschema.json @@ -0,0 +1,22 @@ +{ + "sharded": true, + "vindexes": { + "hash": { + "type": "hash" + } + }, + "tables": { + "delivery_failure": { + "columnVindexes": [ + { + "column": "id", + "name": "hash" + } + ] + }, + "zip_detail": { + "type": "reference", + "source": "uks.zip_detail" + } + } +} \ No newline at end of file diff --git a/go/test/endtoend/vtgate/queries/reference/uschema.sql b/go/test/endtoend/vtgate/queries/reference/uschema.sql new file mode 100644 index 00000000000..52737928469 --- /dev/null +++ b/go/test/endtoend/vtgate/queries/reference/uschema.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS zip( + id BIGINT NOT NULL AUTO_INCREMENT, + code5 INT(5) NOT NULL, + PRIMARY KEY(id) +) ENGINE=InnoDB; + +INSERT INTO zip(id, code5) +VALUES (1, 47107), + (2, 82845), + (3, 11237); + +CREATE TABLE IF NOT EXISTS zip_detail( + id BIGINT NOT NULL AUTO_INCREMENT, + zip_id BIGINT NOT NULL, + discontinued_at DATE, + PRIMARY KEY(id) +) ENGINE=InnoDB; \ No newline at end of file diff --git a/go/test/endtoend/vtgate/queries/reference/uvschema.json b/go/test/endtoend/vtgate/queries/reference/uvschema.json new file mode 100644 index 00000000000..fdcfca0d7a9 --- /dev/null +++ b/go/test/endtoend/vtgate/queries/reference/uvschema.json @@ -0,0 +1,6 @@ +{ + "tables": { + "zip": {}, + "zip_detail": {} + } +} \ No newline at end of file diff --git a/go/vt/vtgate/planbuilder/operators/join_merging.go b/go/vt/vtgate/planbuilder/operators/join_merging.go index 8bba8ba57d9..34ad2c5adbc 100644 --- a/go/vt/vtgate/planbuilder/operators/join_merging.go +++ b/go/vt/vtgate/planbuilder/operators/join_merging.go @@ -22,6 +22,7 @@ import ( "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" + "vitess.io/vitess/go/vt/vtgate/vindexes" ) // mergeJoinInputs checks whether two operators can be merged into a single one. @@ -34,9 +35,9 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr } switch { + // We clone the right hand side and try and push all the join predicates that are solved entirely by that side. + // If a dual is on the left side, and it is a left join (all right joins are changed to left joins), then we can only merge if the right side is a single sharded routing. case a == dual: - // We clone the right hand side and try and push all the join predicates that are solved entirely by that side. - // If a dual is on the left side and it is a left join (all right joins are changed to left joins), then we can only merge if the right side is a single sharded routing. rhsClone := Clone(rhs).(*Route) for _, predicate := range joinPredicates { if ctx.SemTable.DirectDeps(predicate).IsSolvedBy(TableID(rhsClone)) { @@ -47,10 +48,16 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr return nil } return m.merge(ctx, lhsRoute, rhsClone, rhsClone.Routing) + + // If a dual is on the right side. case b == dual: - // If a dual is on the right side. return m.merge(ctx, lhsRoute, rhsRoute, routingA) + // As both are reference route. We need to merge the alternates as well. + case a == anyShard && b == anyShard && sameKeyspace: + newrouting := mergeAnyShardRoutings(ctx, routingA.(*AnyShardRouting), routingB.(*AnyShardRouting), joinPredicates, m.joinType) + return m.merge(ctx, lhsRoute, rhsRoute, newrouting) + // an unsharded/reference route can be merged with anything going to that keyspace case a == anyShard && sameKeyspace: return m.merge(ctx, lhsRoute, rhsRoute, routingB) @@ -76,6 +83,26 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr } } +func mergeAnyShardRoutings(ctx *plancontext.PlanningContext, a, b *AnyShardRouting, joinPredicates []sqlparser.Expr, joinType sqlparser.JoinType) *AnyShardRouting { + alternates := make(map[*vindexes.Keyspace]*Route) + for ak, av := range a.Alternates { + for bk, bv := range b.Alternates { + // only same keyspace alternates can be merged. + if ak != bk { + continue + } + op, _ := mergeOrJoin(ctx, av, bv, joinPredicates, joinType) + if r, ok := op.(*Route); ok { + alternates[ak] = r + } + } + } + return &AnyShardRouting{ + keyspace: a.keyspace, + Alternates: alternates, + } +} + func prepareInputRoutes(lhs Operator, rhs Operator) (*Route, *Route, Routing, Routing, routingType, routingType, bool) { lhsRoute, rhsRoute := operatorsToRoutes(lhs, rhs) if lhsRoute == nil || rhsRoute == nil { diff --git a/go/vt/vtgate/planbuilder/operators/joins.go b/go/vt/vtgate/planbuilder/operators/joins.go index 86cdf06fe7e..fb740fd1920 100644 --- a/go/vt/vtgate/planbuilder/operators/joins.go +++ b/go/vt/vtgate/planbuilder/operators/joins.go @@ -17,7 +17,10 @@ limitations under the License. package operators import ( + "fmt" + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" "vitess.io/vitess/go/vt/vtgate/semantics" ) @@ -86,7 +89,7 @@ func AddPredicate( return join } - return nil + panic(vterrors.VT13001(fmt.Sprintf("pushed wrong predicate to the join: %s", sqlparser.String(expr)))) } // we are looking for predicates like `tbl.col = <>` or `<> = tbl.col`, diff --git a/go/vt/vtgate/planbuilder/testdata/reference_cases.json b/go/vt/vtgate/planbuilder/testdata/reference_cases.json index a89fa103923..6aa01355934 100644 --- a/go/vt/vtgate/planbuilder/testdata/reference_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/reference_cases.json @@ -746,5 +746,30 @@ "user.user" ] } + }, + { + "comment": "two sharded and two unsharded reference table join - all should be merged into one route", + "query": "select 1 from user u join user_extra ue on u.id = ue.user_id join main.source_of_ref sr on sr.foo = ue.foo join main.rerouted_ref rr on rr.bar = sr.bar", + "plan": { + "QueryType": "SELECT", + "Original": "select 1 from user u join user_extra ue on u.id = ue.user_id join main.source_of_ref sr on sr.foo = ue.foo join main.rerouted_ref rr on rr.bar = sr.bar", + "Instructions": { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from `user` as u, user_extra as ue, ref_with_source as sr, ref as rr where 1 != 1", + "Query": "select 1 from `user` as u, user_extra as ue, ref_with_source as sr, ref as rr where rr.bar = sr.bar and u.id = ue.user_id and sr.foo = ue.foo", + "Table": "`user`, ref, ref_with_source, user_extra" + }, + "TablesUsed": [ + "user.ref", + "user.ref_with_source", + "user.user", + "user.user_extra" + ] + } } ] From b88c62f01377182518c12a32cbc025fd8b0848b6 Mon Sep 17 00:00:00 2001 From: Florent Poinsard <35779988+frouioui@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:20:05 +0900 Subject: [PATCH 08/34] Improve the queries upgrade/downgrade CI workflow by using same test code version as binary (#16494) Signed-off-by: Florent Poinsard --- ...e_downgrade_test_query_serving_queries.yml | 44 +++++++++---------- ...est_query_serving_queries_next_release.yml | 32 +++++++------- .../backup/vtbackup/backup_only_test.go | 15 +++---- go/test/endtoend/cluster/vtctld_process.go | 9 +--- go/test/endtoend/cluster/vtgate_process.go | 8 +--- go/test/endtoend/cluster/vtorc_process.go | 7 +-- go/test/endtoend/cluster/vttablet_process.go | 8 +--- .../reparent/plannedreparent/reparent_test.go | 30 +++++-------- .../queries/aggregation/aggregation_test.go | 15 ------- .../vtgate/queries/derived/cte_test.go | 8 ---- .../vtgate/queries/derived/derived_test.go | 2 - .../endtoend/vtgate/queries/dml/dml_test.go | 20 --------- .../vtgate/queries/dml/insert_test.go | 24 ++-------- .../informationschema_test.go | 5 --- .../endtoend/vtgate/queries/misc/misc_test.go | 22 +--------- .../vtgate/queries/orderby/orderby_test.go | 2 - .../queries/reference/reference_test.go | 1 - .../vtgate/queries/subquery/subquery_test.go | 5 --- .../vtgate/queries/timeout/timeout_test.go | 2 - .../endtoend/vtgate/queries/tpch/tpch_test.go | 1 - .../schematracker/sharded/st_sharded_test.go | 38 ++++------------ .../unsharded/st_unsharded_test.go | 1 - .../endtoend/vtgate/vschema/vschema_test.go | 17 +------ 23 files changed, 74 insertions(+), 242 deletions(-) diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml index ded814c6ac9..36fc151bba6 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries.yml @@ -116,46 +116,43 @@ jobs: # install JUnit report formatter go install github.com/vitessio/go-junit-report@HEAD - # Checkout to the last release of Vitess - - name: Check out other version's code (${{ steps.output-previous-release-ref.outputs.previous_release_ref }}) - if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' - uses: actions/checkout@v4 - with: - ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} - - - name: Get dependencies for the last release + # Build current commit's binaries + - name: Get dependencies for this commit if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | go mod download - - name: Building last release's binaries + - name: Building the binaries for this commit if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' timeout-minutes: 10 run: | source build.env NOVTADMINBUILD=1 make build - mkdir -p /tmp/vitess-build-other/ - cp -R bin /tmp/vitess-build-other/ + mkdir -p /tmp/vitess-build-current/ + cp -R bin /tmp/vitess-build-current/ rm -Rf bin/* - # Checkout to this build's commit - - name: Check out commit's code + # Checkout to the last release of Vitess + - name: Check out other version's code (${{ steps.output-previous-release-ref.outputs.previous_release_ref }}) if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' uses: actions/checkout@v4 + with: + ref: ${{ steps.output-previous-release-ref.outputs.previous_release_ref }} - - name: Get dependencies for this commit + - name: Get dependencies for the last release if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | go mod download - - name: Building the binaries for this commit + - name: Building last release's binaries if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' timeout-minutes: 10 run: | source build.env NOVTADMINBUILD=1 make build - mkdir -p /tmp/vitess-build-current/ - cp -R bin /tmp/vitess-build-current/ + mkdir -p /tmp/vitess-build-other/ + cp -R bin /tmp/vitess-build-other/ + rm -Rf bin/* # Swap the binaries in the bin. Use vtgate version n-1 and keep vttablet at version n - name: Use last release's VTGate @@ -163,12 +160,13 @@ jobs: run: | source build.env + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ rm -f $PWD/bin/vtgate cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate vtgate --version - # Running a test with vtgate at version n-1 and vttablet at version n - - name: Run query serving tests (vtgate=N-1, vttablet=N) + # Running a test with vtgate at version n-1 and vttablet/vtctld at version n + - name: Run query serving tests (vtgate=N-1, vttablet=N, vtctld=N) if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | rm -rf /tmp/vtdataroot @@ -177,8 +175,8 @@ jobs: source build.env eatmydata -- go run test.go -skip-build -keep-data=false -docker=false -print-log -follow -tag upgrade_downgrade_query_serving_queries - # Swap the binaries again. This time, vtgate will be at version n, and vttablet will be at version n-1 - - name: Use current version VTGate, and other version VTTablet + # Swap the binaries again. This time, vtgate will be at version n, and vttablet/vtctld will be at version n-1 + - name: Use current version VTGate, and other version VTTablet/VTctld if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | source build.env @@ -197,8 +195,8 @@ jobs: vtgate --version vttablet --version - # Running a test with vtgate at version n and vttablet at version n-1 - - name: Run query serving tests (vtgate=N, vttablet=N-1) + # Running a test with vtgate at version n and vttablet/vtctld at version n-1 + - name: Run query serving tests (vtgate=N, vttablet=N-1, vtctld=N-1) if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | rm -rf /tmp/vtdataroot diff --git a/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml b/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml index 845ab33c6bb..03c9212f449 100644 --- a/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml +++ b/.github/workflows/upgrade_downgrade_test_query_serving_queries_next_release.yml @@ -117,46 +117,43 @@ jobs: # install JUnit report formatter go install github.com/vitessio/go-junit-report@HEAD - # Checkout to the next release of Vitess - - name: Check out other version's code (${{ steps.output-next-release-ref.outputs.next_release_ref }}) - if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' - uses: actions/checkout@v4 - with: - ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} - - - name: Get dependencies for the next release + # Build current commit's binaries + - name: Get dependencies for this commit if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | go mod download - - name: Building next release's binaries + - name: Building the binaries for this commit if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' timeout-minutes: 10 run: | source build.env NOVTADMINBUILD=1 make build - mkdir -p /tmp/vitess-build-other/ - cp -R bin /tmp/vitess-build-other/ + mkdir -p /tmp/vitess-build-current/ + cp -R bin /tmp/vitess-build-current/ rm -Rf bin/* - # Checkout to this build's commit - - name: Check out commit's code + # Checkout to the next release of Vitess + - name: Check out other version's code (${{ steps.output-next-release-ref.outputs.next_release_ref }}) if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' uses: actions/checkout@v4 + with: + ref: ${{ steps.output-next-release-ref.outputs.next_release_ref }} - - name: Get dependencies for this commit + - name: Get dependencies for the next release if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' run: | go mod download - - name: Building the binaries for this commit + - name: Building next release's binaries if: steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' timeout-minutes: 10 run: | source build.env NOVTADMINBUILD=1 make build - mkdir -p /tmp/vitess-build-current/ - cp -R bin /tmp/vitess-build-current/ + mkdir -p /tmp/vitess-build-other/ + cp -R bin /tmp/vitess-build-other/ + rm -Rf bin/* # Swap the binaries in the bin. Use vtgate version n+1 and keep vttablet at version n - name: Use next release's VTGate @@ -164,6 +161,7 @@ jobs: run: | source build.env + cp -r /tmp/vitess-build-current/bin/* $PWD/bin/ rm -f $PWD/bin/vtgate cp /tmp/vitess-build-other/bin/vtgate $PWD/bin/vtgate vtgate --version diff --git a/go/test/endtoend/backup/vtbackup/backup_only_test.go b/go/test/endtoend/backup/vtbackup/backup_only_test.go index 7dada7a77d2..4e018986100 100644 --- a/go/test/endtoend/backup/vtbackup/backup_only_test.go +++ b/go/test/endtoend/backup/vtbackup/backup_only_test.go @@ -69,15 +69,10 @@ func TestTabletInitialBackup(t *testing.T) { // Initialize the tablets initTablets(t, false, false) - vtTabletVersion, err := cluster.GetMajorVersion("vttablet") - require.NoError(t, err) - // For all version at or above v17.0.0, each replica will start in super_read_only mode. Let's verify that is working correctly. - if vtTabletVersion >= 17 { - err := primary.VttabletProcess.CreateDB("testDB") - require.ErrorContains(t, err, "The MySQL server is running with the --super-read-only option so it cannot execute this statement") - err = replica1.VttabletProcess.CreateDB("testDB") - require.ErrorContains(t, err, "The MySQL server is running with the --super-read-only option so it cannot execute this statement") - } + err := primary.VttabletProcess.CreateDB("testDB") + require.ErrorContains(t, err, "The MySQL server is running with the --super-read-only option so it cannot execute this statement") + err = replica1.VttabletProcess.CreateDB("testDB") + require.ErrorContains(t, err, "The MySQL server is running with the --super-read-only option so it cannot execute this statement") // Restore the Tablet restore(t, primary, "replica", "NOT_SERVING") @@ -172,7 +167,7 @@ func firstBackupTest(t *testing.T, tabletType string) { restore(t, replica2, "replica", "SERVING") // Replica2 takes time to serve. Sleeping for 5 sec. time.Sleep(5 * time.Second) - //check the new replica has the data + // check the new replica has the data cluster.VerifyRowsInTablet(t, replica2, keyspaceName, 2) removeBackups(t) diff --git a/go/test/endtoend/cluster/vtctld_process.go b/go/test/endtoend/cluster/vtctld_process.go index d87427af9b9..6ac6ed5d2b0 100644 --- a/go/test/endtoend/cluster/vtctld_process.go +++ b/go/test/endtoend/cluster/vtctld_process.go @@ -65,15 +65,10 @@ func (vtctld *VtctldProcess) Setup(cell string, extraArgs ...string) (err error) "--log_dir", vtctld.LogDir, "--port", fmt.Sprintf("%d", vtctld.Port), "--grpc_port", fmt.Sprintf("%d", vtctld.GrpcPort), + "--bind-address", "127.0.0.1", + "--grpc_bind_address", "127.0.0.1", ) - if v, err := GetMajorVersion("vtctld"); err != nil { - return err - } else if v >= 18 { - vtctld.proc.Args = append(vtctld.proc.Args, "--bind-address", "127.0.0.1") - vtctld.proc.Args = append(vtctld.proc.Args, "--grpc_bind_address", "127.0.0.1") - } - if *isCoverage { vtctld.proc.Args = append(vtctld.proc.Args, "--test.coverprofile="+getCoveragePath("vtctld.out")) } diff --git a/go/test/endtoend/cluster/vtgate_process.go b/go/test/endtoend/cluster/vtgate_process.go index 5143e9ad8a4..d7f5dc3dc01 100644 --- a/go/test/endtoend/cluster/vtgate_process.go +++ b/go/test/endtoend/cluster/vtgate_process.go @@ -85,12 +85,8 @@ func (vtgate *VtgateProcess) Setup() (err error) { "--tablet_types_to_wait", vtgate.TabletTypesToWait, "--service_map", vtgate.ServiceMap, "--mysql_auth_server_impl", vtgate.MySQLAuthServerImpl, - } - if v, err := GetMajorVersion("vtgate"); err != nil { - return err - } else if v >= 18 { - args = append(args, "--bind-address", "127.0.0.1") - args = append(args, "--grpc_bind_address", "127.0.0.1") + "--bind-address", "127.0.0.1", + "--grpc_bind_address", "127.0.0.1", } // If no explicit mysql_server_version has been specified then we autodetect // the MySQL version that will be used for the test and base the vtgate's diff --git a/go/test/endtoend/cluster/vtorc_process.go b/go/test/endtoend/cluster/vtorc_process.go index cac5921d01d..31cf03606e5 100644 --- a/go/test/endtoend/cluster/vtorc_process.go +++ b/go/test/endtoend/cluster/vtorc_process.go @@ -126,14 +126,9 @@ func (orc *VTOrcProcess) Setup() (err error) { "--instance-poll-time", "1s", // Faster topo information refresh speeds up the tests. This doesn't add any significant load either "--topo-information-refresh-duration", "3s", + "--bind-address", "127.0.0.1", ) - if v, err := GetMajorVersion("vtorc"); err != nil { - return err - } else if v >= 18 { - orc.proc.Args = append(orc.proc.Args, "--bind-address", "127.0.0.1") - } - if *isCoverage { orc.proc.Args = append(orc.proc.Args, "--test.coverprofile="+getCoveragePath("orc.out")) } diff --git a/go/test/endtoend/cluster/vttablet_process.go b/go/test/endtoend/cluster/vttablet_process.go index 6bd60b63191..d4f0e3f1963 100644 --- a/go/test/endtoend/cluster/vttablet_process.go +++ b/go/test/endtoend/cluster/vttablet_process.go @@ -111,13 +111,9 @@ func (vttablet *VttabletProcess) Setup() (err error) { "--file_backup_storage_root", vttablet.FileBackupStorageRoot, "--service_map", vttablet.ServiceMap, "--db_charset", vttablet.Charset, + "--bind-address", "127.0.0.1", + "--grpc_bind_address", "127.0.0.1", ) - if v, err := GetMajorVersion("vttablet"); err != nil { - return err - } else if v >= 18 { - vttablet.proc.Args = append(vttablet.proc.Args, "--bind-address", "127.0.0.1") - vttablet.proc.Args = append(vttablet.proc.Args, "--grpc_bind_address", "127.0.0.1") - } if *isCoverage { vttablet.proc.Args = append(vttablet.proc.Args, "--test.coverprofile="+getCoveragePath("vttablet.out")) diff --git a/go/test/endtoend/reparent/plannedreparent/reparent_test.go b/go/test/endtoend/reparent/plannedreparent/reparent_test.go index fb625b691f5..5986056924e 100644 --- a/go/test/endtoend/reparent/plannedreparent/reparent_test.go +++ b/go/test/endtoend/reparent/plannedreparent/reparent_test.go @@ -205,13 +205,13 @@ func TestReparentFromOutsideWithNoPrimary(t *testing.T) { } func reparentFromOutside(t *testing.T, clusterInstance *cluster.LocalProcessCluster, downPrimary bool) { - //This test will start a primary and 3 replicas. - //Then: - //- one replica will be the new primary - //- one replica will be reparented to that new primary - //- one replica will be busted and dead in the water and we'll call TabletExternallyReparented. - //Args: - //downPrimary: kills the old primary first + // This test will start a primary and 3 replicas. + // Then: + // - one replica will be the new primary + // - one replica will be reparented to that new primary + // - one replica will be busted and dead in the water and we'll call TabletExternallyReparented. + // Args: + // downPrimary: kills the old primary first ctx := context.Background() tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets @@ -224,7 +224,7 @@ func reparentFromOutside(t *testing.T, clusterInstance *cluster.LocalProcessClus demoteCommands := []string{"SET GLOBAL read_only = ON", "FLUSH TABLES WITH READ LOCK", "UNLOCK TABLES"} utils.RunSQLs(ctx, t, demoteCommands, tablets[0]) - //Get the position of the old primary and wait for the new one to catch up. + // Get the position of the old primary and wait for the new one to catch up. err := utils.WaitForReplicationPosition(t, tablets[0], tablets[1]) require.NoError(t, err) } @@ -460,14 +460,7 @@ func TestFullStatus(t *testing.T) { assert.Contains(t, primaryStatus.PrimaryStatus.String(), "vt-0000000101-bin") assert.Equal(t, primaryStatus.GtidPurged, "MySQL56/") assert.False(t, primaryStatus.ReadOnly) - vtTabletVersion, err := cluster.GetMajorVersion("vttablet") - require.NoError(t, err) - vtcltlVersion, err := cluster.GetMajorVersion("vtctl") - require.NoError(t, err) - // For all version at or above v17.0.0, each replica will start in super_read_only mode. - if vtTabletVersion >= 17 && vtcltlVersion >= 17 { - assert.False(t, primaryStatus.SuperReadOnly) - } + assert.False(t, primaryStatus.SuperReadOnly) assert.True(t, primaryStatus.SemiSyncPrimaryEnabled) assert.True(t, primaryStatus.SemiSyncReplicaEnabled) assert.True(t, primaryStatus.SemiSyncPrimaryStatus) @@ -521,10 +514,7 @@ func TestFullStatus(t *testing.T) { assert.Contains(t, replicaStatus.PrimaryStatus.String(), "vt-0000000102-bin") assert.Equal(t, replicaStatus.GtidPurged, "MySQL56/") assert.True(t, replicaStatus.ReadOnly) - // For all version at or above v17.0.0, each replica will start in super_read_only mode. - if vtTabletVersion >= 17 && vtcltlVersion >= 17 { - assert.True(t, replicaStatus.SuperReadOnly) - } + assert.True(t, replicaStatus.SuperReadOnly) assert.False(t, replicaStatus.SemiSyncPrimaryEnabled) assert.True(t, replicaStatus.SemiSyncReplicaEnabled) assert.False(t, replicaStatus.SemiSyncPrimaryStatus) diff --git a/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go b/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go index 9a6ad90cc5b..d206f58e17c 100644 --- a/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go +++ b/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go @@ -71,7 +71,6 @@ func start(t *testing.T) (utils.MySQLCompare, func()) { } func TestAggrWithLimit(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate") mcmp, closer := start(t) defer closer() @@ -101,11 +100,9 @@ func TestAggregateTypes(t *testing.T) { mcmp.AssertMatches("select val1 as a, count(*) from aggr_test group by a order by 2, a", `[[VARCHAR("b") INT64(1)] [VARCHAR("d") INT64(1)] [VARCHAR("a") INT64(2)] [VARCHAR("c") INT64(2)] [VARCHAR("e") INT64(2)]]`) mcmp.AssertMatches("select sum(val1) from aggr_test", `[[FLOAT64(0)]]`) mcmp.Run("Average for sharded keyspaces", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches("select avg(val1) from aggr_test", `[[FLOAT64(0)]]`) }) mcmp.Run("Average with group by without selecting the grouped columns", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(20, "vtgate") mcmp.AssertMatches("select avg(val2) from aggr_test group by val1 order by val1", `[[DECIMAL(1.0000)] [DECIMAL(1.0000)] [DECIMAL(3.5000)] [NULL] [DECIMAL(1.0000)]]`) }) } @@ -214,7 +211,6 @@ func TestAggrOnJoin(t *testing.T) { `[[VARCHAR("a")]]`) mcmp.Run("Average in join for sharded", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches(`select avg(a1.val2), avg(a2.val2) from aggr_test a1 join aggr_test a2 on a1.val2 = a2.id join t3 t on a2.val2 = t.id7`, "[[DECIMAL(1.5000) DECIMAL(1.0000)]]") @@ -372,7 +368,6 @@ func TestAggOnTopOfLimit(t *testing.T) { mcmp.AssertMatches("select val1, count(*) from (select id, val1 from aggr_test where val2 < 4 order by val1 limit 2) as x group by val1", `[[NULL INT64(1)] [VARCHAR("a") INT64(1)]]`) mcmp.AssertMatchesNoOrder("select val1, count(val2) from (select val1, val2 from aggr_test limit 8) as x group by val1", `[[NULL INT64(1)] [VARCHAR("a") INT64(2)] [VARCHAR("b") INT64(1)] [VARCHAR("c") INT64(2)]]`) mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches("select avg(val2) from (select id, val2 from aggr_test where val2 is null limit 2) as x", "[[NULL]]") mcmp.AssertMatchesNoOrder("select val1, avg(val2) from (select val1, val2 from aggr_test limit 8) as x group by val1", `[[NULL DECIMAL(2.0000)] [VARCHAR("a") DECIMAL(3.5000)] [VARCHAR("b") DECIMAL(1.0000)] [VARCHAR("c") DECIMAL(3.5000)]]`) }) @@ -384,7 +379,6 @@ func TestAggOnTopOfLimit(t *testing.T) { mcmp.AssertMatches("select count(val2), sum(val2) from (select id, val2 from aggr_test where val2 is null limit 2) as x", "[[INT64(0) NULL]]") mcmp.AssertMatches("select val1, count(*), sum(id) from (select id, val1 from aggr_test where val2 < 4 order by val1 limit 2) as x group by val1", `[[NULL INT64(1) DECIMAL(7)] [VARCHAR("a") INT64(1) DECIMAL(2)]]`) mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches("select count(*), sum(val1), avg(val1) from (select id, val1 from aggr_test where val2 < 4 order by val1 desc limit 2) as x", "[[INT64(2) FLOAT64(0) FLOAT64(0)]]") mcmp.AssertMatches("select count(val1), sum(id), avg(id) from (select id, val1 from aggr_test where val2 < 4 order by val1 desc limit 2) as x", "[[INT64(2) DECIMAL(7) DECIMAL(3.5000)]]") mcmp.AssertMatchesNoOrder("select val1, count(val2), sum(val2), avg(val2) from (select val1, val2 from aggr_test limit 8) as x group by val1", @@ -406,7 +400,6 @@ func TestEmptyTableAggr(t *testing.T) { mcmp.AssertMatches(" select t1.`name`, count(*) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo' group by t1.`name`", "[]") mcmp.AssertMatches(" select t1.`name`, count(*) from t1 inner join t2 on (t1.t1_id = t2.id) where t1.value = 'foo' group by t1.`name`", "[]") mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches(" select count(t1.value) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo'", "[[INT64(0)]]") mcmp.AssertMatches(" select avg(t1.value) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo'", "[[NULL]]") }) @@ -422,7 +415,6 @@ func TestEmptyTableAggr(t *testing.T) { mcmp.AssertMatches(" select count(*) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo'", "[[INT64(0)]]") mcmp.AssertMatches(" select t1.`name`, count(*) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo' group by t1.`name`", "[]") mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches(" select count(t1.value) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo'", "[[INT64(0)]]") mcmp.AssertMatches(" select avg(t1.value) from t2 inner join t1 on (t1.t1_id = t2.id) where t1.value = 'foo'", "[[NULL]]") mcmp.AssertMatches(" select t1.`name`, count(*) from t1 inner join t2 on (t1.t1_id = t2.id) where t1.value = 'foo' group by t1.`name`", "[]") @@ -471,7 +463,6 @@ func TestAggregateLeftJoin(t *testing.T) { mcmp.AssertMatches("SELECT count(*) FROM t2 LEFT JOIN t1 ON t1.t1_id = t2.id WHERE IFNULL(t1.name, 'NOTSET') = 'r'", `[[INT64(1)]]`) mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches("SELECT avg(t1.shardkey) FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.id", `[[DECIMAL(0.5000)]]`) mcmp.AssertMatches("SELECT avg(t2.shardkey) FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.id", `[[DECIMAL(1.0000)]]`) aggregations := []string{ @@ -528,7 +519,6 @@ func TestScalarAggregate(t *testing.T) { mcmp.Exec("insert into aggr_test(id, val1, val2) values(1,'a',1), (2,'A',1), (3,'b',1), (4,'c',3), (5,'c',4)") mcmp.AssertMatches("select count(distinct val1) from aggr_test", `[[INT64(3)]]`) mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.AssertMatches("select avg(val1) from aggr_test", `[[FLOAT64(0)]]`) }) } @@ -588,15 +578,11 @@ func TestComplexAggregation(t *testing.T) { mcmp.Exec(`SELECT name+COUNT(t1_id)+1 FROM t1 GROUP BY name`) mcmp.Exec(`SELECT COUNT(*)+shardkey+MIN(t1_id)+1+MAX(t1_id)*SUM(t1_id)+1+name FROM t1 GROUP BY shardkey, name`) mcmp.Run("Average in sharded query", func(mcmp *utils.MySQLCompare) { - mcmp.SkipIfBinaryIsBelowVersion(19, "vtgate") mcmp.Exec(`SELECT COUNT(t1_id)+MAX(shardkey)+AVG(t1_id) FROM t1`) }) } func TestJoinAggregation(t *testing.T) { - // This is new functionality in Vitess 20 - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -793,7 +779,6 @@ func TestHavingQueries(t *testing.T) { // TestJsonAggregation tests that json aggregation works for single sharded queries. func TestJsonAggregation(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate") mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/derived/cte_test.go b/go/test/endtoend/vtgate/queries/derived/cte_test.go index 54d97261ae6..131342a8562 100644 --- a/go/test/endtoend/vtgate/queries/derived/cte_test.go +++ b/go/test/endtoend/vtgate/queries/derived/cte_test.go @@ -18,12 +18,9 @@ package misc import ( "testing" - - "vitess.io/vitess/go/test/endtoend/utils" ) func TestCTEWithOrderByLimit(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") mcmp, closer := start(t) defer closer() @@ -31,7 +28,6 @@ func TestCTEWithOrderByLimit(t *testing.T) { } func TestCTEAggregationOnRHS(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") mcmp, closer := start(t) defer closer() @@ -40,7 +36,6 @@ func TestCTEAggregationOnRHS(t *testing.T) { } func TestCTERemoveInnerOrderBy(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") mcmp, closer := start(t) defer closer() @@ -48,7 +43,6 @@ func TestCTERemoveInnerOrderBy(t *testing.T) { } func TestCTEWithHaving(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") mcmp, closer := start(t) defer closer() @@ -59,7 +53,6 @@ func TestCTEWithHaving(t *testing.T) { } func TestCTEColumns(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") mcmp, closer := start(t) defer closer() @@ -68,7 +61,6 @@ func TestCTEColumns(t *testing.T) { } func TestCTEAggregationsInUnion(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/derived/derived_test.go b/go/test/endtoend/vtgate/queries/derived/derived_test.go index c41161d9bcf..cb106564b2f 100644 --- a/go/test/endtoend/vtgate/queries/derived/derived_test.go +++ b/go/test/endtoend/vtgate/queries/derived/derived_test.go @@ -92,7 +92,6 @@ func TestDerivedTableColumns(t *testing.T) { // We do this by not using the apply join we usually use, and instead use the hash join engine primitive // These tests exercise these situations func TestDerivedTablesWithLimit(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") // We need full type info before planning this, so we wait for the schema tracker require.NoError(t, utils.WaitForAuthoritative(t, keyspaceName, "user", clusterInstance.VtgateProcess.ReadVSchema)) @@ -116,7 +115,6 @@ func TestDerivedTablesWithLimit(t *testing.T) { // TestDerivedTableColumnAliasWithJoin tests the derived table having alias column and using it in the join condition func TestDerivedTableColumnAliasWithJoin(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/dml/dml_test.go b/go/test/endtoend/vtgate/queries/dml/dml_test.go index 4383f59e6c4..061cc4b9b36 100644 --- a/go/test/endtoend/vtgate/queries/dml/dml_test.go +++ b/go/test/endtoend/vtgate/queries/dml/dml_test.go @@ -47,8 +47,6 @@ func TestMultiEqual(t *testing.T) { // TestMultiTableDelete executed multi-table delete queries func TestMultiTableDelete(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") - mcmp, closer := start(t) defer closer() @@ -78,8 +76,6 @@ func TestMultiTableDelete(t *testing.T) { // TestDeleteWithLimit executed delete queries with limit func TestDeleteWithLimit(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") - mcmp, closer := start(t) defer closer() @@ -133,8 +129,6 @@ func TestDeleteWithLimit(t *testing.T) { // TestUpdateWithLimit executed update queries with limit func TestUpdateWithLimit(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -191,8 +185,6 @@ func TestUpdateWithLimit(t *testing.T) { // TestMultiTableUpdate executed multi-table update queries func TestMultiTableUpdate(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -222,8 +214,6 @@ func TestMultiTableUpdate(t *testing.T) { // TestDeleteWithSubquery executed delete queries with subqueries func TestDeleteWithSubquery(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -268,8 +258,6 @@ func TestDeleteWithSubquery(t *testing.T) { // TestMultiTargetDelete executed multi-target delete queries func TestMultiTargetDelete(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -299,8 +287,6 @@ func TestMultiTargetDelete(t *testing.T) { // TestMultiTargetDeleteMore executed multi-target delete queries with additional cases func TestMultiTargetDeleteMore(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -337,8 +323,6 @@ func TestMultiTargetDeleteMore(t *testing.T) { // TestMultiTargetUpdate executed multi-target update queries func TestMultiTargetUpdate(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -368,8 +352,6 @@ func TestMultiTargetUpdate(t *testing.T) { // TestMultiTargetNonLiteralUpdate executed multi-target update queries with non-literal values. func TestMultiTargetNonLiteralUpdate(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -400,8 +382,6 @@ func TestMultiTargetNonLiteralUpdate(t *testing.T) { // TestDMLInUnique for update/delete statement using an IN clause with the Vindexes, // the query is correctly split according to the corresponding values in the IN list. func TestDMLInUnique(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/dml/insert_test.go b/go/test/endtoend/vtgate/queries/dml/insert_test.go index 026f53fe961..9e89f4f5f3e 100644 --- a/go/test/endtoend/vtgate/queries/dml/insert_test.go +++ b/go/test/endtoend/vtgate/queries/dml/insert_test.go @@ -21,9 +21,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "vitess.io/vitess/go/test/endtoend/cluster" "vitess.io/vitess/go/test/endtoend/utils" ) @@ -56,8 +54,6 @@ func TestSimpleInsertSelect(t *testing.T) { // TestInsertOnDup test the insert on duplicate key update feature with argument and list argument. func TestInsertOnDup(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -92,19 +88,10 @@ func TestFailureInsertSelect(t *testing.T) { // primary key same mcmp.AssertContainsError("insert into s_tbl(id, num) select id, num*20 from s_tbl where id = 1", `AlreadyExists desc = Duplicate entry '1' for key`) // lookup key same (does not fail on MySQL as there is no lookup, and we have not put unique constraint on num column) - vtgateVersion, err := cluster.GetMajorVersion("vtgate") - require.NoError(t, err) - if vtgateVersion >= 19 { - utils.AssertContainsError(t, mcmp.VtConn, "insert into s_tbl(id, num) select id*20, num from s_tbl where id = 1", `(errno 1062) (sqlstate 23000)`) - // mismatch column count - mcmp.AssertContainsError("insert into s_tbl(id, num) select 100,200,300", `column count does not match value count with the row`) - mcmp.AssertContainsError("insert into s_tbl(id, num) select 100", `column count does not match value count with the row`) - } else { - utils.AssertContainsError(t, mcmp.VtConn, "insert into s_tbl(id, num) select id*20, num from s_tbl where id = 1", `lookup.Create: Code: ALREADY_EXISTS`) - // mismatch column count - mcmp.AssertContainsError("insert into s_tbl(id, num) select 100,200,300", `column count does not match value count at row 1`) - mcmp.AssertContainsError("insert into s_tbl(id, num) select 100", `column count does not match value count at row 1`) - } + utils.AssertContainsError(t, mcmp.VtConn, "insert into s_tbl(id, num) select id*20, num from s_tbl where id = 1", `(errno 1062) (sqlstate 23000)`) + // mismatch column count + mcmp.AssertContainsError("insert into s_tbl(id, num) select 100,200,300", `column count does not match value count with the row`) + mcmp.AssertContainsError("insert into s_tbl(id, num) select 100", `column count does not match value count with the row`) }) } } @@ -486,9 +473,6 @@ func TestMixedCases(t *testing.T) { // TestInsertAlias test the alias feature in insert statement. func TestInsertAlias(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - utils.SkipIfBinaryIsBelowVersion(t, 20, "vttablet") - mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/informationschema/informationschema_test.go b/go/test/endtoend/vtgate/queries/informationschema/informationschema_test.go index ec55711a31f..c696e7b0a9d 100644 --- a/go/test/endtoend/vtgate/queries/informationschema/informationschema_test.go +++ b/go/test/endtoend/vtgate/queries/informationschema/informationschema_test.go @@ -225,8 +225,6 @@ func TestInfrSchemaAndUnionAll(t *testing.T) { } func TestInfoschemaTypes(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") - require.NoError(t, utils.WaitForAuthoritative(t, "ks", "t1", clusterInstance.VtgateProcess.ReadVSchema)) @@ -245,9 +243,7 @@ func TestInfoschemaTypes(t *testing.T) { } func TestTypeORMQuery(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") // This test checks that we can run queries similar to the ones that the TypeORM framework uses - require.NoError(t, utils.WaitForAuthoritative(t, "ks", "t1", clusterInstance.VtgateProcess.ReadVSchema)) @@ -294,7 +290,6 @@ WHERE TABLE_SCHEMA = 'ks' AND TABLE_NAME = 't2'; } func TestJoinWithSingleShardQueryOnRHS(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") // This test checks that we can run queries like this, where the RHS is a single shard query mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/misc/misc_test.go b/go/test/endtoend/vtgate/queries/misc/misc_test.go index f003ae3c1b8..8e47bb8581a 100644 --- a/go/test/endtoend/vtgate/queries/misc/misc_test.go +++ b/go/test/endtoend/vtgate/queries/misc/misc_test.go @@ -60,15 +60,8 @@ func TestBitVals(t *testing.T) { mcmp.AssertMatches(`select b'1001', 0x9, B'010011011010'`, `[[VARBINARY("\t") VARBINARY("\t") VARBINARY("\x04\xda")]]`) mcmp.AssertMatches(`select b'1001', 0x9, B'010011011010' from t1`, `[[VARBINARY("\t") VARBINARY("\t") VARBINARY("\x04\xda")]]`) - vtgateVersion, err := cluster.GetMajorVersion("vtgate") - require.NoError(t, err) - if vtgateVersion >= 19 { - mcmp.AssertMatchesNoCompare(`select 1 + b'1001', 2 + 0x9, 3 + B'010011011010'`, `[[INT64(10) UINT64(11) INT64(1245)]]`, `[[INT64(10) UINT64(11) INT64(1245)]]`) - mcmp.AssertMatchesNoCompare(`select 1 + b'1001', 2 + 0x9, 3 + B'010011011010' from t1`, `[[INT64(10) UINT64(11) INT64(1245)]]`, `[[INT64(10) UINT64(11) INT64(1245)]]`) - } else { - mcmp.AssertMatchesNoCompare(`select 1 + b'1001', 2 + 0x9, 3 + B'010011011010'`, `[[INT64(10) UINT64(11) INT64(1245)]]`, `[[UINT64(10) UINT64(11) UINT64(1245)]]`) - mcmp.AssertMatchesNoCompare(`select 1 + b'1001', 2 + 0x9, 3 + B'010011011010' from t1`, `[[INT64(10) UINT64(11) INT64(1245)]]`, `[[UINT64(10) UINT64(11) UINT64(1245)]]`) - } + mcmp.AssertMatchesNoCompare(`select 1 + b'1001', 2 + 0x9, 3 + B'010011011010'`, `[[INT64(10) UINT64(11) INT64(1245)]]`, `[[INT64(10) UINT64(11) INT64(1245)]]`) + mcmp.AssertMatchesNoCompare(`select 1 + b'1001', 2 + 0x9, 3 + B'010011011010' from t1`, `[[INT64(10) UINT64(11) INT64(1245)]]`, `[[INT64(10) UINT64(11) INT64(1245)]]`) } // TestTimeFunctionWithPrecision tests that inserting data with NOW(1) works as intended. @@ -140,7 +133,6 @@ func TestCast(t *testing.T) { // TestVindexHints tests that vindex hints work as intended. func TestVindexHints(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() @@ -323,8 +315,6 @@ func TestAnalyze(t *testing.T) { // TestTransactionModeVar executes SELECT on `transaction_mode` variable func TestTransactionModeVar(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") - mcmp, closer := start(t) defer closer() @@ -356,8 +346,6 @@ func TestTransactionModeVar(t *testing.T) { // TestAliasesInOuterJoinQueries tests that aliases work in queries that have outer join clauses. func TestAliasesInOuterJoinQueries(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() @@ -377,7 +365,6 @@ func TestAliasesInOuterJoinQueries(t *testing.T) { } func TestAlterTableWithView(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() @@ -431,7 +418,6 @@ func TestAlterTableWithView(t *testing.T) { // TestStraightJoin tests that Vitess respects the ordering of join in a STRAIGHT JOIN query. func TestStraightJoin(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() @@ -457,7 +443,6 @@ func TestStraightJoin(t *testing.T) { } func TestColumnAliases(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() @@ -466,7 +451,6 @@ func TestColumnAliases(t *testing.T) { } func TestHandleNullableColumn(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate") require.NoError(t, utils.WaitForAuthoritative(t, keyspaceName, "tbl", clusterInstance.VtgateProcess.ReadVSchema)) mcmp, closer := start(t) @@ -480,8 +464,6 @@ func TestHandleNullableColumn(t *testing.T) { } func TestEnumSetVals(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() require.NoError(t, utils.WaitForAuthoritative(t, keyspaceName, "tbl_enum_set", clusterInstance.VtgateProcess.ReadVSchema)) diff --git a/go/test/endtoend/vtgate/queries/orderby/orderby_test.go b/go/test/endtoend/vtgate/queries/orderby/orderby_test.go index e8d8d4bfef1..c36b52a4e6a 100644 --- a/go/test/endtoend/vtgate/queries/orderby/orderby_test.go +++ b/go/test/endtoend/vtgate/queries/orderby/orderby_test.go @@ -86,8 +86,6 @@ func TestOrderBy(t *testing.T) { func TestOrderByComplex(t *testing.T) { // tests written to try to trick the ORDER BY engine and planner - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/reference/reference_test.go b/go/test/endtoend/vtgate/queries/reference/reference_test.go index c9908e91b4b..08e9cbe13b1 100644 --- a/go/test/endtoend/vtgate/queries/reference/reference_test.go +++ b/go/test/endtoend/vtgate/queries/reference/reference_test.go @@ -84,7 +84,6 @@ func TestReferenceRouting(t *testing.T) { ) t.Run("Complex reference query", func(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") // Verify a complex query using reference tables with a left join having a derived table with an order by clause works as intended. utils.AssertMatches( t, diff --git a/go/test/endtoend/vtgate/queries/subquery/subquery_test.go b/go/test/endtoend/vtgate/queries/subquery/subquery_test.go index eb949e1c697..74af6634198 100644 --- a/go/test/endtoend/vtgate/queries/subquery/subquery_test.go +++ b/go/test/endtoend/vtgate/queries/subquery/subquery_test.go @@ -162,7 +162,6 @@ func TestSubqueryInReference(t *testing.T) { // TestSubqueryInAggregation validates that subquery work inside aggregation functions. func TestSubqueryInAggregation(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") mcmp, closer := start(t) defer closer() @@ -180,7 +179,6 @@ func TestSubqueryInAggregation(t *testing.T) { // TestSubqueryInDerivedTable tests that subqueries and derived tables // are handled correctly when there are joins inside the derived table func TestSubqueryInDerivedTable(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() @@ -194,7 +192,6 @@ func TestSubqueries(t *testing.T) { // This method tests many types of subqueries. The queries should move to a vitess-tester test file once we have a way to run them. // The commented out queries are failing because of wrong types being returned. // The tests are commented out until the issue is fixed. - utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate") mcmp, closer := start(t) defer closer() queries := []string{ @@ -234,8 +231,6 @@ func TestSubqueries(t *testing.T) { } func TestProperTypesOfPullOutValue(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 21, "vtgate") - query := "select (select sum(id) from user) from user_extra" mcmp, closer := start(t) diff --git a/go/test/endtoend/vtgate/queries/timeout/timeout_test.go b/go/test/endtoend/vtgate/queries/timeout/timeout_test.go index f7bd96dca13..d5e116e155b 100644 --- a/go/test/endtoend/vtgate/queries/timeout/timeout_test.go +++ b/go/test/endtoend/vtgate/queries/timeout/timeout_test.go @@ -100,8 +100,6 @@ func TestQueryTimeoutWithTables(t *testing.T) { // TestQueryTimeoutWithShardTargeting tests the query timeout with shard targeting. func TestQueryTimeoutWithShardTargeting(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") - mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/tpch/tpch_test.go b/go/test/endtoend/vtgate/queries/tpch/tpch_test.go index bd35fe3f67c..c4bf71cafa1 100644 --- a/go/test/endtoend/vtgate/queries/tpch/tpch_test.go +++ b/go/test/endtoend/vtgate/queries/tpch/tpch_test.go @@ -48,7 +48,6 @@ func start(t *testing.T) (utils.MySQLCompare, func()) { } func TestTPCHQueries(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") mcmp, closer := start(t) defer closer() err := utils.WaitForColumn(t, clusterInstance.VtgateProcess, keyspaceName, "region", `R_COMMENT`) diff --git a/go/test/endtoend/vtgate/schematracker/sharded/st_sharded_test.go b/go/test/endtoend/vtgate/schematracker/sharded/st_sharded_test.go index 4c495d257b5..50042f3142a 100644 --- a/go/test/endtoend/vtgate/schematracker/sharded/st_sharded_test.go +++ b/go/test/endtoend/vtgate/schematracker/sharded/st_sharded_test.go @@ -178,13 +178,7 @@ func TestInitAndUpdate(t *testing.T) { require.NoError(t, err) defer conn.Close() - vtgateVersion, err := cluster.GetMajorVersion("vtgate") - require.NoError(t, err) - - expected := `[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` - if vtgateVersion >= 17 { - expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` - } + expected := `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` utils.AssertMatchesWithTimeout(t, conn, "SHOW VSCHEMA TABLES", expected, @@ -192,18 +186,13 @@ func TestInitAndUpdate(t *testing.T) { 30*time.Second, "initial table list not complete") - if vtgateVersion >= 19 { - utils.AssertMatches(t, conn, - "SHOW VSCHEMA KEYSPACES", - `[[VARCHAR("ks") VARCHAR("true") VARCHAR("unmanaged") VARCHAR("")]]`) - } + utils.AssertMatches(t, conn, + "SHOW VSCHEMA KEYSPACES", + `[[VARCHAR("ks") VARCHAR("true") VARCHAR("unmanaged") VARCHAR("")]]`) // Init _ = utils.Exec(t, conn, "create table test_sc (id bigint primary key)") - expected = `[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")]]` - if vtgateVersion >= 17 { - expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")]]` - } + expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")]]` utils.AssertMatchesWithTimeout(t, conn, "SHOW VSCHEMA TABLES", expected, @@ -213,10 +202,7 @@ func TestInitAndUpdate(t *testing.T) { // Tables Update via health check. _ = utils.Exec(t, conn, "create table test_sc1 (id bigint primary key)") - expected = `[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")] [VARCHAR("test_sc1")]]` - if vtgateVersion >= 17 { - expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")] [VARCHAR("test_sc1")]]` - } + expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")] [VARCHAR("test_sc")] [VARCHAR("test_sc1")]]` utils.AssertMatchesWithTimeout(t, conn, "SHOW VSCHEMA TABLES", expected, @@ -225,10 +211,7 @@ func TestInitAndUpdate(t *testing.T) { "test_sc1 not in vschema tables") _ = utils.Exec(t, conn, "drop table test_sc, test_sc1") - expected = `[[VARCHAR("dual")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` - if vtgateVersion >= 17 { - expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` - } + expected = `[[VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` utils.AssertMatchesWithTimeout(t, conn, "SHOW VSCHEMA TABLES", expected, @@ -247,12 +230,7 @@ func TestDMLOnNewTable(t *testing.T) { // create a new table which is not part of the VSchema utils.Exec(t, conn, `create table new_table_tracked(id bigint, name varchar(100), primary key(id)) Engine=InnoDB`) - vtgateVersion, err := cluster.GetMajorVersion("vtgate") - require.NoError(t, err) - expected := `[[VARCHAR("dual")] [VARCHAR("new_table_tracked")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` - if vtgateVersion >= 17 { - expected = `[[VARCHAR("new_table_tracked")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` - } + expected := `[[VARCHAR("new_table_tracked")] [VARCHAR("t2")] [VARCHAR("t2_id4_idx")] [VARCHAR("t8")]]` // wait for vttablet's schema reload interval to pass utils.AssertMatchesWithTimeout(t, conn, "SHOW VSCHEMA TABLES", diff --git a/go/test/endtoend/vtgate/schematracker/unsharded/st_unsharded_test.go b/go/test/endtoend/vtgate/schematracker/unsharded/st_unsharded_test.go index 257dd7238f3..5ecf89a5db7 100644 --- a/go/test/endtoend/vtgate/schematracker/unsharded/st_unsharded_test.go +++ b/go/test/endtoend/vtgate/schematracker/unsharded/st_unsharded_test.go @@ -182,7 +182,6 @@ func TestNewUnshardedTable(t *testing.T) { // creating two tables having the same name differing only in casing, but other operating systems don't. // More information at https://dev.mysql.com/doc/refman/8.0/en/identifier-case-sensitivity.html#:~:text=Table%20names%20are%20stored%20in,lowercase%20on%20storage%20and%20lookup. func TestCaseSensitiveSchemaTracking(t *testing.T) { - utils.SkipIfBinaryIsBelowVersion(t, 19, "vttablet") defer cluster.PanicHandler(t) // create a sql connection diff --git a/go/test/endtoend/vtgate/vschema/vschema_test.go b/go/test/endtoend/vtgate/vschema/vschema_test.go index 92863ff7dc8..eec54f8f47f 100644 --- a/go/test/endtoend/vtgate/vschema/vschema_test.go +++ b/go/test/endtoend/vtgate/vschema/vschema_test.go @@ -110,16 +110,7 @@ func TestVSchema(t *testing.T) { `[[INT64(1) VARCHAR("test1")] [INT64(2) VARCHAR("test2")] [INT64(3) VARCHAR("test3")] [INT64(4) VARCHAR("test4")]]`) utils.AssertMatches(t, conn, "delete from vt_user", `[]`) - - vtgateVersion, err := cluster.GetMajorVersion("vtgate") - require.NoError(t, err) - - // Test empty vschema - if vtgateVersion >= 17 { - utils.AssertMatches(t, conn, "SHOW VSCHEMA TABLES", `[]`) - } else { - utils.AssertMatches(t, conn, "SHOW VSCHEMA TABLES", `[[VARCHAR("dual")]]`) - } + utils.AssertMatches(t, conn, "SHOW VSCHEMA TABLES", `[]`) // Use the DDL to create an unsharded vschema and test again @@ -135,11 +126,7 @@ func TestVSchema(t *testing.T) { utils.Exec(t, conn, "commit") // Test Showing Tables - if vtgateVersion >= 17 { - utils.AssertMatches(t, conn, "SHOW VSCHEMA TABLES", `[[VARCHAR("main")] [VARCHAR("vt_user")]]`) - } else { - utils.AssertMatches(t, conn, "SHOW VSCHEMA TABLES", `[[VARCHAR("dual")] [VARCHAR("main")] [VARCHAR("vt_user")]]`) - } + utils.AssertMatches(t, conn, "SHOW VSCHEMA TABLES", `[[VARCHAR("main")] [VARCHAR("vt_user")]]`) // Test Showing Vindexes utils.AssertMatches(t, conn, "SHOW VSCHEMA VINDEXES", `[]`) From 3d104d0bbc1698510897701d642f53f4f5ef32d1 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Tue, 30 Jul 2024 16:59:35 +0530 Subject: [PATCH 09/34] Modify distributed transaction commit flow (#16468) Signed-off-by: Harshit Gangal --- go/mysql/sqlerror/constants.go | 5 +- .../endtoend/transaction/twopc/twopc_test.go | 97 +++++++-- go/vt/vtgate/debug_2pc.go | 48 +++++ go/vt/vtgate/engine/transaction_status.go | 2 +- go/vt/vtgate/production.go | 10 + go/vt/vtgate/tx_conn.go | 189 ++++++++++-------- go/vt/vtgate/tx_conn_test.go | 4 +- 7 files changed, 252 insertions(+), 103 deletions(-) diff --git a/go/mysql/sqlerror/constants.go b/go/mysql/sqlerror/constants.go index ec5afa5e9c3..15c590b92a8 100644 --- a/go/mysql/sqlerror/constants.go +++ b/go/mysql/sqlerror/constants.go @@ -34,8 +34,9 @@ func (e ErrorCode) ToString() string { // See above reference for more information on each code. const ( // Vitess specific errors, (100-999) - ERNotReplica = ErrorCode(100) - ERNonAtomicCommit = ErrorCode(301) + ERNotReplica = ErrorCode(100) + ERNonAtomicCommit = ErrorCode(301) + ERInAtomicRecovery = ErrorCode(302) // unknown ERUnknownError = ErrorCode(1105) diff --git a/go/test/endtoend/transaction/twopc/twopc_test.go b/go/test/endtoend/transaction/twopc/twopc_test.go index dc2aba61b1b..98bc158c4da 100644 --- a/go/test/endtoend/transaction/twopc/twopc_test.go +++ b/go/test/endtoend/transaction/twopc/twopc_test.go @@ -31,11 +31,13 @@ import ( "github.com/stretchr/testify/require" "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/test/endtoend/cluster" "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/callerid" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" querypb "vitess.io/vitess/go/vt/proto/query" + "vitess.io/vitess/go/vt/vtgate/vtgateconn" ) // TestDTCommit tests distributed transaction commit for insert, update and delete operations @@ -580,6 +582,10 @@ func TestDTResolveAfterMMCommit(t *testing.T) { _, err = conn.Execute(newCtx, "commit", nil) require.ErrorContains(t, err, "Fail After MM commit") + testWarningAndTransactionStatus(t, conn, + "distributed transaction ID failed during metadata manager commit; transaction will be committed/rollbacked based on the state on recovery", + false, "COMMIT", "ks:40-80,ks:-40") + // Below check ensures that the transaction is resolved by the resolver on receiving unresolved transaction signal from MM. tableMap := make(map[string][]*querypb.Field) dtMap := make(map[string]string) @@ -656,6 +662,10 @@ func TestDTResolveAfterRMPrepare(t *testing.T) { _, err = conn.Execute(newCtx, "commit", nil) require.ErrorContains(t, err, "Fail After RM prepared") + testWarningAndTransactionStatus(t, conn, + "distributed transaction ID failed during transaction prepare phase; prepare transaction rollback attempted; conclude on recovery", + true /* transaction concluded */, "", "") + // Below check ensures that the transaction is resolved by the resolver on receiving unresolved transaction signal from MM. tableMap := make(map[string][]*querypb.Field) dtMap := make(map[string]string) @@ -714,6 +724,10 @@ func TestDTResolveDuringRMPrepare(t *testing.T) { _, err = conn.Execute(newCtx, "commit", nil) require.ErrorContains(t, err, "Fail During RM prepare") + testWarningAndTransactionStatus(t, conn, + "distributed transaction ID failed during transaction prepare phase; prepare transaction rollback attempted; conclude on recovery", + true, "", "") + // Below check ensures that the transaction is resolved by the resolver on receiving unresolved transaction signal from MM. tableMap := make(map[string][]*querypb.Field) dtMap := make(map[string]string) @@ -776,6 +790,10 @@ func TestDTResolveDuringRMCommit(t *testing.T) { _, err = conn.Execute(newCtx, "commit", nil) require.ErrorContains(t, err, "Fail During RM commit") + testWarningAndTransactionStatus(t, conn, + "distributed transaction ID failed during resource manager commit; transaction will be committed on recovery", + false, "COMMIT", "ks:40-80,ks:-40") + // Below check ensures that the transaction is resolved by the resolver on receiving unresolved transaction signal from MM. tableMap := make(map[string][]*querypb.Field) dtMap := make(map[string]string) @@ -851,18 +869,9 @@ func TestDTResolveAfterTransactionRecord(t *testing.T) { _, err = conn.Execute(newCtx, "commit", nil) require.ErrorContains(t, err, "Fail After TR created") - t.Run("ReadTransactionState", func(t *testing.T) { - errStr := err.Error() - indx := strings.Index(errStr, "Fail") - require.Greater(t, indx, 0) - dtid := errStr[0 : indx-2] - res, err := conn.Execute(context.Background(), fmt.Sprintf(`show transaction status for '%v'`, dtid), nil) - require.NoError(t, err) - resStr := fmt.Sprintf("%v", res.Rows) - require.Contains(t, resStr, `[[VARCHAR("ks:80-`) - require.Contains(t, resStr, `VARCHAR("PREPARE") DATETIME("`) - require.Contains(t, resStr, `+0000 UTC") VARCHAR("ks:40-80")]]`) - }) + testWarningAndTransactionStatus(t, conn, + "distributed transaction ID failed during transaction record creation; rollback attempted; conclude on recovery", + false, "PREPARE", "ks:40-80") // Below check ensures that the transaction is resolved by the resolver on receiving unresolved transaction signal from MM. tableMap := make(map[string][]*querypb.Field) @@ -882,3 +891,67 @@ func TestDTResolveAfterTransactionRecord(t *testing.T) { assert.Equal(t, expectations, logTable, "mismatch expected: \n got: %s, want: %s", prettyPrint(logTable), prettyPrint(expectations)) } + +type warn struct { + level string + code uint16 + msg string +} + +func toWarn(row sqltypes.Row) warn { + code, _ := row[1].ToUint16() + return warn{ + level: row[0].ToString(), + code: code, + msg: row[2].ToString(), + } +} + +type txStatus struct { + dtid string + state string + rTime string + participants string +} + +func toTxStatus(row sqltypes.Row) txStatus { + return txStatus{ + dtid: row[0].ToString(), + state: row[1].ToString(), + rTime: row[2].ToString(), + participants: row[3].ToString(), + } +} + +func testWarningAndTransactionStatus(t *testing.T, conn *vtgateconn.VTGateSession, warnMsg string, + txConcluded bool, txState string, txParticipants string) { + t.Helper() + + qr, err := conn.Execute(context.Background(), "show warnings", nil) + require.NoError(t, err) + require.Len(t, qr.Rows, 1) + + // validate warning output + w := toWarn(qr.Rows[0]) + assert.Equal(t, "Warning", w.level) + assert.EqualValues(t, 302, w.code) + assert.Contains(t, w.msg, warnMsg) + + // extract transaction ID + indx := strings.Index(w.msg, " ") + require.Greater(t, indx, 0) + dtid := w.msg[:indx] + + qr, err = conn.Execute(context.Background(), fmt.Sprintf(`show transaction status for '%v'`, dtid), nil) + require.NoError(t, err) + + // validate transaction status + if txConcluded { + require.Empty(t, qr.Rows) + } else { + tx := toTxStatus(qr.Rows[0]) + assert.Equal(t, dtid, tx.dtid) + assert.Equal(t, txState, tx.state) + assert.Equal(t, txParticipants, tx.participants) + } +} diff --git a/go/vt/vtgate/debug_2pc.go b/go/vt/vtgate/debug_2pc.go index f31f1413007..dc052df33d6 100644 --- a/go/vt/vtgate/debug_2pc.go +++ b/go/vt/vtgate/debug_2pc.go @@ -18,4 +18,52 @@ limitations under the License. package vtgate +import ( + "context" + + "vitess.io/vitess/go/vt/callerid" + "vitess.io/vitess/go/vt/log" + querypb "vitess.io/vitess/go/vt/proto/query" + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/vterrors" +) + const DebugTwoPc = true + +// checkTestFailure is used to simulate failures in 2PC flow for testing when DebugTwoPc is true. +func checkTestFailure(ctx context.Context, expectCaller string, target *querypb.Target) error { + callerID := callerid.EffectiveCallerIDFromContext(ctx) + if callerID == nil || callerID.GetPrincipal() != expectCaller { + return nil + } + switch callerID.Principal { + case "TRCreated_FailNow": + log.Errorf("Fail After TR created") + // no commit decision is made. Transaction should be a rolled back. + return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail After TR created") + case "RMPrepare_-40_FailNow": + if target.Shard != "-40" { + return nil + } + log.Errorf("Fail During RM prepare") + // no commit decision is made. Transaction should be a rolled back. + return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail During RM prepare") + case "RMPrepared_FailNow": + log.Errorf("Fail After RM prepared") + // no commit decision is made. Transaction should be a rolled back. + return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail After RM prepared") + case "MMCommitted_FailNow": + log.Errorf("Fail After MM commit") + // commit decision is made. Transaction should be committed. + return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail After MM commit") + case "RMCommit_-40_FailNow": + if target.Shard != "-40" { + return nil + } + log.Errorf("Fail During RM commit") + // commit decision is made. Transaction should be a committed. + return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail During RM commit") + default: + return nil + } +} diff --git a/go/vt/vtgate/engine/transaction_status.go b/go/vt/vtgate/engine/transaction_status.go index a087db08256..61cc72c08d9 100644 --- a/go/vt/vtgate/engine/transaction_status.go +++ b/go/vt/vtgate/engine/transaction_status.go @@ -84,7 +84,7 @@ func (t *TransactionStatus) TryExecute(ctx context.Context, vcursor VCursor, bin if wantfields { res.Fields = t.getFields() } - if transactionState != nil { + if transactionState != nil && transactionState.Dtid != "" { var participantString []string for _, participant := range transactionState.Participants { participantString = append(participantString, fmt.Sprintf("%s:%s", participant.Keyspace, participant.Shard)) diff --git a/go/vt/vtgate/production.go b/go/vt/vtgate/production.go index 83e0cbdddf5..d3b0ff4fe7e 100644 --- a/go/vt/vtgate/production.go +++ b/go/vt/vtgate/production.go @@ -18,6 +18,12 @@ limitations under the License. package vtgate +import ( + "context" + + querypb "vitess.io/vitess/go/vt/proto/query" +) + // This file defines debug constants that are always false. // This file is used for building production code. // We use go build directives to include a file that defines the constant to true @@ -26,3 +32,7 @@ package vtgate // production performance. const DebugTwoPc = false + +func checkTestFailure(_ context.Context, _ string, _ *querypb.Target) error { + return nil +} diff --git a/go/vt/vtgate/tx_conn.go b/go/vt/vtgate/tx_conn.go index 56d45799175..f8b08def10c 100644 --- a/go/vt/vtgate/tx_conn.go +++ b/go/vt/vtgate/tx_conn.go @@ -22,10 +22,7 @@ import ( "strings" "sync" - "github.com/pkg/errors" - "vitess.io/vitess/go/mysql/sqlerror" - "vitess.io/vitess/go/vt/callerid" "vitess.io/vitess/go/vt/concurrency" "vitess.io/vitess/go/vt/dtids" "vitess.io/vitess/go/vt/log" @@ -62,6 +59,16 @@ var txAccessModeToEOTxAccessMode = map[sqlparser.TxAccessMode]querypb.ExecuteOpt sqlparser.ReadOnly: querypb.ExecuteOptions_READ_ONLY, } +type commitPhase int + +const ( + Commit2pcCreateTransaction commitPhase = iota + Commit2pcPrepare + Commit2pcStartCommit + Commit2pcPrepareCommit + Commit2pcConclude +) + // Begin begins a new transaction. If one is already in progress, it commits it // and starts a new one. func (txc *TxConn) Begin(ctx context.Context, session *SafeSession, txAccessModes []sqlparser.TxAccessMode) error { @@ -179,7 +186,7 @@ func (txc *TxConn) commitNormal(ctx context.Context, session *SafeSession) error } // commit2PC will not used the pinned tablets - to make sure we use the current source, we need to use the gateway's queryservice -func (txc *TxConn) commit2PC(ctx context.Context, session *SafeSession) error { +func (txc *TxConn) commit2PC(ctx context.Context, session *SafeSession) (err error) { if len(session.PreSessions) != 0 || len(session.PostSessions) != 0 { _ = txc.Rollback(ctx, session) return vterrors.New(vtrpcpb.Code_FAILED_PRECONDITION, "pre or post actions not allowed for 2PC commits") @@ -190,114 +197,118 @@ func (txc *TxConn) commit2PC(ctx context.Context, session *SafeSession) error { return txc.commitNormal(ctx, session) } - participants := make([]*querypb.Target, 0, len(session.ShardSessions)-1) - for _, s := range session.ShardSessions[1:] { - participants = append(participants, s.Target) - } mmShard := session.ShardSessions[0] + rmShards := session.ShardSessions[1:] dtid := dtids.New(mmShard) - err := txc.tabletGateway.CreateTransaction(ctx, mmShard.Target, dtid, participants) - if err != nil { - // Normal rollback is safe because nothing was prepared yet. - _ = txc.Rollback(ctx, session) + participants := make([]*querypb.Target, len(rmShards)) + for i, s := range rmShards { + participants[i] = s.Target + } + + var txPhase commitPhase + defer func() { + if err == nil { + return + } + txc.errActionAndLogWarn(ctx, session, txPhase, dtid, mmShard, rmShards) + }() + + txPhase = Commit2pcCreateTransaction + if err = txc.tabletGateway.CreateTransaction(ctx, mmShard.Target, dtid, participants); err != nil { return err } - if DebugTwoPc { - // Test code to simulate a failure after RM prepare - if failNow, err := checkTestFailure(callerid.EffectiveCallerIDFromContext(ctx), "TRCreated_FailNow", nil); failNow { - return errors.Wrapf(err, "%v", dtid) + if DebugTwoPc { // Test code to simulate a failure after RM prepare + if terr := checkTestFailure(ctx, "TRCreated_FailNow", nil); terr != nil { + return terr } } - err = txc.runSessions(ctx, session.ShardSessions[1:], session.logging, func(ctx context.Context, s *vtgatepb.Session_ShardSession, logging *executeLogger) error { - if DebugTwoPc { - // Test code to simulate a failure during RM prepare - if failNow, err := checkTestFailure(callerid.EffectiveCallerIDFromContext(ctx), "RMPrepare_-40_FailNow", s.Target); failNow { - return err + txPhase = Commit2pcPrepare + prepareAction := func(ctx context.Context, s *vtgatepb.Session_ShardSession, logging *executeLogger) error { + if DebugTwoPc { // Test code to simulate a failure during RM prepare + if terr := checkTestFailure(ctx, "RMPrepare_-40_FailNow", s.Target); terr != nil { + return terr } } return txc.tabletGateway.Prepare(ctx, s.Target, s.TransactionId, dtid) - }) - if err != nil { - // TODO(sougou): Perform a more fine-grained cleanup - // including unprepared transactions. - if resumeErr := txc.rollbackTx(ctx, dtid, mmShard, session.ShardSessions[1:], session.logging); resumeErr != nil { - log.Warningf("Rollback failed after Prepare failure: %v", resumeErr) - } - // Return the original error even if the previous operation fails. + } + if err = txc.runSessions(ctx, rmShards, session.logging, prepareAction); err != nil { return err } - if DebugTwoPc { - // Test code to simulate a failure after RM prepare - if failNow, err := checkTestFailure(callerid.EffectiveCallerIDFromContext(ctx), "RMPrepared_FailNow", nil); failNow { - return err + if DebugTwoPc { // Test code to simulate a failure after RM prepare + if terr := checkTestFailure(ctx, "RMPrepared_FailNow", nil); terr != nil { + return terr } } + txPhase = Commit2pcStartCommit err = txc.tabletGateway.StartCommit(ctx, mmShard.Target, mmShard.TransactionId, dtid) if err != nil { return err } - if DebugTwoPc { - // Test code to simulate a failure after MM commit - if failNow, err := checkTestFailure(callerid.EffectiveCallerIDFromContext(ctx), "MMCommitted_FailNow", nil); failNow { - return err + if DebugTwoPc { // Test code to simulate a failure after MM commit + if terr := checkTestFailure(ctx, "MMCommitted_FailNow", nil); terr != nil { + return terr } } - err = txc.runSessions(ctx, session.ShardSessions[1:], session.logging, func(ctx context.Context, s *vtgatepb.Session_ShardSession, logging *executeLogger) error { - if DebugTwoPc { - // Test code to simulate a failure during RM prepare - if failNow, err := checkTestFailure(callerid.EffectiveCallerIDFromContext(ctx), "RMCommit_-40_FailNow", s.Target); failNow { - return err + txPhase = Commit2pcPrepareCommit + prepareCommitAction := func(ctx context.Context, s *vtgatepb.Session_ShardSession, logging *executeLogger) error { + if DebugTwoPc { // Test code to simulate a failure during RM prepare + if terr := checkTestFailure(ctx, "RMCommit_-40_FailNow", s.Target); terr != nil { + return terr } } return txc.tabletGateway.CommitPrepared(ctx, s.Target, dtid) - }) - if err != nil { + } + if err = txc.runSessions(ctx, rmShards, session.logging, prepareCommitAction); err != nil { return err } - return txc.tabletGateway.ConcludeTransaction(ctx, mmShard.Target, dtid) + // At this point, application can continue forward. + // The transaction is already committed. + // This step is to clean up the transaction metadata. + txPhase = Commit2pcConclude + _ = txc.tabletGateway.ConcludeTransaction(ctx, mmShard.Target, dtid) + return nil } -func checkTestFailure(callerID *vtrpcpb.CallerID, expectCaller string, target *querypb.Target) (bool, error) { - if callerID == nil || callerID.GetPrincipal() != expectCaller { - return false, nil - } - switch callerID.Principal { - case "TRCreated_FailNow": - log.Errorf("Fail After TR created") - // no commit decision is made. Transaction should be a rolled back. - return true, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail After TR created") - case "RMPrepare_-40_FailNow": - if target.Shard != "-40" { - return false, nil +func (txc *TxConn) errActionAndLogWarn(ctx context.Context, session *SafeSession, txPhase commitPhase, dtid string, mmShard *vtgatepb.Session_ShardSession, rmShards []*vtgatepb.Session_ShardSession) { + switch txPhase { + case Commit2pcCreateTransaction: + // Normal rollback is safe because nothing was prepared yet. + if rollbackErr := txc.Rollback(ctx, session); rollbackErr != nil { + log.Warningf("Rollback failed after Create Transaction failure: %v", rollbackErr) } - log.Errorf("Fail During RM prepare") - // no commit decision is made. Transaction should be a rolled back. - return true, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail During RM prepare") - case "RMPrepared_FailNow": - log.Errorf("Fail After RM prepared") - // no commit decision is made. Transaction should be a rolled back. - return true, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail After RM prepared") - case "MMCommitted_FailNow": - log.Errorf("Fail After MM commit") - // commit decision is made. Transaction should be committed. - return true, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail After MM commit") - case "RMCommit_-40_FailNow": - if target.Shard != "-40" { - return false, nil + case Commit2pcPrepare: + // Rollback the prepared and unprepared transactions. + if resumeErr := txc.rollbackTx(ctx, dtid, mmShard, rmShards, session.logging); resumeErr != nil { + log.Warningf("Rollback failed after Prepare failure: %v", resumeErr) } - log.Errorf("Fail During RM commit") - // commit decision is made. Transaction should be a committed. - return true, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Fail During RM commit") - default: - return false, nil } + session.RecordWarning(&querypb.QueryWarning{ + Code: uint32(sqlerror.ERInAtomicRecovery), + Message: createWarningMessage(dtid, txPhase)}) +} + +func createWarningMessage(dtid string, txPhase commitPhase) string { + warningMsg := fmt.Sprintf("%s distributed transaction ID failed during", dtid) + switch txPhase { + case Commit2pcCreateTransaction: + warningMsg += " transaction record creation; rollback attempted; conclude on recovery" + case Commit2pcPrepare: + warningMsg += " transaction prepare phase; prepare transaction rollback attempted; conclude on recovery" + case Commit2pcStartCommit: + warningMsg += " metadata manager commit; transaction will be committed/rollbacked based on the state on recovery" + case Commit2pcPrepareCommit: + warningMsg += " resource manager commit; transaction will be committed on recovery" + case Commit2pcConclude: + warningMsg += " transaction conclusion" + } + return warningMsg } // Rollback rolls back the current transaction. There are no retries on this operation. @@ -467,24 +478,32 @@ func (txc *TxConn) resolveTx(ctx context.Context, target *querypb.Target, transa } // rollbackTx rollbacks the specified distributed transaction. +// Rollbacks happens on the metadata manager and all participants irrespective of the failure. func (txc *TxConn) rollbackTx(ctx context.Context, dtid string, mmShard *vtgatepb.Session_ShardSession, participants []*vtgatepb.Session_ShardSession, logging *executeLogger) error { - qs, err := txc.queryService(ctx, mmShard.TabletAlias) - if err != nil { - return err + var errs []error + if mmErr := txc.rollbackMM(ctx, dtid, mmShard); mmErr != nil { + errs = append(errs, mmErr) } - if err := qs.SetRollback(ctx, mmShard.Target, dtid, mmShard.TransactionId); err != nil { - return err - } - err = txc.runSessions(ctx, participants, logging, func(ctx context.Context, session *vtgatepb.Session_ShardSession, logger *executeLogger) error { + if rmErr := txc.runSessions(ctx, participants, logging, func(ctx context.Context, session *vtgatepb.Session_ShardSession, logger *executeLogger) error { return txc.tabletGateway.RollbackPrepared(ctx, session.Target, dtid, session.TransactionId) - }) - if err != nil { + }); rmErr != nil { + errs = append(errs, rmErr) + } + if err := vterrors.Aggregate(errs); err != nil { return err } return txc.tabletGateway.ConcludeTransaction(ctx, mmShard.Target, dtid) } +func (txc *TxConn) rollbackMM(ctx context.Context, dtid string, mmShard *vtgatepb.Session_ShardSession) error { + qs, err := txc.queryService(ctx, mmShard.TabletAlias) + if err != nil { + return err + } + return qs.SetRollback(ctx, mmShard.Target, dtid, mmShard.TransactionId) +} + func (txc *TxConn) resumeRollback(ctx context.Context, target *querypb.Target, transaction *querypb.TransactionMetadata) error { err := txc.runTargets(transaction.Participants, func(t *querypb.Target) error { return txc.tabletGateway.RollbackPrepared(ctx, t, transaction.Dtid, 0) diff --git a/go/vt/vtgate/tx_conn_test.go b/go/vt/vtgate/tx_conn_test.go index 74329153936..ed977b75051 100644 --- a/go/vt/vtgate/tx_conn_test.go +++ b/go/vt/vtgate/tx_conn_test.go @@ -1090,9 +1090,7 @@ func TestTxConnCommit2PCConcludeTransactionFail(t *testing.T) { sbc0.MustFailConcludeTransaction = 1 session.TransactionMode = vtgatepb.TransactionMode_TWOPC err := sc.txConn.Commit(ctx, session) - want := "error: err" - require.Error(t, err) - assert.Contains(t, err.Error(), want, "Commit") + require.NoError(t, err) // ConcludeTransaction is best-effort as it does not impact the outcome. assert.EqualValues(t, 1, sbc0.CreateTransactionCount.Load(), "sbc0.CreateTransactionCount") assert.EqualValues(t, 1, sbc1.PrepareCount.Load(), "sbc1.PrepareCount") assert.EqualValues(t, 1, sbc0.StartCommitCount.Load(), "sbc0.StartCommitCount") From 4377b88533f2ffe744f86102ac20970dc2a3ab88 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:55:48 +0300 Subject: [PATCH 10/34] `shcemadiff`: support `INSTANT` DDL for changing column visibility (#16503) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- go/mysql/capabilities/capability.go | 3 + go/vt/schemadiff/capability.go | 126 ++++++++++++++++------------ go/vt/schemadiff/capability_test.go | 31 +++++++ 3 files changed, 107 insertions(+), 53 deletions(-) diff --git a/go/mysql/capabilities/capability.go b/go/mysql/capabilities/capability.go index 34995081867..4015059e686 100644 --- a/go/mysql/capabilities/capability.go +++ b/go/mysql/capabilities/capability.go @@ -40,6 +40,7 @@ const ( InstantAddDropColumnFlavorCapability // Adding/dropping column in any position/ordinal. InstantChangeColumnDefaultFlavorCapability // InstantExpandEnumCapability // + InstantChangeColumnVisibilityCapability // MySQLUpgradeInServerFlavorCapability // DynamicRedoLogCapacityFlavorCapability // supported in MySQL 8.0.30 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-30.html DisableRedoLogFlavorCapability // supported in MySQL 8.0.21 and above: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-21.html @@ -106,6 +107,8 @@ func MySQLVersionHasCapability(serverVersion string, capability FlavorCapability return atLeast(8, 0, 21) case FastDropTableFlavorCapability: return atLeast(8, 0, 23) + case InstantChangeColumnVisibilityCapability: + return atLeast(8, 0, 23) case InstantAddDropColumnFlavorCapability: return atLeast(8, 0, 29) case DynamicRedoLogCapacityFlavorCapability: diff --git a/go/vt/schemadiff/capability.go b/go/vt/schemadiff/capability.go index cde99ac18c3..2a3e2d97c9b 100644 --- a/go/vt/schemadiff/capability.go +++ b/go/vt/schemadiff/capability.go @@ -73,12 +73,13 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab } return true, col.Type.Options.Storage } - colStringStrippedDown := func(col *sqlparser.ColumnDefinition, stripDefault bool, stripEnum bool) string { + colStringStrippedDown := func(col *sqlparser.ColumnDefinition, stripEnum bool) string { strippedCol := sqlparser.Clone(col) - if stripDefault { - strippedCol.Type.Options.Default = nil - strippedCol.Type.Options.DefaultLiteral = false - } + // strip `default` + strippedCol.Type.Options.Default = nil + strippedCol.Type.Options.DefaultLiteral = false + // strip `visibility` + strippedCol.Type.Options.Invisible = nil if stripEnum { strippedCol.Type.EnumValues = nil } @@ -95,15 +96,53 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab } return true } + changeModifyColumnCapableOfInstantDDL := func(col *sqlparser.ColumnDefinition, newCol *sqlparser.ColumnDefinition) (bool, error) { + // Check if only diff is change of default. + // We temporarily remove the DEFAULT expression (if any) from both + // table and ALTER statement, and compare the columns: if they're otherwise equal, + // then the only change can be an addition/change/removal of DEFAULT, which + // is instant-table. + tableColDefinition := colStringStrippedDown(col, false) + newColDefinition := colStringStrippedDown(newCol, false) + if tableColDefinition == newColDefinition { + return capableOf(capabilities.InstantChangeColumnDefaultFlavorCapability) + } + // Check if: + // 1. this an ENUM/SET + // 2. and the change is to append values to the end of the list + // 3. and the number of added values does not increase the storage size for the enum/set + // 4. while still not caring about a change in the default value + if len(col.Type.EnumValues) > 0 && len(newCol.Type.EnumValues) > 0 { + // both are enum or set + if !hasPrefix(newCol.Type.EnumValues, col.Type.EnumValues) { + return false, nil + } + // we know the new column definition is identical to, or extends, the old definition. + // Now validate storage: + if strings.EqualFold(col.Type.Type, "enum") { + if len(col.Type.EnumValues) <= 255 && len(newCol.Type.EnumValues) > 255 { + // this increases the SET storage size (1 byte for up to 8 values, 2 bytes beyond) + return false, nil + } + } + if strings.EqualFold(col.Type.Type, "set") { + if (len(col.Type.EnumValues)+7)/8 != (len(newCol.Type.EnumValues)+7)/8 { + // this increases the SET storage size (1 byte for up to 8 values, 2 bytes for 8-15, etc.) + return false, nil + } + } + // Now don't care about change of default: + tableColDefinition := colStringStrippedDown(col, true) + newColDefinition := colStringStrippedDown(newCol, true) + if tableColDefinition == newColDefinition { + return capableOf(capabilities.InstantExpandEnumCapability) + } + } + return false, nil + } + // Up to 8.0.26 we could only ADD COLUMN as last column switch opt := alterOption.(type) { - case *sqlparser.ChangeColumn: - // We do not support INSTANT for renaming a column (ALTER TABLE ...CHANGE) because: - // 1. We discourage column rename - // 2. We do not produce CHANGE statements in declarative diff - // 3. The success of the operation depends on whether the column is referenced by a foreign key - // in another table. Which is a bit too much to compute here. - return false, nil case *sqlparser.AddColumns: if tableHasFulltextIndex { // not supported if the table has a FULLTEXT index @@ -157,49 +196,30 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab return capableOf(capabilities.InstantAddDropVirtualColumnFlavorCapability) } return capableOf(capabilities.InstantAddDropColumnFlavorCapability) + case *sqlparser.ChangeColumn: + // We do not support INSTANT for renaming a column (ALTER TABLE ...CHANGE) because: + // 1. We discourage column rename + // 2. We do not produce CHANGE statements in declarative diff + // 3. The success of the operation depends on whether the column is referenced by a foreign key + // in another table. Which is a bit too much to compute here. + if opt.OldColumn.Name.String() != opt.NewColDefinition.Name.String() { + return false, nil + } + if col := findColumn(opt.OldColumn.Name.String()); col != nil { + return changeModifyColumnCapableOfInstantDDL(col, opt.NewColDefinition) + } + return false, nil case *sqlparser.ModifyColumn: if col := findColumn(opt.NewColDefinition.Name.String()); col != nil { - // Check if only diff is change of default. - // We temporarily remove the DEFAULT expression (if any) from both - // table and ALTER statement, and compare the columns: if they're otherwise equal, - // then the only change can be an addition/change/removal of DEFAULT, which - // is instant-table. - tableColDefinition := colStringStrippedDown(col, true, false) - newColDefinition := colStringStrippedDown(opt.NewColDefinition, true, false) - if tableColDefinition == newColDefinition { - return capableOf(capabilities.InstantChangeColumnDefaultFlavorCapability) - } - // Check if: - // 1. this an ENUM/SET - // 2. and the change is to append values to the end of the list - // 3. and the number of added values does not increase the storage size for the enum/set - // 4. while still not caring about a change in the default value - if len(col.Type.EnumValues) > 0 && len(opt.NewColDefinition.Type.EnumValues) > 0 { - // both are enum or set - if !hasPrefix(opt.NewColDefinition.Type.EnumValues, col.Type.EnumValues) { - return false, nil - } - // we know the new column definition is identical to, or extends, the old definition. - // Now validate storage: - if strings.EqualFold(col.Type.Type, "enum") { - if len(col.Type.EnumValues) <= 255 && len(opt.NewColDefinition.Type.EnumValues) > 255 { - // this increases the SET storage size (1 byte for up to 8 values, 2 bytes beyond) - return false, nil - } - } - if strings.EqualFold(col.Type.Type, "set") { - if (len(col.Type.EnumValues)+7)/8 != (len(opt.NewColDefinition.Type.EnumValues)+7)/8 { - // this increases the SET storage size (1 byte for up to 8 values, 2 bytes for 8-15, etc.) - return false, nil - } - } - // Now don't care about change of default: - tableColDefinition := colStringStrippedDown(col, true, true) - newColDefinition := colStringStrippedDown(opt.NewColDefinition, true, true) - if tableColDefinition == newColDefinition { - return capableOf(capabilities.InstantExpandEnumCapability) - } - } + return changeModifyColumnCapableOfInstantDDL(col, opt.NewColDefinition) + } + return false, nil + case *sqlparser.AlterColumn: + if opt.DropDefault || opt.DefaultLiteral || opt.DefaultVal != nil { + return capableOf(capabilities.InstantChangeColumnDefaultFlavorCapability) + } + if opt.Invisible != nil { + return capableOf(capabilities.InstantChangeColumnVisibilityCapability) } return false, nil default: diff --git a/go/vt/schemadiff/capability_test.go b/go/vt/schemadiff/capability_test.go index ca3387bb1a7..b35afb7fe22 100644 --- a/go/vt/schemadiff/capability_test.go +++ b/go/vt/schemadiff/capability_test.go @@ -19,6 +19,7 @@ func TestAlterTableCapableOfInstantDDL(t *testing.T) { capabilities.InstantAddDropVirtualColumnFlavorCapability, capabilities.InstantAddDropColumnFlavorCapability, capabilities.InstantChangeColumnDefaultFlavorCapability, + capabilities.InstantChangeColumnVisibilityCapability, capabilities.InstantExpandEnumCapability: return true, nil } @@ -272,6 +273,36 @@ func TestAlterTableCapableOfInstantDDL(t *testing.T) { alter: "alter table t modify column c1 set('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i')", expectCapableOfInstantDDL: false, }, + { + name: "make a column invisible", + create: "create table t1 (id int, i1 int)", + alter: "alter table t1 modify column i1 int invisible", + expectCapableOfInstantDDL: true, + }, + { + name: "make a column visible", + create: "create table t1 (id int, i1 int)", + alter: "alter table t1 change column i1 i1 int visible", + expectCapableOfInstantDDL: true, + }, + { + name: "make a column visible with rename", + create: "create table t1 (id int, i1 int)", + alter: "alter table t1 change column i1 i2 int visible", + expectCapableOfInstantDDL: false, + }, + { + name: "make a column invisible via SET", + create: "create table t1 (id int, i1 int)", + alter: "alter table t1 alter column i1 set invisible", + expectCapableOfInstantDDL: true, + }, + { + name: "drop column default", + create: "create table t1 (id int, i1 int)", + alter: "alter table t1 alter column i1 drop default", + expectCapableOfInstantDDL: true, + }, } for _, tcase := range tcases { t.Run(tcase.name, func(t *testing.T) { From 67b5a6ddc3845c5722edbc1c7e1b9b72a54b8cf6 Mon Sep 17 00:00:00 2001 From: Matthias Crauwels Date: Tue, 30 Jul 2024 16:56:26 +0200 Subject: [PATCH 11/34] clarify collations are also supported for `db_charset` (#16423) Signed-off-by: Matthias Crauwels Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Co-authored-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- go/flags/endtoend/mysqlctl.txt | 2 +- go/flags/endtoend/mysqlctld.txt | 2 +- go/flags/endtoend/vtbackup.txt | 2 +- go/flags/endtoend/vtcombo.txt | 2 +- go/flags/endtoend/vttablet.txt | 2 +- go/vt/dbconfigs/dbconfigs.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go/flags/endtoend/mysqlctl.txt b/go/flags/endtoend/mysqlctl.txt index 044d12981d5..2b179496fff 100644 --- a/go/flags/endtoend/mysqlctl.txt +++ b/go/flags/endtoend/mysqlctl.txt @@ -40,7 +40,7 @@ Flags: --db-credentials-vault-tls-ca string Path to CA PEM for validating Vault server certificate --db-credentials-vault-tokenfile string Path to file containing Vault auth token; token can also be passed using VAULT_TOKEN environment variable --db-credentials-vault-ttl duration How long to cache DB credentials from the Vault server (default 30m0s) - --db_charset string Character set used for this tablet. (default "utf8mb4") + --db_charset string Character set/collation used for this tablet. Make sure to configure this to a charset/collation supported by the lowest MySQL version in your environment. (default "utf8mb4") --db_conn_query_info enable parsing and processing of QUERY_OK info fields --db_connect_timeout_ms int connection timeout to mysqld in milliseconds (0 for no timeout) --db_dba_password string db dba password diff --git a/go/flags/endtoend/mysqlctld.txt b/go/flags/endtoend/mysqlctld.txt index 6bb1beb5bae..d60a91ae65e 100644 --- a/go/flags/endtoend/mysqlctld.txt +++ b/go/flags/endtoend/mysqlctld.txt @@ -41,7 +41,7 @@ Flags: --db-credentials-vault-tls-ca string Path to CA PEM for validating Vault server certificate --db-credentials-vault-tokenfile string Path to file containing Vault auth token; token can also be passed using VAULT_TOKEN environment variable --db-credentials-vault-ttl duration How long to cache DB credentials from the Vault server (default 30m0s) - --db_charset string Character set used for this tablet. (default "utf8mb4") + --db_charset string Character set/collation used for this tablet. Make sure to configure this to a charset/collation supported by the lowest MySQL version in your environment. (default "utf8mb4") --db_conn_query_info enable parsing and processing of QUERY_OK info fields --db_connect_timeout_ms int connection timeout to mysqld in milliseconds (0 for no timeout) --db_dba_password string db dba password diff --git a/go/flags/endtoend/vtbackup.txt b/go/flags/endtoend/vtbackup.txt index 004871d7c09..fc00df479f5 100644 --- a/go/flags/endtoend/vtbackup.txt +++ b/go/flags/endtoend/vtbackup.txt @@ -92,7 +92,7 @@ Flags: --db_appdebug_password string db appdebug password --db_appdebug_use_ssl Set this flag to false to make the appdebug connection to not use ssl (default true) --db_appdebug_user string db appdebug user userKey (default "vt_appdebug") - --db_charset string Character set used for this tablet. (default "utf8mb4") + --db_charset string Character set/collation used for this tablet. Make sure to configure this to a charset/collation supported by the lowest MySQL version in your environment. (default "utf8mb4") --db_conn_query_info enable parsing and processing of QUERY_OK info fields --db_connect_timeout_ms int connection timeout to mysqld in milliseconds (0 for no timeout) --db_dba_password string db dba password diff --git a/go/flags/endtoend/vtcombo.txt b/go/flags/endtoend/vtcombo.txt index c59cd789ed3..381f7ca48cc 100644 --- a/go/flags/endtoend/vtcombo.txt +++ b/go/flags/endtoend/vtcombo.txt @@ -76,7 +76,7 @@ Flags: --db_appdebug_password string db appdebug password --db_appdebug_use_ssl Set this flag to false to make the appdebug connection to not use ssl (default true) --db_appdebug_user string db appdebug user userKey (default "vt_appdebug") - --db_charset string Character set used for this tablet. (default "utf8mb4") + --db_charset string Character set/collation used for this tablet. Make sure to configure this to a charset/collation supported by the lowest MySQL version in your environment. (default "utf8mb4") --db_conn_query_info enable parsing and processing of QUERY_OK info fields --db_connect_timeout_ms int connection timeout to mysqld in milliseconds (0 for no timeout) --db_dba_password string db dba password diff --git a/go/flags/endtoend/vttablet.txt b/go/flags/endtoend/vttablet.txt index f5a7f8e8f51..d3df3c3009f 100644 --- a/go/flags/endtoend/vttablet.txt +++ b/go/flags/endtoend/vttablet.txt @@ -110,7 +110,7 @@ Flags: --db_appdebug_password string db appdebug password --db_appdebug_use_ssl Set this flag to false to make the appdebug connection to not use ssl (default true) --db_appdebug_user string db appdebug user userKey (default "vt_appdebug") - --db_charset string Character set used for this tablet. (default "utf8mb4") + --db_charset string Character set/collation used for this tablet. Make sure to configure this to a charset/collation supported by the lowest MySQL version in your environment. (default "utf8mb4") --db_conn_query_info enable parsing and processing of QUERY_OK info fields --db_connect_timeout_ms int connection timeout to mysqld in milliseconds (0 for no timeout) --db_dba_password string db dba password diff --git a/go/vt/dbconfigs/dbconfigs.go b/go/vt/dbconfigs/dbconfigs.go index 82c322e7ae9..32fb2435286 100644 --- a/go/vt/dbconfigs/dbconfigs.go +++ b/go/vt/dbconfigs/dbconfigs.go @@ -133,7 +133,7 @@ func registerBaseFlags(fs *pflag.FlagSet) { fs.StringVar(&GlobalDBConfigs.Socket, "db_socket", "", "The unix socket to connect on. If this is specified, host and port will not be used.") fs.StringVar(&GlobalDBConfigs.Host, "db_host", "", "The host name for the tcp connection.") fs.IntVar(&GlobalDBConfigs.Port, "db_port", 0, "tcp port") - fs.StringVar(&GlobalDBConfigs.Charset, "db_charset", "utf8mb4", "Character set used for this tablet.") + fs.StringVar(&GlobalDBConfigs.Charset, "db_charset", "utf8mb4", "Character set/collation used for this tablet. Make sure to configure this to a charset/collation supported by the lowest MySQL version in your environment.") fs.Uint64Var(&GlobalDBConfigs.Flags, "db_flags", 0, "Flag values as defined by MySQL.") fs.StringVar(&GlobalDBConfigs.Flavor, "db_flavor", "", "Flavor overrid. Valid value is FilePos.") fs.Var(&GlobalDBConfigs.SslMode, "db_ssl_mode", "SSL mode to connect with. One of disabled, preferred, required, verify_ca & verify_identity.") From ad27066b460ae6bf34af0175d9eaf195d720c185 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:49:19 +0300 Subject: [PATCH 12/34] `schemadiff`: Online DDL support, declarative based (#16462) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- .../vrepl_suite/onlineddl_vrepl_suite_test.go | 1 + .../expect_failure | 2 +- .../testdata/fail-drop-pk/expect_failure | 2 +- .../testdata/fail-float-unique-key/create.sql | 2 +- .../fail-float-unique-key/expect_failure | 2 +- .../fail-no-unique-key/expect_failure | 2 +- .../testdata/fail-nullable-unique-key/alter | 1 + .../fail-nullable-unique-key/create.sql | 11 + .../fail-nullable-unique-key/expect_failure | 1 + go/vt/schemadiff/capability.go | 16 +- go/vt/schemadiff/column.go | 293 +++++- go/vt/schemadiff/column_test.go | 497 +++++++++ go/vt/schemadiff/key.go | 162 +++ go/vt/schemadiff/key_test.go | 185 ++++ go/vt/schemadiff/mysql.go | 80 +- go/vt/schemadiff/onlineddl.go | 590 +++++++++++ go/vt/schemadiff/onlineddl_test.go | 960 ++++++++++++++++++ go/vt/schemadiff/schema_diff_test.go | 1 - go/vt/schemadiff/table.go | 91 +- go/vt/schemadiff/table_test.go | 22 +- go/vt/schemadiff/view.go | 12 + go/vt/schemadiff/view_test.go | 11 +- go/vt/vttablet/onlineddl/executor.go | 55 +- go/vt/vttablet/onlineddl/schema.go | 100 +- go/vt/vttablet/onlineddl/vrepl.go | 477 ++------- go/vt/vttablet/onlineddl/vrepl/columns.go | 208 ---- .../vttablet/onlineddl/vrepl/columns_test.go | 380 ------- go/vt/vttablet/onlineddl/vrepl/foreign_key.go | 58 -- .../onlineddl/vrepl/foreign_key_test.go | 91 -- go/vt/vttablet/onlineddl/vrepl/parser.go | 112 -- go/vt/vttablet/onlineddl/vrepl/parser_test.go | 190 ---- go/vt/vttablet/onlineddl/vrepl/types.go | 293 ------ go/vt/vttablet/onlineddl/vrepl/types_test.go | 214 ---- go/vt/vttablet/onlineddl/vrepl/unique_key.go | 184 ---- .../onlineddl/vrepl/unique_key_test.go | 666 ------------ go/vt/vttablet/onlineddl/vrepl_test.go | 236 ++++- 36 files changed, 3249 insertions(+), 2959 deletions(-) create mode 100644 go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/alter create mode 100644 go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/create.sql create mode 100644 go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/expect_failure create mode 100644 go/vt/schemadiff/column_test.go create mode 100644 go/vt/schemadiff/key.go create mode 100644 go/vt/schemadiff/key_test.go create mode 100644 go/vt/schemadiff/onlineddl.go create mode 100644 go/vt/schemadiff/onlineddl_test.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/columns.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/columns_test.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/foreign_key.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/foreign_key_test.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/parser.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/parser_test.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/types.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/types_test.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/unique_key.go delete mode 100644 go/vt/vttablet/onlineddl/vrepl/unique_key_test.go diff --git a/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go b/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go index 57397ec64dd..c82b7f13a0d 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go +++ b/go/test/endtoend/onlineddl/vrepl_suite/onlineddl_vrepl_suite_test.go @@ -65,6 +65,7 @@ const ( testFilterEnvVar = "ONLINEDDL_SUITE_TEST_FILTER" ) +// Use $VREPL_SUITE_TEST_FILTER environment variable to filter tests by name. func TestMain(m *testing.M) { defer cluster.PanicHandler(nil) flag.Parse() diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-different-pk-new-pk-column/expect_failure b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-different-pk-new-pk-column/expect_failure index ae3584915dd..5e227f16a3c 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-different-pk-new-pk-column/expect_failure +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-different-pk-new-pk-column/expect_failure @@ -1 +1 @@ -Found no possible +found no possible diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-drop-pk/expect_failure b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-drop-pk/expect_failure index ae3584915dd..5e227f16a3c 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-drop-pk/expect_failure +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-drop-pk/expect_failure @@ -1 +1 @@ -Found no possible +found no possible diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/create.sql b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/create.sql index abd7fbd4266..3712a673838 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/create.sql +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/create.sql @@ -1,6 +1,6 @@ drop table if exists onlineddl_test; create table onlineddl_test ( - f float, + f float not null, i int not null, ts timestamp default current_timestamp, dt datetime, diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/expect_failure b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/expect_failure index ae3584915dd..5e227f16a3c 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/expect_failure +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-float-unique-key/expect_failure @@ -1 +1 @@ -Found no possible +found no possible diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-no-unique-key/expect_failure b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-no-unique-key/expect_failure index ae3584915dd..5e227f16a3c 100644 --- a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-no-unique-key/expect_failure +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-no-unique-key/expect_failure @@ -1 +1 @@ -Found no possible +found no possible diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/alter b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/alter new file mode 100644 index 00000000000..0d2477f5801 --- /dev/null +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/alter @@ -0,0 +1 @@ +add column v varchar(32) diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/create.sql b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/create.sql new file mode 100644 index 00000000000..71f112d33c2 --- /dev/null +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/create.sql @@ -0,0 +1,11 @@ +drop table if exists onlineddl_test; +create table onlineddl_test ( + id int, + i int not null, + ts timestamp default current_timestamp, + dt datetime, + key i_idx(i), + unique key id_uidx(id) +) auto_increment=1; + +drop event if exists onlineddl_test; diff --git a/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/expect_failure b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/expect_failure new file mode 100644 index 00000000000..5e227f16a3c --- /dev/null +++ b/go/test/endtoend/onlineddl/vrepl_suite/testdata/fail-nullable-unique-key/expect_failure @@ -0,0 +1 @@ +found no possible diff --git a/go/vt/schemadiff/capability.go b/go/vt/schemadiff/capability.go index 2a3e2d97c9b..1471599d390 100644 --- a/go/vt/schemadiff/capability.go +++ b/go/vt/schemadiff/capability.go @@ -61,18 +61,6 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab } } - isGeneratedColumn := func(col *sqlparser.ColumnDefinition) (bool, sqlparser.ColumnStorage) { - if col == nil { - return false, 0 - } - if col.Type.Options == nil { - return false, 0 - } - if col.Type.Options.As == nil { - return false, 0 - } - return true, col.Type.Options.Storage - } colStringStrippedDown := func(col *sqlparser.ColumnDefinition, stripEnum bool) string { strippedCol := sqlparser.Clone(col) // strip `default` @@ -153,7 +141,7 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab return false, nil } for _, column := range opt.Columns { - if isGenerated, storage := isGeneratedColumn(column); isGenerated { + if isGenerated, storage := IsGeneratedColumn(column); isGenerated { if storage == sqlparser.StoredStorage { // Adding a generated "STORED" column is unsupported return false, nil @@ -188,7 +176,7 @@ func alterOptionCapableOfInstantDDL(alterOption sqlparser.AlterOption, createTab // not supported if the column is part of an index return false, nil } - if isGenerated, _ := isGeneratedColumn(col); isGenerated { + if isGenerated, _ := IsGeneratedColumn(col); isGenerated { // supported by all 8.0 versions // Note: according to the docs dropping a STORED generated column is not INSTANT-able, // but in practice this is supported. This is why we don't test for STORED here, like diff --git a/go/vt/schemadiff/column.go b/go/vt/schemadiff/column.go index 7e55192cb06..63181cef9cb 100644 --- a/go/vt/schemadiff/column.go +++ b/go/vt/schemadiff/column.go @@ -20,6 +20,7 @@ import ( "strings" "vitess.io/vitess/go/mysql/collations" + "vitess.io/vitess/go/ptr" "vitess.io/vitess/go/vt/sqlparser" ) @@ -71,63 +72,100 @@ func NewModifyColumnDiffByDefinition(definition *sqlparser.ColumnDefinition) *Mo } type ColumnDefinitionEntity struct { - columnDefinition *sqlparser.ColumnDefinition + ColumnDefinition *sqlparser.ColumnDefinition + inPK bool // Does this column appear in the primary key? tableCharsetCollate *charsetCollate Env *Environment } -func NewColumnDefinitionEntity(env *Environment, c *sqlparser.ColumnDefinition, tableCharsetCollate *charsetCollate) *ColumnDefinitionEntity { +func NewColumnDefinitionEntity(env *Environment, c *sqlparser.ColumnDefinition, inPK bool, tableCharsetCollate *charsetCollate) *ColumnDefinitionEntity { return &ColumnDefinitionEntity{ - columnDefinition: c, + ColumnDefinition: c, + inPK: inPK, tableCharsetCollate: tableCharsetCollate, Env: env, } } +func (c *ColumnDefinitionEntity) Name() string { + return c.ColumnDefinition.Name.String() +} + +func (c *ColumnDefinitionEntity) NameLowered() string { + return c.ColumnDefinition.Name.Lowered() +} + func (c *ColumnDefinitionEntity) Clone() *ColumnDefinitionEntity { clone := &ColumnDefinitionEntity{ - columnDefinition: sqlparser.Clone(c.columnDefinition), + ColumnDefinition: sqlparser.Clone(c.ColumnDefinition), + inPK: c.inPK, tableCharsetCollate: c.tableCharsetCollate, Env: c.Env, } return clone } +// SetExplicitDefaultAndNull sets: +// - NOT NULL, if the columns is part of the PRIMARY KEY +// - DEFAULT NULL, if the columns is NULLable and no DEFAULT was mentioned +// Normally in schemadiff we work the opposite way: we strive to have the minimal equivalent representation +// of a definition. But this function can be used (often in conjunction with Clone()) to enrich a column definition +// so as to have explicit and authoritative view on any particular column. +func (c *ColumnDefinitionEntity) SetExplicitDefaultAndNull() { + if c.inPK { + // Any column in the primary key is implicitly NOT NULL. + c.ColumnDefinition.Type.Options.Null = ptr.Of(false) + } + if c.ColumnDefinition.Type.Options.Null == nil || *c.ColumnDefinition.Type.Options.Null { + // Nullable column, let'se see if there's already a DEFAULT. + if c.ColumnDefinition.Type.Options.Default == nil { + // nope, let's add a DEFAULT NULL + c.ColumnDefinition.Type.Options.Default = &sqlparser.NullVal{} + } + } +} + // SetExplicitCharsetCollate enriches this column definition with collation and charset. Those may be // already present, or perhaps just one of them is present (in which case we use the one to populate the other), // or both might be missing, in which case we use the table's charset/collation. +// Normally in schemadiff we work the opposite way: we strive to have the minimal equivalent representation +// of a definition. But this function can be used (often in conjunction with Clone()) to enrich a column definition +// so as to have explicit and authoritative view on any particular column. func (c *ColumnDefinitionEntity) SetExplicitCharsetCollate() error { if !c.IsTextual() { return nil } // We will now denormalize the columns charset & collate as needed (if empty, populate from table.) // Normalizing _this_ column definition: - if c.columnDefinition.Type.Charset.Name != "" && c.columnDefinition.Type.Options.Collate == "" { + if c.ColumnDefinition.Type.Charset.Name != "" && c.ColumnDefinition.Type.Options.Collate == "" { // Charset defined without collation. Assign the default collation for that charset. - collation := c.Env.CollationEnv().DefaultCollationForCharset(c.columnDefinition.Type.Charset.Name) + collation := c.Env.CollationEnv().DefaultCollationForCharset(c.ColumnDefinition.Type.Charset.Name) if collation == collations.Unknown { - return &UnknownColumnCharsetCollationError{Column: c.columnDefinition.Name.String(), Charset: c.tableCharsetCollate.charset} + return &UnknownColumnCharsetCollationError{Column: c.ColumnDefinition.Name.String(), Charset: c.tableCharsetCollate.charset} } - c.columnDefinition.Type.Options.Collate = c.Env.CollationEnv().LookupName(collation) + c.ColumnDefinition.Type.Options.Collate = c.Env.CollationEnv().LookupName(collation) } - if c.columnDefinition.Type.Charset.Name == "" && c.columnDefinition.Type.Options.Collate != "" { + if c.ColumnDefinition.Type.Charset.Name == "" && c.ColumnDefinition.Type.Options.Collate != "" { // Column has explicit collation but no charset. We can infer the charset from the collation. - collationID := c.Env.CollationEnv().LookupByName(c.columnDefinition.Type.Options.Collate) + collationID := c.Env.CollationEnv().LookupByName(c.ColumnDefinition.Type.Options.Collate) charset := c.Env.CollationEnv().LookupCharsetName(collationID) if charset == "" { - return &UnknownColumnCollationCharsetError{Column: c.columnDefinition.Name.String(), Collation: c.columnDefinition.Type.Options.Collate} + return &UnknownColumnCollationCharsetError{Column: c.ColumnDefinition.Name.String(), Collation: c.ColumnDefinition.Type.Options.Collate} } - c.columnDefinition.Type.Charset.Name = charset + c.ColumnDefinition.Type.Charset.Name = charset } - if c.columnDefinition.Type.Charset.Name == "" { + if c.ColumnDefinition.Type.Charset.Name == "" { // Still nothing? Assign the table's charset/collation. - c.columnDefinition.Type.Charset.Name = c.tableCharsetCollate.charset - if c.columnDefinition.Type.Options.Collate = c.tableCharsetCollate.collate; c.columnDefinition.Type.Options.Collate == "" { + c.ColumnDefinition.Type.Charset.Name = c.tableCharsetCollate.charset + if c.ColumnDefinition.Type.Options.Collate == "" { + c.ColumnDefinition.Type.Options.Collate = c.tableCharsetCollate.collate + } + if c.ColumnDefinition.Type.Options.Collate = c.tableCharsetCollate.collate; c.ColumnDefinition.Type.Options.Collate == "" { collation := c.Env.CollationEnv().DefaultCollationForCharset(c.tableCharsetCollate.charset) if collation == collations.Unknown { - return &UnknownColumnCharsetCollationError{Column: c.columnDefinition.Name.String(), Charset: c.tableCharsetCollate.charset} + return &UnknownColumnCharsetCollationError{Column: c.ColumnDefinition.Name.String(), Charset: c.tableCharsetCollate.charset} } - c.columnDefinition.Type.Options.Collate = c.Env.CollationEnv().LookupName(collation) + c.ColumnDefinition.Type.Options.Collate = c.Env.CollationEnv().LookupName(collation) } } return nil @@ -168,7 +206,7 @@ func (c *ColumnDefinitionEntity) ColumnDiff( } } - if sqlparser.Equals.RefOfColumnDefinition(cClone.columnDefinition, otherClone.columnDefinition) { + if sqlparser.Equals.RefOfColumnDefinition(cClone.ColumnDefinition, otherClone.ColumnDefinition) { return nil, nil } @@ -181,19 +219,228 @@ func (c *ColumnDefinitionEntity) ColumnDiff( } switch hints.EnumReorderStrategy { case EnumReorderStrategyReject: - otherEnumValuesMap := getEnumValuesMap(otherClone.columnDefinition.Type.EnumValues) - for ordinal, enumValue := range cClone.columnDefinition.Type.EnumValues { + otherEnumValuesMap := getEnumValuesMap(otherClone.ColumnDefinition.Type.EnumValues) + for ordinal, enumValue := range cClone.ColumnDefinition.Type.EnumValues { if otherOrdinal, ok := otherEnumValuesMap[enumValue]; ok { if ordinal != otherOrdinal { - return nil, &EnumValueOrdinalChangedError{Table: tableName, Column: cClone.columnDefinition.Name.String(), Value: enumValue, Ordinal: ordinal, NewOrdinal: otherOrdinal} + return nil, &EnumValueOrdinalChangedError{Table: tableName, Column: cClone.ColumnDefinition.Name.String(), Value: enumValue, Ordinal: ordinal, NewOrdinal: otherOrdinal} } } } } - return NewModifyColumnDiffByDefinition(other.columnDefinition), nil + return NewModifyColumnDiffByDefinition(other.ColumnDefinition), nil +} + +// Type returns the column's type +func (c *ColumnDefinitionEntity) Type() string { + return c.ColumnDefinition.Type.Type } // IsTextual returns true when this column is of textual type, and is capable of having a character set property func (c *ColumnDefinitionEntity) IsTextual() bool { - return charsetTypes[strings.ToLower(c.columnDefinition.Type.Type)] + return charsetTypes[strings.ToLower(c.Type())] +} + +// IsGenerated returns true when this column is generated, and indicates the storage type (virtual/stored) +func IsGeneratedColumn(col *sqlparser.ColumnDefinition) (bool, sqlparser.ColumnStorage) { + if col == nil { + return false, 0 + } + if col.Type.Options == nil { + return false, 0 + } + if col.Type.Options.As == nil { + return false, 0 + } + return true, col.Type.Options.Storage +} + +// IsGenerated returns true when this column is generated, and indicates the storage type (virtual/stored) +func (c *ColumnDefinitionEntity) IsGenerated() bool { + isGenerated, _ := IsGeneratedColumn(c.ColumnDefinition) + return isGenerated +} + +// IsNullable returns true when this column is NULLable +func (c *ColumnDefinitionEntity) IsNullable() bool { + if c.inPK { + return false + } + return c.ColumnDefinition.Type.Options.Null == nil || *c.ColumnDefinition.Type.Options.Null +} + +// IsDefaultNull returns true when this column has DEFAULT NULL +func (c *ColumnDefinitionEntity) IsDefaultNull() bool { + if !c.IsNullable() { + return false + } + _, ok := c.ColumnDefinition.Type.Options.Default.(*sqlparser.NullVal) + return ok +} + +// IsDefaultNull returns true when this column has DEFAULT NULL +func (c *ColumnDefinitionEntity) HasDefault() bool { + if c.ColumnDefinition.Type.Options.Default == nil { + return false + } + if c.IsDefaultNull() { + return true + } + return true +} + +// IsAutoIncrement returns true when this column is AUTO_INCREMENT +func (c *ColumnDefinitionEntity) IsAutoIncrement() bool { + return c.ColumnDefinition.Type.Options.Autoincrement +} + +// IsUnsigned returns true when this column is UNSIGNED +func (c *ColumnDefinitionEntity) IsUnsigned() bool { + return c.ColumnDefinition.Type.Unsigned +} + +// IsNumeric returns true when this column is a numeric type +func (c *ColumnDefinitionEntity) IsIntegralType() bool { + return IsIntegralType(c.Type()) +} + +// IsFloatingPointType returns true when this column is a floating point type +func (c *ColumnDefinitionEntity) IsFloatingPointType() bool { + return IsFloatingPointType(c.Type()) +} + +// IsDecimalType returns true when this column is a decimal type +func (c *ColumnDefinitionEntity) IsDecimalType() bool { + return IsDecimalType(c.Type()) +} + +// HasBlobTypeStorage returns true when this column is a text/blob type +func (c *ColumnDefinitionEntity) HasBlobTypeStorage() bool { + return BlobTypeStorage(c.Type()) != 0 +} + +// Charset returns the column's charset +func (c *ColumnDefinitionEntity) Charset() string { + return c.ColumnDefinition.Type.Charset.Name +} + +// Collate returns the column's collation +func (c *ColumnDefinitionEntity) Collate() string { + return c.ColumnDefinition.Type.Options.Collate +} + +func (c *ColumnDefinitionEntity) EnumValues() []string { + return c.ColumnDefinition.Type.EnumValues +} + +func (c *ColumnDefinitionEntity) HasEnumValues() bool { + return len(c.EnumValues()) > 0 +} + +// EnumValuesOrdinals returns a map of enum values to their ordinals +func (c *ColumnDefinitionEntity) EnumValuesOrdinals() map[string]int { + m := make(map[string]int, len(c.ColumnDefinition.Type.EnumValues)) + for i, enumValue := range c.ColumnDefinition.Type.EnumValues { + m[enumValue] = i + 1 + } + return m +} + +// EnumOrdinalValues returns a map of enum ordinals to their values +func (c *ColumnDefinitionEntity) EnumOrdinalValues() map[int]string { + m := make(map[int]string, len(c.ColumnDefinition.Type.EnumValues)) + for i, enumValue := range c.ColumnDefinition.Type.EnumValues { + // SET and ENUM values are 1 indexed. + m[i+1] = enumValue + } + return m +} + +// Length returns the type length (e.g. 17 for VARCHAR(17), 10 for DECIMAL(10,2), 6 for TIMESTAMP(6), etc.) +func (c *ColumnDefinitionEntity) Length() int { + if c.ColumnDefinition.Type.Length == nil { + return 0 + } + return *c.ColumnDefinition.Type.Length +} + +// Scale returns the type scale (e.g. 2 for DECIMAL(10,2)) +func (c *ColumnDefinitionEntity) Scale() int { + if c.ColumnDefinition.Type.Scale == nil { + return 0 + } + return *c.ColumnDefinition.Type.Scale +} + +// ColumnDefinitionEntityList is a formalized list of ColumnDefinitionEntity, with some +// utility functions. +type ColumnDefinitionEntityList struct { + Entities []*ColumnDefinitionEntity + byName map[string]*ColumnDefinitionEntity +} + +func NewColumnDefinitionEntityList(entities []*ColumnDefinitionEntity) *ColumnDefinitionEntityList { + list := &ColumnDefinitionEntityList{ + Entities: entities, + byName: make(map[string]*ColumnDefinitionEntity), + } + for _, entity := range entities { + list.byName[entity.Name()] = entity + list.byName[entity.NameLowered()] = entity + } + return list +} + +func (l *ColumnDefinitionEntityList) Len() int { + return len(l.Entities) +} + +// Names returns the names of all the columns in this list +func (l *ColumnDefinitionEntityList) Names() []string { + names := make([]string, len(l.Entities)) + for i, entity := range l.Entities { + names[i] = entity.Name() + } + return names +} + +// GetColumn returns the column with the given name, or nil if not found +func (l *ColumnDefinitionEntityList) GetColumn(name string) *ColumnDefinitionEntity { + return l.byName[name] +} + +// Contains returns true when this list contains all the entities from the other list +func (l *ColumnDefinitionEntityList) Contains(other *ColumnDefinitionEntityList) bool { + for _, entity := range other.Entities { + if l.GetColumn(entity.NameLowered()) == nil { + return false + } + } + return true +} + +// Union returns a new ColumnDefinitionEntityList with all the entities from this list and the other list +func (l *ColumnDefinitionEntityList) Union(other *ColumnDefinitionEntityList) *ColumnDefinitionEntityList { + entities := append(l.Entities, other.Entities...) + return NewColumnDefinitionEntityList(entities) +} + +// Clone creates a copy of this list, with copies of the entities +func (l *ColumnDefinitionEntityList) Clone() *ColumnDefinitionEntityList { + entities := make([]*ColumnDefinitionEntity, len(l.Entities)) + for i, entity := range l.Entities { + entities[i] = entity.Clone() + } + return NewColumnDefinitionEntityList(entities) +} + +// Filter returns a new subset ColumnDefinitionEntityList with only the entities that pass the filter +func (l *ColumnDefinitionEntityList) Filter(include func(entity *ColumnDefinitionEntity) bool) *ColumnDefinitionEntityList { + var entities []*ColumnDefinitionEntity + for _, entity := range l.Entities { + if include(entity) { + entities = append(entities, entity) + } + } + return NewColumnDefinitionEntityList(entities) } diff --git a/go/vt/schemadiff/column_test.go b/go/vt/schemadiff/column_test.go new file mode 100644 index 00000000000..f1b8f9e4f75 --- /dev/null +++ b/go/vt/schemadiff/column_test.go @@ -0,0 +1,497 @@ +/* +Copyright 2024 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 schemadiff + +import ( + "fmt" + "testing" + + "golang.org/x/exp/maps" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestColumnFunctions(t *testing.T) { + table := ` + create table t ( + id int, + col1 int, + col2 int not null, + col3 int default null, + col4 int default 1, + COL5 int default 1, + ts1 timestamp, + ts2 timestamp(3) null, + ts3 timestamp(6) not null, + primary key (id) + )` + env := NewTestEnv() + createTableEntity, err := NewCreateTableEntityFromSQL(env, table) + require.NoError(t, err) + m := createTableEntity.ColumnDefinitionEntitiesMap() + for _, col := range m { + col.SetExplicitDefaultAndNull() + err := col.SetExplicitCharsetCollate() + require.NoError(t, err) + } + + t.Run("nullable", func(t *testing.T) { + assert.False(t, m["id"].IsNullable()) + assert.True(t, m["col1"].IsNullable()) + assert.False(t, m["col2"].IsNullable()) + assert.True(t, m["col3"].IsNullable()) + assert.True(t, m["col4"].IsNullable()) + assert.True(t, m["col5"].IsNullable()) + assert.True(t, m["ts1"].IsNullable()) + assert.True(t, m["ts2"].IsNullable()) + assert.False(t, m["ts3"].IsNullable()) + }) + t.Run("default null", func(t *testing.T) { + assert.False(t, m["id"].IsDefaultNull()) + assert.True(t, m["col1"].IsDefaultNull()) + assert.False(t, m["col2"].IsDefaultNull()) + assert.True(t, m["col3"].IsDefaultNull()) + assert.False(t, m["col4"].IsDefaultNull()) + assert.False(t, m["col5"].IsDefaultNull()) + assert.True(t, m["ts1"].IsDefaultNull()) + assert.True(t, m["ts2"].IsDefaultNull()) + assert.False(t, m["ts3"].IsDefaultNull()) + }) + t.Run("has default", func(t *testing.T) { + assert.False(t, m["id"].HasDefault()) + assert.True(t, m["col1"].HasDefault()) + assert.False(t, m["col2"].HasDefault()) + assert.True(t, m["col3"].HasDefault()) + assert.True(t, m["col4"].HasDefault()) + assert.True(t, m["col5"].HasDefault()) + assert.True(t, m["ts1"].HasDefault()) + assert.True(t, m["ts2"].HasDefault()) + assert.False(t, m["ts3"].HasDefault()) + }) +} + +func TestExpands(t *testing.T) { + tcases := []struct { + source string + target string + expands bool + msg string + }{ + { + source: "int", + target: "int", + }, + { + source: "int", + target: "smallint", + }, + { + source: "int", + target: "smallint unsigned", + }, + { + source: "int unsigned", + target: "tinyint", + expands: true, + msg: "source is unsigned, target is signed", + }, + { + source: "int unsigned", + target: "tinyint signed", + expands: true, + msg: "source is unsigned, target is signed", + }, + { + source: "int", + target: "tinyint", + }, + { + source: "int", + target: "bigint", + expands: true, + msg: "increased integer range", + }, + { + source: "int", + target: "bigint unsigned", + expands: true, + msg: "increased integer range", + }, + { + source: "int", + target: "int unsigned", + expands: true, + msg: "target unsigned value exceeds source unsigned value", + }, + { + source: "int unsigned", + target: "int", + expands: true, + msg: "source is unsigned, target is signed", + }, + { + source: "int", + target: "int default null", + }, + { + source: "int default null", + target: "int", + }, + { + source: "int", + target: "int not null", + }, + { + source: "int not null", + target: "int", + expands: true, + msg: "target is NULL-able, source is not", + }, + { + source: "int not null", + target: "int default null", + expands: true, + msg: "target is NULL-able, source is not", + }, + { + source: "float", + target: "int", + }, + { + source: "int", + target: "float", + expands: true, + msg: "target is floating point, source is not", + }, + { + source: "float", + target: "double", + expands: true, + msg: "increased floating point range", + }, + { + source: "decimal(5,2)", + target: "float", + expands: true, + msg: "target is floating point, source is not", + }, + { + source: "int", + target: "decimal", + expands: true, + msg: "target is decimal, source is not", + }, + { + source: "int", + target: "decimal(5,2)", + expands: true, + msg: "increased length", + }, + { + source: "int", + target: "decimal(5,0)", + expands: true, + msg: "increased length", + }, + { + source: "decimal(5,2)", // 123.45 + target: "decimal(3,2)", // 1.23 + }, + { + source: "decimal(5,2)", // 123.45 + target: "decimal(4,1)", // 123.4 + }, + { + source: "decimal(5,2)", // 123.45 + target: "decimal(5,1)", // 1234.5 + expands: true, + msg: "increased decimal range", + }, + { + source: "char(7)", + target: "char(7)", + }, + { + source: "char(7)", + target: "varchar(7)", + }, + { + source: "char(7)", + target: "varchar(5)", + }, + { + source: "char(5)", + target: "varchar(7)", + expands: true, + msg: "increased length", + }, + { + source: "varchar(5)", + target: "char(7)", + expands: true, + msg: "increased length", + }, + { + source: "tinytext", + target: "tinytext", + }, + { + source: "tinytext", + target: "tinyblob", + }, + { + source: "mediumtext", + target: "tinytext", + }, + { + source: "mediumblob", + target: "tinytext", + }, + { + source: "tinytext", + target: "text", + expands: true, + msg: "increased blob range", + }, + { + source: "tinytext", + target: "mediumblob", + expands: true, + msg: "increased blob range", + }, + { + source: "timestamp", + target: "timestamp", + }, + { + source: "timestamp", + target: "time", + }, + { + source: "datetime", + target: "timestamp", + }, + { + source: "datetime", + target: "date", + }, + { + source: "time", + target: "timestamp", + expands: true, + msg: "target is expanded data type of source", + }, + { + source: "timestamp", + target: "datetime", + expands: true, + msg: "target is expanded data type of source", + }, + { + source: "date", + target: "datetime", + expands: true, + msg: "target is expanded data type of source", + }, + { + source: "timestamp", + target: "timestamp(3)", + expands: true, + msg: "increased length", + }, + { + source: "timestamp", + target: "timestamp(6)", + expands: true, + msg: "increased length", + }, + { + source: "timestamp(3)", + target: "timestamp(6)", + expands: true, + msg: "increased length", + }, + { + source: "timestamp(6)", + target: "timestamp(3)", + }, + { + source: "timestamp(6)", + target: "timestamp", + }, + { + source: "timestamp", + target: "time(3)", + expands: true, + msg: "increased length", + }, + { + source: "datetime", + target: "time(3)", + expands: true, + msg: "increased length", + }, + { + source: "enum('a','b')", + target: "enum('a','b')", + }, + { + source: "enum('a','b')", + target: "enum('a')", + }, + { + source: "enum('a','b')", + target: "enum('b')", + expands: true, + msg: "target enum/set expands or reorders source enum/set", + }, + { + source: "enum('a','b')", + target: "enum('a','b','c')", + expands: true, + msg: "target enum/set expands or reorders source enum/set", + }, + { + source: "enum('a','b')", + target: "enum('a','x')", + expands: true, + msg: "target enum/set expands or reorders source enum/set", + }, + { + source: "set('a','b')", + target: "set('a','b')", + }, + { + source: "set('a','b')", + target: "set('a','b','c')", + expands: true, + msg: "target enum/set expands or reorders source enum/set", + }, + } + env := NewTestEnv() + for _, tcase := range tcases { + t.Run(tcase.source+" -> "+tcase.target, func(t *testing.T) { + fromCreateTableSQL := fmt.Sprintf("create table t (col %s)", tcase.source) + from, err := NewCreateTableEntityFromSQL(env, fromCreateTableSQL) + require.NoError(t, err) + + toCreateTableSQL := fmt.Sprintf("create table t (col %s)", tcase.target) + to, err := NewCreateTableEntityFromSQL(env, toCreateTableSQL) + require.NoError(t, err) + + require.Len(t, from.ColumnDefinitionEntities(), 1) + fromCol := from.ColumnDefinitionEntities()[0] + require.Len(t, to.ColumnDefinitionEntities(), 1) + toCol := to.ColumnDefinitionEntities()[0] + + expands, message := ColumnChangeExpandsDataRange(fromCol, toCol) + assert.Equal(t, tcase.expands, expands, message) + if expands { + require.NotEmpty(t, tcase.msg, message) + } + assert.Contains(t, message, tcase.msg) + }) + } +} + +func TestColumnDefinitionEntityList(t *testing.T) { + table := ` + create table t ( + id int, + col1 int, + Col2 int not null, + primary key (id) + )` + env := NewTestEnv() + createTableEntity, err := NewCreateTableEntityFromSQL(env, table) + require.NoError(t, err) + entities := createTableEntity.ColumnDefinitionEntities() + require.NotEmpty(t, entities) + list := NewColumnDefinitionEntityList(entities) + assert.NotNil(t, list.GetColumn("id")) + assert.NotNil(t, list.GetColumn("col1")) + assert.NotNil(t, list.GetColumn("Col2")) + assert.NotNil(t, list.GetColumn("col2")) // we also allow lower case + assert.Nil(t, list.GetColumn("COL2")) + assert.Nil(t, list.GetColumn("ID")) + assert.Nil(t, list.GetColumn("Col1")) + assert.Nil(t, list.GetColumn("col3")) +} + +func TestColumnDefinitionEntityListSubset(t *testing.T) { + table1 := ` + create table t ( + ID int, + col1 int, + Col2 int not null, + primary key (id) + )` + table2 := ` + create table t ( + id int, + Col1 int, + primary key (id) + )` + env := NewTestEnv() + createTableEntity1, err := NewCreateTableEntityFromSQL(env, table1) + require.NoError(t, err) + entities1 := createTableEntity1.ColumnDefinitionEntities() + require.NotEmpty(t, entities1) + list1 := NewColumnDefinitionEntityList(entities1) + + createTableEntity2, err := NewCreateTableEntityFromSQL(env, table2) + require.NoError(t, err) + entities2 := createTableEntity2.ColumnDefinitionEntities() + require.NotEmpty(t, entities2) + list2 := NewColumnDefinitionEntityList(entities2) + + assert.True(t, list1.Contains(list2)) + assert.False(t, list2.Contains(list1)) +} + +func TestColumnDefinitionEntity(t *testing.T) { + table1 := ` + create table t ( + it int, + e enum('a','b','c'), + primary key (id) + )` + env := NewTestEnv() + createTableEntity1, err := NewCreateTableEntityFromSQL(env, table1) + require.NoError(t, err) + entities1 := createTableEntity1.ColumnDefinitionEntities() + require.NotEmpty(t, entities1) + list1 := NewColumnDefinitionEntityList(entities1) + + t.Run("enum", func(t *testing.T) { + enumCol := list1.GetColumn("e") + require.NotNil(t, enumCol) + assert.Equal(t, []string{"'a'", "'b'", "'c'"}, enumCol.EnumValues()) + + { + ordinalsMap := enumCol.EnumValuesOrdinals() + assert.ElementsMatch(t, []int{1, 2, 3}, maps.Values(ordinalsMap)) + assert.ElementsMatch(t, []string{"'a'", "'b'", "'c'"}, maps.Keys(ordinalsMap)) + } + { + valuesMap := enumCol.EnumOrdinalValues() + assert.ElementsMatch(t, []int{1, 2, 3}, maps.Keys(valuesMap)) + assert.ElementsMatch(t, []string{"'a'", "'b'", "'c'"}, maps.Values(valuesMap)) + } + }) +} diff --git a/go/vt/schemadiff/key.go b/go/vt/schemadiff/key.go new file mode 100644 index 00000000000..865073a5a98 --- /dev/null +++ b/go/vt/schemadiff/key.go @@ -0,0 +1,162 @@ +/* +Copyright 2024 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 schemadiff + +import ( + "vitess.io/vitess/go/vt/sqlparser" +) + +// IndexDefinitionEntity represents an index definition in a CREATE TABLE statement, +// and includes the list of columns that are part of the index. +type IndexDefinitionEntity struct { + IndexDefinition *sqlparser.IndexDefinition + ColumnList *ColumnDefinitionEntityList + Env *Environment +} + +func NewIndexDefinitionEntity(env *Environment, indexDefinition *sqlparser.IndexDefinition, columnDefinitionEntitiesList *ColumnDefinitionEntityList) *IndexDefinitionEntity { + return &IndexDefinitionEntity{ + IndexDefinition: indexDefinition, + ColumnList: columnDefinitionEntitiesList, + Env: env, + } +} + +func (i *IndexDefinitionEntity) Name() string { + return i.IndexDefinition.Info.Name.String() +} + +func (i *IndexDefinitionEntity) NameLowered() string { + return i.IndexDefinition.Info.Name.Lowered() +} + +// Clone returns a copy of this list, with copies of all the entities. +func (i *IndexDefinitionEntity) Clone() *IndexDefinitionEntity { + clone := &IndexDefinitionEntity{ + IndexDefinition: sqlparser.Clone(i.IndexDefinition), + ColumnList: i.ColumnList.Clone(), + Env: i.Env, + } + return clone +} + +func (i *IndexDefinitionEntity) Len() int { + return len(i.IndexDefinition.Columns) +} + +// IsPrimary returns true if the index is a primary key. +func (i *IndexDefinitionEntity) IsPrimary() bool { + return i.IndexDefinition.Info.Type == sqlparser.IndexTypePrimary +} + +// IsUnique returns true if the index is a unique key. +func (i *IndexDefinitionEntity) IsUnique() bool { + return i.IndexDefinition.Info.IsUnique() +} + +// HasNullable returns true if any of the columns in the index are nullable. +func (i *IndexDefinitionEntity) HasNullable() bool { + for _, col := range i.ColumnList.Entities { + if col.IsNullable() { + return true + } + } + return false +} + +// HasFloat returns true if any of the columns in the index are floating point types. +func (i *IndexDefinitionEntity) HasFloat() bool { + for _, col := range i.ColumnList.Entities { + if col.IsFloatingPointType() { + return true + } + } + return false +} + +// HasColumnPrefix returns true if any of the columns in the index have a length prefix. +func (i *IndexDefinitionEntity) HasColumnPrefix() bool { + for _, col := range i.IndexDefinition.Columns { + if col.Length != nil { + return true + } + } + return false +} + +// ColumnNames returns the names of the columns in the index. +func (i *IndexDefinitionEntity) ColumnNames() []string { + names := make([]string, 0, len(i.IndexDefinition.Columns)) + for _, col := range i.IndexDefinition.Columns { + names = append(names, col.Column.String()) + } + return names +} + +// ContainsColumns returns true if the index contains all the columns in the given list. +func (i *IndexDefinitionEntity) ContainsColumns(columns *ColumnDefinitionEntityList) bool { + return i.ColumnList.Contains(columns) +} + +// CoveredByColumns returns true if the index is covered by the given list of columns. +func (i *IndexDefinitionEntity) CoveredByColumns(columns *ColumnDefinitionEntityList) bool { + return columns.Contains(i.ColumnList) +} + +// IndexDefinitionEntityList is a formalized list of IndexDefinitionEntity objects with a few +// utility methods. +type IndexDefinitionEntityList struct { + Entities []*IndexDefinitionEntity +} + +func NewIndexDefinitionEntityList(entities []*IndexDefinitionEntity) *IndexDefinitionEntityList { + return &IndexDefinitionEntityList{ + Entities: entities, + } +} + +func (l *IndexDefinitionEntityList) Len() int { + return len(l.Entities) +} + +// Names returns the names of the indexes in the list. +func (l *IndexDefinitionEntityList) Names() []string { + names := make([]string, len(l.Entities)) + for i, entity := range l.Entities { + names[i] = entity.Name() + } + return names +} + +// SubsetCoveredByColumns returns a new list of indexes that are covered by the given list of columns. +func (l *IndexDefinitionEntityList) SubsetCoveredByColumns(columns *ColumnDefinitionEntityList) *IndexDefinitionEntityList { + var subset []*IndexDefinitionEntity + for _, entity := range l.Entities { + if entity.CoveredByColumns(columns) { + subset = append(subset, entity) + } + } + return NewIndexDefinitionEntityList(subset) +} + +// First returns the first index in the list, or nil if the list is empty. +func (l *IndexDefinitionEntityList) First() *IndexDefinitionEntity { + if len(l.Entities) == 0 { + return nil + } + return l.Entities[0] +} diff --git a/go/vt/schemadiff/key_test.go b/go/vt/schemadiff/key_test.go new file mode 100644 index 00000000000..f11d5589ab3 --- /dev/null +++ b/go/vt/schemadiff/key_test.go @@ -0,0 +1,185 @@ +/* +Copyright 2024 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 schemadiff + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIndexDefinitionEntityMap(t *testing.T) { + table := ` + create table t ( + id int, + col1 int, + Col2 int not null, + col3 int not null default 3, + f float not null, + v varchar(32), + primary key (id), + unique key ukid (id), + unique key uk1 (col1), + unique key uk2 (Col2), + unique key uk3 (col3), + key k1 (col1), + key k2 (Col2), + key k3 (col3), + key kf (f), + key kf2 (f, Col2), + key kv (v), + key kv1 (v, col1), + key kv2 (v(10), Col2), + unique key uk12 (col1, Col2), + unique key uk21 (col2, Col1), + unique key uk23 (col2, col3), + unique key ukid3 (id, col3) + )` + tcases := []struct { + key string + unique bool + columns []string + nullable bool + float bool + prefix bool + }{ + { + key: "primary", + unique: true, + columns: []string{"id"}, + nullable: false, + }, + { + key: "ukid", + unique: true, + columns: []string{"id"}, + nullable: false, + }, + { + key: "uk1", + unique: true, + columns: []string{"col1"}, + nullable: true, + }, + { + key: "uk2", + unique: true, + columns: []string{"Col2"}, + nullable: false, + }, + { + key: "uk3", + unique: true, + columns: []string{"col3"}, + nullable: false, + }, + { + key: "k1", + unique: false, + columns: []string{"col1"}, + nullable: true, + }, + { + key: "k2", + unique: false, + columns: []string{"Col2"}, + nullable: false, + }, + { + key: "k3", + unique: false, + columns: []string{"col3"}, + nullable: false, + }, + { + key: "kf", + unique: false, + columns: []string{"f"}, + nullable: false, + float: true, + }, + { + key: "kf2", + unique: false, + columns: []string{"f", "Col2"}, + nullable: false, + float: true, + }, + { + key: "kv", + unique: false, + columns: []string{"v"}, + nullable: true, + }, + { + key: "kv1", + unique: false, + columns: []string{"v", "col1"}, + nullable: true, + }, + { + key: "kv2", + unique: false, + columns: []string{"v", "Col2"}, + nullable: true, + prefix: true, + }, + { + key: "uk12", + unique: true, + columns: []string{"col1", "Col2"}, + nullable: true, + }, + { + key: "uk21", + unique: true, + columns: []string{"col2", "Col1"}, + nullable: true, + }, + { + key: "uk23", + unique: true, + columns: []string{"col2", "col3"}, + nullable: false, + }, + { + key: "ukid3", + unique: true, + columns: []string{"id", "col3"}, + nullable: false, + }, + } + env := NewTestEnv() + createTableEntity, err := NewCreateTableEntityFromSQL(env, table) + require.NoError(t, err) + err = createTableEntity.validate() + require.NoError(t, err) + m := createTableEntity.IndexDefinitionEntitiesMap() + require.NotEmpty(t, m) + for _, tcase := range tcases { + t.Run(tcase.key, func(t *testing.T) { + key := m[tcase.key] + require.NotNil(t, key) + assert.Equal(t, tcase.unique, key.IsUnique()) + assert.Equal(t, tcase.columns, key.ColumnNames()) + assert.Equal(t, tcase.nullable, key.HasNullable()) + assert.Equal(t, tcase.float, key.HasFloat()) + assert.Equal(t, tcase.prefix, key.HasColumnPrefix()) + }) + } +} diff --git a/go/vt/schemadiff/mysql.go b/go/vt/schemadiff/mysql.go index 624897e2e43..65adcc1b7a1 100644 --- a/go/vt/schemadiff/mysql.go +++ b/go/vt/schemadiff/mysql.go @@ -21,20 +21,26 @@ var engineCasing = map[string]string{ "MYISAM": "MyISAM", } -var integralTypes = map[string]bool{ - "tinyint": true, - "smallint": true, - "mediumint": true, - "int": true, - "bigint": true, +// integralTypes maps known integer types to their byte storage size +var integralTypes = map[string]int{ + "tinyint": 1, + "smallint": 2, + "mediumint": 3, + "int": 4, + "bigint": 8, } -var floatTypes = map[string]bool{ - "float": true, - "float4": true, - "float8": true, - "double": true, - "real": true, +var floatTypes = map[string]int{ + "float": 4, + "float4": 4, + "float8": 8, + "double": 8, + "real": 8, +} + +var decimalTypes = map[string]bool{ + "decimal": true, + "numeric": true, } var charsetTypes = map[string]bool{ @@ -48,6 +54,56 @@ var charsetTypes = map[string]bool{ "set": true, } +var blobStorageExponent = map[string]int{ + "tinyblob": 8, + "tinytext": 8, + "blob": 16, + "text": 16, + "mediumblob": 24, + "mediumtext": 24, + "longblob": 32, + "longtext": 32, +} + +func IsFloatingPointType(columnType string) bool { + _, ok := floatTypes[columnType] + return ok +} + +func FloatingPointTypeStorage(columnType string) int { + return floatTypes[columnType] +} + func IsIntegralType(columnType string) bool { + _, ok := integralTypes[columnType] + return ok +} + +func IntegralTypeStorage(columnType string) int { return integralTypes[columnType] } + +func IsDecimalType(columnType string) bool { + return decimalTypes[columnType] +} + +func BlobTypeStorage(columnType string) int { + return blobStorageExponent[columnType] +} + +// expandedDataTypes maps some known and difficult-to-compute by INFORMATION_SCHEMA data types which expand other data types. +// For example, in "date:datetime", datetime expands date because it has more precision. In "timestamp:date" date expands timestamp +// because it can contain years not covered by timestamp. +var expandedDataTypes = map[string]bool{ + "time:datetime": true, + "date:datetime": true, + "timestamp:datetime": true, + "time:timestamp": true, + "date:timestamp": true, + "timestamp:date": true, +} + +func IsExpandingDataType(sourceType string, targetType string) bool { + _, ok := expandedDataTypes[sourceType+":"+targetType] + return ok +} diff --git a/go/vt/schemadiff/onlineddl.go b/go/vt/schemadiff/onlineddl.go new file mode 100644 index 00000000000..66908e502f5 --- /dev/null +++ b/go/vt/schemadiff/onlineddl.go @@ -0,0 +1,590 @@ +/* +Copyright 2022 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 schemadiff + +import ( + "fmt" + "math" + "sort" + "strings" + + "vitess.io/vitess/go/vt/sqlparser" +) + +// ColumnChangeExpandsDataRange sees if target column has any value set/range that is impossible in source column. +func ColumnChangeExpandsDataRange(source *ColumnDefinitionEntity, target *ColumnDefinitionEntity) (bool, string) { + if target.IsNullable() && !source.IsNullable() { + return true, "target is NULL-able, source is not" + } + if target.Length() > source.Length() { + return true, "increased length" + } + if target.Scale() > source.Scale() { + return true, "increased scale" + } + if source.IsUnsigned() && !target.IsUnsigned() { + return true, "source is unsigned, target is signed" + } + if IntegralTypeStorage(target.Type()) > IntegralTypeStorage(source.Type()) && IntegralTypeStorage(source.Type()) != 0 { + return true, "increased integer range" + } + if IntegralTypeStorage(source.Type()) <= IntegralTypeStorage(target.Type()) && + !source.IsUnsigned() && target.IsUnsigned() { + // e.g. INT SIGNED => INT UNSIGNED, INT SIGNED => BIGINT UNSIGNED + return true, "target unsigned value exceeds source unsigned value" + } + if FloatingPointTypeStorage(target.Type()) > FloatingPointTypeStorage(source.Type()) && FloatingPointTypeStorage(source.Type()) != 0 { + return true, "increased floating point range" + } + if target.IsFloatingPointType() && !source.IsFloatingPointType() { + return true, "target is floating point, source is not" + } + if target.IsDecimalType() && !source.IsDecimalType() { + return true, "target is decimal, source is not" + } + if target.IsDecimalType() && source.IsDecimalType() { + if target.Length()-target.Scale() > source.Length()-source.Scale() { + return true, "increased decimal range" + } + } + if IsExpandingDataType(source.Type(), target.Type()) { + return true, "target is expanded data type of source" + } + if BlobTypeStorage(target.Type()) > BlobTypeStorage(source.Type()) && BlobTypeStorage(source.Type()) != 0 { + return true, "increased blob range" + } + if source.Charset() != target.Charset() { + if target.Charset() == "utf8mb4" { + return true, "expand character set to utf8mb4" + } + if strings.HasPrefix(target.Charset(), "utf8") && !strings.HasPrefix(source.Charset(), "utf8") { + // not utf to utf + return true, "expand character set to utf8" + } + } + for _, colType := range []string{"enum", "set"} { + // enums and sets have very similar properties, and are practically identical in our analysis + if source.Type() == colType { + // this is an enum or a set + if target.Type() != colType { + return true, "conversion from enum/set to non-enum/set adds potential values" + } + // target is an enum or a set. See if all values on target exist in source + sourceEnumTokensMap := source.EnumOrdinalValues() + targetEnumTokensMap := target.EnumOrdinalValues() + for k, v := range targetEnumTokensMap { + if sourceEnumTokensMap[k] != v { + return true, "target enum/set expands or reorders source enum/set" + } + } + } + } + return false, "" +} + +// IsValidIterationKey returns true if the key is eligible for Online DDL iteration. +func IsValidIterationKey(key *IndexDefinitionEntity) bool { + if key == nil { + return false + } + if !key.IsUnique() { + return false + } + if key.HasFloat() { + return false + } + if key.HasColumnPrefix() { + return false + } + if key.HasNullable() { + return false + } + return true +} + +// PrioritizedUniqueKeys returns all unique keys on given table, ordered from "best" to "worst", +// for Online DDL purposes. The list of keys includes some that are not eligible for Online DDL +// iteration. +func PrioritizedUniqueKeys(createTableEntity *CreateTableEntity) *IndexDefinitionEntityList { + uniqueKeys := []*IndexDefinitionEntity{} + for _, key := range createTableEntity.IndexDefinitionEntities() { + if !key.IsUnique() { + continue + } + uniqueKeys = append(uniqueKeys, key) + } + sort.SliceStable(uniqueKeys, func(i, j int) bool { + if uniqueKeys[i].IsPrimary() { + // PRIMARY is always first + return true + } + if uniqueKeys[j].IsPrimary() { + // PRIMARY is always first + return false + } + if !uniqueKeys[i].HasNullable() && uniqueKeys[j].HasNullable() { + // Non NULLable comes first + return true + } + if uniqueKeys[i].HasNullable() && !uniqueKeys[j].HasNullable() { + // NULLable come last + return false + } + if !uniqueKeys[i].HasColumnPrefix() && uniqueKeys[j].HasColumnPrefix() { + // Non prefix comes first + return true + } + if uniqueKeys[i].HasColumnPrefix() && !uniqueKeys[j].HasColumnPrefix() { + // Prefix comes last + return false + } + iFirstColEntity := uniqueKeys[i].ColumnList.Entities[0] + jFirstColEntity := uniqueKeys[j].ColumnList.Entities[0] + if iFirstColEntity.IsIntegralType() && !jFirstColEntity.IsIntegralType() { + // Prioritize integers + return true + } + if !iFirstColEntity.IsIntegralType() && jFirstColEntity.IsIntegralType() { + // Prioritize integers + return false + } + if !iFirstColEntity.HasBlobTypeStorage() && jFirstColEntity.HasBlobTypeStorage() { + return true + } + if iFirstColEntity.HasBlobTypeStorage() && !jFirstColEntity.HasBlobTypeStorage() { + return false + } + if !iFirstColEntity.IsTextual() && jFirstColEntity.IsTextual() { + return true + } + if iFirstColEntity.IsTextual() && !jFirstColEntity.IsTextual() { + return false + } + if storageDiff := IntegralTypeStorage(iFirstColEntity.Type()) - IntegralTypeStorage(jFirstColEntity.Type()); storageDiff != 0 { + return storageDiff < 0 + } + if lenDiff := len(uniqueKeys[i].ColumnList.Entities) - len(uniqueKeys[j].ColumnList.Entities); lenDiff != 0 { + return lenDiff < 0 + } + return false + }) + return NewIndexDefinitionEntityList(uniqueKeys) +} + +// RemovedForeignKeyNames returns the names of removed foreign keys, ignoring mere name changes +func RemovedForeignKeyNames(source *CreateTableEntity, target *CreateTableEntity) (names []string, err error) { + if source == nil || target == nil { + return nil, nil + } + diffHints := DiffHints{ + ConstraintNamesStrategy: ConstraintNamesIgnoreAll, + } + diff, err := source.Diff(target, &diffHints) + if err != nil { + return nil, err + } + names = []string{} + validateWalk := func(node sqlparser.SQLNode) (kontinue bool, err error) { + switch node := node.(type) { + case *sqlparser.DropKey: + if node.Type == sqlparser.ForeignKeyType { + names = append(names, node.Name.String()) + } + } + return true, nil + } + _ = sqlparser.Walk(validateWalk, diff.Statement()) // We never return an error + return names, nil +} + +// AlterTableAnalysis contains useful Online DDL information about an AlterTable statement +type AlterTableAnalysis struct { + ColumnRenameMap map[string]string + DroppedColumnsMap map[string]bool + IsRenameTable bool + IsAutoIncrementChangeRequested bool +} + +// AnalyzeAlter looks for specific changes in the AlterTable statement, that are relevant +// to OnlineDDL/VReplication +func OnlineDDLAlterTableAnalysis(alterTable *sqlparser.AlterTable) *AlterTableAnalysis { + analysis := &AlterTableAnalysis{ + ColumnRenameMap: make(map[string]string), + DroppedColumnsMap: make(map[string]bool), + } + if alterTable == nil { + return analysis + } + for _, opt := range alterTable.AlterOptions { + switch opt := opt.(type) { + case *sqlparser.RenameTableName: + analysis.IsRenameTable = true + case *sqlparser.DropColumn: + analysis.DroppedColumnsMap[opt.Name.Name.String()] = true + case *sqlparser.ChangeColumn: + if opt.OldColumn != nil && opt.NewColDefinition != nil { + oldName := opt.OldColumn.Name.String() + newName := opt.NewColDefinition.Name.String() + analysis.ColumnRenameMap[oldName] = newName + } + case sqlparser.TableOptions: + for _, tableOption := range opt { + if strings.ToUpper(tableOption.Name) == "AUTO_INCREMENT" { + analysis.IsAutoIncrementChangeRequested = true + } + } + } + } + return analysis +} + +// GetExpandedColumnNames is given source and target shared columns, and returns the list of columns whose data type is expanded. +// An expanded data type is one where the target can have a value which the source does not. Examples: +// - any NOT NULL to NULLable (a NULL in the target cannot appear on source) +// - INT -> BIGINT (obvious) +// - BIGINT UNSIGNED -> INT SIGNED (negative values) +// - TIMESTAMP -> TIMESTAMP(3) +// etc. +func GetExpandedColumns( + sourceColumns *ColumnDefinitionEntityList, + targetColumns *ColumnDefinitionEntityList, +) ( + expandedColumns *ColumnDefinitionEntityList, + expandedDescriptions map[string]string, + err error, +) { + if len(sourceColumns.Entities) != len(targetColumns.Entities) { + return nil, nil, fmt.Errorf("source and target columns must be of same length") + } + + expandedEntities := []*ColumnDefinitionEntity{} + expandedDescriptions = map[string]string{} + for i := range sourceColumns.Entities { + // source and target columns assumed to be mapped 1:1, same length + sourceColumn := sourceColumns.Entities[i] + targetColumn := targetColumns.Entities[i] + + if isExpanded, description := ColumnChangeExpandsDataRange(sourceColumn, targetColumn); isExpanded { + expandedEntities = append(expandedEntities, sourceColumn) + expandedDescriptions[sourceColumn.Name()] = description + } + } + return NewColumnDefinitionEntityList(expandedEntities), expandedDescriptions, nil +} + +// AnalyzeSharedColumns returns the intersection of two lists of columns in same order as the first list +func AnalyzeSharedColumns( + sourceColumns, targetColumns *ColumnDefinitionEntityList, + alterTableAnalysis *AlterTableAnalysis, +) ( + sourceSharedColumns *ColumnDefinitionEntityList, + targetSharedColumns *ColumnDefinitionEntityList, + droppedSourceNonGeneratedColumns *ColumnDefinitionEntityList, + sharedColumnsMap map[string]string, +) { + sharedColumnsMap = map[string]string{} + sourceShared := []*ColumnDefinitionEntity{} + targetShared := []*ColumnDefinitionEntity{} + droppedNonGenerated := []*ColumnDefinitionEntity{} + + for _, sourceColumn := range sourceColumns.Entities { + if sourceColumn.IsGenerated() { + continue + } + isDroppedFromSource := false + // Note to a future engineer: you may be tempted to remove this loop based on the + // assumption that the later `targetColumn := targetColumns.GetColumn(expectedTargetName)` + // check is sufficient. It is not. It is possible that a columns was explicitly dropped + // and added (`DROP COLUMN c, ADD COLUMN c INT`) in the same ALTER TABLE statement. + // Without checking the ALTER TABLE statement, we would be fooled to believe that column + // `c` is unchanged in the target, when in fact it was dropped and re-added. + for droppedColumn := range alterTableAnalysis.DroppedColumnsMap { + if strings.EqualFold(sourceColumn.Name(), droppedColumn) { + isDroppedFromSource = true + break + } + } + if isDroppedFromSource { + droppedNonGenerated = append(droppedNonGenerated, sourceColumn) + // Column was dropped, hence cannot be a shared column + continue + } + expectedTargetName := sourceColumn.NameLowered() + if mappedName := alterTableAnalysis.ColumnRenameMap[sourceColumn.Name()]; mappedName != "" { + expectedTargetName = mappedName + } + targetColumn := targetColumns.GetColumn(expectedTargetName) + if targetColumn == nil { + // Column not found in target + droppedNonGenerated = append(droppedNonGenerated, sourceColumn) + continue + } + if targetColumn.IsGenerated() { + // virtual/generated columns are silently skipped. + continue + } + // OK, the column is shared (possibly renamed) between source and target. + sharedColumnsMap[sourceColumn.Name()] = targetColumn.Name() + sourceShared = append(sourceShared, sourceColumn) + targetShared = append(targetShared, targetColumn) + } + return NewColumnDefinitionEntityList(sourceShared), + NewColumnDefinitionEntityList(targetShared), + NewColumnDefinitionEntityList(droppedNonGenerated), + sharedColumnsMap +} + +// KeyAtLeastConstrainedAs returns 'true' when sourceUniqueKey is at least as constrained as targetUniqueKey. +// "More constrained" means the uniqueness constraint is "stronger". Thus, if sourceUniqueKey is as-or-more constrained than targetUniqueKey, then +// rows valid under sourceUniqueKey must also be valid in targetUniqueKey. The opposite is not necessarily so: rows that are valid in targetUniqueKey +// may cause a unique key violation under sourceUniqueKey +func KeyAtLeastConstrainedAs( + sourceUniqueKey *IndexDefinitionEntity, + targetUniqueKey *IndexDefinitionEntity, + columnRenameMap map[string]string, +) bool { + if !sourceUniqueKey.IsUnique() { + return false + } + if !targetUniqueKey.IsUnique() { + return true + } + sourceKeyLengths := map[string]int{} + for _, col := range sourceUniqueKey.IndexDefinition.Columns { + if col.Length == nil { + sourceKeyLengths[col.Column.Lowered()] = math.MaxInt64 + } else { + sourceKeyLengths[col.Column.Lowered()] = *col.Length + } + } + targetKeyLengths := map[string]int{} + for _, col := range targetUniqueKey.IndexDefinition.Columns { + if col.Length == nil { + targetKeyLengths[col.Column.Lowered()] = math.MaxInt64 + } else { + targetKeyLengths[col.Column.Lowered()] = *col.Length + } + } + // source is more constrained than target if every column in source is also in target, order is immaterial + for _, sourceCol := range sourceUniqueKey.ColumnList.Entities { + mappedColName, ok := columnRenameMap[sourceCol.Name()] + if !ok { + mappedColName = sourceCol.NameLowered() + } + targetCol := targetUniqueKey.ColumnList.GetColumn(mappedColName) + if targetCol == nil { + // source can't be more constrained if it covers *more* columns + return false + } + // We now know that sourceCol maps to targetCol + if sourceKeyLengths[sourceCol.NameLowered()] > targetKeyLengths[targetCol.NameLowered()] { + // source column covers a larger prefix than target column. It is therefore less constrained. + return false + } + } + return true +} + +// IntroducedUniqueConstraints returns the unique key constraints added in target. +// This does not necessarily mean that the unique key itself is new, +// rather that there's a new, stricter constraint on a set of columns, that didn't exist before. Example: +// +// before: +// unique key my_key (c1, c2, c3) +// after: +// unique key `other_key`(c1, c2) +// Synopsis: the constraint on (c1, c2) is new; and `other_key` in target table is considered a new key +// +// Order of columns is immaterial to uniqueness of column combination. +func IntroducedUniqueConstraints(sourceUniqueKeys *IndexDefinitionEntityList, targetUniqueKeys *IndexDefinitionEntityList, columnRenameMap map[string]string) *IndexDefinitionEntityList { + introducedUniqueConstraints := []*IndexDefinitionEntity{} + for _, targetUniqueKey := range targetUniqueKeys.Entities { + foundSourceKeyAtLeastAsConstrained := func() bool { + for _, sourceUniqueKey := range sourceUniqueKeys.Entities { + if KeyAtLeastConstrainedAs(sourceUniqueKey, targetUniqueKey, columnRenameMap) { + // target key does not add a new constraint + return true + } + } + return false + } + if !foundSourceKeyAtLeastAsConstrained() { + introducedUniqueConstraints = append(introducedUniqueConstraints, targetUniqueKey) + } + } + return NewIndexDefinitionEntityList(introducedUniqueConstraints) +} + +// RemovedUniqueConstraints returns the list of unique key constraints _removed_ going from source to target. +func RemovedUniqueConstraints(sourceUniqueKeys *IndexDefinitionEntityList, targetUniqueKeys *IndexDefinitionEntityList, columnRenameMap map[string]string) *IndexDefinitionEntityList { + reverseColumnRenameMap := map[string]string{} + for k, v := range columnRenameMap { + reverseColumnRenameMap[v] = k + } + return IntroducedUniqueConstraints(targetUniqueKeys, sourceUniqueKeys, reverseColumnRenameMap) +} + +// IterationKeysByColumns returns the Online DDL compliant unique keys from given list, +// whose columns are all covered by the given column list. +func IterationKeysByColumns(keys *IndexDefinitionEntityList, columns *ColumnDefinitionEntityList) *IndexDefinitionEntityList { + subset := []*IndexDefinitionEntity{} + for _, key := range keys.SubsetCoveredByColumns(columns).Entities { + if IsValidIterationKey(key) { + subset = append(subset, key) + } + } + return NewIndexDefinitionEntityList(subset) +} + +// MappedColumnNames +func MappedColumnNames(columnsList *ColumnDefinitionEntityList, columnNamesMap map[string]string) []string { + names := columnsList.Names() + for i := range names { + if mappedName, ok := columnNamesMap[names[i]]; ok { + names[i] = mappedName + } + } + return names +} + +// AlterTableAnalysis contains useful Online DDL information about an AlterTable statement +type MigrationTablesAnalysis struct { + SourceSharedColumns *ColumnDefinitionEntityList + TargetSharedColumns *ColumnDefinitionEntityList + DroppedNoDefaultColumns *ColumnDefinitionEntityList + ExpandedColumns *ColumnDefinitionEntityList + SharedColumnsMap map[string]string + ChosenSourceUniqueKey *IndexDefinitionEntity + ChosenTargetUniqueKey *IndexDefinitionEntity + AddedUniqueKeys *IndexDefinitionEntityList + RemovedUniqueKeys *IndexDefinitionEntityList + RemovedForeignKeyNames []string + IntToEnumMap map[string]bool + SourceAutoIncrement uint64 + RevertibleNotes []string +} + +func OnlineDDLMigrationTablesAnalysis( + sourceCreateTableEntity *CreateTableEntity, + targetCreateTableEntity *CreateTableEntity, + alterTableAnalysis *AlterTableAnalysis, +) (analysis *MigrationTablesAnalysis, err error) { + analysis = &MigrationTablesAnalysis{ + IntToEnumMap: make(map[string]bool), + RevertibleNotes: []string{}, + } + // columns: + generatedColumns := func(columns *ColumnDefinitionEntityList) *ColumnDefinitionEntityList { + return columns.Filter(func(col *ColumnDefinitionEntity) bool { + return col.IsGenerated() + }) + } + noDefaultColumns := func(columns *ColumnDefinitionEntityList) *ColumnDefinitionEntityList { + return columns.Filter(func(col *ColumnDefinitionEntity) bool { + return !col.HasDefault() + }) + } + sourceColumns := sourceCreateTableEntity.ColumnDefinitionEntitiesList() + targetColumns := targetCreateTableEntity.ColumnDefinitionEntitiesList() + + var droppedSourceNonGeneratedColumns *ColumnDefinitionEntityList + analysis.SourceSharedColumns, analysis.TargetSharedColumns, droppedSourceNonGeneratedColumns, analysis.SharedColumnsMap = AnalyzeSharedColumns(sourceColumns, targetColumns, alterTableAnalysis) + + // unique keys + sourceUniqueKeys := PrioritizedUniqueKeys(sourceCreateTableEntity) + if sourceUniqueKeys.Len() == 0 { + return nil, fmt.Errorf("found no possible unique key on `%s`", sourceCreateTableEntity.Name()) + } + + targetUniqueKeys := PrioritizedUniqueKeys(targetCreateTableEntity) + if targetUniqueKeys.Len() == 0 { + return nil, fmt.Errorf("found no possible unique key on `%s`", targetCreateTableEntity.Name()) + } + // VReplication supports completely different unique keys on source and target, covering + // some/completely different columns. The condition is that the key on source + // must use columns which all exist on target table. + eligibleSourceColumnsForUniqueKey := analysis.SourceSharedColumns.Union(generatedColumns(sourceColumns)) + analysis.ChosenSourceUniqueKey = IterationKeysByColumns(sourceUniqueKeys, eligibleSourceColumnsForUniqueKey).First() + if analysis.ChosenSourceUniqueKey == nil { + return nil, fmt.Errorf("found no possible unique key on `%s` whose columns are in target table `%s`", sourceCreateTableEntity.Name(), targetCreateTableEntity.Name()) + } + + eligibleTargetColumnsForUniqueKey := analysis.TargetSharedColumns.Union(generatedColumns(targetColumns)) + analysis.ChosenTargetUniqueKey = IterationKeysByColumns(targetUniqueKeys, eligibleTargetColumnsForUniqueKey).First() + if analysis.ChosenTargetUniqueKey == nil { + return nil, fmt.Errorf("found no possible unique key on `%s` whose columns are in source table `%s`", targetCreateTableEntity.Name(), sourceCreateTableEntity.Name()) + } + + analysis.AddedUniqueKeys = IntroducedUniqueConstraints(sourceUniqueKeys, targetUniqueKeys, alterTableAnalysis.ColumnRenameMap) + analysis.RemovedUniqueKeys = RemovedUniqueConstraints(sourceUniqueKeys, targetUniqueKeys, alterTableAnalysis.ColumnRenameMap) + analysis.RemovedForeignKeyNames, err = RemovedForeignKeyNames(sourceCreateTableEntity, targetCreateTableEntity) + if err != nil { + return nil, err + } + + formalizeColumns := func(columnsLists ...*ColumnDefinitionEntityList) error { + for _, colList := range columnsLists { + for _, col := range colList.Entities { + col.SetExplicitDefaultAndNull() + if err := col.SetExplicitCharsetCollate(); err != nil { + return err + } + } + } + return nil + } + + if err := formalizeColumns(analysis.SourceSharedColumns, analysis.TargetSharedColumns, droppedSourceNonGeneratedColumns); err != nil { + return nil, err + } + + for i := range analysis.SourceSharedColumns.Entities { + sourceColumn := analysis.SourceSharedColumns.Entities[i] + mappedColumn := analysis.TargetSharedColumns.Entities[i] + + if sourceColumn.IsIntegralType() && mappedColumn.Type() == "enum" { + analysis.IntToEnumMap[sourceColumn.Name()] = true + } + } + + analysis.DroppedNoDefaultColumns = noDefaultColumns(droppedSourceNonGeneratedColumns) + var expandedDescriptions map[string]string + analysis.ExpandedColumns, expandedDescriptions, err = GetExpandedColumns(analysis.SourceSharedColumns, analysis.TargetSharedColumns) + if err != nil { + return nil, err + } + + analysis.SourceAutoIncrement, err = sourceCreateTableEntity.AutoIncrementValue() + if err != nil { + return nil, err + } + + for _, uk := range analysis.RemovedUniqueKeys.Names() { + analysis.RevertibleNotes = append(analysis.RevertibleNotes, fmt.Sprintf("unique constraint removed: %s", uk)) + } + for _, name := range analysis.DroppedNoDefaultColumns.Names() { + analysis.RevertibleNotes = append(analysis.RevertibleNotes, fmt.Sprintf("column %s dropped, and had no default value", name)) + } + for _, name := range analysis.ExpandedColumns.Names() { + analysis.RevertibleNotes = append(analysis.RevertibleNotes, fmt.Sprintf("column %s: %s", name, expandedDescriptions[name])) + } + for _, name := range analysis.RemovedForeignKeyNames { + analysis.RevertibleNotes = append(analysis.RevertibleNotes, fmt.Sprintf("foreign key %s dropped", name)) + } + + return analysis, nil +} diff --git a/go/vt/schemadiff/onlineddl_test.go b/go/vt/schemadiff/onlineddl_test.go new file mode 100644 index 00000000000..bd08bedfe8a --- /dev/null +++ b/go/vt/schemadiff/onlineddl_test.go @@ -0,0 +1,960 @@ +/* +Copyright 2024 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 schemadiff + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/vt/sqlparser" +) + +func TestPrioritizedUniqueKeys(t *testing.T) { + table := ` + create table t ( + idsha varchar(64), + col1 int, + col2 int not null, + col3 bigint not null default 3, + col4 smallint not null, + f float not null, + v varchar(32) not null, + primary key (idsha), + unique key ukidsha (idsha), + unique key uk1 (col1), + unique key uk2 (col2), + unique key uk3 (col3), + key k1 (col1), + key kf (f), + key k1f (col1, f), + key kv (v), + unique key ukv (v), + unique key ukvprefix (v(10)), + unique key uk2vprefix (col2, v(10)), + unique key uk1f (col1, f), + unique key uk41 (col4, col1), + unique key uk42 (col4, col2) + )` + env := NewTestEnv() + createTableEntity, err := NewCreateTableEntityFromSQL(env, table) + require.NoError(t, err) + err = createTableEntity.validate() + require.NoError(t, err) + + keys := PrioritizedUniqueKeys(createTableEntity) + require.NotEmpty(t, keys) + names := make([]string, 0, len(keys.Entities)) + for _, key := range keys.Entities { + names = append(names, key.Name()) + } + expect := []string{ + "PRIMARY", + "uk42", + "uk2", + "uk3", + "ukidsha", + "ukv", + "uk2vprefix", + "ukvprefix", + "uk41", + "uk1", + "uk1f", + } + assert.Equal(t, expect, names) +} + +func TestRemovedForeignKeyNames(t *testing.T) { + env := NewTestEnv() + + tcases := []struct { + before string + after string + names []string + }{ + { + before: "create table t (id int primary key)", + after: "create table t (id2 int primary key, i int)", + }, + { + before: "create table t (id int primary key)", + after: "create table t2 (id2 int primary key, i int)", + }, + { + before: "create table t (id int primary key, i int, constraint f foreign key (i) references parent (id) on delete cascade)", + after: "create table t (id int primary key, i int, constraint f foreign key (i) references parent (id) on delete cascade)", + }, + { + before: "create table t (id int primary key, i int, constraint f1 foreign key (i) references parent (id) on delete cascade)", + after: "create table t (id int primary key, i int, constraint f2 foreign key (i) references parent (id) on delete cascade)", + }, + { + before: "create table t (id int primary key, i int, constraint f foreign key (i) references parent (id) on delete cascade)", + after: "create table t (id int primary key, i int)", + names: []string{"f"}, + }, + { + before: "create table t (id int primary key, i int, i2 int, constraint f1 foreign key (i) references parent (id) on delete cascade, constraint fi2 foreign key (i2) references parent (id) on delete cascade)", + after: "create table t (id int primary key, i int, i2 int, constraint f2 foreign key (i) references parent (id) on delete cascade)", + names: []string{"fi2"}, + }, + { + before: "create table t1 (id int primary key, i int, constraint `check1` CHECK ((`i` < 5)))", + after: "create table t2 (id int primary key, i int)", + }, + } + for _, tcase := range tcases { + t.Run(tcase.before, func(t *testing.T) { + before, err := NewCreateTableEntityFromSQL(env, tcase.before) + require.NoError(t, err) + err = before.validate() + require.NoError(t, err) + + after, err := NewCreateTableEntityFromSQL(env, tcase.after) + require.NoError(t, err) + err = after.validate() + require.NoError(t, err) + + names, err := RemovedForeignKeyNames(before, after) + assert.NoError(t, err) + if tcase.names == nil { + tcase.names = []string{} + } + assert.Equal(t, tcase.names, names) + }) + } +} + +func TestGetAlterTableAnalysis(t *testing.T) { + tcases := []struct { + alter string + renames map[string]string + drops map[string]bool + isrename bool + autoinc bool + }{ + { + alter: "alter table t add column t int, engine=innodb", + }, + { + alter: "alter table t add column t int, change ts ts timestamp, engine=innodb", + renames: map[string]string{"ts": "ts"}, + }, + { + alter: "alter table t AUTO_INCREMENT=7", + autoinc: true, + }, + { + alter: "alter table t add column t int, change ts ts timestamp, auto_increment=7 engine=innodb", + renames: map[string]string{"ts": "ts"}, + autoinc: true, + }, + { + alter: "alter table t add column t int, change ts ts timestamp, CHANGE f `f` float, engine=innodb", + renames: map[string]string{"ts": "ts", "f": "f"}, + }, + { + alter: `alter table t add column b bigint, change f fl float, change i count int, engine=innodb`, + renames: map[string]string{"f": "fl", "i": "count"}, + }, + { + alter: "alter table t add column b bigint, change column `f` fl float, change `i` `count` int, engine=innodb", + renames: map[string]string{"f": "fl", "i": "count"}, + }, + { + alter: "alter table t add column b bigint, change column `f` fl float, change `i` `count` int, change ts ts timestamp, engine=innodb", + renames: map[string]string{"f": "fl", "i": "count", "ts": "ts"}, + }, + { + alter: "alter table t drop column b", + drops: map[string]bool{"b": true}, + }, + { + alter: "alter table t drop column b, drop key c_idx, drop column `d`", + drops: map[string]bool{"b": true, "d": true}, + }, + { + alter: "alter table t drop column b, drop key c_idx, drop column `d`, drop `e`, drop primary key, drop foreign key fk_1", + drops: map[string]bool{"b": true, "d": true, "e": true}, + }, + { + alter: "alter table t rename as something_else", + isrename: true, + }, + { + alter: "alter table t drop column b, rename as something_else", + isrename: true, + drops: map[string]bool{"b": true}, + }, + } + for _, tcase := range tcases { + t.Run(tcase.alter, func(t *testing.T) { + if tcase.renames == nil { + tcase.renames = make(map[string]string) + } + if tcase.drops == nil { + tcase.drops = make(map[string]bool) + } + stmt, err := sqlparser.NewTestParser().ParseStrictDDL(tcase.alter) + require.NoError(t, err) + alter, ok := stmt.(*sqlparser.AlterTable) + require.True(t, ok) + + analysis := OnlineDDLAlterTableAnalysis(alter) + require.NotNil(t, analysis) + assert.Equal(t, tcase.isrename, analysis.IsRenameTable) + assert.Equal(t, tcase.autoinc, analysis.IsAutoIncrementChangeRequested) + assert.Equal(t, tcase.renames, analysis.ColumnRenameMap) + assert.Equal(t, tcase.drops, analysis.DroppedColumnsMap) + }) + } +} + +func TestAnalyzeSharedColumns(t *testing.T) { + sourceTable := ` + create table t ( + id int, + cint int, + cgen1 int generated always as (cint + 1) stored, + cgen2 int generated always as (cint + 2) stored, + cchar char(1), + cremoved int not null default 7, + cnullable int, + cnodefault int not null, + extra1 int, + primary key (id) + ) + ` + targetTable := ` + create table t ( + id int, + cint int, + cgen1 int generated always as (cint + 1) stored, + cchar_alternate char(1), + cnullable int, + cnodefault int not null, + extra2 int, + primary key (id) + ) + ` + tcases := []struct { + name string + sourceTable string + targetTable string + renameMap map[string]string + expectSourceSharedColNames []string + expectTargetSharedColNames []string + expectDroppedSourceNonGeneratedColNames []string + expectSharedColumnsMap map[string]string + }{ + { + name: "rename map empty", + renameMap: map[string]string{}, + expectSourceSharedColNames: []string{"id", "cint", "cnullable", "cnodefault"}, + expectTargetSharedColNames: []string{"id", "cint", "cnullable", "cnodefault"}, + expectDroppedSourceNonGeneratedColNames: []string{"cchar", "cremoved", "extra1"}, + expectSharedColumnsMap: map[string]string{"id": "id", "cint": "cint", "cnullable": "cnullable", "cnodefault": "cnodefault"}, + }, + { + name: "renamed column", + renameMap: map[string]string{"cchar": "cchar_alternate"}, + expectSourceSharedColNames: []string{"id", "cint", "cchar", "cnullable", "cnodefault"}, + expectTargetSharedColNames: []string{"id", "cint", "cchar_alternate", "cnullable", "cnodefault"}, + expectDroppedSourceNonGeneratedColNames: []string{"cremoved", "extra1"}, + expectSharedColumnsMap: map[string]string{"id": "id", "cint": "cint", "cchar": "cchar_alternate", "cnullable": "cnullable", "cnodefault": "cnodefault"}, + }, + } + + env := NewTestEnv() + alterTableAnalysis := OnlineDDLAlterTableAnalysis(nil) // empty + for _, tcase := range tcases { + t.Run(tcase.name, func(t *testing.T) { + if tcase.sourceTable == "" { + tcase.sourceTable = sourceTable + } + if tcase.targetTable == "" { + tcase.targetTable = targetTable + } + if tcase.renameMap != nil { + alterTableAnalysis.ColumnRenameMap = tcase.renameMap + } + + sourceEntity, err := NewCreateTableEntityFromSQL(env, tcase.sourceTable) + require.NoError(t, err) + err = sourceEntity.validate() + require.NoError(t, err) + + targetEntity, err := NewCreateTableEntityFromSQL(env, tcase.targetTable) + require.NoError(t, err) + err = targetEntity.validate() + require.NoError(t, err) + + sourceSharedCols, targetSharedCols, droppedNonGeneratedCols, sharedColumnsMap := AnalyzeSharedColumns( + sourceEntity.ColumnDefinitionEntitiesList(), + targetEntity.ColumnDefinitionEntitiesList(), + alterTableAnalysis, + ) + assert.Equal(t, tcase.expectSourceSharedColNames, sourceSharedCols.Names()) + assert.Equal(t, tcase.expectTargetSharedColNames, targetSharedCols.Names()) + assert.Equal(t, tcase.expectDroppedSourceNonGeneratedColNames, droppedNonGeneratedCols.Names()) + assert.Equal(t, tcase.expectSharedColumnsMap, sharedColumnsMap) + }) + } +} + +func TestKeyAtLeastConstrainedAs(t *testing.T) { + env := NewTestEnv() + sourceTable := ` + create table source_table ( + id int, + c1 int, + c2 int, + c3 int, + c9 int, + v varchar(32), + primary key (id), + unique key uk1 (c1), + unique key uk2 (c2), + unique key uk3 (c3), + unique key uk9 (c9), + unique key uk12 (c1, c2), + unique key uk13 (c1, c3), + unique key uk23 (c2, c3), + unique key uk123 (c1, c2, c3), + unique key uk21 (c2, c1), + unique key ukv (v), + unique key ukv3 (v(3)), + unique key ukv5 (v(5)), + unique key uk2v5 (c2, v(5)) + )` + targetTable := ` + create table target_table ( + id int, + c1 int, + c2 int, + c3_renamed int, + v varchar(32), + primary key (id), + unique key uk1 (c1), + unique key uk2 (c2), + unique key uk3 (c3_renamed), + unique key uk12 (c1, c2), + unique key uk13 (c1, c3_renamed), + unique key uk23 (c2, c3_renamed), + unique key uk123 (c1, c2, c3_renamed), + unique key uk21 (c2, c1), + unique key ukv (v), + unique key ukv3 (v(3)), + unique key ukv5 (v(5)), + unique key uk2v5 (c2, v(5)) + )` + renameMap := map[string]string{ + "c3": "c3_renamed", + } + tcases := []struct { + sourceKey string + targetKey string + renameMap map[string]string + expect bool + }{ + { + sourceKey: "uk1", + targetKey: "uk1", + expect: true, + }, + { + sourceKey: "uk2", + targetKey: "uk2", + expect: true, + }, + { + sourceKey: "uk3", + targetKey: "uk3", + expect: false, // c3 is renamed + }, + { + sourceKey: "uk2", + targetKey: "uk1", + expect: false, + }, + { + sourceKey: "uk12", + targetKey: "uk1", + expect: false, + }, + { + sourceKey: "uk1", + targetKey: "uk12", + expect: true, + }, + { + sourceKey: "uk1", + targetKey: "uk21", + expect: true, + }, + { + sourceKey: "uk12", + targetKey: "uk21", + expect: true, + }, + { + sourceKey: "uk123", + targetKey: "uk21", + expect: false, + }, + { + sourceKey: "uk123", + targetKey: "uk123", + expect: false, // c3 is renamed + }, + { + sourceKey: "uk1", + targetKey: "uk123", + expect: true, // c3 is renamed but not referenced + }, + { + sourceKey: "uk21", + targetKey: "uk123", + expect: true, // c3 is renamed but not referenced + }, + { + sourceKey: "uk9", + targetKey: "uk123", + expect: false, // c9 not in target + }, + { + sourceKey: "uk3", + targetKey: "uk3", + renameMap: renameMap, + expect: true, + }, + { + sourceKey: "uk123", + targetKey: "uk123", + renameMap: renameMap, + expect: true, + }, + { + sourceKey: "uk3", + targetKey: "uk123", + renameMap: renameMap, + expect: true, + }, + { + sourceKey: "ukv", + targetKey: "ukv", + expect: true, + }, + { + sourceKey: "ukv3", + targetKey: "ukv3", + expect: true, + }, + { + sourceKey: "ukv", + targetKey: "ukv3", + expect: false, + }, + { + sourceKey: "ukv5", + targetKey: "ukv3", + expect: false, + }, + { + sourceKey: "ukv3", + targetKey: "ukv5", + expect: true, + }, + { + sourceKey: "ukv3", + targetKey: "ukv", + expect: true, + }, + { + sourceKey: "uk2", + targetKey: "uk2v5", + expect: true, + }, + { + sourceKey: "ukv5", + targetKey: "uk2v5", + expect: true, + }, + { + sourceKey: "ukv3", + targetKey: "uk2v5", + expect: true, + }, + { + sourceKey: "ukv", + targetKey: "uk2v5", + expect: false, + }, + { + sourceKey: "uk2v5", + targetKey: "ukv5", + expect: false, + }, + } + + sourceEntity, err := NewCreateTableEntityFromSQL(env, sourceTable) + require.NoError(t, err) + err = sourceEntity.validate() + require.NoError(t, err) + sourceKeys := sourceEntity.IndexDefinitionEntitiesMap() + + targetEntity, err := NewCreateTableEntityFromSQL(env, targetTable) + require.NoError(t, err) + err = targetEntity.validate() + require.NoError(t, err) + targetKeys := targetEntity.IndexDefinitionEntitiesMap() + + for _, tcase := range tcases { + t.Run(tcase.sourceKey+"/"+tcase.targetKey, func(t *testing.T) { + if tcase.renameMap == nil { + tcase.renameMap = make(map[string]string) + } + sourceKey := sourceKeys[tcase.sourceKey] + require.NotNil(t, sourceKey) + + targetKey := targetKeys[tcase.targetKey] + require.NotNil(t, targetKey) + + result := KeyAtLeastConstrainedAs(sourceKey, targetKey, tcase.renameMap) + assert.Equal(t, tcase.expect, result) + }) + } +} + +func TestIntroducedUniqueConstraints(t *testing.T) { + env := NewTestEnv() + tcases := []struct { + sourceTable string + targetTable string + expectIntroduced []string + expectRemoved []string + }{ + { + sourceTable: ` + create table source_table ( + id int, + c1 int, + c2 int, + c3 int, + primary key (id), + unique key uk1 (c1), + unique key uk2 (c2), + unique key uk31 (c3, c1), + key k1 (c1) + )`, + targetTable: ` + create table source_table ( + id int, + c1 int, + c2 int, + c3 int, + primary key (id), + unique key uk1 (c1), + unique key uk3 (c3), + unique key uk31_alias (c3, c1), + key k2 (c2) + )`, + expectIntroduced: []string{"uk3"}, + expectRemoved: []string{"uk2"}, + }, + { + sourceTable: ` + create table source_table ( + id int, + c1 int, + c2 int, + c3 int, + primary key (id), + unique key uk1 (c1), + unique key uk2 (c2), + unique key uk31 (c3, c1), + key k1 (c1) + )`, + targetTable: ` + create table source_table ( + id int, + c1 int, + c2 int, + c3 int, + primary key (id), + unique key uk1 (c1), + unique key uk3 (c3), + key k2 (c2) + )`, + expectIntroduced: []string{"uk3"}, // uk31 (c3, c1) not considered removed because the new "uk3" is even more constrained + expectRemoved: []string{"uk2"}, + }, + { + sourceTable: ` + create table source_table ( + id int, + c1 int, + c2 int, + v varchar(128), + primary key (id), + unique key uk12 (c1, c2), + unique key ukv5 (v(5)), + key k1 (c1) + )`, + targetTable: ` + create table source_table ( + id int, + c1 int, + c2 int, + c3 int, + v varchar(128), + primary key (id), + unique key uk1v2 (c1, v(2)), + unique key uk1v7 (c1, v(7)), + unique key ukv3 (v(3)), + key k2 (c2) + )`, + expectIntroduced: []string{"uk1v2", "ukv3"}, + expectRemoved: []string{"uk12"}, + }, + } + for _, tcase := range tcases { + t.Run("", func(t *testing.T) { + sourceEntity, err := NewCreateTableEntityFromSQL(env, tcase.sourceTable) + require.NoError(t, err) + err = sourceEntity.validate() + require.NoError(t, err) + sourceUniqueKeys := PrioritizedUniqueKeys(sourceEntity) + + targetEntity, err := NewCreateTableEntityFromSQL(env, tcase.targetTable) + require.NoError(t, err) + err = targetEntity.validate() + require.NoError(t, err) + targetUniqueKeys := PrioritizedUniqueKeys(targetEntity) + + introduced := IntroducedUniqueConstraints(sourceUniqueKeys, targetUniqueKeys, nil) + assert.Equal(t, tcase.expectIntroduced, introduced.Names()) + }) + } +} + +func TestUniqueKeysCoveredByColumns(t *testing.T) { + env := NewTestEnv() + table := ` + create table t ( + id int, + c1 int not null, + c2 int not null, + c3 int not null, + c9 int, + v varchar(32) not null, + primary key (id), + unique key uk1 (c1), + unique key uk3 (c3), + unique key uk9 (c9), + key k3 (c3), + unique key uk12 (c1, c2), + unique key uk13 (c1, c3), + unique key uk23 (c2, c3), + unique key uk123 (c1, c2, c3), + unique key uk21 (c2, c1), + unique key ukv (v), + unique key ukv3 (v(3)), + unique key uk2v5 (c2, v(5)), + unique key uk3v (c3, v) + ) + ` + tcases := []struct { + columns []string + expect []string + }{ + { + columns: []string{"id"}, + expect: []string{"PRIMARY"}, + }, + { + columns: []string{"c1"}, + expect: []string{"uk1"}, + }, + { + columns: []string{"id", "c1"}, + expect: []string{"PRIMARY", "uk1"}, + }, + { + columns: []string{"c1", "id"}, + expect: []string{"PRIMARY", "uk1"}, + }, + { + columns: []string{"c9"}, + expect: []string{}, // nullable column + }, + { + columns: []string{"v"}, + expect: []string{"ukv"}, + }, + { + columns: []string{"v", "c9"}, + expect: []string{"ukv"}, + }, + { + columns: []string{"v", "c2"}, + expect: []string{"ukv"}, + }, + { + columns: []string{"v", "c2", "c3"}, + expect: []string{"uk3", "uk23", "uk3v", "ukv"}, + }, + { + columns: []string{"id", "c1", "c2", "c3", "v"}, + expect: []string{"PRIMARY", "uk1", "uk3", "uk12", "uk13", "uk23", "uk21", "uk3v", "uk123", "ukv"}, + }, + } + + entity, err := NewCreateTableEntityFromSQL(env, table) + require.NoError(t, err) + err = entity.validate() + require.NoError(t, err) + tableColumns := entity.ColumnDefinitionEntitiesList() + tableKeys := PrioritizedUniqueKeys(entity) + assert.Equal(t, []string{ + "PRIMARY", + "uk1", + "uk3", + "uk12", + "uk13", + "uk23", + "uk21", + "uk3v", + "uk123", + "ukv", + "uk2v5", + "ukv3", + "uk9", + }, tableKeys.Names()) + + for _, tcase := range tcases { + t.Run(strings.Join(tcase.columns, ","), func(t *testing.T) { + columns := []*ColumnDefinitionEntity{} + for _, tcaseCol := range tcase.columns { + col := tableColumns.GetColumn(tcaseCol) + require.NotNil(t, col) + columns = append(columns, col) + } + columnsList := NewColumnDefinitionEntityList(columns) + + covered := IterationKeysByColumns(tableKeys, columnsList) + assert.Equal(t, tcase.expect, covered.Names()) + }) + } +} + +func TestRevertible(t *testing.T) { + + type revertibleTestCase struct { + name string + fromSchema string + toSchema string + // expectProblems bool + removedForeignKeyNames string + removedUniqueKeyNames string + droppedNoDefaultColumnNames string + expandedColumnNames string + } + + var testCases = []revertibleTestCase{ + { + name: "identical schemas", + fromSchema: `id int primary key, i1 int not null default 0`, + toSchema: `id int primary key, i2 int not null default 0`, + }, + { + name: "different schemas, nothing to note", + fromSchema: `id int primary key, i1 int not null default 0, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i1 int not null default 0, i2 int not null default 0, unique key i1_uidx(i1)`, + }, + { + name: "removed non-nullable unique key", + fromSchema: `id int primary key, i1 int not null default 0, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i2 int not null default 0`, + removedUniqueKeyNames: `i1_uidx`, + }, + { + name: "removed nullable unique key", + fromSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i2 int default null`, + removedUniqueKeyNames: `i1_uidx`, + }, + { + name: "expanding unique key removes unique constraint", + fromSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1, id)`, + removedUniqueKeyNames: `i1_uidx`, + }, + { + name: "expanding unique key prefix removes unique constraint", + fromSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(20))`, + toSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(21))`, + removedUniqueKeyNames: `v_uidx`, + }, + { + name: "reducing unique key does not remove unique constraint", + fromSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1, id)`, + toSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`, + removedUniqueKeyNames: ``, + }, + { + name: "reducing unique key does not remove unique constraint", + fromSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(21))`, + toSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(20))`, + }, + { + name: "removed foreign key", + fromSchema: "id int primary key, i int, constraint some_fk_1 foreign key (i) references parent (id) on delete cascade", + toSchema: "id int primary key, i int", + removedForeignKeyNames: "some_fk_1", + }, + + { + name: "renamed foreign key", + fromSchema: "id int primary key, i int, constraint f1 foreign key (i) references parent (id) on delete cascade", + toSchema: "id int primary key, i int, constraint f2 foreign key (i) references parent (id) on delete cascade", + }, + { + name: "remove column without default", + fromSchema: `id int primary key, i1 int not null, i2 int not null default 0, i3 int default null`, + toSchema: `id int primary key, i4 int not null default 0`, + droppedNoDefaultColumnNames: `i1`, + }, + { + name: "expanded: nullable", + fromSchema: `id int primary key, i1 int not null, i2 int default null`, + toSchema: `id int primary key, i1 int default null, i2 int not null`, + expandedColumnNames: `i1`, + }, + { + name: "expanded: longer text", + fromSchema: `id int primary key, i1 int default null, v1 varchar(40) not null, v2 varchar(5), v3 varchar(3)`, + toSchema: `id int primary key, i1 int not null, v1 varchar(100) not null, v2 char(3), v3 char(5)`, + expandedColumnNames: `v1,v3`, + }, + { + name: "expanded: int numeric precision and scale", + fromSchema: `id int primary key, i1 int, i2 tinyint, i3 mediumint, i4 bigint`, + toSchema: `id int primary key, i1 int, i2 mediumint, i3 int, i4 tinyint`, + expandedColumnNames: `i2,i3`, + }, + { + name: "expanded: floating point", + fromSchema: `id int primary key, i1 int, n2 bigint, n3 bigint, n4 float, n5 double`, + toSchema: `id int primary key, i1 int, n2 float, n3 double, n4 double, n5 float`, + expandedColumnNames: `n2,n3,n4`, + }, + { + name: "expanded: decimal numeric precision and scale", + fromSchema: `id int primary key, i1 int, d1 decimal(10,2), d2 decimal (10,2), d3 decimal (10,2)`, + toSchema: `id int primary key, i1 int, d1 decimal(11,2), d2 decimal (9,1), d3 decimal (10,3)`, + expandedColumnNames: `d1,d3`, + }, + { + name: "expanded: signed, unsigned", + fromSchema: `id int primary key, i1 bigint signed, i2 int unsigned, i3 bigint unsigned`, + toSchema: `id int primary key, i1 int signed, i2 int signed, i3 int signed`, + expandedColumnNames: `i2,i3`, + }, + { + name: "expanded: signed, unsigned: range", + fromSchema: `id int primary key, i1 int signed, i2 bigint signed, i3 int signed`, + toSchema: `id int primary key, i1 int unsigned, i2 int unsigned, i3 bigint unsigned`, + expandedColumnNames: `i1,i3`, + }, + { + name: "expanded: datetime precision", + fromSchema: `id int primary key, dt1 datetime, ts1 timestamp, ti1 time, dt2 datetime(3), dt3 datetime(6), ts2 timestamp(3)`, + toSchema: `id int primary key, dt1 datetime(3), ts1 timestamp(6), ti1 time(3), dt2 datetime(6), dt3 datetime(3), ts2 timestamp`, + expandedColumnNames: `dt1,ts1,ti1,dt2`, + }, + { + name: "expanded: strange data type changes", + fromSchema: `id int primary key, dt1 datetime, ts1 timestamp, i1 int, d1 date, e1 enum('a', 'b')`, + toSchema: `id int primary key, dt1 char(32), ts1 varchar(32), i1 tinytext, d1 char(2), e1 varchar(2)`, + expandedColumnNames: `dt1,ts1,i1,d1,e1`, + }, + { + name: "expanded: temporal types", + fromSchema: `id int primary key, t1 time, t2 timestamp, t3 date, t4 datetime, t5 time, t6 date`, + toSchema: `id int primary key, t1 datetime, t2 datetime, t3 timestamp, t4 timestamp, t5 timestamp, t6 datetime`, + expandedColumnNames: `t1,t2,t3,t5,t6`, + }, + { + name: "expanded: character sets", + fromSchema: `id int primary key, c1 char(3) charset utf8, c2 char(3) charset utf8mb4, c3 char(3) charset ascii, c4 char(3) charset utf8mb4, c5 char(3) charset utf8, c6 char(3) charset latin1`, + toSchema: `id int primary key, c1 char(3) charset utf8mb4, c2 char(3) charset utf8, c3 char(3) charset utf8, c4 char(3) charset ascii, c5 char(3) charset utf8, c6 char(3) charset utf8mb4`, + expandedColumnNames: `c1,c3,c6`, + }, + { + name: "expanded: enum", + fromSchema: `id int primary key, e1 enum('a', 'b'), e2 enum('a', 'b'), e3 enum('a', 'b'), e4 enum('a', 'b'), e5 enum('a', 'b'), e6 enum('a', 'b'), e7 enum('a', 'b'), e8 enum('a', 'b')`, + toSchema: `id int primary key, e1 enum('a', 'b'), e2 enum('a'), e3 enum('a', 'b', 'c'), e4 enum('a', 'x'), e5 enum('a', 'x', 'b'), e6 enum('b'), e7 varchar(1), e8 tinyint`, + expandedColumnNames: `e3,e4,e5,e6,e7,e8`, + }, + { + name: "expanded: set", + fromSchema: `id int primary key, e1 set('a', 'b'), e2 set('a', 'b'), e3 set('a', 'b'), e4 set('a', 'b'), e5 set('a', 'b'), e6 set('a', 'b'), e7 set('a', 'b'), e8 set('a', 'b')`, + toSchema: `id int primary key, e1 set('a', 'b'), e2 set('a'), e3 set('a', 'b', 'c'), e4 set('a', 'x'), e5 set('a', 'x', 'b'), e6 set('b'), e7 varchar(1), e8 tinyint`, + expandedColumnNames: `e3,e4,e5,e6,e7,e8`, + }, + } + + var ( + createTableWrapper = `CREATE TABLE t (%s)` + ) + + env := NewTestEnv() + diffHints := &DiffHints{} + for _, tcase := range testCases { + t.Run(tcase.name, func(t *testing.T) { + tcase.fromSchema = fmt.Sprintf(createTableWrapper, tcase.fromSchema) + sourceTableEntity, err := NewCreateTableEntityFromSQL(env, tcase.fromSchema) + require.NoError(t, err) + + tcase.toSchema = fmt.Sprintf(createTableWrapper, tcase.toSchema) + targetTableEntity, err := NewCreateTableEntityFromSQL(env, tcase.toSchema) + require.NoError(t, err) + + diff, err := sourceTableEntity.TableDiff(targetTableEntity, diffHints) + require.NoError(t, err) + alterTableAnalysis := OnlineDDLAlterTableAnalysis(diff.AlterTable()) + + analysis, err := OnlineDDLMigrationTablesAnalysis(sourceTableEntity, targetTableEntity, alterTableAnalysis) + require.NoError(t, err) + + toStringSlice := func(s string) []string { + if s == "" { + return []string{} + } + return strings.Split(s, ",") + } + assert.Equal(t, toStringSlice(tcase.removedForeignKeyNames), analysis.RemovedForeignKeyNames) + assert.Equal(t, toStringSlice(tcase.removedUniqueKeyNames), analysis.RemovedUniqueKeys.Names()) + assert.Equal(t, toStringSlice(tcase.droppedNoDefaultColumnNames), analysis.DroppedNoDefaultColumns.Names()) + assert.Equal(t, toStringSlice(tcase.expandedColumnNames), analysis.ExpandedColumns.Names()) + }) + } +} diff --git a/go/vt/schemadiff/schema_diff_test.go b/go/vt/schemadiff/schema_diff_test.go index 10ad260100b..8088cc896ed 100644 --- a/go/vt/schemadiff/schema_diff_test.go +++ b/go/vt/schemadiff/schema_diff_test.go @@ -1321,7 +1321,6 @@ func TestSchemaDiff(t *testing.T) { instantCapability := schemaDiff.InstantDDLCapability() assert.Equal(t, tc.instantCapability, instantCapability, "for instant capability") }) - } } diff --git a/go/vt/schemadiff/table.go b/go/vt/schemadiff/table.go index 5629210b6c1..c326b2763b3 100644 --- a/go/vt/schemadiff/table.go +++ b/go/vt/schemadiff/table.go @@ -445,6 +445,18 @@ type CreateTableEntity struct { Env *Environment } +func NewCreateTableEntityFromSQL(env *Environment, sql string) (*CreateTableEntity, error) { + stmt, err := env.Parser().ParseStrictDDL(sql) + if err != nil { + return nil, err + } + createTable, ok := stmt.(*sqlparser.CreateTable) + if !ok { + return nil, ErrExpectedCreateTable + } + return NewCreateTableEntity(env, createTable) +} + func NewCreateTableEntity(env *Environment, c *sqlparser.CreateTable) (*CreateTableEntity, error) { if !c.IsFullyParsed() { return nil, &NotFullyParsedError{Entity: c.Table.Name.String(), Statement: sqlparser.CanonicalString(c)} @@ -454,15 +466,64 @@ func NewCreateTableEntity(env *Environment, c *sqlparser.CreateTable) (*CreateTa return entity, nil } +// ColumnDefinitionEntities returns the list of column entities for the table. func (c *CreateTableEntity) ColumnDefinitionEntities() []*ColumnDefinitionEntity { cc := getTableCharsetCollate(c.Env, &c.CreateTable.TableSpec.Options) + pkColumnsMaps := c.primaryKeyColumnsMap() entities := make([]*ColumnDefinitionEntity, len(c.CreateTable.TableSpec.Columns)) for i := range c.CreateTable.TableSpec.Columns { - entities[i] = NewColumnDefinitionEntity(c.Env, c.CreateTable.TableSpec.Columns[i], cc) + col := c.CreateTable.TableSpec.Columns[i] + _, inPK := pkColumnsMaps[col.Name.Lowered()] + entities[i] = NewColumnDefinitionEntity(c.Env, col, inPK, cc) } return entities } +// ColumnDefinitionEntities returns the list of column entities for the table. +func (c *CreateTableEntity) ColumnDefinitionEntitiesList() *ColumnDefinitionEntityList { + return NewColumnDefinitionEntityList(c.ColumnDefinitionEntities()) +} + +// ColumnDefinitionEntities returns column entities mapped by their lower cased name +func (c *CreateTableEntity) ColumnDefinitionEntitiesMap() map[string]*ColumnDefinitionEntity { + entities := c.ColumnDefinitionEntities() + m := make(map[string]*ColumnDefinitionEntity, len(entities)) + for _, entity := range entities { + m[entity.NameLowered()] = entity + } + return m +} + +// IndexDefinitionEntities returns the list of index entities for the table. +func (c *CreateTableEntity) IndexDefinitionEntities() []*IndexDefinitionEntity { + colMap := c.ColumnDefinitionEntitiesMap() + keys := c.CreateTable.TableSpec.Indexes + entities := make([]*IndexDefinitionEntity, len(keys)) + for i, key := range keys { + colEntities := make([]*ColumnDefinitionEntity, len(key.Columns)) + for i, keyCol := range key.Columns { + colEntities[i] = colMap[keyCol.Column.Lowered()] + } + entities[i] = NewIndexDefinitionEntity(c.Env, key, NewColumnDefinitionEntityList(colEntities)) + } + return entities +} + +// IndexDefinitionEntityList returns the list of index entities for the table. +func (c *CreateTableEntity) IndexDefinitionEntitiesList() *IndexDefinitionEntityList { + return NewIndexDefinitionEntityList(c.IndexDefinitionEntities()) +} + +// IndexDefinitionEntitiesMap returns index entities mapped by their lower cased name. +func (c *CreateTableEntity) IndexDefinitionEntitiesMap() map[string]*IndexDefinitionEntity { + entities := c.IndexDefinitionEntities() + m := make(map[string]*IndexDefinitionEntity, len(entities)) + for _, entity := range entities { + m[entity.NameLowered()] = entity + } + return m +} + // normalize cleans up the table definition: // - setting names to all keys // - table option case (upper/lower/special) @@ -1740,8 +1801,8 @@ func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, t2ColName := t2Col.Name.Lowered() // we know that column exists in both tables t1Col := t1ColumnsMap[t2ColName] - t1ColEntity := NewColumnDefinitionEntity(c.Env, t1Col.col, t1cc) - t2ColEntity := NewColumnDefinitionEntity(c.Env, t2Col, t2cc) + t1ColEntity := NewColumnDefinitionEntity(c.Env, t1Col.col, false, t1cc) + t2ColEntity := NewColumnDefinitionEntity(c.Env, t2Col, false, t2cc) // check diff between before/after columns: modifyColumnDiff, err := t1ColEntity.ColumnDiff(c.Env, c.Name(), t2ColEntity, hints) @@ -1892,6 +1953,15 @@ func (c *CreateTableEntity) primaryKeyColumns() []*sqlparser.IndexColumn { return nil } +func (c *CreateTableEntity) primaryKeyColumnsMap() map[string]*sqlparser.IndexColumn { + columns := c.primaryKeyColumns() + m := make(map[string]*sqlparser.IndexColumn, len(columns)) + for _, col := range columns { + m[col.Column.Lowered()] = col + } + return m +} + // Create implements Entity interface func (c *CreateTableEntity) Create() EntityDiff { if c == nil { @@ -2648,3 +2718,18 @@ func (c *CreateTableEntity) identicalOtherThanName(other *CreateTableEntity) boo return sqlparser.Equals.RefOfTableSpec(c.TableSpec, other.TableSpec) && sqlparser.Equals.RefOfParsedComments(c.Comments, other.Comments) } + +// AutoIncrementValue returns the value of the AUTO_INCREMENT option, or zero if not exists. +func (c *CreateTableEntity) AutoIncrementValue() (autoIncrement uint64, err error) { + for _, option := range c.CreateTable.TableSpec.Options { + if strings.ToUpper(option.Name) == "AUTO_INCREMENT" { + autoIncrement, err := strconv.ParseUint(option.Value.Val, 10, 64) + if err != nil { + return 0, err + } + return autoIncrement, nil + } + } + // Auto increment not found + return 0, nil +} diff --git a/go/vt/schemadiff/table_test.go b/go/vt/schemadiff/table_test.go index 1168f53f3b6..389e55f447c 100644 --- a/go/vt/schemadiff/table_test.go +++ b/go/vt/schemadiff/table_test.go @@ -2781,9 +2781,10 @@ func TestValidate(t *testing.T) { func TestNormalize(t *testing.T) { tt := []struct { - name string - from string - to string + name string + from string + to string + autoinc uint64 }{ { name: "basic table", @@ -2795,6 +2796,17 @@ func TestNormalize(t *testing.T) { from: "create table t (id int primary key, i int)", to: "CREATE TABLE `t` (\n\t`id` int,\n\t`i` int,\n\tPRIMARY KEY (`id`)\n)", }, + { + name: "basic table, auto increment", + from: "create table t (id int auto_increment primary key, i int)", + to: "CREATE TABLE `t` (\n\t`id` int AUTO_INCREMENT,\n\t`i` int,\n\tPRIMARY KEY (`id`)\n)", + }, + { + name: "basic table, auto increment val", + from: "create table t (id int auto_increment primary key, i int) auto_increment = 123", + to: "CREATE TABLE `t` (\n\t`id` int AUTO_INCREMENT,\n\t`i` int,\n\tPRIMARY KEY (`id`)\n) AUTO_INCREMENT 123", + autoinc: 123, + }, { name: "removes default null", from: "create table t (id int, i int default null, primary key (id))", @@ -3067,6 +3079,10 @@ func TestNormalize(t *testing.T) { from, err := NewCreateTableEntity(env, fromCreateTable) require.NoError(t, err) assert.Equal(t, ts.to, sqlparser.CanonicalString(from)) + + autoinc, err := from.AutoIncrementValue() + require.NoError(t, err) + assert.EqualValues(t, ts.autoinc, autoinc) }) } } diff --git a/go/vt/schemadiff/view.go b/go/vt/schemadiff/view.go index d2dc4dfb76f..8783f1803bb 100644 --- a/go/vt/schemadiff/view.go +++ b/go/vt/schemadiff/view.go @@ -310,6 +310,18 @@ func NewCreateViewEntity(env *Environment, c *sqlparser.CreateView) (*CreateView return entity, nil } +func NewCreateViewEntityFromSQL(env *Environment, sql string) (*CreateViewEntity, error) { + stmt, err := env.Parser().ParseStrictDDL(sql) + if err != nil { + return nil, err + } + createView, ok := stmt.(*sqlparser.CreateView) + if !ok { + return nil, ErrExpectedCreateTable + } + return NewCreateViewEntity(env, createView) +} + func (c *CreateViewEntity) normalize() { // Drop the default algorithm if strings.EqualFold(c.CreateView.Algorithm, "undefined") { diff --git a/go/vt/schemadiff/view_test.go b/go/vt/schemadiff/view_test.go index d1a26c3cdaa..d020649b17e 100644 --- a/go/vt/schemadiff/view_test.go +++ b/go/vt/schemadiff/view_test.go @@ -150,19 +150,16 @@ func TestCreateViewDiff(t *testing.T) { for _, ts := range tt { t.Run(ts.name, func(t *testing.T) { fromStmt, err := env.Parser().ParseStrictDDL(ts.from) - assert.NoError(t, err) + require.NoError(t, err) fromCreateView, ok := fromStmt.(*sqlparser.CreateView) assert.True(t, ok) - toStmt, err := env.Parser().ParseStrictDDL(ts.to) - assert.NoError(t, err) - toCreateView, ok := toStmt.(*sqlparser.CreateView) - assert.True(t, ok) - c, err := NewCreateViewEntity(env, fromCreateView) require.NoError(t, err) - other, err := NewCreateViewEntity(env, toCreateView) + // Test from SQL: + other, err := NewCreateViewEntityFromSQL(env, ts.to) require.NoError(t, err) + alter, err := c.Diff(other, hints) switch { case ts.isError: diff --git a/go/vt/vttablet/onlineddl/executor.go b/go/vt/vttablet/onlineddl/executor.go index 757caa711b7..0d43d52d7f4 100644 --- a/go/vt/vttablet/onlineddl/executor.go +++ b/go/vt/vttablet/onlineddl/executor.go @@ -616,7 +616,7 @@ func (e *Executor) getCreateTableStatement(ctx context.Context, tableName string } createTable, ok := stmt.(*sqlparser.CreateTable) if !ok { - return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "expected CREATE TABLE. Got %v", sqlparser.CanonicalString(stmt)) + return nil, schemadiff.ErrExpectedCreateTable } return createTable, nil } @@ -1518,7 +1518,10 @@ func (e *Executor) initVreplicationOriginalMigration(ctx context.Context, online return v, err } - v = NewVRepl(e.env.Environment(), onlineDDL.UUID, e.keyspace, e.shard, e.dbName, onlineDDL.Table, vreplTableName, originalCreateTable, vreplCreateTable, alterTable, onlineDDL.StrategySetting().IsAnalyzeTableFlag()) + v, err = NewVRepl(e.env.Environment(), onlineDDL.UUID, e.keyspace, e.shard, e.dbName, originalCreateTable, vreplCreateTable, alterTable, onlineDDL.StrategySetting().IsAnalyzeTableFlag()) + if err != nil { + return v, err + } return v, nil } @@ -1526,7 +1529,7 @@ func (e *Executor) initVreplicationOriginalMigration(ctx context.Context, online // This function is called after both source and target tables have been analyzed, so there's more information // about the two, and about the transition between the two. func (e *Executor) postInitVreplicationOriginalMigration(ctx context.Context, onlineDDL *schema.OnlineDDL, v *VRepl, conn *dbconnpool.DBConnection) (err error) { - if v.sourceAutoIncrement > 0 && !v.parser.IsAutoIncrementDefined() { + if v.analysis.SourceAutoIncrement > 0 && !v.alterTableAnalysis.IsAutoIncrementChangeRequested { restoreSQLModeFunc, err := e.initMigrationSQLMode(ctx, onlineDDL, conn) defer restoreSQLModeFunc() if err != nil { @@ -1534,9 +1537,9 @@ func (e *Executor) postInitVreplicationOriginalMigration(ctx context.Context, on } // Apply ALTER TABLE AUTO_INCREMENT=? - parsed := sqlparser.BuildParsedQuery(sqlAlterTableAutoIncrement, v.targetTable, ":auto_increment") + parsed := sqlparser.BuildParsedQuery(sqlAlterTableAutoIncrement, v.targetTableName(), ":auto_increment") bindVars := map[string]*querypb.BindVariable{ - "auto_increment": sqltypes.Uint64BindVariable(v.sourceAutoIncrement), + "auto_increment": sqltypes.Uint64BindVariable(v.analysis.SourceAutoIncrement), } bound, err := parsed.GenerateQuery(bindVars, nil) if err != nil { @@ -1572,7 +1575,18 @@ func (e *Executor) initVreplicationRevertMigration(ctx context.Context, onlineDD if err := e.updateArtifacts(ctx, onlineDDL.UUID, vreplTableName); err != nil { return v, err } - v = NewVRepl(e.env.Environment(), onlineDDL.UUID, e.keyspace, e.shard, e.dbName, onlineDDL.Table, vreplTableName, nil, nil, nil, false) + originalCreateTable, err := e.getCreateTableStatement(ctx, onlineDDL.Table) + if err != nil { + return v, err + } + vreplCreateTable, err := e.getCreateTableStatement(ctx, vreplTableName) + if err != nil { + return v, err + } + v, err = NewVRepl(e.env.Environment(), onlineDDL.UUID, e.keyspace, e.shard, e.dbName, originalCreateTable, vreplCreateTable, nil, false) + if err != nil { + return v, err + } v.pos = revertStream.pos return v, nil } @@ -1614,19 +1628,15 @@ func (e *Executor) ExecuteWithVReplication(ctx context.Context, onlineDDL *schem if err := e.updateMigrationTableRows(ctx, onlineDDL.UUID, v.tableRows); err != nil { return err } - removedUniqueKeyNames := []string{} - for _, uniqueKey := range v.removedUniqueKeys { - removedUniqueKeyNames = append(removedUniqueKeyNames, uniqueKey.Name) - } if err := e.updateSchemaAnalysis(ctx, onlineDDL.UUID, - len(v.addedUniqueKeys), - len(v.removedUniqueKeys), - strings.Join(sqlescape.EscapeIDs(removedUniqueKeyNames), ","), - strings.Join(sqlescape.EscapeIDs(v.removedForeignKeyNames), ","), - strings.Join(sqlescape.EscapeIDs(v.droppedNoDefaultColumnNames), ","), - strings.Join(sqlescape.EscapeIDs(v.expandedColumnNames), ","), - v.revertibleNotes, + v.analysis.AddedUniqueKeys.Len(), + v.analysis.RemovedUniqueKeys.Len(), + strings.Join(sqlescape.EscapeIDs(v.analysis.RemovedUniqueKeys.Names()), ","), + strings.Join(sqlescape.EscapeIDs(v.analysis.RemovedForeignKeyNames), ","), + strings.Join(sqlescape.EscapeIDs(v.analysis.DroppedNoDefaultColumns.Names()), ","), + strings.Join(sqlescape.EscapeIDs(v.analysis.ExpandedColumns.Names()), ","), + v.analysis.RevertibleNotes, ); err != nil { return err } @@ -1654,7 +1664,7 @@ func (e *Executor) ExecuteWithVReplication(ctx context.Context, onlineDDL *schem } // create vreplication entry - insertVReplicationQuery, err := v.generateInsertStatement(ctx) + insertVReplicationQuery, err := v.generateInsertStatement() if err != nil { return err } @@ -1671,7 +1681,7 @@ func (e *Executor) ExecuteWithVReplication(ctx context.Context, onlineDDL *schem } } // start stream! - startVReplicationQuery, err := v.generateStartStatement(ctx) + startVReplicationQuery, err := v.generateStartStatement() if err != nil { return err } @@ -2967,7 +2977,7 @@ func (e *Executor) analyzeDropDDLActionMigration(ctx context.Context, onlineDDL // Write analysis: } if err := e.updateSchemaAnalysis(ctx, onlineDDL.UUID, - 0, 0, "", strings.Join(sqlescape.EscapeIDs(removedForeignKeyNames), ","), "", "", "", + 0, 0, "", strings.Join(sqlescape.EscapeIDs(removedForeignKeyNames), ","), "", "", nil, ); err != nil { return err } @@ -4492,7 +4502,8 @@ func (e *Executor) updateSchemaAnalysis(ctx context.Context, uuid string, addedUniqueKeys, removedUniqueKeys int, removedUniqueKeyNames string, removedForeignKeyNames string, droppedNoDefaultColumnNames string, expandedColumnNames string, - revertibleNotes string) error { + revertibleNotes []string) error { + notes := strings.Join(revertibleNotes, "\n") query, err := sqlparser.ParseAndBind(sqlUpdateSchemaAnalysis, sqltypes.Int64BindVariable(int64(addedUniqueKeys)), sqltypes.Int64BindVariable(int64(removedUniqueKeys)), @@ -4500,7 +4511,7 @@ func (e *Executor) updateSchemaAnalysis(ctx context.Context, uuid string, sqltypes.StringBindVariable(removedForeignKeyNames), sqltypes.StringBindVariable(droppedNoDefaultColumnNames), sqltypes.StringBindVariable(expandedColumnNames), - sqltypes.StringBindVariable(revertibleNotes), + sqltypes.StringBindVariable(notes), sqltypes.StringBindVariable(uuid), ) if err != nil { diff --git a/go/vt/vttablet/onlineddl/schema.go b/go/vt/vttablet/onlineddl/schema.go index 4f65864cbfa..28e32e7dab4 100644 --- a/go/vt/vttablet/onlineddl/schema.go +++ b/go/vt/vttablet/onlineddl/schema.go @@ -461,16 +461,6 @@ const ( AND ACTION_TIMING='AFTER' AND LEFT(TRIGGER_NAME, 7)='pt_osc_' ` - sqlSelectColumnTypes = ` - select - *, - COLUMN_DEFAULT IS NULL AS is_default_null - from - information_schema.columns - where - table_schema=%a - and table_name=%a - ` selSelectCountFKParentConstraints = ` SELECT COUNT(*) as num_fk_constraints @@ -487,75 +477,10 @@ const ( TABLE_SCHEMA=%a AND TABLE_NAME=%a AND REFERENCED_TABLE_NAME IS NOT NULL ` - sqlSelectUniqueKeys = ` - SELECT - COLUMNS.TABLE_SCHEMA as table_schema, - COLUMNS.TABLE_NAME as table_name, - COLUMNS.COLUMN_NAME as column_name, - UNIQUES.INDEX_NAME as index_name, - UNIQUES.COLUMN_NAMES as column_names, - UNIQUES.COUNT_COLUMN_IN_INDEX as count_column_in_index, - COLUMNS.DATA_TYPE as data_type, - COLUMNS.CHARACTER_SET_NAME as character_set_name, - LOCATE('auto_increment', EXTRA) > 0 as is_auto_increment, - (DATA_TYPE='float' OR DATA_TYPE='double') AS is_float, - has_subpart, - has_nullable - FROM INFORMATION_SCHEMA.COLUMNS INNER JOIN ( - SELECT - TABLE_SCHEMA, - TABLE_NAME, - INDEX_NAME, - COUNT(*) AS COUNT_COLUMN_IN_INDEX, - GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC) AS COLUMN_NAMES, - SUBSTRING_INDEX(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC), ',', 1) AS FIRST_COLUMN_NAME, - SUM(SUB_PART IS NOT NULL) > 0 AS has_subpart, - SUM(NULLABLE='YES') > 0 AS has_nullable - FROM INFORMATION_SCHEMA.STATISTICS - WHERE - NON_UNIQUE=0 - AND TABLE_SCHEMA=%a - AND TABLE_NAME=%a - GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME - ) AS UNIQUES - ON ( - COLUMNS.COLUMN_NAME = UNIQUES.FIRST_COLUMN_NAME - ) - WHERE - COLUMNS.TABLE_SCHEMA=%a - AND COLUMNS.TABLE_NAME=%a - ORDER BY - COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME, - CASE UNIQUES.INDEX_NAME - WHEN 'PRIMARY' THEN 0 - ELSE 1 - END, - CASE has_nullable - WHEN 0 THEN 0 - ELSE 1 - END, - CASE has_subpart - WHEN 0 THEN 0 - ELSE 1 - END, - CASE IFNULL(CHARACTER_SET_NAME, '') - WHEN '' THEN 0 - ELSE 1 - END, - CASE DATA_TYPE - WHEN 'tinyint' THEN 0 - WHEN 'smallint' THEN 1 - WHEN 'int' THEN 2 - WHEN 'bigint' THEN 3 - ELSE 100 - END, - COUNT_COLUMN_IN_INDEX - ` sqlDropTrigger = "DROP TRIGGER IF EXISTS `%a`.`%a`" sqlShowTablesLike = "SHOW TABLES LIKE '%a'" sqlDropTable = "DROP TABLE `%a`" sqlDropTableIfExists = "DROP TABLE IF EXISTS `%a`" - sqlShowColumnsFrom = "SHOW COLUMNS FROM `%a`" sqlShowTableStatus = "SHOW TABLE STATUS LIKE '%a'" sqlAnalyzeTable = "ANALYZE NO_WRITE_TO_BINLOG TABLE `%a`" sqlShowCreateTable = "SHOW CREATE TABLE `%a`" @@ -563,23 +488,14 @@ const ( sqlShowVariablesLikeFastAnalyzeTable = "show global variables like 'fast_analyze_table'" sqlEnableFastAnalyzeTable = "set @@fast_analyze_table = 1" sqlDisableFastAnalyzeTable = "set @@fast_analyze_table = 0" - sqlGetAutoIncrement = ` - SELECT - AUTO_INCREMENT - FROM INFORMATION_SCHEMA.TABLES - WHERE - TABLES.TABLE_SCHEMA=%a - AND TABLES.TABLE_NAME=%a - AND AUTO_INCREMENT IS NOT NULL - ` - sqlAlterTableAutoIncrement = "ALTER TABLE `%s` AUTO_INCREMENT=%a" - sqlAlterTableExchangePartition = "ALTER TABLE `%a` EXCHANGE PARTITION `%a` WITH TABLE `%a`" - sqlAlterTableRemovePartitioning = "ALTER TABLE `%a` REMOVE PARTITIONING" - sqlAlterTableDropPartition = "ALTER TABLE `%a` DROP PARTITION `%a`" - sqlStartVReplStream = "UPDATE _vt.vreplication set state='Running' where db_name=%a and workflow=%a" - sqlStopVReplStream = "UPDATE _vt.vreplication set state='Stopped' where db_name=%a and workflow=%a" - sqlDeleteVReplStream = "DELETE FROM _vt.vreplication where db_name=%a and workflow=%a" - sqlReadVReplStream = `SELECT + sqlAlterTableAutoIncrement = "ALTER TABLE `%s` AUTO_INCREMENT=%a" + sqlAlterTableExchangePartition = "ALTER TABLE `%a` EXCHANGE PARTITION `%a` WITH TABLE `%a`" + sqlAlterTableRemovePartitioning = "ALTER TABLE `%a` REMOVE PARTITIONING" + sqlAlterTableDropPartition = "ALTER TABLE `%a` DROP PARTITION `%a`" + sqlStartVReplStream = "UPDATE _vt.vreplication set state='Running' where db_name=%a and workflow=%a" + sqlStopVReplStream = "UPDATE _vt.vreplication set state='Stopped' where db_name=%a and workflow=%a" + sqlDeleteVReplStream = "DELETE FROM _vt.vreplication where db_name=%a and workflow=%a" + sqlReadVReplStream = `SELECT id, workflow, source, diff --git a/go/vt/vttablet/onlineddl/vrepl.go b/go/vt/vttablet/onlineddl/vrepl.go index 42fe33a855f..14c52d352bf 100644 --- a/go/vt/vttablet/onlineddl/vrepl.go +++ b/go/vt/vttablet/onlineddl/vrepl.go @@ -38,11 +38,10 @@ import ( "vitess.io/vitess/go/textutil" "vitess.io/vitess/go/vt/dbconnpool" "vitess.io/vitess/go/vt/log" - "vitess.io/vitess/go/vt/schema" + "vitess.io/vitess/go/vt/schemadiff" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vtenv" "vitess.io/vitess/go/vt/vterrors" - "vitess.io/vitess/go/vt/vttablet/onlineddl/vrepl" "vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" @@ -99,43 +98,23 @@ func (v *VReplStream) hasError() (isTerminal bool, vreplError error) { // VRepl is an online DDL helper for VReplication based migrations (ddl_strategy="online") type VRepl struct { - workflow string - keyspace string - shard string - dbName string - sourceTable string - targetTable string - pos string - alterQuery *sqlparser.AlterTable - tableRows int64 - - originalCreateTable *sqlparser.CreateTable - vreplCreateTable *sqlparser.CreateTable + workflow string + keyspace string + shard string + dbName string + pos string + tableRows int64 - analyzeTable bool - - sourceSharedColumns *vrepl.ColumnList - targetSharedColumns *vrepl.ColumnList - droppedSourceNonGeneratedColumns *vrepl.ColumnList - droppedNoDefaultColumnNames []string - expandedColumnNames []string - sharedColumnsMap map[string]string - sourceAutoIncrement uint64 + sourceCreateTableEntity *schemadiff.CreateTableEntity + targetCreateTableEntity *schemadiff.CreateTableEntity + analysis *schemadiff.MigrationTablesAnalysis - chosenSourceUniqueKey *vrepl.UniqueKey - chosenTargetUniqueKey *vrepl.UniqueKey - - addedUniqueKeys []*vrepl.UniqueKey - removedUniqueKeys []*vrepl.UniqueKey - removedForeignKeyNames []string + analyzeTable bool - revertibleNotes string - filterQuery string - enumToTextMap map[string]string - intToEnumMap map[string]bool - bls *binlogdatapb.BinlogSource + filterQuery string + bls *binlogdatapb.BinlogSource - parser *vrepl.AlterTableParser + alterTableAnalysis *schemadiff.AlterTableAnalysis convertCharset map[string](*binlogdatapb.CharsetConversion) @@ -149,110 +128,40 @@ func NewVRepl( keyspace string, shard string, dbName string, - sourceTable string, - targetTable string, - originalCreateTable *sqlparser.CreateTable, - vreplCreateTable *sqlparser.CreateTable, + sourceCreateTable *sqlparser.CreateTable, + targetCreateTable *sqlparser.CreateTable, alterQuery *sqlparser.AlterTable, analyzeTable bool, -) *VRepl { - return &VRepl{ - env: env, - workflow: workflow, - keyspace: keyspace, - shard: shard, - dbName: dbName, - sourceTable: sourceTable, - targetTable: targetTable, - originalCreateTable: originalCreateTable, - vreplCreateTable: vreplCreateTable, - alterQuery: alterQuery, - analyzeTable: analyzeTable, - parser: vrepl.NewAlterTableParser(), - enumToTextMap: map[string]string{}, - intToEnumMap: map[string]bool{}, - convertCharset: map[string](*binlogdatapb.CharsetConversion){}, - } -} - -// readAutoIncrement reads the AUTO_INCREMENT value, if any, for a give ntable -func (v *VRepl) readAutoIncrement(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (autoIncrement uint64, err error) { - query, err := sqlparser.ParseAndBind(sqlGetAutoIncrement, - sqltypes.StringBindVariable(v.dbName), - sqltypes.StringBindVariable(tableName), - ) +) (*VRepl, error) { + v := &VRepl{ + env: env, + workflow: workflow, + keyspace: keyspace, + shard: shard, + dbName: dbName, + alterTableAnalysis: schemadiff.OnlineDDLAlterTableAnalysis(alterQuery), + analyzeTable: analyzeTable, + convertCharset: map[string](*binlogdatapb.CharsetConversion){}, + } + senv := schemadiff.NewEnv(v.env, v.env.CollationEnv().DefaultConnectionCharset()) + var err error + v.sourceCreateTableEntity, err = schemadiff.NewCreateTableEntity(senv, sourceCreateTable) if err != nil { - return 0, err + return nil, err } - - rs, err := conn.ExecuteFetch(query, -1, true) + v.targetCreateTableEntity, err = schemadiff.NewCreateTableEntity(senv, targetCreateTable) if err != nil { - return 0, err - } - for _, row := range rs.Named().Rows { - autoIncrement = row.AsUint64("AUTO_INCREMENT", 0) + return nil, err } - - return autoIncrement, nil + return v, nil } -// readTableColumns reads column list from given table -func (v *VRepl) readTableColumns(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (columns *vrepl.ColumnList, virtualColumns *vrepl.ColumnList, pkColumns *vrepl.ColumnList, err error) { - parsed := sqlparser.BuildParsedQuery(sqlShowColumnsFrom, tableName) - rs, err := conn.ExecuteFetch(parsed.Query, -1, true) - if err != nil { - return nil, nil, nil, err - } - columnNames := []string{} - virtualColumnNames := []string{} - pkColumnNames := []string{} - for _, row := range rs.Named().Rows { - columnName := row.AsString("Field", "") - columnNames = append(columnNames, columnName) - - extra := row.AsString("Extra", "") - if strings.Contains(extra, "STORED GENERATED") || strings.Contains(extra, "VIRTUAL GENERATED") { - virtualColumnNames = append(virtualColumnNames, columnName) - } - - key := row.AsString("Key", "") - if key == "PRI" { - pkColumnNames = append(pkColumnNames, columnName) - } - } - if len(columnNames) == 0 { - return nil, nil, nil, fmt.Errorf("Found 0 columns on `%s`", tableName) - } - return vrepl.NewColumnList(columnNames), vrepl.NewColumnList(virtualColumnNames), vrepl.NewColumnList(pkColumnNames), nil +func (v *VRepl) sourceTableName() string { + return v.sourceCreateTableEntity.Name() } -// readTableUniqueKeys reads all unique keys from a given table, by order of usefulness/performance: PRIMARY first, integers are better, non-null are better -func (v *VRepl) readTableUniqueKeys(ctx context.Context, conn *dbconnpool.DBConnection, tableName string) (uniqueKeys []*vrepl.UniqueKey, err error) { - query, err := sqlparser.ParseAndBind(sqlSelectUniqueKeys, - sqltypes.StringBindVariable(v.dbName), - sqltypes.StringBindVariable(tableName), - sqltypes.StringBindVariable(v.dbName), - sqltypes.StringBindVariable(tableName), - ) - if err != nil { - return nil, err - } - rs, err := conn.ExecuteFetch(query, -1, true) - if err != nil { - return nil, err - } - for _, row := range rs.Named().Rows { - uniqueKey := &vrepl.UniqueKey{ - Name: row.AsString("index_name", ""), - Columns: *vrepl.ParseColumnList(row.AsString("column_names", "")), - HasNullable: row.AsBool("has_nullable", false), - HasSubpart: row.AsBool("has_subpart", false), - HasFloat: row.AsBool("is_float", false), - IsAutoIncrement: row.AsBool("is_auto_increment", false), - } - uniqueKeys = append(uniqueKeys, uniqueKey) - } - return uniqueKeys, nil +func (v *VRepl) targetTableName() string { + return v.targetCreateTableEntity.Name() } // isFastAnalyzeTableSupported checks if the underlying MySQL server supports 'fast_analyze_table', @@ -307,255 +216,64 @@ func (v *VRepl) readTableStatus(ctx context.Context, conn *dbconnpool.DBConnecti return tableRows, err } -// applyColumnTypes -func (v *VRepl) applyColumnTypes(ctx context.Context, conn *dbconnpool.DBConnection, tableName string, columnsLists ...*vrepl.ColumnList) error { - query, err := sqlparser.ParseAndBind(sqlSelectColumnTypes, - sqltypes.StringBindVariable(v.dbName), - sqltypes.StringBindVariable(tableName), - ) - if err != nil { - return err - } - rs, err := conn.ExecuteFetch(query, -1, true) - if err != nil { - return err - } - for _, row := range rs.Named().Rows { - columnName := row["COLUMN_NAME"].ToString() - columnType := row["COLUMN_TYPE"].ToString() - columnOctetLength := row.AsUint64("CHARACTER_OCTET_LENGTH", 0) - - for _, columnsList := range columnsLists { - column := columnsList.GetColumn(columnName) - if column == nil { - continue - } - - column.DataType = row.AsString("DATA_TYPE", "") // a more canonical form of column_type - column.IsNullable = (row.AsString("IS_NULLABLE", "") == "YES") - column.IsDefaultNull = row.AsBool("is_default_null", false) - - column.CharacterMaximumLength = row.AsInt64("CHARACTER_MAXIMUM_LENGTH", 0) - column.NumericPrecision = row.AsInt64("NUMERIC_PRECISION", 0) - column.NumericScale = row.AsInt64("NUMERIC_SCALE", 0) - column.DateTimePrecision = row.AsInt64("DATETIME_PRECISION", -1) - - column.Type = vrepl.UnknownColumnType - if strings.Contains(columnType, "unsigned") { - column.IsUnsigned = true - } - if strings.Contains(columnType, "mediumint") { - column.SetTypeIfUnknown(vrepl.MediumIntColumnType) - } - if strings.Contains(columnType, "timestamp") { - column.SetTypeIfUnknown(vrepl.TimestampColumnType) - } - if strings.Contains(columnType, "datetime") { - column.SetTypeIfUnknown(vrepl.DateTimeColumnType) - } - if strings.Contains(columnType, "json") { - column.SetTypeIfUnknown(vrepl.JSONColumnType) - } - if strings.Contains(columnType, "float") { - column.SetTypeIfUnknown(vrepl.FloatColumnType) - } - if strings.Contains(columnType, "double") { - column.SetTypeIfUnknown(vrepl.DoubleColumnType) - } - if strings.HasPrefix(columnType, "enum") { - column.SetTypeIfUnknown(vrepl.EnumColumnType) - column.EnumValues = schema.ParseEnumValues(columnType) - } - if strings.HasPrefix(columnType, "set(") { - column.SetTypeIfUnknown(vrepl.SetColumnType) - column.EnumValues = schema.ParseSetValues(columnType) - } - if strings.HasPrefix(columnType, "binary") { - column.SetTypeIfUnknown(vrepl.BinaryColumnType) - column.BinaryOctetLength = columnOctetLength - } - if charset := row.AsString("CHARACTER_SET_NAME", ""); charset != "" { - column.Charset = charset - } - if collation := row.AsString("COLLATION_NAME", ""); collation != "" { - column.SetTypeIfUnknown(vrepl.StringColumnType) - column.Collation = collation +// formalizeColumns +func formalizeColumns(columnsLists ...*schemadiff.ColumnDefinitionEntityList) error { + for _, colList := range columnsLists { + for _, col := range colList.Entities { + col.SetExplicitDefaultAndNull() + if err := col.SetExplicitCharsetCollate(); err != nil { + return err } } } return nil } -func (v *VRepl) analyzeAlter(ctx context.Context) error { - if v.alterQuery == nil { - // Happens for REVERT - return nil - } - v.parser.AnalyzeAlter(v.alterQuery) - if v.parser.IsRenameTable() { - return fmt.Errorf("Renaming the table is not supported in ALTER TABLE: %s", sqlparser.CanonicalString(v.alterQuery)) +func (v *VRepl) analyzeAlter() error { + if v.alterTableAnalysis.IsRenameTable { + return fmt.Errorf("renaming the table is not supported in ALTER TABLE") } return nil } -func (v *VRepl) analyzeTables(ctx context.Context, conn *dbconnpool.DBConnection) (err error) { - if v.analyzeTable { - if err := v.executeAnalyzeTable(ctx, conn, v.sourceTable); err != nil { - return err - } - } - v.tableRows, err = v.readTableStatus(ctx, conn, v.sourceTable) +func (v *VRepl) analyzeTables() (err error) { + analysis, err := schemadiff.OnlineDDLMigrationTablesAnalysis(v.sourceCreateTableEntity, v.targetCreateTableEntity, v.alterTableAnalysis) if err != nil { return err } - // columns: - sourceColumns, sourceVirtualColumns, sourcePKColumns, err := v.readTableColumns(ctx, conn, v.sourceTable) - if err != nil { - return err - } - targetColumns, targetVirtualColumns, targetPKColumns, err := v.readTableColumns(ctx, conn, v.targetTable) - if err != nil { - return err - } - v.sourceSharedColumns, v.targetSharedColumns, v.droppedSourceNonGeneratedColumns, v.sharedColumnsMap = vrepl.GetSharedColumns(sourceColumns, targetColumns, sourceVirtualColumns, targetVirtualColumns, v.parser) + v.analysis = analysis - // unique keys - sourceUniqueKeys, err := v.readTableUniqueKeys(ctx, conn, v.sourceTable) - if err != nil { - return err - } - if len(sourceUniqueKeys) == 0 { - return fmt.Errorf("Found no possible unique key on `%s`", v.sourceTable) - } - targetUniqueKeys, err := v.readTableUniqueKeys(ctx, conn, v.targetTable) - if err != nil { - return err - } - if len(targetUniqueKeys) == 0 { - return fmt.Errorf("Found no possible unique key on `%s`", v.targetTable) - } - v.chosenSourceUniqueKey, v.chosenTargetUniqueKey = vrepl.GetSharedUniqueKeys(sourceUniqueKeys, targetUniqueKeys, v.parser.ColumnRenameMap()) - if v.chosenSourceUniqueKey == nil { - // VReplication supports completely different unique keys on source and target, covering - // some/completely different columns. The condition is that the key on source - // must use columns which all exist on target table. - v.chosenSourceUniqueKey = vrepl.GetUniqueKeyCoveredByColumns(sourceUniqueKeys, v.sourceSharedColumns) - if v.chosenSourceUniqueKey == nil { - // Still no luck. - return fmt.Errorf("Found no possible unique key on `%s` whose columns are in target table `%s`", v.sourceTable, v.targetTable) - } - } - if v.chosenTargetUniqueKey == nil { - // VReplication supports completely different unique keys on source and target, covering - // some/completely different columns. The condition is that the key on target - // must use columns which all exist on source table. - v.chosenTargetUniqueKey = vrepl.GetUniqueKeyCoveredByColumns(targetUniqueKeys, v.targetSharedColumns) - if v.chosenTargetUniqueKey == nil { - // Still no luck. - return fmt.Errorf("Found no possible unique key on `%s` whose columns are in source table `%s`", v.targetTable, v.sourceTable) - } - } - if v.chosenSourceUniqueKey == nil || v.chosenTargetUniqueKey == nil { - return fmt.Errorf("Found no shared, not nullable, unique keys between `%s` and `%s`", v.sourceTable, v.targetTable) - } - v.addedUniqueKeys = vrepl.AddedUniqueKeys(sourceUniqueKeys, targetUniqueKeys, v.parser.ColumnRenameMap()) - v.removedUniqueKeys = vrepl.RemovedUniqueKeys(sourceUniqueKeys, targetUniqueKeys, v.parser.ColumnRenameMap()) - v.removedForeignKeyNames, err = vrepl.RemovedForeignKeyNames(v.env, v.originalCreateTable, v.vreplCreateTable) - if err != nil { - return err - } - - // chosen source & target unique keys have exact columns in same order - sharedPKColumns := &v.chosenSourceUniqueKey.Columns - - if err := v.applyColumnTypes(ctx, conn, v.sourceTable, sourceColumns, sourceVirtualColumns, sourcePKColumns, v.sourceSharedColumns, sharedPKColumns, v.droppedSourceNonGeneratedColumns); err != nil { - return err - } - if err := v.applyColumnTypes(ctx, conn, v.targetTable, targetColumns, targetVirtualColumns, targetPKColumns, v.targetSharedColumns); err != nil { - return err - } - - for _, sourcePKColumn := range sharedPKColumns.Columns() { - mappedColumn := v.targetSharedColumns.GetColumn(sourcePKColumn.Name) - if sourcePKColumn.Type == vrepl.EnumColumnType && mappedColumn.Type == vrepl.EnumColumnType { - // An ENUM as part of PRIMARY KEY. We must convert it to text because OMG that's complicated. - // There's a scenario where a query may modify the enum value (and it's bad practice, seeing - // that it's part of the PK, but it's still valid), and in that case we must have the string value - // to be able to DELETE the old row - v.targetSharedColumns.SetEnumToTextConversion(mappedColumn.Name, sourcePKColumn.EnumValues) - v.enumToTextMap[sourcePKColumn.Name] = sourcePKColumn.EnumValues - } - } - - for i := range v.sourceSharedColumns.Columns() { - sourceColumn := v.sourceSharedColumns.Columns()[i] - mappedColumn := v.targetSharedColumns.Columns()[i] - if sourceColumn.Type == vrepl.EnumColumnType { - switch { - // Either this is an ENUM column that stays an ENUM, or it is converted to a textual type. - // We take note of the enum values, and make it available in vreplication's Filter.Rule.ConvertEnumToText. - // This, in turn, will be used by vplayer (in TablePlan) like so: - // - In the binary log, enum values are integers. - // - Upon seeing this map, PlanBuilder will convert said int to the enum's logical string value. - // - And will apply the value as a string (`StringBindVariable`) in the query. - // What this allows is for enum values to have different ordering in the before/after table schema, - // so that for example you could modify an enum column: - // - from `('red', 'green', 'blue')` to `('red', 'blue')` - // - from `('red', 'green', 'blue')` to `('blue', 'red', 'green')` - case mappedColumn.Type == vrepl.EnumColumnType: - v.enumToTextMap[sourceColumn.Name] = sourceColumn.EnumValues - case mappedColumn.Charset != "": - v.enumToTextMap[sourceColumn.Name] = sourceColumn.EnumValues - v.targetSharedColumns.SetEnumToTextConversion(mappedColumn.Name, sourceColumn.EnumValues) - } - } + return nil +} - if sourceColumn.IsIntegralType() && mappedColumn.Type == vrepl.EnumColumnType { - v.intToEnumMap[sourceColumn.Name] = true +// analyzeTableStatus reads information from SHOW TABLE STATUS +func (v *VRepl) analyzeTableStatus(ctx context.Context, conn *dbconnpool.DBConnection) (err error) { + if v.analyzeTable { + if err := v.executeAnalyzeTable(ctx, conn, v.sourceTableName()); err != nil { + return err } } - - v.droppedNoDefaultColumnNames = vrepl.GetNoDefaultColumnNames(v.droppedSourceNonGeneratedColumns) - var expandedDescriptions map[string]string - v.expandedColumnNames, expandedDescriptions = vrepl.GetExpandedColumnNames(v.sourceSharedColumns, v.targetSharedColumns) - - v.sourceAutoIncrement, err = v.readAutoIncrement(ctx, conn, v.sourceTable) - - notes := []string{} - for _, uk := range v.removedUniqueKeys { - notes = append(notes, fmt.Sprintf("unique constraint removed: %s", uk.Name)) - } - for _, name := range v.droppedNoDefaultColumnNames { - notes = append(notes, fmt.Sprintf("column %s dropped, and had no default value", name)) - } - for _, name := range v.expandedColumnNames { - notes = append(notes, fmt.Sprintf("column %s: %s", name, expandedDescriptions[name])) - } - for _, name := range v.removedForeignKeyNames { - notes = append(notes, fmt.Sprintf("foreign key %s dropped", name)) - } - v.revertibleNotes = strings.Join(notes, "\n") + v.tableRows, err = v.readTableStatus(ctx, conn, v.sourceTableName()) if err != nil { return err } - return nil } // generateFilterQuery creates a SELECT query used by vreplication as a filter. It SELECTs all // non-generated columns between source & target tables, and takes care of column renames. -func (v *VRepl) generateFilterQuery(ctx context.Context) error { - if v.sourceSharedColumns.Len() == 0 { - return fmt.Errorf("Empty column list") +func (v *VRepl) generateFilterQuery() error { + if v.analysis.SourceSharedColumns.Len() == 0 { + return fmt.Errorf("empty column list") } var sb strings.Builder sb.WriteString("select ") - for i, sourceCol := range v.sourceSharedColumns.Columns() { - name := sourceCol.Name - targetName := v.sharedColumnsMap[name] + for i, sourceCol := range v.analysis.SourceSharedColumns.Entities { + name := sourceCol.Name() + targetName := v.analysis.SharedColumnsMap[name] - targetCol := v.targetSharedColumns.GetColumn(targetName) + targetCol := v.analysis.TargetSharedColumns.GetColumn(targetName) if targetCol == nil { return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Cannot find target column %s", targetName) } @@ -564,35 +282,36 @@ func (v *VRepl) generateFilterQuery(ctx context.Context) error { sb.WriteString(", ") } switch { - case sourceCol.EnumToTextConversion: + case sourceCol.HasEnumValues(): + // Source is `enum` or `set`. We always take the textual represenation rather than the numeric one. sb.WriteString(fmt.Sprintf("CONCAT(%s)", escapeName(name))) - case v.intToEnumMap[name]: + case v.analysis.IntToEnumMap[name]: sb.WriteString(fmt.Sprintf("CONCAT(%s)", escapeName(name))) - case sourceCol.Type == vrepl.JSONColumnType: + case sourceCol.Type() == "json": sb.WriteString(fmt.Sprintf("convert(%s using utf8mb4)", escapeName(name))) - case sourceCol.Type == vrepl.StringColumnType: + case sourceCol.IsTextual(): // Check source and target charset/encoding. If needed, create // a binlogdatapb.CharsetConversion entry (later written to vreplication) - fromCollation := v.env.CollationEnv().DefaultCollationForCharset(sourceCol.Charset) + fromCollation := v.env.CollationEnv().DefaultCollationForCharset(sourceCol.Charset()) if fromCollation == collations.Unknown { - return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Character set %s not supported for column %s", sourceCol.Charset, sourceCol.Name) + return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Character set %s not supported for column %s", sourceCol.Charset(), sourceCol.Name()) } - toCollation := v.env.CollationEnv().DefaultCollationForCharset(targetCol.Charset) + toCollation := v.env.CollationEnv().DefaultCollationForCharset(targetCol.Charset()) // Let's see if target col is at all textual - if targetCol.Type == vrepl.StringColumnType && toCollation == collations.Unknown { - return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Character set %s not supported for column %s", targetCol.Charset, targetCol.Name) + if targetCol.IsTextual() && toCollation == collations.Unknown { + return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Character set %s not supported for column %s", targetCol.Charset(), targetCol.Name()) } - if trivialCharset(fromCollation) && trivialCharset(toCollation) && targetCol.Type != vrepl.JSONColumnType { + if trivialCharset(fromCollation) && trivialCharset(toCollation) && targetCol.Type() != "json" { sb.WriteString(escapeName(name)) } else { v.convertCharset[targetName] = &binlogdatapb.CharsetConversion{ - FromCharset: sourceCol.Charset, - ToCharset: targetCol.Charset, + FromCharset: sourceCol.Charset(), + ToCharset: targetCol.Charset(), } sb.WriteString(fmt.Sprintf("convert(%s using utf8mb4)", escapeName(name))) } - case targetCol.Type == vrepl.JSONColumnType && sourceCol.Type != vrepl.JSONColumnType: + case targetCol.Type() == "json" && sourceCol.Type() != "json": // Convert any type to JSON: encode the type as utf8mb4 text sb.WriteString(fmt.Sprintf("convert(%s using utf8mb4)", escapeName(name))) default: @@ -602,7 +321,7 @@ func (v *VRepl) generateFilterQuery(ctx context.Context) error { sb.WriteString(escapeName(targetName)) } sb.WriteString(" from ") - sb.WriteString(escapeName(v.sourceTable)) + sb.WriteString(escapeName(v.sourceTableName())) v.filterQuery = sb.String() return nil @@ -624,25 +343,22 @@ func (v *VRepl) analyzeBinlogSource(ctx context.Context) { StopAfterCopy: false, } - encodeColumns := func(columns *vrepl.ColumnList) string { - return textutil.EscapeJoin(columns.Names(), ",") + encodeColumns := func(names []string) string { + return textutil.EscapeJoin(names, ",") } rule := &binlogdatapb.Rule{ - Match: v.targetTable, + Match: v.targetTableName(), Filter: v.filterQuery, - SourceUniqueKeyColumns: encodeColumns(&v.chosenSourceUniqueKey.Columns), - TargetUniqueKeyColumns: encodeColumns(&v.chosenTargetUniqueKey.Columns), - SourceUniqueKeyTargetColumns: encodeColumns(v.chosenSourceUniqueKey.Columns.MappedNamesColumnList(v.sharedColumnsMap)), - ForceUniqueKey: url.QueryEscape(v.chosenSourceUniqueKey.Name), + SourceUniqueKeyColumns: encodeColumns(v.analysis.ChosenSourceUniqueKey.ColumnList.Names()), + TargetUniqueKeyColumns: encodeColumns(v.analysis.ChosenTargetUniqueKey.ColumnList.Names()), + SourceUniqueKeyTargetColumns: encodeColumns(schemadiff.MappedColumnNames(v.analysis.ChosenSourceUniqueKey.ColumnList, v.analysis.SharedColumnsMap)), + ForceUniqueKey: url.QueryEscape(v.analysis.ChosenSourceUniqueKey.Name()), } if len(v.convertCharset) > 0 { rule.ConvertCharset = v.convertCharset } - if len(v.enumToTextMap) > 0 { - rule.ConvertEnumToText = v.enumToTextMap - } - if len(v.intToEnumMap) > 0 { - rule.ConvertIntToEnum = v.intToEnumMap + if len(v.analysis.IntToEnumMap) > 0 { + rule.ConvertIntToEnum = v.analysis.IntToEnumMap } bls.Filter.Rules = append(bls.Filter.Rules, rule) @@ -650,13 +366,16 @@ func (v *VRepl) analyzeBinlogSource(ctx context.Context) { } func (v *VRepl) analyze(ctx context.Context, conn *dbconnpool.DBConnection) error { - if err := v.analyzeAlter(ctx); err != nil { + if err := v.analyzeAlter(); err != nil { + return err + } + if err := v.analyzeTables(); err != nil { return err } - if err := v.analyzeTables(ctx, conn); err != nil { + if err := v.generateFilterQuery(); err != nil { return err } - if err := v.generateFilterQuery(ctx); err != nil { + if err := v.analyzeTableStatus(ctx, conn); err != nil { return err } v.analyzeBinlogSource(ctx) @@ -664,7 +383,7 @@ func (v *VRepl) analyze(ctx context.Context, conn *dbconnpool.DBConnection) erro } // generateInsertStatement generates the INSERT INTO _vt.replication statement that creates the vreplication workflow -func (v *VRepl) generateInsertStatement(ctx context.Context) (string, error) { +func (v *VRepl) generateInsertStatement() (string, error) { ig := vreplication.NewInsertGenerator(binlogdatapb.VReplicationWorkflowState_Stopped, v.dbName) ig.AddRow(v.workflow, v.bls, v.pos, "", "in_order:REPLICA,PRIMARY", binlogdatapb.VReplicationWorkflowType_OnlineDDL, binlogdatapb.VReplicationWorkflowSubType_None, false) @@ -673,7 +392,7 @@ func (v *VRepl) generateInsertStatement(ctx context.Context) (string, error) { } // generateStartStatement Generates the statement to start VReplication running on the workflow -func (v *VRepl) generateStartStatement(ctx context.Context) (string, error) { +func (v *VRepl) generateStartStatement() (string, error) { return sqlparser.ParseAndBind(sqlStartVReplStream, sqltypes.StringBindVariable(v.dbName), sqltypes.StringBindVariable(v.workflow), diff --git a/go/vt/vttablet/onlineddl/vrepl/columns.go b/go/vt/vttablet/onlineddl/vrepl/columns.go deleted file mode 100644 index f2bb8f6d3f2..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/columns.go +++ /dev/null @@ -1,208 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "fmt" - "strings" - - "vitess.io/vitess/go/vt/schema" -) - -// expandedDataTypes maps some known and difficult-to-compute by INFORMATION_SCHEMA data types which expand other data types. -// For example, in "date:datetime", datetime expands date because it has more precision. In "timestamp:date" date expands timestamp -// because it can contain years not covered by timestamp. -var expandedDataTypes = map[string]bool{ - "time:datetime": true, - "date:datetime": true, - "timestamp:datetime": true, - "time:timestamp": true, - "date:timestamp": true, - "timestamp:date": true, -} - -// GetSharedColumns returns the intersection of two lists of columns in same order as the first list -func GetSharedColumns( - sourceColumns, targetColumns *ColumnList, - sourceVirtualColumns, targetVirtualColumns *ColumnList, - parser *AlterTableParser, -) ( - sourceSharedColumns *ColumnList, - targetSharedColumns *ColumnList, - droppedSourceNonGeneratedColumns *ColumnList, - sharedColumnsMap map[string]string, -) { - sharedColumnNames := []string{} - droppedSourceNonGeneratedColumnsNames := []string{} - for _, sourceColumn := range sourceColumns.Names() { - isSharedColumn := false - isVirtualColumnOnSource := false - for _, targetColumn := range targetColumns.Names() { - if strings.EqualFold(sourceColumn, targetColumn) { - // both tables have this column. Good start. - isSharedColumn = true - break - } - if strings.EqualFold(parser.columnRenameMap[sourceColumn], targetColumn) { - // column in source is renamed in target - isSharedColumn = true - break - } - } - for droppedColumn := range parser.DroppedColumnsMap() { - if strings.EqualFold(sourceColumn, droppedColumn) { - isSharedColumn = false - break - } - } - for _, virtualColumn := range sourceVirtualColumns.Names() { - // virtual/generated columns on source are silently skipped - if strings.EqualFold(sourceColumn, virtualColumn) { - isSharedColumn = false - isVirtualColumnOnSource = true - } - } - for _, virtualColumn := range targetVirtualColumns.Names() { - // virtual/generated columns on target are silently skipped - if strings.EqualFold(sourceColumn, virtualColumn) { - isSharedColumn = false - } - } - if isSharedColumn { - sharedColumnNames = append(sharedColumnNames, sourceColumn) - } else if !isVirtualColumnOnSource { - droppedSourceNonGeneratedColumnsNames = append(droppedSourceNonGeneratedColumnsNames, sourceColumn) - } - } - sharedColumnsMap = map[string]string{} - for _, columnName := range sharedColumnNames { - if mapped, ok := parser.columnRenameMap[columnName]; ok { - sharedColumnsMap[columnName] = mapped - } else { - sharedColumnsMap[columnName] = columnName - } - } - mappedSharedColumnNames := []string{} - for _, columnName := range sharedColumnNames { - mappedSharedColumnNames = append(mappedSharedColumnNames, sharedColumnsMap[columnName]) - } - return NewColumnList(sharedColumnNames), NewColumnList(mappedSharedColumnNames), NewColumnList(droppedSourceNonGeneratedColumnsNames), sharedColumnsMap -} - -// isExpandedColumn sees if target column has any value set/range that is impossible in source column. See GetExpandedColumns comment for examples -func isExpandedColumn(sourceColumn *Column, targetColumn *Column) (bool, string) { - if targetColumn.IsNullable && !sourceColumn.IsNullable { - return true, "target is NULL-able, source is not" - } - if targetColumn.CharacterMaximumLength > sourceColumn.CharacterMaximumLength { - return true, "increased CHARACTER_MAXIMUM_LENGTH" - } - if targetColumn.NumericPrecision > sourceColumn.NumericPrecision { - return true, "increased NUMERIC_PRECISION" - } - if targetColumn.NumericScale > sourceColumn.NumericScale { - return true, "increased NUMERIC_SCALE" - } - if targetColumn.DateTimePrecision > sourceColumn.DateTimePrecision { - return true, "increased DATETIME_PRECISION" - } - if sourceColumn.IsNumeric() && targetColumn.IsNumeric() { - if sourceColumn.IsUnsigned && !targetColumn.IsUnsigned { - return true, "source is unsigned, target is signed" - } - if sourceColumn.NumericPrecision <= targetColumn.NumericPrecision && !sourceColumn.IsUnsigned && targetColumn.IsUnsigned { - // e.g. INT SIGNED => INT UNSIGNED, INT SIGNED => BIGINT UNSIGNED - return true, "target unsigned value exceeds source unsigned value" - } - if targetColumn.IsFloatingPoint() && !sourceColumn.IsFloatingPoint() { - return true, "target is floating point, source is not" - } - } - if expandedDataTypes[fmt.Sprintf("%s:%s", sourceColumn.DataType, targetColumn.DataType)] { - return true, "target is expanded data type of source" - } - if sourceColumn.Charset != targetColumn.Charset { - if targetColumn.Charset == "utf8mb4" { - return true, "expand character set to utf8mb4" - } - if strings.HasPrefix(targetColumn.Charset, "utf8") && !strings.HasPrefix(sourceColumn.Charset, "utf8") { - // not utf to utf - return true, "expand character set to utf8" - } - } - for _, colType := range []ColumnType{EnumColumnType, SetColumnType} { - // enums and sets have very similar properties, and are practically identical in our analysis - if sourceColumn.Type == colType { - // this is an enum or a set - if targetColumn.Type != colType { - return true, "conversion from enum/set to non-enum/set adds potential values" - } - // target is an enum or a set. See if all values on target exist in source - sourceEnumTokensMap := schema.ParseEnumOrSetTokensMap(sourceColumn.EnumValues) - targetEnumTokensMap := schema.ParseEnumOrSetTokensMap(targetColumn.EnumValues) - for k, v := range targetEnumTokensMap { - if sourceEnumTokensMap[k] != v { - return true, "target enum/set expands source enum/set" - } - } - } - } - return false, "" -} - -// GetExpandedColumnNames is given source and target shared columns, and returns the list of columns whose data type is expanded. -// An expanded data type is one where the target can have a value which the source does not. Examples: -// - any NOT NULL to NULLable (a NULL in the target cannot appear on source) -// - INT -> BIGINT (obvious) -// - BIGINT UNSIGNED -> INT SIGNED (negative values) -// - TIMESTAMP -> TIMESTAMP(3) -// etc. -func GetExpandedColumnNames( - sourceSharedColumns *ColumnList, - targetSharedColumns *ColumnList, -) ( - expandedColumnNames []string, - expandedDescriptions map[string]string, -) { - expandedDescriptions = map[string]string{} - for i := range sourceSharedColumns.Columns() { - // source and target columns assumed to be mapped 1:1, same length - sourceColumn := sourceSharedColumns.Columns()[i] - targetColumn := targetSharedColumns.Columns()[i] - - if isExpanded, description := isExpandedColumn(&sourceColumn, &targetColumn); isExpanded { - expandedColumnNames = append(expandedColumnNames, sourceColumn.Name) - expandedDescriptions[sourceColumn.Name] = description - } - } - return expandedColumnNames, expandedDescriptions -} - -// GetNoDefaultColumnNames returns names of columns which have no default value, out of given list of columns -func GetNoDefaultColumnNames(columns *ColumnList) (names []string) { - names = []string{} - for _, col := range columns.Columns() { - if !col.HasDefault() { - names = append(names, col.Name) - } - } - return names -} diff --git a/go/vt/vttablet/onlineddl/vrepl/columns_test.go b/go/vt/vttablet/onlineddl/vrepl/columns_test.go deleted file mode 100644 index 32efd104cc1..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/columns_test.go +++ /dev/null @@ -1,380 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - columnsA = &ColumnList{ - columns: []Column{ - { - Name: "id", - }, - { - Name: "cint", - }, - { - Name: "cgen1", - }, - { - Name: "cgen2", - }, - { - Name: "cchar", - }, - { - Name: "cremoved", - }, - { - Name: "cnullable", - IsNullable: true, - }, - { - Name: "cnodefault", - IsNullable: false, - IsDefaultNull: true, - }, - }, - Ordinals: ColumnsMap{}, - } - columnsB = &ColumnList{ - columns: []Column{ - { - Name: "id", - }, - { - Name: "cint", - }, - { - Name: "cgen1", - }, - { - Name: "cchar_alternate", - }, - { - Name: "cnullable", - IsNullable: true, - }, - { - Name: "cnodefault", - IsNullable: false, - IsDefaultNull: true, - }, - }, - Ordinals: ColumnsMap{}, - } - columnsVirtual = ParseColumnList("cgen1,cgen2") -) - -func TestGetSharedColumns(t *testing.T) { - tt := []struct { - name string - sourceCols *ColumnList - targetCols *ColumnList - renameMap map[string]string - expectSourceSharedColNames []string - expectTargetSharedColNames []string - expectDroppedSourceNonGeneratedColNames []string - }{ - { - name: "rename map empty", - sourceCols: columnsA, - targetCols: columnsB, - renameMap: map[string]string{}, - expectSourceSharedColNames: []string{"id", "cint", "cnullable", "cnodefault"}, - expectTargetSharedColNames: []string{"id", "cint", "cnullable", "cnodefault"}, - expectDroppedSourceNonGeneratedColNames: []string{"cchar", "cremoved"}, - }, - { - name: "renamed column", - sourceCols: columnsA, - targetCols: columnsB, - renameMap: map[string]string{"cchar": "cchar_alternate"}, - expectSourceSharedColNames: []string{"id", "cint", "cchar", "cnullable", "cnodefault"}, - expectTargetSharedColNames: []string{"id", "cint", "cchar_alternate", "cnullable", "cnodefault"}, - expectDroppedSourceNonGeneratedColNames: []string{"cremoved"}, - }, - } - - parser := NewAlterTableParser() - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - parser.columnRenameMap = tc.renameMap - sourceSharedCols, targetSharedCols, droppedNonGeneratedCols, _ := GetSharedColumns( - tc.sourceCols, tc.targetCols, - columnsVirtual, columnsVirtual, - parser, - ) - assert.Equal(t, tc.expectSourceSharedColNames, sourceSharedCols.Names()) - assert.Equal(t, tc.expectTargetSharedColNames, targetSharedCols.Names()) - assert.Equal(t, tc.expectDroppedSourceNonGeneratedColNames, droppedNonGeneratedCols.Names()) - }) - } -} - -func TestGetExpandedColumnNames(t *testing.T) { - var ( - columnsA = &ColumnList{ - columns: []Column{ - { - Name: "c1", - IsNullable: true, - }, - { - Name: "c2", - IsNullable: true, - }, - { - Name: "c3", - IsNullable: false, - }, - }, - Ordinals: ColumnsMap{}, - } - columnsB = &ColumnList{ - columns: []Column{ - { - Name: "c1", - IsNullable: true, - }, - { - Name: "c2", - IsNullable: false, - }, - { - Name: "c3", - IsNullable: true, - }, - }, - Ordinals: ColumnsMap{}, - } - ) - tcases := []struct { - name string - sourceCol Column - targetCol Column - expanded bool - }{ - { - "both nullable", - Column{ - IsNullable: true, - }, - Column{ - IsNullable: true, - }, - false, - }, - { - "nullable to non nullable", - Column{ - IsNullable: true, - }, - Column{ - IsNullable: false, - }, - false, - }, - { - "non nullable to nullable", - Column{ - IsNullable: false, - }, - Column{ - IsNullable: true, - }, - true, - }, - { - "signed to unsigned", - Column{ - Type: IntegerColumnType, - NumericPrecision: 4, - IsUnsigned: false, - }, - Column{ - Type: IntegerColumnType, - NumericPrecision: 4, - IsUnsigned: true, - }, - true, - }, - { - "unsigned to signed", - Column{ - Type: IntegerColumnType, - NumericPrecision: 4, - IsUnsigned: true, - }, - Column{ - Type: IntegerColumnType, - NumericPrecision: 4, - IsUnsigned: false, - }, - true, - }, - { - "signed to smaller unsigned", - Column{ - Type: IntegerColumnType, - NumericPrecision: 8, - IsUnsigned: false, - }, - Column{ - Type: IntegerColumnType, - NumericPrecision: 4, - IsUnsigned: true, - }, - false, - }, - { - "same char length", - Column{ - CharacterMaximumLength: 20, - }, - Column{ - CharacterMaximumLength: 20, - }, - false, - }, - { - "reduced char length", - Column{ - CharacterMaximumLength: 20, - }, - Column{ - CharacterMaximumLength: 19, - }, - false, - }, - { - "increased char length", - Column{ - CharacterMaximumLength: 20, - }, - Column{ - CharacterMaximumLength: 21, - }, - true, - }, - { - "expand temporal", - Column{ - DataType: "time", - }, - Column{ - DataType: "timestamp", - }, - true, - }, - { - "expand temporal", - Column{ - DataType: "date", - }, - Column{ - DataType: "timestamp", - }, - true, - }, - { - "expand temporal", - Column{ - DataType: "date", - }, - Column{ - DataType: "datetime", - }, - true, - }, - { - "non expand temporal", - Column{ - DataType: "datetime", - }, - Column{ - DataType: "timestamp", - }, - false, - }, - { - "expand temporal", - Column{ - DataType: "timestamp", - }, - Column{ - DataType: "datetime", - }, - true, - }, - { - "expand enum", - Column{ - Type: EnumColumnType, - EnumValues: "'a','b'", - }, - Column{ - Type: EnumColumnType, - EnumValues: "'a','x'", - }, - true, - }, - { - "expand enum", - Column{ - Type: EnumColumnType, - EnumValues: "'a','b'", - }, - Column{ - Type: EnumColumnType, - EnumValues: "'a','b','c'", - }, - true, - }, - { - "reduce enum", - Column{ - Type: EnumColumnType, - EnumValues: "'a','b','c'", - }, - Column{ - Type: EnumColumnType, - EnumValues: "'a','b'", - }, - false, - }, - } - - expectedExpandedColumnNames := []string{"c3"} - expandedColumnNames, _ := GetExpandedColumnNames(columnsA, columnsB) - assert.Equal(t, expectedExpandedColumnNames, expandedColumnNames) - - for _, tcase := range tcases { - t.Run(tcase.name, func(t *testing.T) { - expanded, _ := isExpandedColumn(&tcase.sourceCol, &tcase.targetCol) - assert.Equal(t, tcase.expanded, expanded) - }) - } -} diff --git a/go/vt/vttablet/onlineddl/vrepl/foreign_key.go b/go/vt/vttablet/onlineddl/vrepl/foreign_key.go deleted file mode 100644 index 006beb7345c..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/foreign_key.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "vitess.io/vitess/go/vt/schemadiff" - "vitess.io/vitess/go/vt/sqlparser" - "vitess.io/vitess/go/vt/vtenv" -) - -// RemovedForeignKeyNames returns the names of removed foreign keys, ignoring mere name changes -func RemovedForeignKeyNames( - venv *vtenv.Environment, - originalCreateTable *sqlparser.CreateTable, - vreplCreateTable *sqlparser.CreateTable, -) (names []string, err error) { - if originalCreateTable == nil || vreplCreateTable == nil { - return nil, nil - } - env := schemadiff.NewEnv(venv, venv.CollationEnv().DefaultConnectionCharset()) - diffHints := schemadiff.DiffHints{ - ConstraintNamesStrategy: schemadiff.ConstraintNamesIgnoreAll, - } - diff, err := schemadiff.DiffTables(env, originalCreateTable, vreplCreateTable, &diffHints) - if err != nil { - return nil, err - } - - validateWalk := func(node sqlparser.SQLNode) (kontinue bool, err error) { - switch node := node.(type) { - case *sqlparser.DropKey: - if node.Type == sqlparser.ForeignKeyType { - names = append(names, node.Name.String()) - } - } - return true, nil - } - _ = sqlparser.Walk(validateWalk, diff.Statement()) // We never return an error - return names, nil -} diff --git a/go/vt/vttablet/onlineddl/vrepl/foreign_key_test.go b/go/vt/vttablet/onlineddl/vrepl/foreign_key_test.go deleted file mode 100644 index 66775092dcb..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/foreign_key_test.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "vitess.io/vitess/go/vt/sqlparser" - "vitess.io/vitess/go/vt/vtenv" -) - -func TestRemovedForeignKeyNames(t *testing.T) { - - tcases := []struct { - before string - after string - names []string - }{ - { - before: "create table t (id int primary key)", - after: "create table t (id2 int primary key, i int)", - }, - { - before: "create table t (id int primary key)", - after: "create table t2 (id2 int primary key, i int)", - }, - { - before: "create table t (id int primary key, i int, constraint f foreign key (i) references parent (id) on delete cascade)", - after: "create table t (id int primary key, i int, constraint f foreign key (i) references parent (id) on delete cascade)", - }, - { - before: "create table t (id int primary key, i int, constraint f1 foreign key (i) references parent (id) on delete cascade)", - after: "create table t (id int primary key, i int, constraint f2 foreign key (i) references parent (id) on delete cascade)", - }, - { - before: "create table t (id int primary key, i int, constraint f foreign key (i) references parent (id) on delete cascade)", - after: "create table t (id int primary key, i int)", - names: []string{"f"}, - }, - { - before: "create table t (id int primary key, i int, i2 int, constraint f1 foreign key (i) references parent (id) on delete cascade, constraint fi2 foreign key (i2) references parent (id) on delete cascade)", - after: "create table t (id int primary key, i int, i2 int, constraint f2 foreign key (i) references parent (id) on delete cascade)", - names: []string{"fi2"}, - }, - { - before: "create table t1 (id int primary key, i int, constraint `check1` CHECK ((`i` < 5)))", - after: "create table t2 (id int primary key, i int)", - }, - } - for _, tcase := range tcases { - t.Run(tcase.before, func(t *testing.T) { - env := vtenv.NewTestEnv() - beforeStmt, err := env.Parser().ParseStrictDDL(tcase.before) - require.NoError(t, err) - beforeCreateTable, ok := beforeStmt.(*sqlparser.CreateTable) - require.True(t, ok) - require.NotNil(t, beforeCreateTable) - - afterStmt, err := env.Parser().ParseStrictDDL(tcase.after) - require.NoError(t, err) - afterCreateTable, ok := afterStmt.(*sqlparser.CreateTable) - require.True(t, ok) - require.NotNil(t, afterCreateTable) - - names, err := RemovedForeignKeyNames(env, beforeCreateTable, afterCreateTable) - assert.NoError(t, err) - assert.Equal(t, tcase.names, names) - }) - } -} diff --git a/go/vt/vttablet/onlineddl/vrepl/parser.go b/go/vt/vttablet/onlineddl/vrepl/parser.go deleted file mode 100644 index f76f8735016..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/parser.go +++ /dev/null @@ -1,112 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "strings" - - "vitess.io/vitess/go/vt/sqlparser" -) - -// AlterTableParser is a parser tool for ALTER TABLE statements -// This is imported from gh-ost. In the future, we should replace that with Vitess parsing. -type AlterTableParser struct { - columnRenameMap map[string]string - droppedColumns map[string]bool - isRenameTable bool - isAutoIncrementDefined bool -} - -// NewAlterTableParser creates a new parser -func NewAlterTableParser() *AlterTableParser { - return &AlterTableParser{ - columnRenameMap: make(map[string]string), - droppedColumns: make(map[string]bool), - } -} - -// NewParserFromAlterStatement creates a new parser with a ALTER TABLE statement -func NewParserFromAlterStatement(alterTable *sqlparser.AlterTable) *AlterTableParser { - parser := NewAlterTableParser() - parser.AnalyzeAlter(alterTable) - return parser -} - -// AnalyzeAlter looks for specific changes in the AlterTable statement, that are relevant -// to OnlineDDL/VReplication -func (p *AlterTableParser) AnalyzeAlter(alterTable *sqlparser.AlterTable) { - for _, opt := range alterTable.AlterOptions { - switch opt := opt.(type) { - case *sqlparser.RenameTableName: - p.isRenameTable = true - case *sqlparser.DropColumn: - p.droppedColumns[opt.Name.Name.String()] = true - case *sqlparser.ChangeColumn: - if opt.OldColumn != nil && opt.NewColDefinition != nil { - oldName := opt.OldColumn.Name.String() - newName := opt.NewColDefinition.Name.String() - p.columnRenameMap[oldName] = newName - } - case sqlparser.TableOptions: - for _, tableOption := range opt { - if strings.ToUpper(tableOption.Name) == "AUTO_INCREMENT" { - p.isAutoIncrementDefined = true - } - } - } - } -} - -// GetNonTrivialRenames gets a list of renamed column -func (p *AlterTableParser) GetNonTrivialRenames() map[string]string { - result := make(map[string]string) - for column, renamed := range p.columnRenameMap { - if column != renamed { - result[column] = renamed - } - } - return result -} - -// HasNonTrivialRenames is true when columns have been renamed -func (p *AlterTableParser) HasNonTrivialRenames() bool { - return len(p.GetNonTrivialRenames()) > 0 -} - -// DroppedColumnsMap returns list of dropped columns -func (p *AlterTableParser) DroppedColumnsMap() map[string]bool { - return p.droppedColumns -} - -// IsRenameTable returns true when the ALTER TABLE statement includes renaming the table -func (p *AlterTableParser) IsRenameTable() bool { - return p.isRenameTable -} - -// IsAutoIncrementDefined returns true when alter options include an explicit AUTO_INCREMENT value -func (p *AlterTableParser) IsAutoIncrementDefined() bool { - return p.isAutoIncrementDefined -} - -// ColumnRenameMap returns the renamed column mapping -func (p *AlterTableParser) ColumnRenameMap() map[string]string { - return p.columnRenameMap -} diff --git a/go/vt/vttablet/onlineddl/vrepl/parser_test.go b/go/vt/vttablet/onlineddl/vrepl/parser_test.go deleted file mode 100644 index 93e2ef25a15..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/parser_test.go +++ /dev/null @@ -1,190 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "vitess.io/vitess/go/vt/sqlparser" -) - -func alterTableStatement(t *testing.T, sql string) *sqlparser.AlterTable { - stmt, err := sqlparser.NewTestParser().ParseStrictDDL(sql) - require.NoError(t, err) - alter, ok := stmt.(*sqlparser.AlterTable) - require.True(t, ok) - return alter -} - -func TestParseAlterStatement(t *testing.T) { - statement := "alter table t add column t int, engine=innodb" - alterStatement := alterTableStatement(t, statement) - parser := NewAlterTableParser() - parser.AnalyzeAlter(alterStatement) - assert.False(t, parser.HasNonTrivialRenames()) - assert.False(t, parser.IsAutoIncrementDefined()) -} - -func TestParseAlterStatementTrivialRename(t *testing.T) { - statement := "alter table t add column t int, change ts ts timestamp, engine=innodb" - alterStatement := alterTableStatement(t, statement) - parser := NewAlterTableParser() - parser.AnalyzeAlter(alterStatement) - assert.False(t, parser.HasNonTrivialRenames()) - assert.False(t, parser.IsAutoIncrementDefined()) - assert.Equal(t, len(parser.columnRenameMap), 1) - assert.Equal(t, parser.columnRenameMap["ts"], "ts") -} - -func TestParseAlterStatementWithAutoIncrement(t *testing.T) { - - statements := []string{ - "auto_increment=7", - "auto_increment = 7", - "AUTO_INCREMENT = 71", - "AUTO_INCREMENT 23", - "AUTO_INCREMENT 23", - "add column t int, change ts ts timestamp, auto_increment=7 engine=innodb", - "add column t int, change ts ts timestamp, auto_increment =7 engine=innodb", - "add column t int, change ts ts timestamp, AUTO_INCREMENT = 7 engine=innodb", - "add column t int, change ts ts timestamp, engine=innodb auto_increment=73425", - "add column t int, change ts ts timestamp, engine=innodb, auto_increment=73425", - "add column t int, change ts ts timestamp, engine=innodb, auto_increment 73425", - "add column t int, change ts ts timestamp, engine innodb, auto_increment 73425", - "add column t int, change ts ts timestamp, engine innodb auto_increment 73425", - } - for _, statement := range statements { - parser := NewAlterTableParser() - statement := "alter table t " + statement - alterStatement := alterTableStatement(t, statement) - parser.AnalyzeAlter(alterStatement) - assert.True(t, parser.IsAutoIncrementDefined()) - } -} - -func TestParseAlterStatementTrivialRenames(t *testing.T) { - statement := "alter table t add column t int, change ts ts timestamp, CHANGE f `f` float, engine=innodb" - alterStatement := alterTableStatement(t, statement) - parser := NewAlterTableParser() - parser.AnalyzeAlter(alterStatement) - assert.False(t, parser.HasNonTrivialRenames()) - assert.False(t, parser.IsAutoIncrementDefined()) - assert.Equal(t, len(parser.columnRenameMap), 2) - assert.Equal(t, parser.columnRenameMap["ts"], "ts") - assert.Equal(t, parser.columnRenameMap["f"], "f") -} - -func TestParseAlterStatementNonTrivial(t *testing.T) { - statements := []string{ - `add column b bigint, change f fl float, change i count int, engine=innodb`, - "add column b bigint, change column `f` fl float, change `i` `count` int, engine=innodb", - "add column b bigint, change column `f` fl float, change `i` `count` int, change ts ts timestamp, engine=innodb", - `change - f fl float, - CHANGE COLUMN i - count int, engine=innodb`, - } - - for _, statement := range statements { - statement := "alter table t " + statement - alterStatement := alterTableStatement(t, statement) - parser := NewAlterTableParser() - parser.AnalyzeAlter(alterStatement) - assert.False(t, parser.IsAutoIncrementDefined()) - renames := parser.GetNonTrivialRenames() - assert.Equal(t, len(renames), 2) - assert.Equal(t, renames["i"], "count") - assert.Equal(t, renames["f"], "fl") - } -} - -func TestParseAlterStatementDroppedColumns(t *testing.T) { - - { - parser := NewAlterTableParser() - statement := "alter table t drop column b" - alterStatement := alterTableStatement(t, statement) - parser.AnalyzeAlter(alterStatement) - assert.Equal(t, len(parser.droppedColumns), 1) - assert.True(t, parser.droppedColumns["b"]) - } - { - parser := NewAlterTableParser() - statement := "alter table t drop column b, drop key c_idx, drop column `d`" - alterStatement := alterTableStatement(t, statement) - parser.AnalyzeAlter(alterStatement) - assert.Equal(t, len(parser.droppedColumns), 2) - assert.True(t, parser.droppedColumns["b"]) - assert.True(t, parser.droppedColumns["d"]) - } - { - parser := NewAlterTableParser() - statement := "alter table t drop column b, drop key c_idx, drop column `d`, drop `e`, drop primary key, drop foreign key fk_1" - alterStatement := alterTableStatement(t, statement) - parser.AnalyzeAlter(alterStatement) - assert.Equal(t, len(parser.droppedColumns), 3) - assert.True(t, parser.droppedColumns["b"]) - assert.True(t, parser.droppedColumns["d"]) - assert.True(t, parser.droppedColumns["e"]) - } -} - -func TestParseAlterStatementRenameTable(t *testing.T) { - tt := []struct { - alter string - isRename bool - }{ - { - alter: "alter table t drop column b", - }, - { - alter: "alter table t rename as something_else", - isRename: true, - }, - { - alter: "alter table t rename to something_else", - isRename: true, - }, - { - alter: "alter table t drop column b, rename as something_else", - isRename: true, - }, - { - alter: "alter table t engine=innodb, rename as something_else", - isRename: true, - }, - { - alter: "alter table t rename as something_else, engine=innodb", - isRename: true, - }, - } - for _, tc := range tt { - t.Run(tc.alter, func(t *testing.T) { - parser := NewAlterTableParser() - alterStatement := alterTableStatement(t, tc.alter) - parser.AnalyzeAlter(alterStatement) - assert.Equal(t, tc.isRename, parser.isRenameTable) - }) - } -} diff --git a/go/vt/vttablet/onlineddl/vrepl/types.go b/go/vt/vttablet/onlineddl/vrepl/types.go deleted file mode 100644 index 0ca834ffdf0..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/types.go +++ /dev/null @@ -1,293 +0,0 @@ -/* - Original copyright by GitHub as follows. Additions by the Vitess authors as follows. -*/ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "fmt" - "reflect" - "strings" - - "vitess.io/vitess/go/vt/schemadiff" -) - -// ColumnType indicated some MySQL data types -type ColumnType int - -const ( - UnknownColumnType ColumnType = iota - TimestampColumnType - DateTimeColumnType - EnumColumnType - SetColumnType - MediumIntColumnType - JSONColumnType - FloatColumnType - DoubleColumnType - BinaryColumnType - StringColumnType - IntegerColumnType -) - -// Column represents a table column -type Column struct { - Name string - IsUnsigned bool - Charset string - Collation string - Type ColumnType - EnumValues string - EnumToTextConversion bool - DataType string // from COLUMN_TYPE column - - IsNullable bool - IsDefaultNull bool - - CharacterMaximumLength int64 - NumericPrecision int64 - NumericScale int64 - DateTimePrecision int64 - - // add Octet length for binary type, fix bytes with suffix "00" get clipped in mysql binlog. - // https://github.com/github/gh-ost/issues/909 - BinaryOctetLength uint64 -} - -// SetTypeIfUnknown will set a new column type only if the current type is unknown, otherwise silently skip -func (c *Column) SetTypeIfUnknown(t ColumnType) { - if c.Type == UnknownColumnType { - c.Type = t - } -} - -// HasDefault returns true if the column at all has a default value (possibly NULL) -func (c *Column) HasDefault() bool { - if c.IsDefaultNull && !c.IsNullable { - // based on INFORMATION_SCHEMA.COLUMNS, this is the indicator for a 'NOT NULL' column with no default value. - return false - } - return true -} - -// IsNumeric returns true if the column is of a numeric type -func (c *Column) IsNumeric() bool { - return c.NumericPrecision > 0 -} - -// IsIntegralType returns true if the column is some form of an integer -func (c *Column) IsIntegralType() bool { - return schemadiff.IsIntegralType(c.DataType) -} - -// IsFloatingPoint returns true if the column is of a floating point numeric type -func (c *Column) IsFloatingPoint() bool { - return c.Type == FloatColumnType || c.Type == DoubleColumnType -} - -// IsFloatingPoint returns true if the column is of a temporal type -func (c *Column) IsTemporal() bool { - return c.DateTimePrecision >= 0 -} - -// NewColumns creates a new column array from non empty names -func NewColumns(names []string) []Column { - result := []Column{} - for _, name := range names { - if name == "" { - continue - } - result = append(result, Column{Name: name}) - } - return result -} - -// ParseColumns creates a new column array fby parsing comma delimited names list -func ParseColumns(names string) []Column { - namesArray := strings.Split(names, ",") - return NewColumns(namesArray) -} - -// ColumnsMap maps a column name onto its ordinal position -type ColumnsMap map[string]int - -// NewEmptyColumnsMap creates an empty map -func NewEmptyColumnsMap() ColumnsMap { - columnsMap := make(map[string]int) - return ColumnsMap(columnsMap) -} - -// NewColumnsMap creates a column map based on ordered list of columns -func NewColumnsMap(orderedColumns []Column) ColumnsMap { - columnsMap := NewEmptyColumnsMap() - for i, column := range orderedColumns { - columnsMap[column.Name] = i - } - return columnsMap -} - -// ColumnList makes for a named list of columns -type ColumnList struct { - columns []Column - Ordinals ColumnsMap -} - -// NewColumnList creates an object given ordered list of column names -func NewColumnList(names []string) *ColumnList { - result := &ColumnList{ - columns: NewColumns(names), - } - result.Ordinals = NewColumnsMap(result.columns) - return result -} - -// ParseColumnList parses a comma delimited list of column names -func ParseColumnList(names string) *ColumnList { - result := &ColumnList{ - columns: ParseColumns(names), - } - result.Ordinals = NewColumnsMap(result.columns) - return result -} - -// Columns returns the list of columns -func (l *ColumnList) Columns() []Column { - return l.columns -} - -// Names returns list of column names -func (l *ColumnList) Names() []string { - names := make([]string, len(l.columns)) - for i := range l.columns { - names[i] = l.columns[i].Name - } - return names -} - -// GetColumn gets a column by name -func (l *ColumnList) GetColumn(columnName string) *Column { - if ordinal, ok := l.Ordinals[columnName]; ok { - return &l.columns[ordinal] - } - return nil -} - -// ColumnExists returns true if this column list has a column by a given name -func (l *ColumnList) ColumnExists(columnName string) bool { - _, ok := l.Ordinals[columnName] - return ok -} - -// String returns a comma separated list of column names -func (l *ColumnList) String() string { - return strings.Join(l.Names(), ",") -} - -// Equals checks for complete (deep) identities of columns, in order. -func (l *ColumnList) Equals(other *ColumnList) bool { - return reflect.DeepEqual(l.Columns, other.Columns) -} - -// EqualsByNames checks if the names in this list equals the names of another list, in order. Type is ignored. -func (l *ColumnList) EqualsByNames(other *ColumnList) bool { - return reflect.DeepEqual(l.Names(), other.Names()) -} - -// IsSubsetOf returns 'true' when column names of this list are a subset of -// another list, in arbitrary order (order agnostic) -func (l *ColumnList) IsSubsetOf(other *ColumnList) bool { - for _, column := range l.columns { - if _, exists := other.Ordinals[column.Name]; !exists { - return false - } - } - return true -} - -// Difference returns a (new copy) subset of this column list, consisting of all -// column NOT in given list. -// The result is never nil, even if the difference is empty -func (l *ColumnList) Difference(other *ColumnList) (diff *ColumnList) { - names := []string{} - for _, column := range l.columns { - if !other.ColumnExists(column.Name) { - names = append(names, column.Name) - } - } - return NewColumnList(names) -} - -// Len returns the length of this list -func (l *ColumnList) Len() int { - return len(l.columns) -} - -// MappedNamesColumnList returns a column list based on this list, with names possibly mapped by given map -func (l *ColumnList) MappedNamesColumnList(columnNamesMap map[string]string) *ColumnList { - names := l.Names() - for i := range names { - if mappedName, ok := columnNamesMap[names[i]]; ok { - names[i] = mappedName - } - } - return NewColumnList(names) -} - -// SetEnumToTextConversion tells this column list that an enum is converted to text -func (l *ColumnList) SetEnumToTextConversion(columnName string, enumValues string) { - l.GetColumn(columnName).EnumToTextConversion = true - l.GetColumn(columnName).EnumValues = enumValues -} - -// IsEnumToTextConversion tells whether an enum was converted to text -func (l *ColumnList) IsEnumToTextConversion(columnName string) bool { - return l.GetColumn(columnName).EnumToTextConversion -} - -// UniqueKey is the combination of a key's name and columns -type UniqueKey struct { - Name string - Columns ColumnList - HasNullable bool - HasSubpart bool - HasFloat bool - IsAutoIncrement bool -} - -// IsPrimary checks if this unique key is primary -func (k *UniqueKey) IsPrimary() bool { - return k.Name == "PRIMARY" -} - -// Len returns the length of this list -func (k *UniqueKey) Len() int { - return k.Columns.Len() -} - -// String returns a visual representation of this key -func (k *UniqueKey) String() string { - description := k.Name - if k.IsAutoIncrement { - description = fmt.Sprintf("%s (auto_increment)", description) - } - return fmt.Sprintf("%s: %s; has nullable: %+v", description, k.Columns.Names(), k.HasNullable) -} diff --git a/go/vt/vttablet/onlineddl/vrepl/types_test.go b/go/vt/vttablet/onlineddl/vrepl/types_test.go deleted file mode 100644 index d146d286d3a..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/types_test.go +++ /dev/null @@ -1,214 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseColumnList(t *testing.T) { - names := "id,category,max_len" - - columnList := ParseColumnList(names) - assert.Equal(t, columnList.Len(), 3) - assert.Equal(t, columnList.Names(), []string{"id", "category", "max_len"}) - assert.Equal(t, columnList.Ordinals["id"], 0) - assert.Equal(t, columnList.Ordinals["category"], 1) - assert.Equal(t, columnList.Ordinals["max_len"], 2) -} - -func TestGetColumn(t *testing.T) { - names := "id,category,max_len" - columnList := ParseColumnList(names) - { - column := columnList.GetColumn("category") - assert.NotNil(t, column) - assert.Equal(t, column.Name, "category") - } - { - column := columnList.GetColumn("no_such_column") - assert.True(t, column == nil) - } -} - -func TestIsSubsetOf(t *testing.T) { - tt := []struct { - columns1 *ColumnList - columns2 *ColumnList - expectSubset bool - }{ - { - columns1: ParseColumnList(""), - columns2: ParseColumnList("a,b,c"), - expectSubset: true, - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList("a,b,c"), - expectSubset: true, - }, - { - columns1: ParseColumnList("a,c"), - columns2: ParseColumnList("a,b,c"), - expectSubset: true, - }, - { - columns1: ParseColumnList("b,c"), - columns2: ParseColumnList("a,b,c"), - expectSubset: true, - }, - { - columns1: ParseColumnList("b"), - columns2: ParseColumnList("a,b,c"), - expectSubset: true, - }, - { - columns1: ParseColumnList(""), - columns2: ParseColumnList("a,b,c"), - expectSubset: true, - }, - { - columns1: ParseColumnList("a,d"), - columns2: ParseColumnList("a,b,c"), - expectSubset: false, - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList("a,b"), - expectSubset: false, - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList(""), - expectSubset: false, - }, - } - for _, tc := range tt { - name := fmt.Sprintf("%v:%v", tc.columns1.Names(), tc.columns2.Names()) - t.Run(name, func(t *testing.T) { - isSubset := tc.columns1.IsSubsetOf(tc.columns2) - assert.Equal(t, tc.expectSubset, isSubset) - }, - ) - } -} - -func TestDifference(t *testing.T) { - tt := []struct { - columns1 *ColumnList - columns2 *ColumnList - expect *ColumnList - }{ - { - columns1: ParseColumnList(""), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList(""), - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList(""), - }, - { - columns1: ParseColumnList("a,c"), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList(""), - }, - { - columns1: ParseColumnList("b,c"), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList(""), - }, - { - columns1: ParseColumnList("b"), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList(""), - }, - { - columns1: ParseColumnList(""), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList(""), - }, - { - columns1: ParseColumnList("a,d"), - columns2: ParseColumnList("a,b,c"), - expect: ParseColumnList("d"), - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList("a,b"), - expect: ParseColumnList("c"), - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList(""), - expect: ParseColumnList("a,b,c"), - }, - { - columns1: ParseColumnList("a,b,c"), - columns2: ParseColumnList("b,d,e"), - expect: ParseColumnList("a,c"), - }, - } - for _, tc := range tt { - name := fmt.Sprintf("%v:%v", tc.columns1.Names(), tc.columns2.Names()) - t.Run(name, func(t *testing.T) { - diff := tc.columns1.Difference(tc.columns2) - assert.Equal(t, tc.expect, diff) - }, - ) - } -} - -func TestMappedNamesColumnList(t *testing.T) { - tt := []struct { - columns *ColumnList - namesMap map[string]string - expected *ColumnList - }{ - { - columns: ParseColumnList("a,b,c"), - namesMap: map[string]string{}, - expected: ParseColumnList("a,b,c"), - }, - { - columns: ParseColumnList("a,b,c"), - namesMap: map[string]string{"x": "y"}, - expected: ParseColumnList("a,b,c"), - }, - { - columns: ParseColumnList("a,b,c"), - namesMap: map[string]string{"a": "x", "c": "y"}, - expected: ParseColumnList("x,b,y"), - }, - } - for _, tc := range tt { - name := fmt.Sprintf("%v:%v", tc.columns.Names(), tc.namesMap) - t.Run(name, func(t *testing.T) { - mappedNames := tc.columns.MappedNamesColumnList(tc.namesMap) - assert.Equal(t, tc.expected, mappedNames) - }, - ) - } -} diff --git a/go/vt/vttablet/onlineddl/vrepl/unique_key.go b/go/vt/vttablet/onlineddl/vrepl/unique_key.go deleted file mode 100644 index cc649b4ea37..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/unique_key.go +++ /dev/null @@ -1,184 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "strings" -) - -// UniqueKeyValidForIteration returns 'false' if we should not use this unique key as the main -// iteration key in vreplication. -func UniqueKeyValidForIteration(uniqueKey *UniqueKey) bool { - if uniqueKey.HasNullable { - // NULLable columns in a unique key means the set of values is not really unique (two identical rows with NULLs are allowed). - // Thus, we cannot use this unique key for iteration. - return false - } - if uniqueKey.HasSubpart { - // vreplication does not fully support indexes on column prefixes such as: - // UNIQUE KEY `name_idx` (`name`(15)) - // "HasSubpart" means some column covered by the index has a key length spec. - return false - } - if uniqueKey.HasFloat { - // float & double data types are imprecise and we cannot use them while iterating unique keys - return false - } - return true // good to go! -} - -// GetSharedUniqueKeys returns the unique keys shared between the source & target tables -func GetSharedUniqueKeys(sourceUniqueKeys, targetUniqueKeys [](*UniqueKey), columnRenameMap map[string]string) (chosenSourceUniqueKey, chosenTargetUniqueKey *UniqueKey) { - type ukPair struct{ source, target *UniqueKey } - var sharedUKPairs []*ukPair - - for _, sourceUniqueKey := range sourceUniqueKeys { - if !UniqueKeyValidForIteration(sourceUniqueKey) { - continue - } - for _, targetUniqueKey := range targetUniqueKeys { - if !UniqueKeyValidForIteration(targetUniqueKey) { - continue - } - uniqueKeyMatches := func() bool { - // Compare two unique keys - if sourceUniqueKey.Columns.Len() != targetUniqueKey.Columns.Len() { - return false - } - // Expect same columns, same order, potentially column name mapping - sourceUniqueKeyNames := sourceUniqueKey.Columns.Names() - targetUniqueKeyNames := targetUniqueKey.Columns.Names() - for i := range sourceUniqueKeyNames { - sourceColumnName := sourceUniqueKeyNames[i] - targetColumnName := targetUniqueKeyNames[i] - mappedSourceColumnName := sourceColumnName - if mapped, ok := columnRenameMap[sourceColumnName]; ok { - mappedSourceColumnName = mapped - } - if !strings.EqualFold(mappedSourceColumnName, targetColumnName) { - return false - } - } - return true - } - if uniqueKeyMatches() { - sharedUKPairs = append(sharedUKPairs, &ukPair{source: sourceUniqueKey, target: targetUniqueKey}) - } - } - } - // Now that we know what the shared unique keys are, let's find the "best" shared one. - // Source and target unique keys can have different name, even though they cover the exact same - // columns and in same order. - for _, pair := range sharedUKPairs { - if pair.source.HasNullable { - continue - } - if pair.target.HasNullable { - continue - } - return pair.source, pair.target - } - return nil, nil -} - -// SourceUniqueKeyAsOrMoreConstrainedThanTarget returns 'true' when sourceUniqueKey is at least as constrained as targetUniqueKey. -// "More constrained" means the uniqueness constraint is "stronger". Thus, if sourceUniqueKey is as-or-more constrained than targetUniqueKey, then -// rows valid under sourceUniqueKey must also be valid in targetUniqueKey. The opposite is not necessarily so: rows that are valid in targetUniqueKey -// may cause a unique key violation under sourceUniqueKey -func SourceUniqueKeyAsOrMoreConstrainedThanTarget(sourceUniqueKey, targetUniqueKey *UniqueKey, columnRenameMap map[string]string) bool { - // Compare two unique keys - if sourceUniqueKey.Columns.Len() > targetUniqueKey.Columns.Len() { - // source can't be more constrained if it covers *more* columns - return false - } - // we know that len(sourceUniqueKeyNames) <= len(targetUniqueKeyNames) - sourceUniqueKeyNames := sourceUniqueKey.Columns.Names() - targetUniqueKeyNames := targetUniqueKey.Columns.Names() - // source is more constrained than target if every column in source is also in target, order is immaterial - for i := range sourceUniqueKeyNames { - sourceColumnName := sourceUniqueKeyNames[i] - mappedSourceColumnName := sourceColumnName - if mapped, ok := columnRenameMap[sourceColumnName]; ok { - mappedSourceColumnName = mapped - } - columnFoundInTarget := func() bool { - for _, targetColumnName := range targetUniqueKeyNames { - if strings.EqualFold(mappedSourceColumnName, targetColumnName) { - return true - } - } - return false - } - if !columnFoundInTarget() { - return false - } - } - return true -} - -// AddedUniqueKeys returns the unique key constraints added in target. This does not necessarily mean that the unique key itself is new, -// rather that there's a new, stricter constraint on a set of columns, that didn't exist before. Example: -// -// before: unique key `my_key`(c1, c2, c3); after: unique key `my_key`(c1, c2) -// The constraint on (c1, c2) is new; and `my_key` in target table ("after") is considered a new key -// -// Order of columns is immaterial to uniqueness of column combination. -func AddedUniqueKeys(sourceUniqueKeys, targetUniqueKeys [](*UniqueKey), columnRenameMap map[string]string) (addedUKs [](*UniqueKey)) { - addedUKs = [](*UniqueKey){} - for _, targetUniqueKey := range targetUniqueKeys { - foundAsOrMoreConstrainingSourceKey := func() bool { - for _, sourceUniqueKey := range sourceUniqueKeys { - if SourceUniqueKeyAsOrMoreConstrainedThanTarget(sourceUniqueKey, targetUniqueKey, columnRenameMap) { - // target key does not add a new constraint - return true - } - } - return false - } - if !foundAsOrMoreConstrainingSourceKey() { - addedUKs = append(addedUKs, targetUniqueKey) - } - } - return addedUKs -} - -// RemovedUniqueKeys returns the list of unique key constraints _removed_ going from source to target. -func RemovedUniqueKeys(sourceUniqueKeys, targetUniqueKeys [](*UniqueKey), columnRenameMap map[string]string) (removedUKs [](*UniqueKey)) { - reverseColumnRenameMap := map[string]string{} - for k, v := range columnRenameMap { - reverseColumnRenameMap[v] = k - } - return AddedUniqueKeys(targetUniqueKeys, sourceUniqueKeys, reverseColumnRenameMap) -} - -// GetUniqueKeyCoveredByColumns returns the first unique key from given list, whose columns all appear -// in given column list. -func GetUniqueKeyCoveredByColumns(uniqueKeys [](*UniqueKey), columns *ColumnList) (chosenUniqueKey *UniqueKey) { - for _, uniqueKey := range uniqueKeys { - if !UniqueKeyValidForIteration(uniqueKey) { - continue - } - if uniqueKey.Columns.IsSubsetOf(columns) { - return uniqueKey - } - } - return nil -} diff --git a/go/vt/vttablet/onlineddl/vrepl/unique_key_test.go b/go/vt/vttablet/onlineddl/vrepl/unique_key_test.go deleted file mode 100644 index 3364c55a308..00000000000 --- a/go/vt/vttablet/onlineddl/vrepl/unique_key_test.go +++ /dev/null @@ -1,666 +0,0 @@ -/* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE -*/ -/* -Copyright 2021 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 vrepl - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - columns1 = ParseColumnList("c1") - columns12 = ParseColumnList("c1,c2") - columns123 = ParseColumnList("c1,c2,c3") - columns2 = ParseColumnList("c2") - columns21 = ParseColumnList("c2,c1") - columns12A = ParseColumnList("c1,c2,ca") -) - -func TestGetSharedUniqueKeys(t *testing.T) { - tt := []struct { - name string - sourceUKs, targetUKs [](*UniqueKey) - renameMap map[string]string - expectSourceUK, expectTargetUK *UniqueKey - }{ - { - name: "empty", - sourceUKs: []*UniqueKey{}, - targetUKs: []*UniqueKey{}, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "half empty", - sourceUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{}, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "single identical", - sourceUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "PRIMARY", Columns: *columns1}, - expectTargetUK: &UniqueKey{Name: "PRIMARY", Columns: *columns1}, - }, - { - name: "single identical non pk", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "uidx", Columns: *columns1}, - expectTargetUK: &UniqueKey{Name: "uidx", Columns: *columns1}, - }, - { - name: "single identical, source is nullable", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1, HasNullable: true}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "single identical, target is nullable", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1, HasNullable: true}, - }, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "single no shared", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - }, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "single no shared different order", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns21}, - }, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "single identical, source has FLOAT", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1, HasFloat: true}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "exact match", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "uidx123", Columns: *columns123}, - expectTargetUK: &UniqueKey{Name: "uidx123", Columns: *columns123}, - }, - { - name: "exact match from multiple options", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidx12", Columns: *columns12}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "uidx123", Columns: *columns123}, - expectTargetUK: &UniqueKey{Name: "uidx123", Columns: *columns123}, - }, - { - name: "exact match from multiple options reorder", - sourceUKs: []*UniqueKey{ - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx", Columns: *columns1}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns21}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidx12", Columns: *columns12}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "uidx12", Columns: *columns12}, - expectTargetUK: &UniqueKey{Name: "uidx12", Columns: *columns12}, - }, - { - name: "match different names", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidxother", Columns: *columns12}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "uidx12", Columns: *columns12}, - expectTargetUK: &UniqueKey{Name: "uidxother", Columns: *columns12}, - }, - { - name: "match different names, nullable", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx123other", Columns: *columns123}, - {Name: "uidx12", Columns: *columns12, HasNullable: true}, - }, - renameMap: map[string]string{}, - expectSourceUK: &UniqueKey{Name: "uidx123", Columns: *columns123}, - expectTargetUK: &UniqueKey{Name: "uidx123other", Columns: *columns123}, - }, - { - name: "match different column names", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx12A", Columns: *columns12A}, - }, - renameMap: map[string]string{"c3": "ca"}, - expectSourceUK: &UniqueKey{Name: "uidx123", Columns: *columns123}, - expectTargetUK: &UniqueKey{Name: "uidx12A", Columns: *columns12A}, - }, - { - // enforce mapping from c3 to ca; will not match c3<->c3 - name: "no match identical column names", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{"c3": "ca"}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - { - name: "no match different column names", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx12A", Columns: *columns12A}, - }, - renameMap: map[string]string{"c3": "cx"}, - expectSourceUK: nil, - expectTargetUK: nil, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - sourceUK, targetUK := GetSharedUniqueKeys(tc.sourceUKs, tc.targetUKs, tc.renameMap) - assert.Equal(t, tc.expectSourceUK, sourceUK) - assert.Equal(t, tc.expectTargetUK, targetUK) - }) - } -} - -func TestAddedUniqueKeys(t *testing.T) { - emptyUniqueKeys := []*UniqueKey{} - tt := []struct { - name string - sourceUKs, targetUKs [](*UniqueKey) - renameMap map[string]string - expectAddedUKs [](*UniqueKey) - expectRemovedUKs [](*UniqueKey) - }{ - { - name: "empty", - sourceUKs: emptyUniqueKeys, - targetUKs: emptyUniqueKeys, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "UK removed", - sourceUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - targetUKs: emptyUniqueKeys, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - }, - { - name: "NULLable UK removed", - sourceUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1, HasNullable: true}, - }, - targetUKs: emptyUniqueKeys, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1, HasNullable: true}, - }, - }, - { - name: "UK added", - sourceUKs: emptyUniqueKeys, - targetUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectAddedUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "single identical", - sourceUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "PRIMARY", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "single identical non pk", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "single identical, source is nullable", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1, HasNullable: true}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "single identical, target is nullable", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1, HasNullable: true}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "expand columns: not considered added", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - }, - { - name: "expand columns, different order: not considered added", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns21}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - }, - { - name: "reduced columns: considered added", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectAddedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "reduced columns, multiple: considered added", - sourceUKs: []*UniqueKey{ - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidx2", Columns: *columns2}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - renameMap: map[string]string{}, - expectAddedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx2", Columns: *columns2}, - }, - }, - { - name: "different order: not considered added", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns21}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "no match, different columns", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx2", Columns: *columns2}, - }, - renameMap: map[string]string{}, - expectAddedUKs: []*UniqueKey{ - {Name: "uidx2", Columns: *columns2}, - }, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - }, - { - name: "one match, one expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - }, - { - name: "exact match from multiple options", - sourceUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidx12", Columns: *columns12}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - }, - { - name: "exact match from multiple options reorder", - sourceUKs: []*UniqueKey{ - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx", Columns: *columns1}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns21}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidx12", Columns: *columns12}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx", Columns: *columns1}, - }, - }, - { - name: "match different names", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx123", Columns: *columns123}, - {Name: "uidxother", Columns: *columns12}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - }, - { - name: "match different names, nullable", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx123other", Columns: *columns123}, - {Name: "uidx12", Columns: *columns12, HasNullable: true}, - }, - renameMap: map[string]string{}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - }, - { - name: "match different column names, expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx12A", Columns: *columns12A}, - }, - renameMap: map[string]string{"c3": "ca"}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - }, - { - name: "match different column names, no expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx12A", Columns: *columns12A}, - }, - renameMap: map[string]string{"c3": "ca"}, - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: emptyUniqueKeys, - }, - { - // enforce mapping from c3 to ca; will not match c3<->c3 - name: "no match identical column names, expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{"c3": "ca"}, - // 123 expands 12, so even though 3 is mapped to A, 123 is still not more constrained. - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - }, - { - // enforce mapping from c3 to ca; will not match c3<->c3 - name: "no match identical column names, no expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx123", Columns: *columns123}, - }, - renameMap: map[string]string{"c3": "ca"}, - expectAddedUKs: []*UniqueKey{ - {Name: "uidx123", Columns: *columns123}, - }, - expectRemovedUKs: emptyUniqueKeys, - }, - { - name: "no match for different column names, expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - {Name: "uidx12", Columns: *columns12}, - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx21", Columns: *columns21}, - {Name: "uidx12A", Columns: *columns12A}, - }, - renameMap: map[string]string{"c3": "cx"}, - // 123 expands 12, so even though 3 is mapped to x, 123 is still not more constrained. - expectAddedUKs: emptyUniqueKeys, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx1", Columns: *columns1}, - }, - }, - { - name: "no match for different column names, no expand", - sourceUKs: []*UniqueKey{ - {Name: "uidx123", Columns: *columns123}, - }, - targetUKs: []*UniqueKey{ - {Name: "uidx12A", Columns: *columns12A}, - }, - renameMap: map[string]string{"c3": "cx"}, - expectAddedUKs: []*UniqueKey{ - {Name: "uidx12A", Columns: *columns12A}, - }, - expectRemovedUKs: []*UniqueKey{ - {Name: "uidx123", Columns: *columns123}, - }, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - addedUKs := AddedUniqueKeys(tc.sourceUKs, tc.targetUKs, tc.renameMap) - assert.Equal(t, tc.expectAddedUKs, addedUKs) - removedUKs := RemovedUniqueKeys(tc.sourceUKs, tc.targetUKs, tc.renameMap) - assert.Equal(t, tc.expectRemovedUKs, removedUKs) - }) - } -} diff --git a/go/vt/vttablet/onlineddl/vrepl_test.go b/go/vt/vttablet/onlineddl/vrepl_test.go index ddb723ed7b7..b9875c3f6d2 100644 --- a/go/vt/vttablet/onlineddl/vrepl_test.go +++ b/go/vt/vttablet/onlineddl/vrepl_test.go @@ -1,6 +1,238 @@ /* - Copyright 2016 GitHub Inc. - See https://github.com/github/gh-ost/blob/master/LICENSE +Copyright 2024 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 onlineddl + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/vt/schemadiff" + "vitess.io/vitess/go/vt/vtenv" +) + +func TestRevertible(t *testing.T) { + + type revertibleTestCase struct { + name string + fromSchema string + toSchema string + // expectProblems bool + removedForeignKeyNames string + removedUniqueKeyNames string + droppedNoDefaultColumnNames string + expandedColumnNames string + } + + var testCases = []revertibleTestCase{ + { + name: "identical schemas", + fromSchema: `id int primary key, i1 int not null default 0`, + toSchema: `id int primary key, i2 int not null default 0`, + }, + { + name: "different schemas, nothing to note", + fromSchema: `id int primary key, i1 int not null default 0, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i1 int not null default 0, i2 int not null default 0, unique key i1_uidx(i1)`, + }, + { + name: "removed non-nullable unique key", + fromSchema: `id int primary key, i1 int not null default 0, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i2 int not null default 0`, + removedUniqueKeyNames: `i1_uidx`, + }, + { + name: "removed nullable unique key", + fromSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i2 int default null`, + removedUniqueKeyNames: `i1_uidx`, + }, + { + name: "expanding unique key removes unique constraint", + fromSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`, + toSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1, id)`, + removedUniqueKeyNames: `i1_uidx`, + }, + { + name: "expanding unique key prefix removes unique constraint", + fromSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(20))`, + toSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(21))`, + removedUniqueKeyNames: `v_uidx`, + }, + { + name: "reducing unique key does not remove unique constraint", + fromSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1, id)`, + toSchema: `id int primary key, i1 int default null, unique key i1_uidx(i1)`, + removedUniqueKeyNames: ``, + }, + { + name: "reducing unique key does not remove unique constraint", + fromSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(21))`, + toSchema: `id int primary key, v varchar(100) default null, unique key v_uidx(v(20))`, + }, + { + name: "removed foreign key", + fromSchema: "id int primary key, i int, constraint some_fk_1 foreign key (i) references parent (id) on delete cascade", + toSchema: "id int primary key, i int", + removedForeignKeyNames: "some_fk_1", + }, + + { + name: "renamed foreign key", + fromSchema: "id int primary key, i int, constraint f1 foreign key (i) references parent (id) on delete cascade", + toSchema: "id int primary key, i int, constraint f2 foreign key (i) references parent (id) on delete cascade", + }, + { + name: "remove column without default", + fromSchema: `id int primary key, i1 int not null, i2 int not null default 0, i3 int default null`, + toSchema: `id int primary key, i4 int not null default 0`, + droppedNoDefaultColumnNames: `i1`, + }, + { + name: "expanded: nullable", + fromSchema: `id int primary key, i1 int not null, i2 int default null`, + toSchema: `id int primary key, i1 int default null, i2 int not null`, + expandedColumnNames: `i1`, + }, + { + name: "expanded: longer text", + fromSchema: `id int primary key, i1 int default null, v1 varchar(40) not null, v2 varchar(5), v3 varchar(3)`, + toSchema: `id int primary key, i1 int not null, v1 varchar(100) not null, v2 char(3), v3 char(5)`, + expandedColumnNames: `v1,v3`, + }, + { + name: "expanded: int numeric precision and scale", + fromSchema: `id int primary key, i1 int, i2 tinyint, i3 mediumint, i4 bigint`, + toSchema: `id int primary key, i1 int, i2 mediumint, i3 int, i4 tinyint`, + expandedColumnNames: `i2,i3`, + }, + { + name: "expanded: floating point", + fromSchema: `id int primary key, i1 int, n2 bigint, n3 bigint, n4 float, n5 double`, + toSchema: `id int primary key, i1 int, n2 float, n3 double, n4 double, n5 float`, + expandedColumnNames: `n2,n3,n4`, + }, + { + name: "expanded: decimal numeric precision and scale", + fromSchema: `id int primary key, i1 int, d1 decimal(10,2), d2 decimal (10,2), d3 decimal (10,2)`, + toSchema: `id int primary key, i1 int, d1 decimal(11,2), d2 decimal (9,1), d3 decimal (10,3)`, + expandedColumnNames: `d1,d3`, + }, + { + name: "expanded: signed, unsigned", + fromSchema: `id int primary key, i1 bigint signed, i2 int unsigned, i3 bigint unsigned`, + toSchema: `id int primary key, i1 int signed, i2 int signed, i3 int signed`, + expandedColumnNames: `i2,i3`, + }, + { + name: "expanded: signed, unsigned: range", + fromSchema: `id int primary key, i1 int signed, i2 bigint signed, i3 int signed`, + toSchema: `id int primary key, i1 int unsigned, i2 int unsigned, i3 bigint unsigned`, + expandedColumnNames: `i1,i3`, + }, + { + name: "expanded: datetime precision", + fromSchema: `id int primary key, dt1 datetime, ts1 timestamp, ti1 time, dt2 datetime(3), dt3 datetime(6), ts2 timestamp(3)`, + toSchema: `id int primary key, dt1 datetime(3), ts1 timestamp(6), ti1 time(3), dt2 datetime(6), dt3 datetime(3), ts2 timestamp`, + expandedColumnNames: `dt1,ts1,ti1,dt2`, + }, + { + name: "expanded: strange data type changes", + fromSchema: `id int primary key, dt1 datetime, ts1 timestamp, i1 int, d1 date, e1 enum('a', 'b')`, + toSchema: `id int primary key, dt1 char(32), ts1 varchar(32), i1 tinytext, d1 char(2), e1 varchar(2)`, + expandedColumnNames: `dt1,ts1,i1,d1,e1`, + }, + { + name: "expanded: temporal types", + fromSchema: `id int primary key, t1 time, t2 timestamp, t3 date, t4 datetime, t5 time, t6 date`, + toSchema: `id int primary key, t1 datetime, t2 datetime, t3 timestamp, t4 timestamp, t5 timestamp, t6 datetime`, + expandedColumnNames: `t1,t2,t3,t5,t6`, + }, + { + name: "expanded: character sets", + fromSchema: `id int primary key, c1 char(3) charset utf8, c2 char(3) charset utf8mb4, c3 char(3) charset ascii, c4 char(3) charset utf8mb4, c5 char(3) charset utf8, c6 char(3) charset latin1`, + toSchema: `id int primary key, c1 char(3) charset utf8mb4, c2 char(3) charset utf8, c3 char(3) charset utf8, c4 char(3) charset ascii, c5 char(3) charset utf8, c6 char(3) charset utf8mb4`, + expandedColumnNames: `c1,c3,c6`, + }, + { + name: "expanded: enum", + fromSchema: `id int primary key, e1 enum('a', 'b'), e2 enum('a', 'b'), e3 enum('a', 'b'), e4 enum('a', 'b'), e5 enum('a', 'b'), e6 enum('a', 'b'), e7 enum('a', 'b'), e8 enum('a', 'b')`, + toSchema: `id int primary key, e1 enum('a', 'b'), e2 enum('a'), e3 enum('a', 'b', 'c'), e4 enum('a', 'x'), e5 enum('a', 'x', 'b'), e6 enum('b'), e7 varchar(1), e8 tinyint`, + expandedColumnNames: `e3,e4,e5,e6,e7,e8`, + }, + { + name: "expanded: set", + fromSchema: `id int primary key, e1 set('a', 'b'), e2 set('a', 'b'), e3 set('a', 'b'), e4 set('a', 'b'), e5 set('a', 'b'), e6 set('a', 'b'), e7 set('a', 'b'), e8 set('a', 'b')`, + toSchema: `id int primary key, e1 set('a', 'b'), e2 set('a'), e3 set('a', 'b', 'c'), e4 set('a', 'x'), e5 set('a', 'x', 'b'), e6 set('b'), e7 varchar(1), e8 tinyint`, + expandedColumnNames: `e3,e4,e5,e6,e7,e8`, + }, + } + + var ( + createTableWrapper = `CREATE TABLE t (%s)` + ) + + senv := schemadiff.NewTestEnv() + venv := vtenv.NewTestEnv() + diffHints := &schemadiff.DiffHints{} + for _, tcase := range testCases { + t.Run(tcase.name, func(t *testing.T) { + tcase.fromSchema = fmt.Sprintf(createTableWrapper, tcase.fromSchema) + sourceTableEntity, err := schemadiff.NewCreateTableEntityFromSQL(senv, tcase.fromSchema) + require.NoError(t, err) + + tcase.toSchema = fmt.Sprintf(createTableWrapper, tcase.toSchema) + targetTableEntity, err := schemadiff.NewCreateTableEntityFromSQL(senv, tcase.toSchema) + require.NoError(t, err) + + diff, err := sourceTableEntity.TableDiff(targetTableEntity, diffHints) + require.NoError(t, err) + + v, err := NewVRepl( + venv, + "7cee19dd_354b_11eb_82cd_f875a4d24e90", + "ks", + "0", + "mydb", + sourceTableEntity.CreateTable, + targetTableEntity.CreateTable, + diff.AlterTable(), + false, + ) + require.NoError(t, err) + + err = v.analyzeAlter() + require.NoError(t, err) + err = v.analyzeTables() + require.NoError(t, err) + + toStringSlice := func(s string) []string { + if s == "" { + return []string{} + } + return strings.Split(s, ",") + } + assert.Equal(t, toStringSlice(tcase.removedForeignKeyNames), v.analysis.RemovedForeignKeyNames) + assert.Equal(t, toStringSlice(tcase.removedUniqueKeyNames), v.analysis.RemovedUniqueKeys.Names()) + assert.Equal(t, toStringSlice(tcase.droppedNoDefaultColumnNames), v.analysis.DroppedNoDefaultColumns.Names()) + assert.Equal(t, toStringSlice(tcase.expandedColumnNames), v.analysis.ExpandedColumns.Names()) + }) + } +} From 2af2884a59d7f65f97847c2aade713705320b091 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:39:03 +0300 Subject: [PATCH 13/34] Throttler: `CheckThrottlerResponseCode` to replace HTTP status codes (#16491) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- .../throttler_topo/throttler_test.go | 95 +- .../tabletmanagerdata/tabletmanagerdata.pb.go | 920 ++++++++++-------- .../tabletmanagerdata_vtproto.pb.go | 102 +- go/vt/vttablet/tabletmanager/rpc_throttler.go | 28 +- go/vt/vttablet/tabletserver/tabletserver.go | 4 +- .../tabletserver/throttle/base/recent_app.go | 18 +- go/vt/vttablet/tabletserver/throttle/check.go | 49 +- .../tabletserver/throttle/check_result.go | 107 +- .../throttle/check_result_test.go | 77 ++ .../vttablet/tabletserver/throttle/client.go | 5 +- .../tabletserver/throttle/throttler.go | 14 +- .../tabletserver/throttle/throttler_test.go | 71 +- proto/tabletmanagerdata.proto | 15 + web/vtadmin/src/proto/vtadmin.d.ts | 28 + web/vtadmin/src/proto/vtadmin.js | 208 ++++ 15 files changed, 1201 insertions(+), 540 deletions(-) diff --git a/go/test/endtoend/tabletmanager/throttler_topo/throttler_test.go b/go/test/endtoend/tabletmanager/throttler_topo/throttler_test.go index 89649f2ce4c..08cea643940 100644 --- a/go/test/endtoend/tabletmanager/throttler_topo/throttler_test.go +++ b/go/test/endtoend/tabletmanager/throttler_topo/throttler_test.go @@ -38,6 +38,7 @@ import ( "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/base" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/throttlerapp" + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" ) @@ -203,18 +204,18 @@ func throttleStatus(t *testing.T, tablet *cluster.Vttablet) string { return string(b) } -func warmUpHeartbeat(t *testing.T) (respStatus int) { +func warmUpHeartbeat(t *testing.T) tabletmanagerdatapb.CheckThrottlerResponseCode { // because we run with -heartbeat_on_demand_duration=5s, the heartbeat is "cold" right now. // Let's warm it up. resp, err := throttleCheck(primaryTablet, false) require.NoError(t, err) time.Sleep(time.Second) - return int(resp.Check.StatusCode) + return throttle.ResponseCodeFromStatus(resp.Check.ResponseCode, int(resp.Check.StatusCode)) } // waitForThrottleCheckStatus waits for the tablet to return the provided HTTP code in a throttle check -func waitForThrottleCheckStatus(t *testing.T, tablet *cluster.Vttablet, wantCode int) bool { +func waitForThrottleCheckStatus(t *testing.T, tablet *cluster.Vttablet, wantCode tabletmanagerdatapb.CheckThrottlerResponseCode) bool { _ = warmUpHeartbeat(t) ctx, cancel := context.WithTimeout(context.Background(), onDemandHeartbeatDuration*4) defer cancel() @@ -225,7 +226,7 @@ func waitForThrottleCheckStatus(t *testing.T, tablet *cluster.Vttablet, wantCode resp, err := throttleCheck(tablet, true) require.NoError(t, err) - if wantCode == int(resp.Check.StatusCode) { + if wantCode == resp.Check.ResponseCode { // Wait for any cached check values to be cleared and the new // status value to be in effect everywhere before returning. return true @@ -260,7 +261,7 @@ func TestInitialThrottler(t *testing.T) { defer cluster.PanicHandler(t) t.Run("validating OK response from disabled throttler", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("enabling throttler with very low threshold", func(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Enable: true, Threshold: unreasonablyLowThreshold.Seconds()} @@ -273,7 +274,7 @@ func TestInitialThrottler(t *testing.T) { } }) t.Run("validating pushback response from throttler", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("disabling throttler", func(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Disable: true, Threshold: unreasonablyLowThreshold.Seconds()} @@ -286,7 +287,7 @@ func TestInitialThrottler(t *testing.T) { } }) t.Run("validating OK response from disabled throttler, again", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("enabling throttler, again", func(t *testing.T) { // Enable the throttler again with the default query which also moves us back @@ -301,7 +302,7 @@ func TestInitialThrottler(t *testing.T) { } }) t.Run("validating pushback response from throttler, again", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("setting high threshold", func(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: extremelyHighThreshold.Seconds()} @@ -314,7 +315,7 @@ func TestInitialThrottler(t *testing.T) { } }) t.Run("validating OK response from throttler with high threshold", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("setting low threshold", func(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} @@ -327,11 +328,11 @@ func TestInitialThrottler(t *testing.T) { } }) t.Run("validating pushback response from throttler on low threshold", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("requesting heartbeats", func(t *testing.T) { respStatus := warmUpHeartbeat(t) - assert.NotEqual(t, http.StatusOK, respStatus) + assert.NotEqual(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, respStatus) }) t.Run("validating OK response from throttler with low threshold, heartbeats running", func(t *testing.T) { time.Sleep(1 * time.Second) @@ -350,6 +351,13 @@ func TestInitialThrottler(t *testing.T) { t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) } + if !assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) { + rs, err := replicaTablet.VttabletProcess.QueryTablet("show replica status", keyspaceName, false) + assert.NoError(t, err) + t.Logf("Seconds_Behind_Source: %s", rs.Named().Row()["Seconds_Behind_Source"].ToString()) + t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) + t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) + } }) t.Run("validating OK response from throttler with low threshold, heartbeats running still", func(t *testing.T) { @@ -368,10 +376,17 @@ func TestInitialThrottler(t *testing.T) { t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) } + if !assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) { + rs, err := replicaTablet.VttabletProcess.QueryTablet("show replica status", keyspaceName, false) + assert.NoError(t, err) + t.Logf("Seconds_Behind_Source: %s", rs.Named().Row()["Seconds_Behind_Source"].ToString()) + t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) + t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) + } }) t.Run("validating pushback response from throttler on low threshold once heartbeats go stale", func(t *testing.T) { time.Sleep(2 * onDemandHeartbeatDuration) // just... really wait long enough, make sure on-demand stops - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) } @@ -424,7 +439,7 @@ func TestThrottlerAfterMetricsCollected(t *testing.T) { // By this time metrics will have been collected. We expect no lag, and something like: // {"StatusCode":200,"Value":0.282278,"Threshold":1,"Message":""} t.Run("validating throttler OK", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("validating throttled apps", func(t *testing.T) { resp, body, err := throttledApps(primaryTablet) @@ -437,11 +452,13 @@ func TestThrottlerAfterMetricsCollected(t *testing.T) { resp, err := throttleCheckSelf(primaryTablet) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) t.Run("validating replica check self", func(t *testing.T) { resp, err := throttleCheckSelf(replicaTablet) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) } @@ -469,6 +486,7 @@ func TestLag(t *testing.T) { resp, err := throttleCheck(primaryTablet, false) require.NoError(t, err) assert.EqualValues(t, http.StatusTooManyRequests, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) t.Run("primary self-check should still be fine", func(t *testing.T) { resp, err := throttleCheckSelf(primaryTablet) @@ -482,6 +500,10 @@ func TestLag(t *testing.T) { t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) } + if !assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) { + t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) + t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) + } }) t.Run("replica self-check should show error", func(t *testing.T) { resp, err := throttleCheckSelf(replicaTablet) @@ -491,6 +513,7 @@ func TestLag(t *testing.T) { assert.Equal(t, base.SelfScope.String(), metrics.Scope) } assert.EqualValues(t, http.StatusTooManyRequests, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) t.Run("exempting test app", func(t *testing.T) { appRule := &topodatapb.ThrottledAppRule{ @@ -501,7 +524,7 @@ func TestLag(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} _, err := throttler.UpdateThrottlerTopoConfig(clusterInstance, req, appRule, nil) assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("unexempting test app", func(t *testing.T) { appRule := &topodatapb.ThrottledAppRule{ @@ -511,7 +534,7 @@ func TestLag(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} _, err := throttler.UpdateThrottlerTopoConfig(clusterInstance, req, appRule, nil) assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("exempting all apps", func(t *testing.T) { appRule := &topodatapb.ThrottledAppRule{ @@ -522,7 +545,7 @@ func TestLag(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} _, err := throttler.UpdateThrottlerTopoConfig(clusterInstance, req, appRule, nil) assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("throttling test app", func(t *testing.T) { appRule := &topodatapb.ThrottledAppRule{ @@ -533,7 +556,7 @@ func TestLag(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} _, err := throttler.UpdateThrottlerTopoConfig(clusterInstance, req, appRule, nil) assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusExpectationFailed) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED) }) t.Run("unthrottling test app", func(t *testing.T) { appRule := &topodatapb.ThrottledAppRule{ @@ -543,7 +566,7 @@ func TestLag(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} _, err := throttler.UpdateThrottlerTopoConfig(clusterInstance, req, appRule, nil) assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("unexempting all apps", func(t *testing.T) { appRule := &topodatapb.ThrottledAppRule{ @@ -553,7 +576,7 @@ func TestLag(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} _, err := throttler.UpdateThrottlerTopoConfig(clusterInstance, req, appRule, nil) assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("starting replication", func(t *testing.T) { @@ -561,18 +584,20 @@ func TestLag(t *testing.T) { assert.NoError(t, err) }) t.Run("expecting replication to catch up and throttler check to return OK", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("primary self-check should be fine", func(t *testing.T) { resp, err := throttleCheckSelf(primaryTablet) require.NoError(t, err) // self (on primary) is unaffected by replication lag assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) t.Run("replica self-check should be fine", func(t *testing.T) { resp, err := throttleCheckSelf(replicaTablet) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) } @@ -584,13 +609,13 @@ func TestNoReplicas(t *testing.T) { // This makes no REPLICA servers available. We expect something like: // {"StatusCode":200,"Value":0,"Threshold":1,"Message":""} - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("restoring to REPLICA", func(t *testing.T) { err := clusterInstance.VtctldClientProcess.ExecuteCommand("ChangeTabletType", replicaTablet.Alias, "REPLICA") assert.NoError(t, err) - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) } @@ -617,6 +642,7 @@ func TestCustomQuery(t *testing.T) { resp, err := throttleCheck(primaryTablet, false) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) t.Run("test threads running", func(t *testing.T) { sleepDuration := 20 * time.Second @@ -638,22 +664,24 @@ func TestCustomQuery(t *testing.T) { // Now we should be reporting ~ customThreshold+1 threads_running, and we should // hit the threshold. For example: // {"StatusCode":429,"Value":6,"Threshold":5,"Message":"Threshold exceeded"} - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) { resp, err := throttleCheckSelf(primaryTablet) require.NoError(t, err) assert.EqualValues(t, http.StatusTooManyRequests, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) } }) t.Run("wait for queries to terminate", func(t *testing.T) { wg.Wait() }) t.Run("restored below threshold", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) { resp, err := throttleCheckSelf(primaryTablet) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) } }) }) @@ -677,10 +705,11 @@ func TestRestoreDefaultQuery(t *testing.T) { resp, err := throttleCheck(primaryTablet, false) require.NoError(t, err) assert.EqualValues(t, http.StatusOK, resp.Check.StatusCode, "Unexpected response from throttler: %+v", resp) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, resp.Check.ResponseCode, "Unexpected response from throttler: %+v", resp) }) t.Run("validating pushback response from throttler on default threshold once heartbeats go stale", func(t *testing.T) { time.Sleep(2 * onDemandHeartbeatDuration) // just... really wait long enough, make sure on-demand stops - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) } @@ -693,7 +722,7 @@ func TestUpdateMetricThresholds(t *testing.T) { for _, tablet := range []cluster.Vttablet{*primaryTablet, *replicaTablet} { throttler.WaitForThrottlerStatusEnabled(t, &clusterInstance.VtctldClientProcess, &tablet, true, &throttler.Config{Query: throttler.DefaultQuery, Threshold: throttler.DefaultThreshold.Seconds()}, throttlerEnabledTimeout) } - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("setting low general threshold, and high threshold for 'lag' metric", func(t *testing.T) { { @@ -713,7 +742,7 @@ func TestUpdateMetricThresholds(t *testing.T) { }) t.Run("validating OK response from throttler thanks to high 'lag' threshold", func(t *testing.T) { // Note that the default threshold is extremely low, but gets overriden. - waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) }) t.Run("removing explicit 'lag' threshold", func(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{MetricName: "lag", Threshold: 0} @@ -721,7 +750,7 @@ func TestUpdateMetricThresholds(t *testing.T) { assert.NoError(t, err) }) t.Run("validating pushback from throttler again", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("restoring standard threshold", func(t *testing.T) { req := &vtctldatapb.UpdateThrottlerConfigRequest{Threshold: throttler.DefaultThreshold.Seconds()} @@ -731,7 +760,7 @@ func TestUpdateMetricThresholds(t *testing.T) { for _, tablet := range []cluster.Vttablet{*primaryTablet, *replicaTablet} { throttler.WaitForThrottlerStatusEnabled(t, &clusterInstance.VtctldClientProcess, &tablet, true, &throttler.Config{Query: throttler.DefaultQuery, Threshold: throttler.DefaultThreshold.Seconds()}, throttlerEnabledTimeout) } - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) } @@ -748,7 +777,7 @@ func TestUpdateAppCheckedMetrics(t *testing.T) { for _, tablet := range []cluster.Vttablet{*primaryTablet, *replicaTablet} { throttler.WaitForThrottlerStatusEnabled(t, &clusterInstance.VtctldClientProcess, &tablet, true, &throttler.Config{Query: throttler.DefaultQuery, Threshold: throttler.DefaultThreshold.Seconds()}, throttlerEnabledTimeout) } - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) t.Run("assigning 'loadavg' metrics to 'test' app", func(t *testing.T) { { @@ -774,7 +803,7 @@ func TestUpdateAppCheckedMetrics(t *testing.T) { throttler.WaitForThrottlerStatusEnabled(t, &clusterInstance.VtctldClientProcess, &tablet, true, &throttler.Config{Query: throttler.DefaultQuery, Threshold: unreasonablyLowThreshold.Seconds()}, throttlerEnabledTimeout) } t.Run("validating OK response from throttler since it's checking loadavg", func(t *testing.T) { - if !waitForThrottleCheckStatus(t, primaryTablet, http.StatusOK) { + if !waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_OK) { t.Logf("throttler primary status: %+v", throttleStatus(t, primaryTablet)) t.Logf("throttler replica status: %+v", throttleStatus(t, replicaTablet)) } @@ -799,7 +828,7 @@ func TestUpdateAppCheckedMetrics(t *testing.T) { throttler.WaitForThrottlerStatusEnabled(t, &clusterInstance.VtctldClientProcess, &tablet, true, &throttler.Config{Query: throttler.DefaultQuery, Threshold: unreasonablyLowThreshold.Seconds()}, throttlerEnabledTimeout) } t.Run("validating pushback from throttler since lag is above threshold", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) }) t.Run("removing assignment from 'test' app and restoring defaults", func(t *testing.T) { @@ -826,7 +855,7 @@ func TestUpdateAppCheckedMetrics(t *testing.T) { throttler.WaitForThrottlerStatusEnabled(t, &clusterInstance.VtctldClientProcess, &tablet, true, &throttler.Config{Query: throttler.DefaultQuery, Threshold: throttler.DefaultThreshold.Seconds()}, throttlerEnabledTimeout) } t.Run("validating error response from throttler since lag is still high", func(t *testing.T) { - waitForThrottleCheckStatus(t, primaryTablet, http.StatusTooManyRequests) + waitForThrottleCheckStatus(t, primaryTablet, tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED) }) }) } diff --git a/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go b/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go index 715e2a2ab36..b324845cec1 100644 --- a/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go +++ b/go/vt/proto/tabletmanagerdata/tabletmanagerdata.pb.go @@ -96,6 +96,64 @@ func (TabletSelectionPreference) EnumDescriptor() ([]byte, []int) { return file_tabletmanagerdata_proto_rawDescGZIP(), []int{0} } +type CheckThrottlerResponseCode int32 + +const ( + CheckThrottlerResponseCode_UNDEFINED CheckThrottlerResponseCode = 0 + CheckThrottlerResponseCode_OK CheckThrottlerResponseCode = 1 + CheckThrottlerResponseCode_THRESHOLD_EXCEEDED CheckThrottlerResponseCode = 2 + CheckThrottlerResponseCode_APP_DENIED CheckThrottlerResponseCode = 3 + CheckThrottlerResponseCode_UNKNOWN_METRIC CheckThrottlerResponseCode = 4 + CheckThrottlerResponseCode_INTERNAL_ERROR CheckThrottlerResponseCode = 5 +) + +// Enum value maps for CheckThrottlerResponseCode. +var ( + CheckThrottlerResponseCode_name = map[int32]string{ + 0: "UNDEFINED", + 1: "OK", + 2: "THRESHOLD_EXCEEDED", + 3: "APP_DENIED", + 4: "UNKNOWN_METRIC", + 5: "INTERNAL_ERROR", + } + CheckThrottlerResponseCode_value = map[string]int32{ + "UNDEFINED": 0, + "OK": 1, + "THRESHOLD_EXCEEDED": 2, + "APP_DENIED": 3, + "UNKNOWN_METRIC": 4, + "INTERNAL_ERROR": 5, + } +) + +func (x CheckThrottlerResponseCode) Enum() *CheckThrottlerResponseCode { + p := new(CheckThrottlerResponseCode) + *p = x + return p +} + +func (x CheckThrottlerResponseCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CheckThrottlerResponseCode) Descriptor() protoreflect.EnumDescriptor { + return file_tabletmanagerdata_proto_enumTypes[1].Descriptor() +} + +func (CheckThrottlerResponseCode) Type() protoreflect.EnumType { + return &file_tabletmanagerdata_proto_enumTypes[1] +} + +func (x CheckThrottlerResponseCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CheckThrottlerResponseCode.Descriptor instead. +func (CheckThrottlerResponseCode) EnumDescriptor() ([]byte, []int) { + return file_tabletmanagerdata_proto_rawDescGZIP(), []int{1} +} + type TableDefinition struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -6640,6 +6698,8 @@ type CheckThrottlerResponse struct { AppName string `protobuf:"bytes,8,opt,name=app_name,json=appName,proto3" json:"app_name,omitempty"` // Summary is a human readable analysis of the result Summary string `protobuf:"bytes,9,opt,name=summary,proto3" json:"summary,omitempty"` + // ResponseCode is the enum representation of the response + ResponseCode CheckThrottlerResponseCode `protobuf:"varint,10,opt,name=response_code,json=responseCode,proto3,enum=tabletmanagerdata.CheckThrottlerResponseCode" json:"response_code,omitempty"` } func (x *CheckThrottlerResponse) Reset() { @@ -6737,6 +6797,13 @@ func (x *CheckThrottlerResponse) GetSummary() string { return "" } +func (x *CheckThrottlerResponse) GetResponseCode() CheckThrottlerResponseCode { + if x != nil { + return x.ResponseCode + } + return CheckThrottlerResponseCode_UNDEFINED +} + type GetThrottlerStatusRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -7146,6 +7213,8 @@ type CheckThrottlerResponse_Metric struct { Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` // Scope used in this check Scope string `protobuf:"bytes,7,opt,name=scope,proto3" json:"scope,omitempty"` + // ResponseCode is the enum representation of the response + ResponseCode CheckThrottlerResponseCode `protobuf:"varint,8,opt,name=response_code,json=responseCode,proto3,enum=tabletmanagerdata.CheckThrottlerResponseCode" json:"response_code,omitempty"` } func (x *CheckThrottlerResponse_Metric) Reset() { @@ -7229,6 +7298,13 @@ func (x *CheckThrottlerResponse_Metric) GetScope() string { return "" } +func (x *CheckThrottlerResponse_Metric) GetResponseCode() CheckThrottlerResponseCode { + if x != nil { + return x.ResponseCode + } + return CheckThrottlerResponseCode_UNDEFINED +} + type GetThrottlerStatusResponse_MetricResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -7346,6 +7422,8 @@ type GetThrottlerStatusResponse_RecentApp struct { CheckedAt *vttime.Time `protobuf:"bytes,1,opt,name=checked_at,json=checkedAt,proto3" json:"checked_at,omitempty"` StatusCode int32 `protobuf:"varint,2,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + // ResponseCode is the enum representation of the response + ResponseCode CheckThrottlerResponseCode `protobuf:"varint,3,opt,name=response_code,json=responseCode,proto3,enum=tabletmanagerdata.CheckThrottlerResponseCode" json:"response_code,omitempty"` } func (x *GetThrottlerStatusResponse_RecentApp) Reset() { @@ -7394,6 +7472,13 @@ func (x *GetThrottlerStatusResponse_RecentApp) GetStatusCode() int32 { return 0 } +func (x *GetThrottlerStatusResponse_RecentApp) GetResponseCode() CheckThrottlerResponseCode { + if x != nil { + return x.ResponseCode + } + return CheckThrottlerResponseCode_UNDEFINED +} + var File_tabletmanagerdata_proto protoreflect.FileDescriptor var file_tabletmanagerdata_proto_rawDesc = []byte{ @@ -8229,7 +8314,7 @@ var file_tabletmanagerdata_proto_rawDesc = []byte{ 0x74, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xf7, 0x04, 0x0a, 0x16, + 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x9f, 0x06, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, @@ -8250,161 +8335,186 @@ var file_tabletmanagerdata_proto_rawDesc = []byte{ 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x1a, 0xb7, 0x01, - 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x1a, 0x6c, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, - 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0xe1, 0x0f, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, - 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, - 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6c, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x4c, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x69, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, - 0x73, 0x5f, 0x64, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x69, 0x73, 0x44, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x61, - 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x61, 0x67, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, - 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x5f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x73, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4e, 0x61, - 0x6d, 0x65, 0x55, 0x73, 0x65, 0x64, 0x41, 0x73, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, - 0x73, 0x0a, 0x12, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, - 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x11, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x12, 0x70, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x43, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, - 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x12, 0x67, 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, - 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, - 0x67, 0x0a, 0x0e, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, - 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x52, 0x0a, + 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, + 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, + 0x65, 0x1a, 0x8b, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, + 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, + 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, + 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, + 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x1a, + 0x6c, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x30, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, + 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1b, 0x0a, + 0x19, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb6, 0x10, 0x0a, 0x1a, 0x47, + 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1a, 0x0a, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x69, 0x73, 0x4c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, + 0x73, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, + 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x6f, 0x72, 0x6d, 0x61, 0x6e, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x6f, 0x72, 0x6d, 0x61, + 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x61, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x61, + 0x67, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x13, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2b, 0x0a, 0x11, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x3c, 0x0a, 0x1b, 0x6d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x61, 0x73, + 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, + 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x64, 0x41, 0x73, + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x73, 0x0a, 0x12, 0x61, 0x67, 0x67, 0x72, 0x65, + 0x67, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x0c, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, + 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x67, 0x67, 0x72, 0x65, + 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x70, 0x0a, 0x11, + 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, + 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, - 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x68, 0x72, 0x6f, 0x74, - 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, 0x12, 0x74, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, - 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, - 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x70, 0x70, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x29, - 0x0a, 0x10, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, - 0x6c, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x5e, 0x0a, 0x0b, 0x72, 0x65, 0x63, - 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, + 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x12, 0x67, + 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, + 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x67, 0x0a, 0x0e, 0x74, 0x68, 0x72, 0x6f, 0x74, + 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x40, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, + 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0d, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, + 0x12, 0x74, 0x0a, 0x13, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x5f, + 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x70, 0x70, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, + 0x6c, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, + 0x64, 0x12, 0x5e, 0x0a, 0x0b, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x70, 0x70, 0x73, + 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, + 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, + 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x80, 0x01, + 0x0a, 0x16, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x1a, 0x43, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, + 0x6f, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x81, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x34, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x0d, 0x6c, + 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x41, 0x74, 0x12, 0x3b, 0x0a, 0x1a, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x17, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, + 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x1a, 0x7c, 0x0a, 0x12, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, + 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5c, 0x0a, 0x12, 0x54, 0x68, 0x72, 0x6f, 0x74, + 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, + 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x41, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xad, 0x01, 0x0a, 0x09, + 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x12, 0x2b, 0x0a, 0x0a, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x09, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, - 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x72, - 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x80, 0x01, 0x0a, 0x16, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, - 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x43, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x81, 0x01, - 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x34, - 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x5f, 0x61, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x79, 0x41, 0x74, 0x12, 0x3b, 0x0a, 0x1a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, - 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x79, 0x1a, 0x7c, 0x0a, 0x12, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x50, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, - 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, - 0x5c, 0x0a, 0x12, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x64, 0x61, 0x74, - 0x61, 0x2e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x41, 0x70, 0x70, 0x52, 0x75, - 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, - 0x16, 0x41, 0x70, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x09, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, - 0x12, 0x2b, 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x52, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x76, - 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x4d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, - 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x3e, 0x0a, 0x19, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x49, 0x4e, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x42, 0x30, 0x5a, 0x2e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, - 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x61, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x76, 0x0a, 0x0f, 0x52, + 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x4d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x37, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, + 0x65, 0x63, 0x65, 0x6e, 0x74, 0x41, 0x70, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x2a, 0x3e, 0x0a, 0x19, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x4f, + 0x52, 0x44, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x10, 0x03, 0x2a, 0x83, 0x01, 0x0a, 0x1a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x72, + 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x48, 0x52, + 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x50, 0x50, 0x5f, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4d, 0x45, 0x54, + 0x52, 0x49, 0x43, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, + 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x42, 0x30, 0x5a, 0x2e, 0x76, 0x69, 0x74, + 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, + 0x2f, 0x76, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -8419,263 +8529,267 @@ func file_tabletmanagerdata_proto_rawDescGZIP() []byte { return file_tabletmanagerdata_proto_rawDescData } -var file_tabletmanagerdata_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_tabletmanagerdata_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_tabletmanagerdata_proto_msgTypes = make([]protoimpl.MessageInfo, 142) var file_tabletmanagerdata_proto_goTypes = []any{ (TabletSelectionPreference)(0), // 0: tabletmanagerdata.TabletSelectionPreference - (*TableDefinition)(nil), // 1: tabletmanagerdata.TableDefinition - (*SchemaDefinition)(nil), // 2: tabletmanagerdata.SchemaDefinition - (*SchemaChangeResult)(nil), // 3: tabletmanagerdata.SchemaChangeResult - (*UserPermission)(nil), // 4: tabletmanagerdata.UserPermission - (*DbPermission)(nil), // 5: tabletmanagerdata.DbPermission - (*Permissions)(nil), // 6: tabletmanagerdata.Permissions - (*PingRequest)(nil), // 7: tabletmanagerdata.PingRequest - (*PingResponse)(nil), // 8: tabletmanagerdata.PingResponse - (*SleepRequest)(nil), // 9: tabletmanagerdata.SleepRequest - (*SleepResponse)(nil), // 10: tabletmanagerdata.SleepResponse - (*ExecuteHookRequest)(nil), // 11: tabletmanagerdata.ExecuteHookRequest - (*ExecuteHookResponse)(nil), // 12: tabletmanagerdata.ExecuteHookResponse - (*GetSchemaRequest)(nil), // 13: tabletmanagerdata.GetSchemaRequest - (*GetSchemaResponse)(nil), // 14: tabletmanagerdata.GetSchemaResponse - (*GetPermissionsRequest)(nil), // 15: tabletmanagerdata.GetPermissionsRequest - (*GetPermissionsResponse)(nil), // 16: tabletmanagerdata.GetPermissionsResponse - (*GetGlobalStatusVarsRequest)(nil), // 17: tabletmanagerdata.GetGlobalStatusVarsRequest - (*GetGlobalStatusVarsResponse)(nil), // 18: tabletmanagerdata.GetGlobalStatusVarsResponse - (*SetReadOnlyRequest)(nil), // 19: tabletmanagerdata.SetReadOnlyRequest - (*SetReadOnlyResponse)(nil), // 20: tabletmanagerdata.SetReadOnlyResponse - (*SetReadWriteRequest)(nil), // 21: tabletmanagerdata.SetReadWriteRequest - (*SetReadWriteResponse)(nil), // 22: tabletmanagerdata.SetReadWriteResponse - (*ChangeTypeRequest)(nil), // 23: tabletmanagerdata.ChangeTypeRequest - (*ChangeTypeResponse)(nil), // 24: tabletmanagerdata.ChangeTypeResponse - (*RefreshStateRequest)(nil), // 25: tabletmanagerdata.RefreshStateRequest - (*RefreshStateResponse)(nil), // 26: tabletmanagerdata.RefreshStateResponse - (*RunHealthCheckRequest)(nil), // 27: tabletmanagerdata.RunHealthCheckRequest - (*RunHealthCheckResponse)(nil), // 28: tabletmanagerdata.RunHealthCheckResponse - (*ReloadSchemaRequest)(nil), // 29: tabletmanagerdata.ReloadSchemaRequest - (*ReloadSchemaResponse)(nil), // 30: tabletmanagerdata.ReloadSchemaResponse - (*PreflightSchemaRequest)(nil), // 31: tabletmanagerdata.PreflightSchemaRequest - (*PreflightSchemaResponse)(nil), // 32: tabletmanagerdata.PreflightSchemaResponse - (*ApplySchemaRequest)(nil), // 33: tabletmanagerdata.ApplySchemaRequest - (*ApplySchemaResponse)(nil), // 34: tabletmanagerdata.ApplySchemaResponse - (*LockTablesRequest)(nil), // 35: tabletmanagerdata.LockTablesRequest - (*LockTablesResponse)(nil), // 36: tabletmanagerdata.LockTablesResponse - (*UnlockTablesRequest)(nil), // 37: tabletmanagerdata.UnlockTablesRequest - (*UnlockTablesResponse)(nil), // 38: tabletmanagerdata.UnlockTablesResponse - (*ExecuteQueryRequest)(nil), // 39: tabletmanagerdata.ExecuteQueryRequest - (*ExecuteQueryResponse)(nil), // 40: tabletmanagerdata.ExecuteQueryResponse - (*ExecuteFetchAsDbaRequest)(nil), // 41: tabletmanagerdata.ExecuteFetchAsDbaRequest - (*ExecuteFetchAsDbaResponse)(nil), // 42: tabletmanagerdata.ExecuteFetchAsDbaResponse - (*ExecuteMultiFetchAsDbaRequest)(nil), // 43: tabletmanagerdata.ExecuteMultiFetchAsDbaRequest - (*ExecuteMultiFetchAsDbaResponse)(nil), // 44: tabletmanagerdata.ExecuteMultiFetchAsDbaResponse - (*ExecuteFetchAsAllPrivsRequest)(nil), // 45: tabletmanagerdata.ExecuteFetchAsAllPrivsRequest - (*ExecuteFetchAsAllPrivsResponse)(nil), // 46: tabletmanagerdata.ExecuteFetchAsAllPrivsResponse - (*ExecuteFetchAsAppRequest)(nil), // 47: tabletmanagerdata.ExecuteFetchAsAppRequest - (*ExecuteFetchAsAppResponse)(nil), // 48: tabletmanagerdata.ExecuteFetchAsAppResponse - (*ReplicationStatusRequest)(nil), // 49: tabletmanagerdata.ReplicationStatusRequest - (*ReplicationStatusResponse)(nil), // 50: tabletmanagerdata.ReplicationStatusResponse - (*PrimaryStatusRequest)(nil), // 51: tabletmanagerdata.PrimaryStatusRequest - (*PrimaryStatusResponse)(nil), // 52: tabletmanagerdata.PrimaryStatusResponse - (*PrimaryPositionRequest)(nil), // 53: tabletmanagerdata.PrimaryPositionRequest - (*PrimaryPositionResponse)(nil), // 54: tabletmanagerdata.PrimaryPositionResponse - (*WaitForPositionRequest)(nil), // 55: tabletmanagerdata.WaitForPositionRequest - (*WaitForPositionResponse)(nil), // 56: tabletmanagerdata.WaitForPositionResponse - (*StopReplicationRequest)(nil), // 57: tabletmanagerdata.StopReplicationRequest - (*StopReplicationResponse)(nil), // 58: tabletmanagerdata.StopReplicationResponse - (*StopReplicationMinimumRequest)(nil), // 59: tabletmanagerdata.StopReplicationMinimumRequest - (*StopReplicationMinimumResponse)(nil), // 60: tabletmanagerdata.StopReplicationMinimumResponse - (*StartReplicationRequest)(nil), // 61: tabletmanagerdata.StartReplicationRequest - (*StartReplicationResponse)(nil), // 62: tabletmanagerdata.StartReplicationResponse - (*StartReplicationUntilAfterRequest)(nil), // 63: tabletmanagerdata.StartReplicationUntilAfterRequest - (*StartReplicationUntilAfterResponse)(nil), // 64: tabletmanagerdata.StartReplicationUntilAfterResponse - (*GetReplicasRequest)(nil), // 65: tabletmanagerdata.GetReplicasRequest - (*GetReplicasResponse)(nil), // 66: tabletmanagerdata.GetReplicasResponse - (*ResetReplicationRequest)(nil), // 67: tabletmanagerdata.ResetReplicationRequest - (*ResetReplicationResponse)(nil), // 68: tabletmanagerdata.ResetReplicationResponse - (*VReplicationExecRequest)(nil), // 69: tabletmanagerdata.VReplicationExecRequest - (*VReplicationExecResponse)(nil), // 70: tabletmanagerdata.VReplicationExecResponse - (*VReplicationWaitForPosRequest)(nil), // 71: tabletmanagerdata.VReplicationWaitForPosRequest - (*VReplicationWaitForPosResponse)(nil), // 72: tabletmanagerdata.VReplicationWaitForPosResponse - (*InitPrimaryRequest)(nil), // 73: tabletmanagerdata.InitPrimaryRequest - (*InitPrimaryResponse)(nil), // 74: tabletmanagerdata.InitPrimaryResponse - (*PopulateReparentJournalRequest)(nil), // 75: tabletmanagerdata.PopulateReparentJournalRequest - (*PopulateReparentJournalResponse)(nil), // 76: tabletmanagerdata.PopulateReparentJournalResponse - (*InitReplicaRequest)(nil), // 77: tabletmanagerdata.InitReplicaRequest - (*InitReplicaResponse)(nil), // 78: tabletmanagerdata.InitReplicaResponse - (*DemotePrimaryRequest)(nil), // 79: tabletmanagerdata.DemotePrimaryRequest - (*DemotePrimaryResponse)(nil), // 80: tabletmanagerdata.DemotePrimaryResponse - (*UndoDemotePrimaryRequest)(nil), // 81: tabletmanagerdata.UndoDemotePrimaryRequest - (*UndoDemotePrimaryResponse)(nil), // 82: tabletmanagerdata.UndoDemotePrimaryResponse - (*ReplicaWasPromotedRequest)(nil), // 83: tabletmanagerdata.ReplicaWasPromotedRequest - (*ReplicaWasPromotedResponse)(nil), // 84: tabletmanagerdata.ReplicaWasPromotedResponse - (*ResetReplicationParametersRequest)(nil), // 85: tabletmanagerdata.ResetReplicationParametersRequest - (*ResetReplicationParametersResponse)(nil), // 86: tabletmanagerdata.ResetReplicationParametersResponse - (*FullStatusRequest)(nil), // 87: tabletmanagerdata.FullStatusRequest - (*FullStatusResponse)(nil), // 88: tabletmanagerdata.FullStatusResponse - (*SetReplicationSourceRequest)(nil), // 89: tabletmanagerdata.SetReplicationSourceRequest - (*SetReplicationSourceResponse)(nil), // 90: tabletmanagerdata.SetReplicationSourceResponse - (*ReplicaWasRestartedRequest)(nil), // 91: tabletmanagerdata.ReplicaWasRestartedRequest - (*ReplicaWasRestartedResponse)(nil), // 92: tabletmanagerdata.ReplicaWasRestartedResponse - (*StopReplicationAndGetStatusRequest)(nil), // 93: tabletmanagerdata.StopReplicationAndGetStatusRequest - (*StopReplicationAndGetStatusResponse)(nil), // 94: tabletmanagerdata.StopReplicationAndGetStatusResponse - (*PromoteReplicaRequest)(nil), // 95: tabletmanagerdata.PromoteReplicaRequest - (*PromoteReplicaResponse)(nil), // 96: tabletmanagerdata.PromoteReplicaResponse - (*BackupRequest)(nil), // 97: tabletmanagerdata.BackupRequest - (*BackupResponse)(nil), // 98: tabletmanagerdata.BackupResponse - (*RestoreFromBackupRequest)(nil), // 99: tabletmanagerdata.RestoreFromBackupRequest - (*RestoreFromBackupResponse)(nil), // 100: tabletmanagerdata.RestoreFromBackupResponse - (*CreateVReplicationWorkflowRequest)(nil), // 101: tabletmanagerdata.CreateVReplicationWorkflowRequest - (*CreateVReplicationWorkflowResponse)(nil), // 102: tabletmanagerdata.CreateVReplicationWorkflowResponse - (*DeleteVReplicationWorkflowRequest)(nil), // 103: tabletmanagerdata.DeleteVReplicationWorkflowRequest - (*DeleteVReplicationWorkflowResponse)(nil), // 104: tabletmanagerdata.DeleteVReplicationWorkflowResponse - (*HasVReplicationWorkflowsRequest)(nil), // 105: tabletmanagerdata.HasVReplicationWorkflowsRequest - (*HasVReplicationWorkflowsResponse)(nil), // 106: tabletmanagerdata.HasVReplicationWorkflowsResponse - (*ReadVReplicationWorkflowsRequest)(nil), // 107: tabletmanagerdata.ReadVReplicationWorkflowsRequest - (*ReadVReplicationWorkflowsResponse)(nil), // 108: tabletmanagerdata.ReadVReplicationWorkflowsResponse - (*ReadVReplicationWorkflowRequest)(nil), // 109: tabletmanagerdata.ReadVReplicationWorkflowRequest - (*ReadVReplicationWorkflowResponse)(nil), // 110: tabletmanagerdata.ReadVReplicationWorkflowResponse - (*VDiffRequest)(nil), // 111: tabletmanagerdata.VDiffRequest - (*VDiffResponse)(nil), // 112: tabletmanagerdata.VDiffResponse - (*VDiffPickerOptions)(nil), // 113: tabletmanagerdata.VDiffPickerOptions - (*VDiffReportOptions)(nil), // 114: tabletmanagerdata.VDiffReportOptions - (*VDiffCoreOptions)(nil), // 115: tabletmanagerdata.VDiffCoreOptions - (*VDiffOptions)(nil), // 116: tabletmanagerdata.VDiffOptions - (*UpdateVReplicationWorkflowRequest)(nil), // 117: tabletmanagerdata.UpdateVReplicationWorkflowRequest - (*UpdateVReplicationWorkflowResponse)(nil), // 118: tabletmanagerdata.UpdateVReplicationWorkflowResponse - (*UpdateVReplicationWorkflowsRequest)(nil), // 119: tabletmanagerdata.UpdateVReplicationWorkflowsRequest - (*UpdateVReplicationWorkflowsResponse)(nil), // 120: tabletmanagerdata.UpdateVReplicationWorkflowsResponse - (*ResetSequencesRequest)(nil), // 121: tabletmanagerdata.ResetSequencesRequest - (*ResetSequencesResponse)(nil), // 122: tabletmanagerdata.ResetSequencesResponse - (*CheckThrottlerRequest)(nil), // 123: tabletmanagerdata.CheckThrottlerRequest - (*CheckThrottlerResponse)(nil), // 124: tabletmanagerdata.CheckThrottlerResponse - (*GetThrottlerStatusRequest)(nil), // 125: tabletmanagerdata.GetThrottlerStatusRequest - (*GetThrottlerStatusResponse)(nil), // 126: tabletmanagerdata.GetThrottlerStatusResponse - nil, // 127: tabletmanagerdata.UserPermission.PrivilegesEntry - nil, // 128: tabletmanagerdata.DbPermission.PrivilegesEntry - nil, // 129: tabletmanagerdata.ExecuteHookRequest.ExtraEnvEntry - nil, // 130: tabletmanagerdata.GetGlobalStatusVarsResponse.StatusValuesEntry - (*ReadVReplicationWorkflowResponse_Stream)(nil), // 131: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream - (*CheckThrottlerResponse_Metric)(nil), // 132: tabletmanagerdata.CheckThrottlerResponse.Metric - nil, // 133: tabletmanagerdata.CheckThrottlerResponse.MetricsEntry - (*GetThrottlerStatusResponse_MetricResult)(nil), // 134: tabletmanagerdata.GetThrottlerStatusResponse.MetricResult - nil, // 135: tabletmanagerdata.GetThrottlerStatusResponse.AggregatedMetricsEntry - nil, // 136: tabletmanagerdata.GetThrottlerStatusResponse.MetricThresholdsEntry - (*GetThrottlerStatusResponse_MetricHealth)(nil), // 137: tabletmanagerdata.GetThrottlerStatusResponse.MetricHealth - nil, // 138: tabletmanagerdata.GetThrottlerStatusResponse.MetricsHealthEntry - nil, // 139: tabletmanagerdata.GetThrottlerStatusResponse.ThrottledAppsEntry - nil, // 140: tabletmanagerdata.GetThrottlerStatusResponse.AppCheckedMetricsEntry - (*GetThrottlerStatusResponse_RecentApp)(nil), // 141: tabletmanagerdata.GetThrottlerStatusResponse.RecentApp - nil, // 142: tabletmanagerdata.GetThrottlerStatusResponse.RecentAppsEntry - (*query.Field)(nil), // 143: query.Field - (topodata.TabletType)(0), // 144: topodata.TabletType - (*vtrpc.CallerID)(nil), // 145: vtrpc.CallerID - (*query.QueryResult)(nil), // 146: query.QueryResult - (*replicationdata.Status)(nil), // 147: replicationdata.Status - (*replicationdata.PrimaryStatus)(nil), // 148: replicationdata.PrimaryStatus - (*topodata.TabletAlias)(nil), // 149: topodata.TabletAlias - (*replicationdata.FullStatus)(nil), // 150: replicationdata.FullStatus - (replicationdata.StopReplicationMode)(0), // 151: replicationdata.StopReplicationMode - (*replicationdata.StopReplicationStatus)(nil), // 152: replicationdata.StopReplicationStatus - (*logutil.Event)(nil), // 153: logutil.Event - (*vttime.Time)(nil), // 154: vttime.Time - (*binlogdata.BinlogSource)(nil), // 155: binlogdata.BinlogSource - (binlogdata.VReplicationWorkflowType)(0), // 156: binlogdata.VReplicationWorkflowType - (binlogdata.VReplicationWorkflowSubType)(0), // 157: binlogdata.VReplicationWorkflowSubType - (binlogdata.VReplicationWorkflowState)(0), // 158: binlogdata.VReplicationWorkflowState - (binlogdata.OnDDLAction)(0), // 159: binlogdata.OnDDLAction - (*topodata.ThrottledAppRule)(nil), // 160: topodata.ThrottledAppRule + (CheckThrottlerResponseCode)(0), // 1: tabletmanagerdata.CheckThrottlerResponseCode + (*TableDefinition)(nil), // 2: tabletmanagerdata.TableDefinition + (*SchemaDefinition)(nil), // 3: tabletmanagerdata.SchemaDefinition + (*SchemaChangeResult)(nil), // 4: tabletmanagerdata.SchemaChangeResult + (*UserPermission)(nil), // 5: tabletmanagerdata.UserPermission + (*DbPermission)(nil), // 6: tabletmanagerdata.DbPermission + (*Permissions)(nil), // 7: tabletmanagerdata.Permissions + (*PingRequest)(nil), // 8: tabletmanagerdata.PingRequest + (*PingResponse)(nil), // 9: tabletmanagerdata.PingResponse + (*SleepRequest)(nil), // 10: tabletmanagerdata.SleepRequest + (*SleepResponse)(nil), // 11: tabletmanagerdata.SleepResponse + (*ExecuteHookRequest)(nil), // 12: tabletmanagerdata.ExecuteHookRequest + (*ExecuteHookResponse)(nil), // 13: tabletmanagerdata.ExecuteHookResponse + (*GetSchemaRequest)(nil), // 14: tabletmanagerdata.GetSchemaRequest + (*GetSchemaResponse)(nil), // 15: tabletmanagerdata.GetSchemaResponse + (*GetPermissionsRequest)(nil), // 16: tabletmanagerdata.GetPermissionsRequest + (*GetPermissionsResponse)(nil), // 17: tabletmanagerdata.GetPermissionsResponse + (*GetGlobalStatusVarsRequest)(nil), // 18: tabletmanagerdata.GetGlobalStatusVarsRequest + (*GetGlobalStatusVarsResponse)(nil), // 19: tabletmanagerdata.GetGlobalStatusVarsResponse + (*SetReadOnlyRequest)(nil), // 20: tabletmanagerdata.SetReadOnlyRequest + (*SetReadOnlyResponse)(nil), // 21: tabletmanagerdata.SetReadOnlyResponse + (*SetReadWriteRequest)(nil), // 22: tabletmanagerdata.SetReadWriteRequest + (*SetReadWriteResponse)(nil), // 23: tabletmanagerdata.SetReadWriteResponse + (*ChangeTypeRequest)(nil), // 24: tabletmanagerdata.ChangeTypeRequest + (*ChangeTypeResponse)(nil), // 25: tabletmanagerdata.ChangeTypeResponse + (*RefreshStateRequest)(nil), // 26: tabletmanagerdata.RefreshStateRequest + (*RefreshStateResponse)(nil), // 27: tabletmanagerdata.RefreshStateResponse + (*RunHealthCheckRequest)(nil), // 28: tabletmanagerdata.RunHealthCheckRequest + (*RunHealthCheckResponse)(nil), // 29: tabletmanagerdata.RunHealthCheckResponse + (*ReloadSchemaRequest)(nil), // 30: tabletmanagerdata.ReloadSchemaRequest + (*ReloadSchemaResponse)(nil), // 31: tabletmanagerdata.ReloadSchemaResponse + (*PreflightSchemaRequest)(nil), // 32: tabletmanagerdata.PreflightSchemaRequest + (*PreflightSchemaResponse)(nil), // 33: tabletmanagerdata.PreflightSchemaResponse + (*ApplySchemaRequest)(nil), // 34: tabletmanagerdata.ApplySchemaRequest + (*ApplySchemaResponse)(nil), // 35: tabletmanagerdata.ApplySchemaResponse + (*LockTablesRequest)(nil), // 36: tabletmanagerdata.LockTablesRequest + (*LockTablesResponse)(nil), // 37: tabletmanagerdata.LockTablesResponse + (*UnlockTablesRequest)(nil), // 38: tabletmanagerdata.UnlockTablesRequest + (*UnlockTablesResponse)(nil), // 39: tabletmanagerdata.UnlockTablesResponse + (*ExecuteQueryRequest)(nil), // 40: tabletmanagerdata.ExecuteQueryRequest + (*ExecuteQueryResponse)(nil), // 41: tabletmanagerdata.ExecuteQueryResponse + (*ExecuteFetchAsDbaRequest)(nil), // 42: tabletmanagerdata.ExecuteFetchAsDbaRequest + (*ExecuteFetchAsDbaResponse)(nil), // 43: tabletmanagerdata.ExecuteFetchAsDbaResponse + (*ExecuteMultiFetchAsDbaRequest)(nil), // 44: tabletmanagerdata.ExecuteMultiFetchAsDbaRequest + (*ExecuteMultiFetchAsDbaResponse)(nil), // 45: tabletmanagerdata.ExecuteMultiFetchAsDbaResponse + (*ExecuteFetchAsAllPrivsRequest)(nil), // 46: tabletmanagerdata.ExecuteFetchAsAllPrivsRequest + (*ExecuteFetchAsAllPrivsResponse)(nil), // 47: tabletmanagerdata.ExecuteFetchAsAllPrivsResponse + (*ExecuteFetchAsAppRequest)(nil), // 48: tabletmanagerdata.ExecuteFetchAsAppRequest + (*ExecuteFetchAsAppResponse)(nil), // 49: tabletmanagerdata.ExecuteFetchAsAppResponse + (*ReplicationStatusRequest)(nil), // 50: tabletmanagerdata.ReplicationStatusRequest + (*ReplicationStatusResponse)(nil), // 51: tabletmanagerdata.ReplicationStatusResponse + (*PrimaryStatusRequest)(nil), // 52: tabletmanagerdata.PrimaryStatusRequest + (*PrimaryStatusResponse)(nil), // 53: tabletmanagerdata.PrimaryStatusResponse + (*PrimaryPositionRequest)(nil), // 54: tabletmanagerdata.PrimaryPositionRequest + (*PrimaryPositionResponse)(nil), // 55: tabletmanagerdata.PrimaryPositionResponse + (*WaitForPositionRequest)(nil), // 56: tabletmanagerdata.WaitForPositionRequest + (*WaitForPositionResponse)(nil), // 57: tabletmanagerdata.WaitForPositionResponse + (*StopReplicationRequest)(nil), // 58: tabletmanagerdata.StopReplicationRequest + (*StopReplicationResponse)(nil), // 59: tabletmanagerdata.StopReplicationResponse + (*StopReplicationMinimumRequest)(nil), // 60: tabletmanagerdata.StopReplicationMinimumRequest + (*StopReplicationMinimumResponse)(nil), // 61: tabletmanagerdata.StopReplicationMinimumResponse + (*StartReplicationRequest)(nil), // 62: tabletmanagerdata.StartReplicationRequest + (*StartReplicationResponse)(nil), // 63: tabletmanagerdata.StartReplicationResponse + (*StartReplicationUntilAfterRequest)(nil), // 64: tabletmanagerdata.StartReplicationUntilAfterRequest + (*StartReplicationUntilAfterResponse)(nil), // 65: tabletmanagerdata.StartReplicationUntilAfterResponse + (*GetReplicasRequest)(nil), // 66: tabletmanagerdata.GetReplicasRequest + (*GetReplicasResponse)(nil), // 67: tabletmanagerdata.GetReplicasResponse + (*ResetReplicationRequest)(nil), // 68: tabletmanagerdata.ResetReplicationRequest + (*ResetReplicationResponse)(nil), // 69: tabletmanagerdata.ResetReplicationResponse + (*VReplicationExecRequest)(nil), // 70: tabletmanagerdata.VReplicationExecRequest + (*VReplicationExecResponse)(nil), // 71: tabletmanagerdata.VReplicationExecResponse + (*VReplicationWaitForPosRequest)(nil), // 72: tabletmanagerdata.VReplicationWaitForPosRequest + (*VReplicationWaitForPosResponse)(nil), // 73: tabletmanagerdata.VReplicationWaitForPosResponse + (*InitPrimaryRequest)(nil), // 74: tabletmanagerdata.InitPrimaryRequest + (*InitPrimaryResponse)(nil), // 75: tabletmanagerdata.InitPrimaryResponse + (*PopulateReparentJournalRequest)(nil), // 76: tabletmanagerdata.PopulateReparentJournalRequest + (*PopulateReparentJournalResponse)(nil), // 77: tabletmanagerdata.PopulateReparentJournalResponse + (*InitReplicaRequest)(nil), // 78: tabletmanagerdata.InitReplicaRequest + (*InitReplicaResponse)(nil), // 79: tabletmanagerdata.InitReplicaResponse + (*DemotePrimaryRequest)(nil), // 80: tabletmanagerdata.DemotePrimaryRequest + (*DemotePrimaryResponse)(nil), // 81: tabletmanagerdata.DemotePrimaryResponse + (*UndoDemotePrimaryRequest)(nil), // 82: tabletmanagerdata.UndoDemotePrimaryRequest + (*UndoDemotePrimaryResponse)(nil), // 83: tabletmanagerdata.UndoDemotePrimaryResponse + (*ReplicaWasPromotedRequest)(nil), // 84: tabletmanagerdata.ReplicaWasPromotedRequest + (*ReplicaWasPromotedResponse)(nil), // 85: tabletmanagerdata.ReplicaWasPromotedResponse + (*ResetReplicationParametersRequest)(nil), // 86: tabletmanagerdata.ResetReplicationParametersRequest + (*ResetReplicationParametersResponse)(nil), // 87: tabletmanagerdata.ResetReplicationParametersResponse + (*FullStatusRequest)(nil), // 88: tabletmanagerdata.FullStatusRequest + (*FullStatusResponse)(nil), // 89: tabletmanagerdata.FullStatusResponse + (*SetReplicationSourceRequest)(nil), // 90: tabletmanagerdata.SetReplicationSourceRequest + (*SetReplicationSourceResponse)(nil), // 91: tabletmanagerdata.SetReplicationSourceResponse + (*ReplicaWasRestartedRequest)(nil), // 92: tabletmanagerdata.ReplicaWasRestartedRequest + (*ReplicaWasRestartedResponse)(nil), // 93: tabletmanagerdata.ReplicaWasRestartedResponse + (*StopReplicationAndGetStatusRequest)(nil), // 94: tabletmanagerdata.StopReplicationAndGetStatusRequest + (*StopReplicationAndGetStatusResponse)(nil), // 95: tabletmanagerdata.StopReplicationAndGetStatusResponse + (*PromoteReplicaRequest)(nil), // 96: tabletmanagerdata.PromoteReplicaRequest + (*PromoteReplicaResponse)(nil), // 97: tabletmanagerdata.PromoteReplicaResponse + (*BackupRequest)(nil), // 98: tabletmanagerdata.BackupRequest + (*BackupResponse)(nil), // 99: tabletmanagerdata.BackupResponse + (*RestoreFromBackupRequest)(nil), // 100: tabletmanagerdata.RestoreFromBackupRequest + (*RestoreFromBackupResponse)(nil), // 101: tabletmanagerdata.RestoreFromBackupResponse + (*CreateVReplicationWorkflowRequest)(nil), // 102: tabletmanagerdata.CreateVReplicationWorkflowRequest + (*CreateVReplicationWorkflowResponse)(nil), // 103: tabletmanagerdata.CreateVReplicationWorkflowResponse + (*DeleteVReplicationWorkflowRequest)(nil), // 104: tabletmanagerdata.DeleteVReplicationWorkflowRequest + (*DeleteVReplicationWorkflowResponse)(nil), // 105: tabletmanagerdata.DeleteVReplicationWorkflowResponse + (*HasVReplicationWorkflowsRequest)(nil), // 106: tabletmanagerdata.HasVReplicationWorkflowsRequest + (*HasVReplicationWorkflowsResponse)(nil), // 107: tabletmanagerdata.HasVReplicationWorkflowsResponse + (*ReadVReplicationWorkflowsRequest)(nil), // 108: tabletmanagerdata.ReadVReplicationWorkflowsRequest + (*ReadVReplicationWorkflowsResponse)(nil), // 109: tabletmanagerdata.ReadVReplicationWorkflowsResponse + (*ReadVReplicationWorkflowRequest)(nil), // 110: tabletmanagerdata.ReadVReplicationWorkflowRequest + (*ReadVReplicationWorkflowResponse)(nil), // 111: tabletmanagerdata.ReadVReplicationWorkflowResponse + (*VDiffRequest)(nil), // 112: tabletmanagerdata.VDiffRequest + (*VDiffResponse)(nil), // 113: tabletmanagerdata.VDiffResponse + (*VDiffPickerOptions)(nil), // 114: tabletmanagerdata.VDiffPickerOptions + (*VDiffReportOptions)(nil), // 115: tabletmanagerdata.VDiffReportOptions + (*VDiffCoreOptions)(nil), // 116: tabletmanagerdata.VDiffCoreOptions + (*VDiffOptions)(nil), // 117: tabletmanagerdata.VDiffOptions + (*UpdateVReplicationWorkflowRequest)(nil), // 118: tabletmanagerdata.UpdateVReplicationWorkflowRequest + (*UpdateVReplicationWorkflowResponse)(nil), // 119: tabletmanagerdata.UpdateVReplicationWorkflowResponse + (*UpdateVReplicationWorkflowsRequest)(nil), // 120: tabletmanagerdata.UpdateVReplicationWorkflowsRequest + (*UpdateVReplicationWorkflowsResponse)(nil), // 121: tabletmanagerdata.UpdateVReplicationWorkflowsResponse + (*ResetSequencesRequest)(nil), // 122: tabletmanagerdata.ResetSequencesRequest + (*ResetSequencesResponse)(nil), // 123: tabletmanagerdata.ResetSequencesResponse + (*CheckThrottlerRequest)(nil), // 124: tabletmanagerdata.CheckThrottlerRequest + (*CheckThrottlerResponse)(nil), // 125: tabletmanagerdata.CheckThrottlerResponse + (*GetThrottlerStatusRequest)(nil), // 126: tabletmanagerdata.GetThrottlerStatusRequest + (*GetThrottlerStatusResponse)(nil), // 127: tabletmanagerdata.GetThrottlerStatusResponse + nil, // 128: tabletmanagerdata.UserPermission.PrivilegesEntry + nil, // 129: tabletmanagerdata.DbPermission.PrivilegesEntry + nil, // 130: tabletmanagerdata.ExecuteHookRequest.ExtraEnvEntry + nil, // 131: tabletmanagerdata.GetGlobalStatusVarsResponse.StatusValuesEntry + (*ReadVReplicationWorkflowResponse_Stream)(nil), // 132: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream + (*CheckThrottlerResponse_Metric)(nil), // 133: tabletmanagerdata.CheckThrottlerResponse.Metric + nil, // 134: tabletmanagerdata.CheckThrottlerResponse.MetricsEntry + (*GetThrottlerStatusResponse_MetricResult)(nil), // 135: tabletmanagerdata.GetThrottlerStatusResponse.MetricResult + nil, // 136: tabletmanagerdata.GetThrottlerStatusResponse.AggregatedMetricsEntry + nil, // 137: tabletmanagerdata.GetThrottlerStatusResponse.MetricThresholdsEntry + (*GetThrottlerStatusResponse_MetricHealth)(nil), // 138: tabletmanagerdata.GetThrottlerStatusResponse.MetricHealth + nil, // 139: tabletmanagerdata.GetThrottlerStatusResponse.MetricsHealthEntry + nil, // 140: tabletmanagerdata.GetThrottlerStatusResponse.ThrottledAppsEntry + nil, // 141: tabletmanagerdata.GetThrottlerStatusResponse.AppCheckedMetricsEntry + (*GetThrottlerStatusResponse_RecentApp)(nil), // 142: tabletmanagerdata.GetThrottlerStatusResponse.RecentApp + nil, // 143: tabletmanagerdata.GetThrottlerStatusResponse.RecentAppsEntry + (*query.Field)(nil), // 144: query.Field + (topodata.TabletType)(0), // 145: topodata.TabletType + (*vtrpc.CallerID)(nil), // 146: vtrpc.CallerID + (*query.QueryResult)(nil), // 147: query.QueryResult + (*replicationdata.Status)(nil), // 148: replicationdata.Status + (*replicationdata.PrimaryStatus)(nil), // 149: replicationdata.PrimaryStatus + (*topodata.TabletAlias)(nil), // 150: topodata.TabletAlias + (*replicationdata.FullStatus)(nil), // 151: replicationdata.FullStatus + (replicationdata.StopReplicationMode)(0), // 152: replicationdata.StopReplicationMode + (*replicationdata.StopReplicationStatus)(nil), // 153: replicationdata.StopReplicationStatus + (*logutil.Event)(nil), // 154: logutil.Event + (*vttime.Time)(nil), // 155: vttime.Time + (*binlogdata.BinlogSource)(nil), // 156: binlogdata.BinlogSource + (binlogdata.VReplicationWorkflowType)(0), // 157: binlogdata.VReplicationWorkflowType + (binlogdata.VReplicationWorkflowSubType)(0), // 158: binlogdata.VReplicationWorkflowSubType + (binlogdata.VReplicationWorkflowState)(0), // 159: binlogdata.VReplicationWorkflowState + (binlogdata.OnDDLAction)(0), // 160: binlogdata.OnDDLAction + (*topodata.ThrottledAppRule)(nil), // 161: topodata.ThrottledAppRule } var file_tabletmanagerdata_proto_depIdxs = []int32{ - 143, // 0: tabletmanagerdata.TableDefinition.fields:type_name -> query.Field - 1, // 1: tabletmanagerdata.SchemaDefinition.table_definitions:type_name -> tabletmanagerdata.TableDefinition - 2, // 2: tabletmanagerdata.SchemaChangeResult.before_schema:type_name -> tabletmanagerdata.SchemaDefinition - 2, // 3: tabletmanagerdata.SchemaChangeResult.after_schema:type_name -> tabletmanagerdata.SchemaDefinition - 127, // 4: tabletmanagerdata.UserPermission.privileges:type_name -> tabletmanagerdata.UserPermission.PrivilegesEntry - 128, // 5: tabletmanagerdata.DbPermission.privileges:type_name -> tabletmanagerdata.DbPermission.PrivilegesEntry - 4, // 6: tabletmanagerdata.Permissions.user_permissions:type_name -> tabletmanagerdata.UserPermission - 5, // 7: tabletmanagerdata.Permissions.db_permissions:type_name -> tabletmanagerdata.DbPermission - 129, // 8: tabletmanagerdata.ExecuteHookRequest.extra_env:type_name -> tabletmanagerdata.ExecuteHookRequest.ExtraEnvEntry - 2, // 9: tabletmanagerdata.GetSchemaResponse.schema_definition:type_name -> tabletmanagerdata.SchemaDefinition - 6, // 10: tabletmanagerdata.GetPermissionsResponse.permissions:type_name -> tabletmanagerdata.Permissions - 130, // 11: tabletmanagerdata.GetGlobalStatusVarsResponse.status_values:type_name -> tabletmanagerdata.GetGlobalStatusVarsResponse.StatusValuesEntry - 144, // 12: tabletmanagerdata.ChangeTypeRequest.tablet_type:type_name -> topodata.TabletType - 3, // 13: tabletmanagerdata.PreflightSchemaResponse.change_results:type_name -> tabletmanagerdata.SchemaChangeResult - 2, // 14: tabletmanagerdata.ApplySchemaRequest.before_schema:type_name -> tabletmanagerdata.SchemaDefinition - 2, // 15: tabletmanagerdata.ApplySchemaRequest.after_schema:type_name -> tabletmanagerdata.SchemaDefinition - 2, // 16: tabletmanagerdata.ApplySchemaResponse.before_schema:type_name -> tabletmanagerdata.SchemaDefinition - 2, // 17: tabletmanagerdata.ApplySchemaResponse.after_schema:type_name -> tabletmanagerdata.SchemaDefinition - 145, // 18: tabletmanagerdata.ExecuteQueryRequest.caller_id:type_name -> vtrpc.CallerID - 146, // 19: tabletmanagerdata.ExecuteQueryResponse.result:type_name -> query.QueryResult - 146, // 20: tabletmanagerdata.ExecuteFetchAsDbaResponse.result:type_name -> query.QueryResult - 146, // 21: tabletmanagerdata.ExecuteMultiFetchAsDbaResponse.results:type_name -> query.QueryResult - 146, // 22: tabletmanagerdata.ExecuteFetchAsAllPrivsResponse.result:type_name -> query.QueryResult - 146, // 23: tabletmanagerdata.ExecuteFetchAsAppResponse.result:type_name -> query.QueryResult - 147, // 24: tabletmanagerdata.ReplicationStatusResponse.status:type_name -> replicationdata.Status - 148, // 25: tabletmanagerdata.PrimaryStatusResponse.status:type_name -> replicationdata.PrimaryStatus - 146, // 26: tabletmanagerdata.VReplicationExecResponse.result:type_name -> query.QueryResult - 149, // 27: tabletmanagerdata.PopulateReparentJournalRequest.primary_alias:type_name -> topodata.TabletAlias - 149, // 28: tabletmanagerdata.InitReplicaRequest.parent:type_name -> topodata.TabletAlias - 148, // 29: tabletmanagerdata.DemotePrimaryResponse.primary_status:type_name -> replicationdata.PrimaryStatus - 150, // 30: tabletmanagerdata.FullStatusResponse.status:type_name -> replicationdata.FullStatus - 149, // 31: tabletmanagerdata.SetReplicationSourceRequest.parent:type_name -> topodata.TabletAlias - 149, // 32: tabletmanagerdata.ReplicaWasRestartedRequest.parent:type_name -> topodata.TabletAlias - 151, // 33: tabletmanagerdata.StopReplicationAndGetStatusRequest.stop_replication_mode:type_name -> replicationdata.StopReplicationMode - 152, // 34: tabletmanagerdata.StopReplicationAndGetStatusResponse.status:type_name -> replicationdata.StopReplicationStatus - 153, // 35: tabletmanagerdata.BackupResponse.event:type_name -> logutil.Event - 154, // 36: tabletmanagerdata.RestoreFromBackupRequest.backup_time:type_name -> vttime.Time - 154, // 37: tabletmanagerdata.RestoreFromBackupRequest.restore_to_timestamp:type_name -> vttime.Time - 153, // 38: tabletmanagerdata.RestoreFromBackupResponse.event:type_name -> logutil.Event - 155, // 39: tabletmanagerdata.CreateVReplicationWorkflowRequest.binlog_source:type_name -> binlogdata.BinlogSource - 144, // 40: tabletmanagerdata.CreateVReplicationWorkflowRequest.tablet_types:type_name -> topodata.TabletType + 144, // 0: tabletmanagerdata.TableDefinition.fields:type_name -> query.Field + 2, // 1: tabletmanagerdata.SchemaDefinition.table_definitions:type_name -> tabletmanagerdata.TableDefinition + 3, // 2: tabletmanagerdata.SchemaChangeResult.before_schema:type_name -> tabletmanagerdata.SchemaDefinition + 3, // 3: tabletmanagerdata.SchemaChangeResult.after_schema:type_name -> tabletmanagerdata.SchemaDefinition + 128, // 4: tabletmanagerdata.UserPermission.privileges:type_name -> tabletmanagerdata.UserPermission.PrivilegesEntry + 129, // 5: tabletmanagerdata.DbPermission.privileges:type_name -> tabletmanagerdata.DbPermission.PrivilegesEntry + 5, // 6: tabletmanagerdata.Permissions.user_permissions:type_name -> tabletmanagerdata.UserPermission + 6, // 7: tabletmanagerdata.Permissions.db_permissions:type_name -> tabletmanagerdata.DbPermission + 130, // 8: tabletmanagerdata.ExecuteHookRequest.extra_env:type_name -> tabletmanagerdata.ExecuteHookRequest.ExtraEnvEntry + 3, // 9: tabletmanagerdata.GetSchemaResponse.schema_definition:type_name -> tabletmanagerdata.SchemaDefinition + 7, // 10: tabletmanagerdata.GetPermissionsResponse.permissions:type_name -> tabletmanagerdata.Permissions + 131, // 11: tabletmanagerdata.GetGlobalStatusVarsResponse.status_values:type_name -> tabletmanagerdata.GetGlobalStatusVarsResponse.StatusValuesEntry + 145, // 12: tabletmanagerdata.ChangeTypeRequest.tablet_type:type_name -> topodata.TabletType + 4, // 13: tabletmanagerdata.PreflightSchemaResponse.change_results:type_name -> tabletmanagerdata.SchemaChangeResult + 3, // 14: tabletmanagerdata.ApplySchemaRequest.before_schema:type_name -> tabletmanagerdata.SchemaDefinition + 3, // 15: tabletmanagerdata.ApplySchemaRequest.after_schema:type_name -> tabletmanagerdata.SchemaDefinition + 3, // 16: tabletmanagerdata.ApplySchemaResponse.before_schema:type_name -> tabletmanagerdata.SchemaDefinition + 3, // 17: tabletmanagerdata.ApplySchemaResponse.after_schema:type_name -> tabletmanagerdata.SchemaDefinition + 146, // 18: tabletmanagerdata.ExecuteQueryRequest.caller_id:type_name -> vtrpc.CallerID + 147, // 19: tabletmanagerdata.ExecuteQueryResponse.result:type_name -> query.QueryResult + 147, // 20: tabletmanagerdata.ExecuteFetchAsDbaResponse.result:type_name -> query.QueryResult + 147, // 21: tabletmanagerdata.ExecuteMultiFetchAsDbaResponse.results:type_name -> query.QueryResult + 147, // 22: tabletmanagerdata.ExecuteFetchAsAllPrivsResponse.result:type_name -> query.QueryResult + 147, // 23: tabletmanagerdata.ExecuteFetchAsAppResponse.result:type_name -> query.QueryResult + 148, // 24: tabletmanagerdata.ReplicationStatusResponse.status:type_name -> replicationdata.Status + 149, // 25: tabletmanagerdata.PrimaryStatusResponse.status:type_name -> replicationdata.PrimaryStatus + 147, // 26: tabletmanagerdata.VReplicationExecResponse.result:type_name -> query.QueryResult + 150, // 27: tabletmanagerdata.PopulateReparentJournalRequest.primary_alias:type_name -> topodata.TabletAlias + 150, // 28: tabletmanagerdata.InitReplicaRequest.parent:type_name -> topodata.TabletAlias + 149, // 29: tabletmanagerdata.DemotePrimaryResponse.primary_status:type_name -> replicationdata.PrimaryStatus + 151, // 30: tabletmanagerdata.FullStatusResponse.status:type_name -> replicationdata.FullStatus + 150, // 31: tabletmanagerdata.SetReplicationSourceRequest.parent:type_name -> topodata.TabletAlias + 150, // 32: tabletmanagerdata.ReplicaWasRestartedRequest.parent:type_name -> topodata.TabletAlias + 152, // 33: tabletmanagerdata.StopReplicationAndGetStatusRequest.stop_replication_mode:type_name -> replicationdata.StopReplicationMode + 153, // 34: tabletmanagerdata.StopReplicationAndGetStatusResponse.status:type_name -> replicationdata.StopReplicationStatus + 154, // 35: tabletmanagerdata.BackupResponse.event:type_name -> logutil.Event + 155, // 36: tabletmanagerdata.RestoreFromBackupRequest.backup_time:type_name -> vttime.Time + 155, // 37: tabletmanagerdata.RestoreFromBackupRequest.restore_to_timestamp:type_name -> vttime.Time + 154, // 38: tabletmanagerdata.RestoreFromBackupResponse.event:type_name -> logutil.Event + 156, // 39: tabletmanagerdata.CreateVReplicationWorkflowRequest.binlog_source:type_name -> binlogdata.BinlogSource + 145, // 40: tabletmanagerdata.CreateVReplicationWorkflowRequest.tablet_types:type_name -> topodata.TabletType 0, // 41: tabletmanagerdata.CreateVReplicationWorkflowRequest.tablet_selection_preference:type_name -> tabletmanagerdata.TabletSelectionPreference - 156, // 42: tabletmanagerdata.CreateVReplicationWorkflowRequest.workflow_type:type_name -> binlogdata.VReplicationWorkflowType - 157, // 43: tabletmanagerdata.CreateVReplicationWorkflowRequest.workflow_sub_type:type_name -> binlogdata.VReplicationWorkflowSubType - 146, // 44: tabletmanagerdata.CreateVReplicationWorkflowResponse.result:type_name -> query.QueryResult - 146, // 45: tabletmanagerdata.DeleteVReplicationWorkflowResponse.result:type_name -> query.QueryResult - 158, // 46: tabletmanagerdata.ReadVReplicationWorkflowsRequest.include_states:type_name -> binlogdata.VReplicationWorkflowState - 158, // 47: tabletmanagerdata.ReadVReplicationWorkflowsRequest.exclude_states:type_name -> binlogdata.VReplicationWorkflowState - 110, // 48: tabletmanagerdata.ReadVReplicationWorkflowsResponse.workflows:type_name -> tabletmanagerdata.ReadVReplicationWorkflowResponse - 144, // 49: tabletmanagerdata.ReadVReplicationWorkflowResponse.tablet_types:type_name -> topodata.TabletType + 157, // 42: tabletmanagerdata.CreateVReplicationWorkflowRequest.workflow_type:type_name -> binlogdata.VReplicationWorkflowType + 158, // 43: tabletmanagerdata.CreateVReplicationWorkflowRequest.workflow_sub_type:type_name -> binlogdata.VReplicationWorkflowSubType + 147, // 44: tabletmanagerdata.CreateVReplicationWorkflowResponse.result:type_name -> query.QueryResult + 147, // 45: tabletmanagerdata.DeleteVReplicationWorkflowResponse.result:type_name -> query.QueryResult + 159, // 46: tabletmanagerdata.ReadVReplicationWorkflowsRequest.include_states:type_name -> binlogdata.VReplicationWorkflowState + 159, // 47: tabletmanagerdata.ReadVReplicationWorkflowsRequest.exclude_states:type_name -> binlogdata.VReplicationWorkflowState + 111, // 48: tabletmanagerdata.ReadVReplicationWorkflowsResponse.workflows:type_name -> tabletmanagerdata.ReadVReplicationWorkflowResponse + 145, // 49: tabletmanagerdata.ReadVReplicationWorkflowResponse.tablet_types:type_name -> topodata.TabletType 0, // 50: tabletmanagerdata.ReadVReplicationWorkflowResponse.tablet_selection_preference:type_name -> tabletmanagerdata.TabletSelectionPreference - 156, // 51: tabletmanagerdata.ReadVReplicationWorkflowResponse.workflow_type:type_name -> binlogdata.VReplicationWorkflowType - 157, // 52: tabletmanagerdata.ReadVReplicationWorkflowResponse.workflow_sub_type:type_name -> binlogdata.VReplicationWorkflowSubType - 131, // 53: tabletmanagerdata.ReadVReplicationWorkflowResponse.streams:type_name -> tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream - 116, // 54: tabletmanagerdata.VDiffRequest.options:type_name -> tabletmanagerdata.VDiffOptions - 146, // 55: tabletmanagerdata.VDiffResponse.output:type_name -> query.QueryResult - 113, // 56: tabletmanagerdata.VDiffOptions.picker_options:type_name -> tabletmanagerdata.VDiffPickerOptions - 115, // 57: tabletmanagerdata.VDiffOptions.core_options:type_name -> tabletmanagerdata.VDiffCoreOptions - 114, // 58: tabletmanagerdata.VDiffOptions.report_options:type_name -> tabletmanagerdata.VDiffReportOptions - 144, // 59: tabletmanagerdata.UpdateVReplicationWorkflowRequest.tablet_types:type_name -> topodata.TabletType + 157, // 51: tabletmanagerdata.ReadVReplicationWorkflowResponse.workflow_type:type_name -> binlogdata.VReplicationWorkflowType + 158, // 52: tabletmanagerdata.ReadVReplicationWorkflowResponse.workflow_sub_type:type_name -> binlogdata.VReplicationWorkflowSubType + 132, // 53: tabletmanagerdata.ReadVReplicationWorkflowResponse.streams:type_name -> tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream + 117, // 54: tabletmanagerdata.VDiffRequest.options:type_name -> tabletmanagerdata.VDiffOptions + 147, // 55: tabletmanagerdata.VDiffResponse.output:type_name -> query.QueryResult + 114, // 56: tabletmanagerdata.VDiffOptions.picker_options:type_name -> tabletmanagerdata.VDiffPickerOptions + 116, // 57: tabletmanagerdata.VDiffOptions.core_options:type_name -> tabletmanagerdata.VDiffCoreOptions + 115, // 58: tabletmanagerdata.VDiffOptions.report_options:type_name -> tabletmanagerdata.VDiffReportOptions + 145, // 59: tabletmanagerdata.UpdateVReplicationWorkflowRequest.tablet_types:type_name -> topodata.TabletType 0, // 60: tabletmanagerdata.UpdateVReplicationWorkflowRequest.tablet_selection_preference:type_name -> tabletmanagerdata.TabletSelectionPreference - 159, // 61: tabletmanagerdata.UpdateVReplicationWorkflowRequest.on_ddl:type_name -> binlogdata.OnDDLAction - 158, // 62: tabletmanagerdata.UpdateVReplicationWorkflowRequest.state:type_name -> binlogdata.VReplicationWorkflowState - 146, // 63: tabletmanagerdata.UpdateVReplicationWorkflowResponse.result:type_name -> query.QueryResult - 158, // 64: tabletmanagerdata.UpdateVReplicationWorkflowsRequest.state:type_name -> binlogdata.VReplicationWorkflowState - 146, // 65: tabletmanagerdata.UpdateVReplicationWorkflowsResponse.result:type_name -> query.QueryResult - 133, // 66: tabletmanagerdata.CheckThrottlerResponse.metrics:type_name -> tabletmanagerdata.CheckThrottlerResponse.MetricsEntry - 135, // 67: tabletmanagerdata.GetThrottlerStatusResponse.aggregated_metrics:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.AggregatedMetricsEntry - 136, // 68: tabletmanagerdata.GetThrottlerStatusResponse.metric_thresholds:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricThresholdsEntry - 138, // 69: tabletmanagerdata.GetThrottlerStatusResponse.metrics_health:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricsHealthEntry - 139, // 70: tabletmanagerdata.GetThrottlerStatusResponse.throttled_apps:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.ThrottledAppsEntry - 140, // 71: tabletmanagerdata.GetThrottlerStatusResponse.app_checked_metrics:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.AppCheckedMetricsEntry - 142, // 72: tabletmanagerdata.GetThrottlerStatusResponse.recent_apps:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.RecentAppsEntry - 155, // 73: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.bls:type_name -> binlogdata.BinlogSource - 154, // 74: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.time_updated:type_name -> vttime.Time - 154, // 75: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.transaction_timestamp:type_name -> vttime.Time - 158, // 76: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.state:type_name -> binlogdata.VReplicationWorkflowState - 154, // 77: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.time_heartbeat:type_name -> vttime.Time - 154, // 78: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.time_throttled:type_name -> vttime.Time - 132, // 79: tabletmanagerdata.CheckThrottlerResponse.MetricsEntry.value:type_name -> tabletmanagerdata.CheckThrottlerResponse.Metric - 134, // 80: tabletmanagerdata.GetThrottlerStatusResponse.AggregatedMetricsEntry.value:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricResult - 154, // 81: tabletmanagerdata.GetThrottlerStatusResponse.MetricHealth.last_healthy_at:type_name -> vttime.Time - 137, // 82: tabletmanagerdata.GetThrottlerStatusResponse.MetricsHealthEntry.value:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricHealth - 160, // 83: tabletmanagerdata.GetThrottlerStatusResponse.ThrottledAppsEntry.value:type_name -> topodata.ThrottledAppRule - 154, // 84: tabletmanagerdata.GetThrottlerStatusResponse.RecentApp.checked_at:type_name -> vttime.Time - 141, // 85: tabletmanagerdata.GetThrottlerStatusResponse.RecentAppsEntry.value:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.RecentApp - 86, // [86:86] is the sub-list for method output_type - 86, // [86:86] is the sub-list for method input_type - 86, // [86:86] is the sub-list for extension type_name - 86, // [86:86] is the sub-list for extension extendee - 0, // [0:86] is the sub-list for field type_name + 160, // 61: tabletmanagerdata.UpdateVReplicationWorkflowRequest.on_ddl:type_name -> binlogdata.OnDDLAction + 159, // 62: tabletmanagerdata.UpdateVReplicationWorkflowRequest.state:type_name -> binlogdata.VReplicationWorkflowState + 147, // 63: tabletmanagerdata.UpdateVReplicationWorkflowResponse.result:type_name -> query.QueryResult + 159, // 64: tabletmanagerdata.UpdateVReplicationWorkflowsRequest.state:type_name -> binlogdata.VReplicationWorkflowState + 147, // 65: tabletmanagerdata.UpdateVReplicationWorkflowsResponse.result:type_name -> query.QueryResult + 134, // 66: tabletmanagerdata.CheckThrottlerResponse.metrics:type_name -> tabletmanagerdata.CheckThrottlerResponse.MetricsEntry + 1, // 67: tabletmanagerdata.CheckThrottlerResponse.response_code:type_name -> tabletmanagerdata.CheckThrottlerResponseCode + 136, // 68: tabletmanagerdata.GetThrottlerStatusResponse.aggregated_metrics:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.AggregatedMetricsEntry + 137, // 69: tabletmanagerdata.GetThrottlerStatusResponse.metric_thresholds:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricThresholdsEntry + 139, // 70: tabletmanagerdata.GetThrottlerStatusResponse.metrics_health:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricsHealthEntry + 140, // 71: tabletmanagerdata.GetThrottlerStatusResponse.throttled_apps:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.ThrottledAppsEntry + 141, // 72: tabletmanagerdata.GetThrottlerStatusResponse.app_checked_metrics:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.AppCheckedMetricsEntry + 143, // 73: tabletmanagerdata.GetThrottlerStatusResponse.recent_apps:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.RecentAppsEntry + 156, // 74: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.bls:type_name -> binlogdata.BinlogSource + 155, // 75: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.time_updated:type_name -> vttime.Time + 155, // 76: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.transaction_timestamp:type_name -> vttime.Time + 159, // 77: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.state:type_name -> binlogdata.VReplicationWorkflowState + 155, // 78: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.time_heartbeat:type_name -> vttime.Time + 155, // 79: tabletmanagerdata.ReadVReplicationWorkflowResponse.Stream.time_throttled:type_name -> vttime.Time + 1, // 80: tabletmanagerdata.CheckThrottlerResponse.Metric.response_code:type_name -> tabletmanagerdata.CheckThrottlerResponseCode + 133, // 81: tabletmanagerdata.CheckThrottlerResponse.MetricsEntry.value:type_name -> tabletmanagerdata.CheckThrottlerResponse.Metric + 135, // 82: tabletmanagerdata.GetThrottlerStatusResponse.AggregatedMetricsEntry.value:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricResult + 155, // 83: tabletmanagerdata.GetThrottlerStatusResponse.MetricHealth.last_healthy_at:type_name -> vttime.Time + 138, // 84: tabletmanagerdata.GetThrottlerStatusResponse.MetricsHealthEntry.value:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.MetricHealth + 161, // 85: tabletmanagerdata.GetThrottlerStatusResponse.ThrottledAppsEntry.value:type_name -> topodata.ThrottledAppRule + 155, // 86: tabletmanagerdata.GetThrottlerStatusResponse.RecentApp.checked_at:type_name -> vttime.Time + 1, // 87: tabletmanagerdata.GetThrottlerStatusResponse.RecentApp.response_code:type_name -> tabletmanagerdata.CheckThrottlerResponseCode + 142, // 88: tabletmanagerdata.GetThrottlerStatusResponse.RecentAppsEntry.value:type_name -> tabletmanagerdata.GetThrottlerStatusResponse.RecentApp + 89, // [89:89] is the sub-list for method output_type + 89, // [89:89] is the sub-list for method input_type + 89, // [89:89] is the sub-list for extension type_name + 89, // [89:89] is the sub-list for extension extendee + 0, // [0:89] is the sub-list for field type_name } func init() { file_tabletmanagerdata_proto_init() } @@ -10262,7 +10376,7 @@ func file_tabletmanagerdata_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tabletmanagerdata_proto_rawDesc, - NumEnums: 1, + NumEnums: 2, NumMessages: 142, NumExtensions: 0, NumServices: 0, diff --git a/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go b/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go index eb0b058a56e..343e96b59b5 100644 --- a/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go +++ b/go/vt/proto/tabletmanagerdata/tabletmanagerdata_vtproto.pb.go @@ -2499,13 +2499,14 @@ func (m *CheckThrottlerResponse_Metric) CloneVT() *CheckThrottlerResponse_Metric return (*CheckThrottlerResponse_Metric)(nil) } r := &CheckThrottlerResponse_Metric{ - Name: m.Name, - StatusCode: m.StatusCode, - Value: m.Value, - Threshold: m.Threshold, - Error: m.Error, - Message: m.Message, - Scope: m.Scope, + Name: m.Name, + StatusCode: m.StatusCode, + Value: m.Value, + Threshold: m.Threshold, + Error: m.Error, + Message: m.Message, + Scope: m.Scope, + ResponseCode: m.ResponseCode, } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -2531,6 +2532,7 @@ func (m *CheckThrottlerResponse) CloneVT() *CheckThrottlerResponse { RecentlyChecked: m.RecentlyChecked, AppName: m.AppName, Summary: m.Summary, + ResponseCode: m.ResponseCode, } if rhs := m.Metrics; rhs != nil { tmpContainer := make(map[string]*CheckThrottlerResponse_Metric, len(rhs)) @@ -2609,8 +2611,9 @@ func (m *GetThrottlerStatusResponse_RecentApp) CloneVT() *GetThrottlerStatusResp return (*GetThrottlerStatusResponse_RecentApp)(nil) } r := &GetThrottlerStatusResponse_RecentApp{ - CheckedAt: m.CheckedAt.CloneVT(), - StatusCode: m.StatusCode, + CheckedAt: m.CheckedAt.CloneVT(), + StatusCode: m.StatusCode, + ResponseCode: m.ResponseCode, } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -8757,6 +8760,11 @@ func (m *CheckThrottlerResponse_Metric) MarshalToSizedBufferVT(dAtA []byte) (int i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.ResponseCode != 0 { + i = encodeVarint(dAtA, i, uint64(m.ResponseCode)) + i-- + dAtA[i] = 0x40 + } if len(m.Scope) > 0 { i -= len(m.Scope) copy(dAtA[i:], m.Scope) @@ -8835,6 +8843,11 @@ func (m *CheckThrottlerResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.ResponseCode != 0 { + i = encodeVarint(dAtA, i, uint64(m.ResponseCode)) + i-- + dAtA[i] = 0x50 + } if len(m.Summary) > 0 { i -= len(m.Summary) copy(dAtA[i:], m.Summary) @@ -9072,6 +9085,11 @@ func (m *GetThrottlerStatusResponse_RecentApp) MarshalToSizedBufferVT(dAtA []byt i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.ResponseCode != 0 { + i = encodeVarint(dAtA, i, uint64(m.ResponseCode)) + i-- + dAtA[i] = 0x18 + } if m.StatusCode != 0 { i = encodeVarint(dAtA, i, uint64(m.StatusCode)) i-- @@ -11539,6 +11557,9 @@ func (m *CheckThrottlerResponse_Metric) SizeVT() (n int) { if l > 0 { n += 1 + l + sov(uint64(l)) } + if m.ResponseCode != 0 { + n += 1 + sov(uint64(m.ResponseCode)) + } n += len(m.unknownFields) return n } @@ -11590,6 +11611,9 @@ func (m *CheckThrottlerResponse) SizeVT() (n int) { if l > 0 { n += 1 + l + sov(uint64(l)) } + if m.ResponseCode != 0 { + n += 1 + sov(uint64(m.ResponseCode)) + } n += len(m.unknownFields) return n } @@ -11651,6 +11675,9 @@ func (m *GetThrottlerStatusResponse_RecentApp) SizeVT() (n int) { if m.StatusCode != 0 { n += 1 + sov(uint64(m.StatusCode)) } + if m.ResponseCode != 0 { + n += 1 + sov(uint64(m.ResponseCode)) + } n += len(m.unknownFields) return n } @@ -25313,6 +25340,25 @@ func (m *CheckThrottlerResponse_Metric) UnmarshalVT(dAtA []byte) error { } m.Scope = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseCode", wireType) + } + m.ResponseCode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ResponseCode |= CheckThrottlerResponseCode(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -25682,6 +25728,25 @@ func (m *CheckThrottlerResponse) UnmarshalVT(dAtA []byte) error { } m.Summary = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseCode", wireType) + } + m.ResponseCode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ResponseCode |= CheckThrottlerResponseCode(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -26039,6 +26104,25 @@ func (m *GetThrottlerStatusResponse_RecentApp) UnmarshalVT(dAtA []byte) error { break } } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseCode", wireType) + } + m.ResponseCode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ResponseCode |= CheckThrottlerResponseCode(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/go/vt/vttablet/tabletmanager/rpc_throttler.go b/go/vt/vttablet/tabletmanager/rpc_throttler.go index 5facbb01229..ec75db6da43 100644 --- a/go/vt/vttablet/tabletmanager/rpc_throttler.go +++ b/go/vt/vttablet/tabletmanager/rpc_throttler.go @@ -53,6 +53,7 @@ func (tm *TabletManager) CheckThrottler(ctx context.Context, req *tabletmanagerd return nil, vterrors.Errorf(vtrpc.Code_INTERNAL, "nil checkResult") } resp := &tabletmanagerdatapb.CheckThrottlerResponse{ + ResponseCode: throttle.ResponseCodeFromStatus(checkResult.ResponseCode, checkResult.StatusCode), StatusCode: int32(checkResult.StatusCode), Value: checkResult.Value, Threshold: checkResult.Threshold, @@ -64,22 +65,24 @@ func (tm *TabletManager) CheckThrottler(ctx context.Context, req *tabletmanagerd } for name, metric := range checkResult.Metrics { resp.Metrics[name] = &tabletmanagerdatapb.CheckThrottlerResponse_Metric{ - Name: name, - Scope: metric.Scope, - StatusCode: int32(metric.StatusCode), - Value: metric.Value, - Threshold: metric.Threshold, - Message: metric.Message, + Name: name, + Scope: metric.Scope, + StatusCode: int32(metric.StatusCode), + ResponseCode: throttle.ResponseCodeFromStatus(metric.ResponseCode, metric.StatusCode), + Value: metric.Value, + Threshold: metric.Threshold, + Message: metric.Message, } } if len(checkResult.Metrics) == 0 { // For backwards compatibility, when the checked tablet is of lower version, it does not return a // matrics map, but only the one metric. resp.Metrics[base.DefaultMetricName.String()] = &tabletmanagerdatapb.CheckThrottlerResponse_Metric{ - StatusCode: int32(checkResult.StatusCode), - Value: checkResult.Value, - Threshold: checkResult.Threshold, - Message: checkResult.Message, + StatusCode: int32(checkResult.StatusCode), + ResponseCode: throttle.ResponseCodeFromStatus(checkResult.ResponseCode, checkResult.StatusCode), + Value: checkResult.Value, + Threshold: checkResult.Threshold, + Message: checkResult.Message, } } if checkResult.Error != nil { @@ -140,8 +143,9 @@ func (tm *TabletManager) GetThrottlerStatus(ctx context.Context, req *tabletmana } for _, recentApp := range status.RecentApps { resp.RecentApps[recentApp.AppName] = &tabletmanagerdatapb.GetThrottlerStatusResponse_RecentApp{ - CheckedAt: protoutil.TimeToProto(recentApp.CheckedAt), - StatusCode: int32(recentApp.StatusCode), + CheckedAt: protoutil.TimeToProto(recentApp.CheckedAt), + StatusCode: int32(recentApp.StatusCode), + ResponseCode: recentApp.ResponseCode, } } return resp, nil diff --git a/go/vt/vttablet/tabletserver/tabletserver.go b/go/vt/vttablet/tabletserver/tabletserver.go index 8a6d1a0be39..167d55a4e6f 100644 --- a/go/vt/vttablet/tabletserver/tabletserver.go +++ b/go/vt/vttablet/tabletserver/tabletserver.go @@ -46,6 +46,7 @@ import ( "vitess.io/vitess/go/vt/mysqlctl" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" querypb "vitess.io/vitess/go/vt/proto/query" + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/servenv" @@ -1800,8 +1801,9 @@ func (tsv *TabletServer) registerThrottlerCheckHandlers() { } metricNames := tsv.lagThrottler.MetricNames(r.URL.Query()["m"]) checkResult := tsv.lagThrottler.Check(ctx, appName, metricNames, flags) - if checkResult.StatusCode == http.StatusNotFound && flags.OKIfNotExists { + if checkResult.ResponseCode == tabletmanagerdatapb.CheckThrottlerResponseCode_UNKNOWN_METRIC && flags.OKIfNotExists { checkResult.StatusCode = http.StatusOK // 200 + checkResult.ResponseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_OK } if r.Method == http.MethodGet { diff --git a/go/vt/vttablet/tabletserver/throttle/base/recent_app.go b/go/vt/vttablet/tabletserver/throttle/base/recent_app.go index 148e6b31fe4..7ae2bf789af 100644 --- a/go/vt/vttablet/tabletserver/throttle/base/recent_app.go +++ b/go/vt/vttablet/tabletserver/throttle/base/recent_app.go @@ -42,21 +42,25 @@ package base import ( "time" + + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" ) // RecentApp indicates when an app was last checked type RecentApp struct { - AppName string - CheckedAt time.Time - StatusCode int + AppName string + CheckedAt time.Time + StatusCode int + ResponseCode tabletmanagerdatapb.CheckThrottlerResponseCode } // NewRecentApp creates a RecentApp -func NewRecentApp(appName string, statusCode int) *RecentApp { +func NewRecentApp(appName string, statusCode int, responseCode tabletmanagerdatapb.CheckThrottlerResponseCode) *RecentApp { result := &RecentApp{ - AppName: appName, - CheckedAt: time.Now(), - StatusCode: statusCode, + AppName: appName, + CheckedAt: time.Now(), + StatusCode: statusCode, + ResponseCode: responseCode, } return result } diff --git a/go/vt/vttablet/tabletserver/throttle/check.go b/go/vt/vttablet/tabletserver/throttle/check.go index 460d27a5181..ccdfcb2ce23 100644 --- a/go/vt/vttablet/tabletserver/throttle/check.go +++ b/go/vt/vttablet/tabletserver/throttle/check.go @@ -51,6 +51,8 @@ import ( "vitess.io/vitess/go/textutil" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/base" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/throttlerapp" + + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" ) const ( @@ -100,37 +102,44 @@ func (check *ThrottlerCheck) checkAppMetricResult(ctx context.Context, appName s } value, err := metricResult.Get() if appName == "" { - return NewCheckResult(http.StatusExpectationFailed, value, threshold, "", fmt.Errorf("no app indicated")) + return NewCheckResult(tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED, http.StatusExpectationFailed, value, threshold, "", fmt.Errorf("no app indicated")) } var statusCode int + var responseCode tabletmanagerdatapb.CheckThrottlerResponseCode switch { case err == base.ErrAppDenied: // app specifically not allowed to get metrics statusCode = http.StatusExpectationFailed // 417 + responseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED case err == base.ErrNoSuchMetric: // not collected yet, or metric does not exist statusCode = http.StatusNotFound // 404 + responseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_UNKNOWN_METRIC case err != nil: // any error statusCode = http.StatusInternalServerError // 500 + responseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_INTERNAL_ERROR case value > threshold: // casual throttling statusCode = http.StatusTooManyRequests // 429 + responseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED err = base.ErrThresholdExceeded default: // all good! statusCode = http.StatusOK // 200 + responseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_OK } - return NewCheckResult(statusCode, value, threshold, matchedApp, err) + return NewCheckResult(responseCode, statusCode, value, threshold, matchedApp, err) } // Check is the core function that runs when a user wants to check a metric func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope base.Scope, metricNames base.MetricNames, flags *CheckFlags) (checkResult *CheckResult) { checkResult = &CheckResult{ - StatusCode: http.StatusOK, - Metrics: make(map[string]*MetricResult), + StatusCode: http.StatusOK, + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + Metrics: make(map[string]*MetricResult), } if len(metricNames) == 0 { metricNames = base.MetricNames{check.throttler.metricNameUsedAsDefault()} @@ -138,6 +147,7 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba metricNames = metricNames.Unique() applyMetricToCheckResult := func(metricName base.MetricName, metric *MetricResult) { checkResult.StatusCode = metric.StatusCode + checkResult.ResponseCode = metric.ResponseCode checkResult.Value = metric.Value checkResult.Threshold = metric.Threshold checkResult.Error = metric.Error @@ -171,7 +181,7 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba metricCheckResult := check.checkAppMetricResult(ctx, appName, metricResultFunc, flags) if !throttlerapp.VitessName.Equals(appName) { - go func(statusCode int) { + go func(metricCheckResult *CheckResult) { if metricScope == base.UndefinedScope { // While we should never get here, the following code will panic if we do // because it will attempt to recreate ThrottlerCheckAnyTotal. @@ -179,22 +189,23 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba return } stats.GetOrNewCounter(fmt.Sprintf("ThrottlerCheck%s%sTotal", textutil.SingleWordCamel(metricScope.String()), textutil.SingleWordCamel(metricName.String())), "").Add(1) - if statusCode != http.StatusOK { + if !metricCheckResult.IsOK() { stats.GetOrNewCounter(fmt.Sprintf("ThrottlerCheck%s%sError", textutil.SingleWordCamel(metricScope.String()), textutil.SingleWordCamel(metricName.String())), "").Add(1) } - }(metricCheckResult.StatusCode) + }(metricCheckResult) } if metricCheckResult.RecentlyChecked { checkResult.RecentlyChecked = true } metric := &MetricResult{ - StatusCode: metricCheckResult.StatusCode, - Value: metricCheckResult.Value, - Threshold: metricCheckResult.Threshold, - Error: metricCheckResult.Error, - Message: metricCheckResult.Message, - AppName: metricCheckResult.AppName, - Scope: metricScope.String(), // This reports back the actual scope used for the check + StatusCode: metricCheckResult.StatusCode, + ResponseCode: metricCheckResult.ResponseCode, + Value: metricCheckResult.Value, + Threshold: metricCheckResult.Threshold, + Error: metricCheckResult.Error, + Message: metricCheckResult.Message, + AppName: metricCheckResult.AppName, + Scope: metricScope.String(), // This reports back the actual scope used for the check } checkResult.Metrics[metricName.String()] = metric if flags.MultiMetricsEnabled && !metricCheckResult.IsOK() && metricName != base.DefaultMetricName { @@ -216,13 +227,13 @@ func (check *ThrottlerCheck) Check(ctx context.Context, appName string, scope ba // If checkResult is not OK, then we will have populated these fields already by the failing metric. applyMetricToCheckResult(base.DefaultMetricName, metric) } - go func(statusCode int) { + go func(checkResult *CheckResult) { statsThrottlerCheckAnyTotal.Add(1) - if statusCode != http.StatusOK { + if !checkResult.IsOK() { statsThrottlerCheckAnyError.Add(1) } - }(checkResult.StatusCode) - go check.throttler.markRecentApp(appName, checkResult.StatusCode) + }(checkResult) + go check.throttler.markRecentApp(appName, checkResult.StatusCode, checkResult.ResponseCode) return checkResult } @@ -234,7 +245,7 @@ func (check *ThrottlerCheck) localCheck(ctx context.Context, aggregatedMetricNam } checkResult = check.Check(ctx, throttlerapp.VitessName.String(), scope, base.MetricNames{metricName}, selfCheckFlags) - if checkResult.StatusCode == http.StatusOK { + if checkResult.IsOK() { check.throttler.markMetricHealthy(aggregatedMetricName) } if timeSinceHealthy, found := check.throttler.timeSinceMetricHealthy(aggregatedMetricName); found { diff --git a/go/vt/vttablet/tabletserver/throttle/check_result.go b/go/vt/vttablet/tabletserver/throttle/check_result.go index ad32ba33d6f..34532b7ce37 100644 --- a/go/vt/vttablet/tabletserver/throttle/check_result.go +++ b/go/vt/vttablet/tabletserver/throttle/check_result.go @@ -45,41 +45,75 @@ import ( "fmt" "net/http" + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/base" ) +// ResponseCodeFromStatus returns a ResponseCode based on either given response code or HTTP status code. +// It is used to handle the transition period from v20 to v21 where v20 only returns HTTP status code. +// In v22 and beyond, the HTTP status code will be removed, and so will this function. +func ResponseCodeFromStatus(responseCode tabletmanagerdatapb.CheckThrottlerResponseCode, statusCode int) tabletmanagerdatapb.CheckThrottlerResponseCode { + if responseCode != tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED { + return responseCode + } + switch statusCode { + case http.StatusOK: + return tabletmanagerdatapb.CheckThrottlerResponseCode_OK + case http.StatusExpectationFailed: + return tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED + case http.StatusTooManyRequests: + return tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED + case http.StatusNotFound: + return tabletmanagerdatapb.CheckThrottlerResponseCode_UNKNOWN_METRIC + case http.StatusInternalServerError: + return tabletmanagerdatapb.CheckThrottlerResponseCode_INTERNAL_ERROR + default: + return tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED + } +} + type MetricResult struct { - StatusCode int `json:"StatusCode"` - Scope string `json:"Scope"` - Value float64 `json:"Value"` - Threshold float64 `json:"Threshold"` - Error error `json:"-"` - Message string `json:"Message"` - AppName string `json:"AppName"` + ResponseCode tabletmanagerdatapb.CheckThrottlerResponseCode `json:"ResponseCode"` + StatusCode int `json:"StatusCode"` + Scope string `json:"Scope"` + Value float64 `json:"Value"` + Threshold float64 `json:"Threshold"` + Error error `json:"-"` + Message string `json:"Message"` + AppName string `json:"AppName"` +} + +func (m *MetricResult) IsOK() bool { + if m.ResponseCode != tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED { + return m.ResponseCode == tabletmanagerdatapb.CheckThrottlerResponseCode_OK + } + return m.StatusCode == http.StatusOK } // CheckResult is the result for an app inquiring on a metric. It also exports as JSON via the API type CheckResult struct { - StatusCode int `json:"StatusCode"` - Value float64 `json:"Value"` - Threshold float64 `json:"Threshold"` - Error error `json:"-"` - Message string `json:"Message"` - RecentlyChecked bool `json:"RecentlyChecked"` - AppName string `json:"AppName"` - MetricName string `json:"MetricName"` - Scope string `json:"Scope"` - Metrics map[string]*MetricResult `json:"Metrics"` // New in multi-metrics support. Will eventually replace the above fields. + ResponseCode tabletmanagerdatapb.CheckThrottlerResponseCode `json:"ResponseCode"` + StatusCode int `json:"StatusCode"` + Value float64 `json:"Value"` + Threshold float64 `json:"Threshold"` + Error error `json:"-"` + Message string `json:"Message"` + RecentlyChecked bool `json:"RecentlyChecked"` + AppName string `json:"AppName"` + MetricName string `json:"MetricName"` + Scope string `json:"Scope"` + Metrics map[string]*MetricResult `json:"Metrics"` // New in multi-metrics support. Will eventually replace the above fields. } // NewCheckResult returns a CheckResult -func NewCheckResult(statusCode int, value float64, threshold float64, appName string, err error) *CheckResult { +func NewCheckResult(responseCode tabletmanagerdatapb.CheckThrottlerResponseCode, statusCode int, value float64, threshold float64, appName string, err error) *CheckResult { result := &CheckResult{ - StatusCode: statusCode, - Value: value, - Threshold: threshold, - AppName: appName, - Error: err, + ResponseCode: responseCode, + StatusCode: statusCode, + Value: value, + Threshold: threshold, + AppName: appName, + Error: err, } if err != nil { result.Message = err.Error() @@ -88,35 +122,38 @@ func NewCheckResult(statusCode int, value float64, threshold float64, appName st } func (c *CheckResult) IsOK() bool { + if c.ResponseCode != tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED { + return c.ResponseCode == tabletmanagerdatapb.CheckThrottlerResponseCode_OK + } return c.StatusCode == http.StatusOK } // Summary returns a human-readable summary of the check result func (c *CheckResult) Summary() string { - switch c.StatusCode { - case http.StatusOK: + switch ResponseCodeFromStatus(c.ResponseCode, c.StatusCode) { + case tabletmanagerdatapb.CheckThrottlerResponseCode_OK: return fmt.Sprintf("%s is granted access", c.AppName) - case http.StatusExpectationFailed: + case tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED: return fmt.Sprintf("%s is explicitly denied access", c.AppName) - case http.StatusInternalServerError: + case tabletmanagerdatapb.CheckThrottlerResponseCode_INTERNAL_ERROR: return fmt.Sprintf("%s is denied access due to unexpected error: %v", c.AppName, c.Error) - case http.StatusTooManyRequests: + case tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED: return fmt.Sprintf("%s is denied access due to %s/%s metric value %v exceeding threshold %v", c.AppName, c.Scope, c.MetricName, c.Value, c.Threshold) - case http.StatusNotFound: + case tabletmanagerdatapb.CheckThrottlerResponseCode_UNKNOWN_METRIC: return fmt.Sprintf("%s is denied access due to unknown or uncollected metric", c.AppName) - case 0: + case tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED: return "" default: - return fmt.Sprintf("unknown status code: %v", c.StatusCode) + return fmt.Sprintf("unknown response code: %v", c.ResponseCode) } } // NewErrorCheckResult returns a check result that indicates an error -func NewErrorCheckResult(statusCode int, err error) *CheckResult { - return NewCheckResult(statusCode, 0, 0, "", err) +func NewErrorCheckResult(responseCode tabletmanagerdatapb.CheckThrottlerResponseCode, statusCode int, err error) *CheckResult { + return NewCheckResult(responseCode, statusCode, 0, 0, "", err) } // NoSuchMetricCheckResult is a result returns when a metric is unknown -var NoSuchMetricCheckResult = NewErrorCheckResult(http.StatusNotFound, base.ErrNoSuchMetric) +var NoSuchMetricCheckResult = NewErrorCheckResult(tabletmanagerdatapb.CheckThrottlerResponseCode_UNKNOWN_METRIC, http.StatusNotFound, base.ErrNoSuchMetric) -var okMetricCheckResult = NewCheckResult(http.StatusOK, 0, 0, "", nil) +var okMetricCheckResult = NewCheckResult(tabletmanagerdatapb.CheckThrottlerResponseCode_OK, http.StatusOK, 0, 0, "", nil) diff --git a/go/vt/vttablet/tabletserver/throttle/check_result_test.go b/go/vt/vttablet/tabletserver/throttle/check_result_test.go index f5c984c5943..fdb0ee600ba 100644 --- a/go/vt/vttablet/tabletserver/throttle/check_result_test.go +++ b/go/vt/vttablet/tabletserver/throttle/check_result_test.go @@ -21,8 +21,60 @@ import ( "testing" "github.com/stretchr/testify/assert" + + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" ) +func TestReponseCodeFromStatus(t *testing.T) { + tcases := []struct { + responseCode tabletmanagerdatapb.CheckThrottlerResponseCode + statusCode int + expect tabletmanagerdatapb.CheckThrottlerResponseCode + }{ + { + tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED, + http.StatusOK, + tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + }, + { + tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED, + http.StatusExpectationFailed, + tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED, + }, + { + tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED, + http.StatusNotFound, + tabletmanagerdatapb.CheckThrottlerResponseCode_UNKNOWN_METRIC, + }, + { + tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED, + http.StatusInternalServerError, + tabletmanagerdatapb.CheckThrottlerResponseCode_INTERNAL_ERROR, + }, + { + tabletmanagerdatapb.CheckThrottlerResponseCode_UNDEFINED, + http.StatusTooManyRequests, + tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, + }, + { + tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, + http.StatusTooManyRequests, + tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, + }, + { + tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, + http.StatusOK, + tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, + }, + } + for _, tcase := range tcases { + t.Run("", func(t *testing.T) { + result := ResponseCodeFromStatus(tcase.responseCode, tcase.statusCode) + assert.Equal(t, tcase.expect, result) + }) + } +} + func TestCheckResultSummary(t *testing.T) { tcases := []struct { checkResult *CheckResult @@ -57,6 +109,31 @@ func TestCheckResultSummary(t *testing.T) { }, summary: "test is explicitly denied access", }, + { + checkResult: &CheckResult{ + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + AppName: "test", + }, + summary: "test is granted access", + }, + { + checkResult: &CheckResult{ + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_THRESHOLD_EXCEEDED, + AppName: "test", + MetricName: "bugginess", + Threshold: 100, + Value: 200, + Scope: "self", + }, + summary: "test is denied access due to self/bugginess metric value 200 exceeding threshold 100", + }, + { + checkResult: &CheckResult{ + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_APP_DENIED, + AppName: "test", + }, + summary: "test is explicitly denied access", + }, } for _, tcase := range tcases { t.Run(tcase.summary, func(t *testing.T) { diff --git a/go/vt/vttablet/tabletserver/throttle/client.go b/go/vt/vttablet/tabletserver/throttle/client.go index 972e63724f9..8549fb099b6 100644 --- a/go/vt/vttablet/tabletserver/throttle/client.go +++ b/go/vt/vttablet/tabletserver/throttle/client.go @@ -18,7 +18,6 @@ package throttle import ( "context" - "net/http" "sync" "sync/atomic" "time" @@ -111,11 +110,11 @@ func (c *Client) ThrottleCheckOK(ctx context.Context, overrideAppName throttlera } // It's time to run a throttler check checkResult = c.throttler.Check(ctx, checkApp.String(), nil, &c.flags) - if checkResult.StatusCode != http.StatusOK { + if !checkResult.IsOK() { return checkResult, false } for _, metricResult := range checkResult.Metrics { - if metricResult.StatusCode != http.StatusOK { + if !metricResult.IsOK() { return checkResult, false } } diff --git a/go/vt/vttablet/tabletserver/throttle/throttler.go b/go/vt/vttablet/tabletserver/throttle/throttler.go index 01c4fb3c622..7fb83769a96 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler.go @@ -66,8 +66,6 @@ import ( "vitess.io/vitess/go/textutil" "vitess.io/vitess/go/timer" "vitess.io/vitess/go/vt/log" - tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" - topodatapb "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/servenv" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/srvtopo" @@ -80,6 +78,9 @@ import ( "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/config" "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/throttlerapp" "vitess.io/vitess/go/vt/vttablet/tmclient" + + tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" ) const ( @@ -1028,8 +1029,11 @@ func (throttler *Throttler) generateTabletProbeFunction(scope base.Scope, tmClie return metricsWithError(fmt.Errorf("gRPC error accessing tablet %v. Err=%v", probe.Alias, gRPCErr)) } throttleMetric.Value = resp.Value + if resp.ResponseCode == tabletmanagerdatapb.CheckThrottlerResponseCode_INTERNAL_ERROR { + throttleMetric.Err = fmt.Errorf("response code: %d", resp.ResponseCode) + } if resp.StatusCode == http.StatusInternalServerError { - throttleMetric.Err = fmt.Errorf("Status code: %d", resp.StatusCode) + throttleMetric.Err = fmt.Errorf("status code: %d", resp.StatusCode) } if resp.RecentlyChecked { // We have just probed a tablet, and it reported back that someone just recently "check"ed it. @@ -1477,8 +1481,8 @@ func (throttler *Throttler) ThrottledAppsMap() (result map[string](*base.AppThro } // markRecentApp takes note that an app has just asked about throttling, making it "recent" -func (throttler *Throttler) markRecentApp(appName string, statusCode int) { - recentApp := base.NewRecentApp(appName, statusCode) +func (throttler *Throttler) markRecentApp(appName string, statusCode int, responseCode tabletmanagerdatapb.CheckThrottlerResponseCode) { + recentApp := base.NewRecentApp(appName, statusCode, responseCode) throttler.recentApps.Set(appName, recentApp, cache.DefaultExpiration) } diff --git a/go/vt/vttablet/tabletserver/throttle/throttler_test.go b/go/vt/vttablet/tabletserver/throttle/throttler_test.go index 508f542478e..9ea75090515 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler_test.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler_test.go @@ -74,20 +74,24 @@ var ( } replicaMetrics = map[string]*MetricResult{ base.LagMetricName.String(): { - StatusCode: http.StatusOK, - Value: 0.9, + StatusCode: http.StatusOK, + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + Value: 0.9, }, base.ThreadsRunningMetricName.String(): { - StatusCode: http.StatusOK, - Value: 13, + StatusCode: http.StatusOK, + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + Value: 13, }, base.CustomMetricName.String(): { - StatusCode: http.StatusOK, - Value: 14, + StatusCode: http.StatusOK, + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + Value: 14, }, base.LoadAvgMetricName.String(): { - StatusCode: http.StatusOK, - Value: 5.1, + StatusCode: http.StatusOK, + ResponseCode: tabletmanagerdatapb.CheckThrottlerResponseCode_OK, + Value: 5.1, }, } ) @@ -116,14 +120,16 @@ func (c *fakeTMClient) CheckThrottler(ctx context.Context, tablet *topodatapb.Ta RecentlyChecked: false, } if !c.v20.Load() { + resp.ResponseCode = tabletmanagerdatapb.CheckThrottlerResponseCode_OK resp.Metrics = make(map[string]*tabletmanagerdatapb.CheckThrottlerResponse_Metric) for name, metric := range replicaMetrics { resp.Metrics[name] = &tabletmanagerdatapb.CheckThrottlerResponse_Metric{ - Name: name, - StatusCode: int32(metric.StatusCode), - Value: metric.Value, - Threshold: metric.Threshold, - Message: metric.Message, + Name: name, + StatusCode: int32(metric.StatusCode), + ResponseCode: metric.ResponseCode, + Value: metric.Value, + Threshold: metric.Threshold, + Message: metric.Message, } } } @@ -417,6 +423,7 @@ func TestApplyThrottlerConfigMetricThresholds(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -440,6 +447,7 @@ func TestApplyThrottlerConfigMetricThresholds(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value, "unexpected result: %+v", checkResult) // self lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to self/lag metric value") }) @@ -464,6 +472,7 @@ func TestApplyThrottlerConfigMetricThresholds(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value, "unexpected result: %+v", checkResult) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -522,6 +531,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to shard/lag metric value") }) @@ -537,6 +547,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -552,6 +563,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -571,6 +583,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 2.718, checkResult.Value) // self loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to self/loadavg metric value") }) @@ -590,6 +603,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 5.1, checkResult.Value) // shard loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to shard/loadavg metric value") }) @@ -608,6 +622,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 2.718, checkResult.Value) // self loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to self/loadavg metric value") }) @@ -626,6 +641,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 5.1, checkResult.Value) // shard loadavg value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is denied access due to shard/loadavg metric value") }) @@ -640,6 +656,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 1, len(checkResult.Metrics), "unexpected metrics: %+v", checkResult.Metrics) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -658,6 +675,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -676,6 +694,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 2.718, checkResult.Value) // loadavg self value exceeds threshold assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is denied access due to self/loadavg metric value") }) @@ -691,6 +710,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -705,6 +725,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 2.718, checkResult.Value) // loadavg self value exceeds threshold assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is denied access due to self/loadavg metric value") }) @@ -716,6 +737,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 2.718, checkResult.Value) // loadavg self value exceeds threshold assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is denied access due to self/loadavg metric value") }) @@ -726,6 +748,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -741,6 +764,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), throttlerapp.AllName.String()+" is granted access") }) @@ -756,6 +780,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, 2, len(checkResult.Metrics)) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -770,6 +795,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), throttlerapp.OnlineDDLName.String()+" is granted access") }) @@ -787,6 +813,7 @@ func TestApplyThrottlerConfigAppCheckedMetrics(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) assert.Contains(t, checkResult.Summary(), testAppName.String()+" is granted access") }) @@ -1707,6 +1734,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Len(t, checkResult.Metrics, 1) }) @@ -1719,6 +1747,12 @@ func TestChecks(t *testing.T) { t.Logf("%s: %+v", k, v) } } + if !assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) { + for k, v := range checkResult.Metrics { + t.Logf("%s: %+v", k, v) + } + } + assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) @@ -1766,6 +1800,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Len(t, checkResult.Metrics, 1) @@ -1775,6 +1810,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) assert.Equal(t, testAppName.String(), checkResult.AppName) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) @@ -1798,6 +1834,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) assert.Len(t, checkResult.Metrics, 1) }) @@ -1806,6 +1843,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) @@ -1837,6 +1875,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.9, checkResult.Value) // shard lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.ErrorIs(t, checkResult.Error, base.ErrThresholdExceeded) assert.Equal(t, len(metricNames), len(checkResult.Metrics)) @@ -1865,6 +1904,7 @@ func TestChecks(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value) // explicitly set self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Equal(t, len(metricNames), len(checkResult.Metrics)) assert.EqualValues(t, 0.3, checkResult.Metrics[base.LagMetricName.String()].Value) // self lag value, because scope name is in metric name @@ -1927,6 +1967,7 @@ func TestReplica(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) select { case <-ctx.Done(): @@ -1944,6 +1985,7 @@ func TestReplica(t *testing.T) { require.NotNil(t, checkResult) assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.Len(t, checkResult.Metrics, 1) assert.True(t, checkResult.RecentlyChecked) assert.True(t, throttler.recentlyChecked()) @@ -1951,6 +1993,7 @@ func TestReplica(t *testing.T) { recentApp, ok := throttler.recentAppsSnapshot()[throttlerapp.OnlineDDLName.String()] require.True(t, ok) assert.EqualValues(t, http.StatusOK, recentApp.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, recentApp.ResponseCode) } } { @@ -1981,6 +2024,7 @@ func TestReplica(t *testing.T) { // loadavg value exceeds threshold. This will show up in the check result as an error. assert.EqualValues(t, 2.718, checkResult.Value, "unexpected result: %+v", checkResult) // self lag value assert.NotEqualValues(t, http.StatusOK, checkResult.StatusCode, "unexpected result: %+v", checkResult) + assert.NotEqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode, "unexpected result: %+v", checkResult) assert.Equal(t, len(base.KnownMetricNames), len(checkResult.Metrics)) }) t.Run("validate v20 non-multi-metric results", func(t *testing.T) { @@ -1996,6 +2040,7 @@ func TestReplica(t *testing.T) { // reports the default metric. assert.EqualValues(t, 0.3, checkResult.Value) // self lag value assert.EqualValues(t, http.StatusOK, checkResult.StatusCode) + assert.EqualValues(t, tabletmanagerdatapb.CheckThrottlerResponseCode_OK, checkResult.ResponseCode) assert.EqualValues(t, 0.75, checkResult.Threshold) // The replica will still report the multi-metrics, and that's fine. As long // as it does not reflect any of their values in the checkResult.Value/StatusCode/Threshold/Error/Message. diff --git a/proto/tabletmanagerdata.proto b/proto/tabletmanagerdata.proto index 549c6c09782..cd74e79fa5d 100644 --- a/proto/tabletmanagerdata.proto +++ b/proto/tabletmanagerdata.proto @@ -736,6 +736,14 @@ message CheckThrottlerRequest { bool multi_metrics_enabled = 5; } +enum CheckThrottlerResponseCode { + UNDEFINED = 0; + OK = 1; + THRESHOLD_EXCEEDED = 2; + APP_DENIED = 3; + UNKNOWN_METRIC = 4; + INTERNAL_ERROR = 5; +} message CheckThrottlerResponse { // StatusCode is HTTP compliant response code (e.g. 200 for OK) @@ -767,6 +775,8 @@ message CheckThrottlerResponse { string message = 6; // Scope used in this check string scope = 7; + // ResponseCode is the enum representation of the response + CheckThrottlerResponseCode response_code = 8; } // Metrics is a map (metric name -> metric value/error) so that the client has as much // information as possible about all the checked metrics. @@ -777,6 +787,9 @@ message CheckThrottlerResponse { // Summary is a human readable analysis of the result string summary = 9; + + // ResponseCode is the enum representation of the response + CheckThrottlerResponseCode response_code = 10; } message GetThrottlerStatusRequest { @@ -835,6 +848,8 @@ message GetThrottlerStatusResponse { message RecentApp { vttime.Time checked_at = 1; int32 status_code = 2; + // ResponseCode is the enum representation of the response + CheckThrottlerResponseCode response_code = 3; } // RecentApps is a map of app names to their recent check status map recent_apps = 18; diff --git a/web/vtadmin/src/proto/vtadmin.d.ts b/web/vtadmin/src/proto/vtadmin.d.ts index d69d72daaac..5dc567d11fe 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -30630,6 +30630,16 @@ export namespace tabletmanagerdata { public static getTypeUrl(typeUrlPrefix?: string): string; } + /** CheckThrottlerResponseCode enum. */ + enum CheckThrottlerResponseCode { + UNDEFINED = 0, + OK = 1, + THRESHOLD_EXCEEDED = 2, + APP_DENIED = 3, + UNKNOWN_METRIC = 4, + INTERNAL_ERROR = 5 + } + /** Properties of a CheckThrottlerResponse. */ interface ICheckThrottlerResponse { @@ -30659,6 +30669,9 @@ export namespace tabletmanagerdata { /** CheckThrottlerResponse summary */ summary?: (string|null); + + /** CheckThrottlerResponse response_code */ + response_code?: (tabletmanagerdata.CheckThrottlerResponseCode|null); } /** Represents a CheckThrottlerResponse. */ @@ -30697,6 +30710,9 @@ export namespace tabletmanagerdata { /** CheckThrottlerResponse summary. */ public summary: string; + /** CheckThrottlerResponse response_code. */ + public response_code: tabletmanagerdata.CheckThrottlerResponseCode; + /** * Creates a new CheckThrottlerResponse instance using the specified properties. * @param [properties] Properties to set @@ -30800,6 +30816,9 @@ export namespace tabletmanagerdata { /** Metric scope */ scope?: (string|null); + + /** Metric response_code */ + response_code?: (tabletmanagerdata.CheckThrottlerResponseCode|null); } /** Represents a Metric. */ @@ -30832,6 +30851,9 @@ export namespace tabletmanagerdata { /** Metric scope. */ public scope: string; + /** Metric response_code. */ + public response_code: tabletmanagerdata.CheckThrottlerResponseCode; + /** * Creates a new Metric instance using the specified properties. * @param [properties] Properties to set @@ -31417,6 +31439,9 @@ export namespace tabletmanagerdata { /** RecentApp status_code */ status_code?: (number|null); + + /** RecentApp response_code */ + response_code?: (tabletmanagerdata.CheckThrottlerResponseCode|null); } /** Represents a RecentApp. */ @@ -31434,6 +31459,9 @@ export namespace tabletmanagerdata { /** RecentApp status_code. */ public status_code: number; + /** RecentApp response_code. */ + public response_code: tabletmanagerdata.CheckThrottlerResponseCode; + /** * Creates a new RecentApp instance using the specified properties. * @param [properties] Properties to set diff --git a/web/vtadmin/src/proto/vtadmin.js b/web/vtadmin/src/proto/vtadmin.js index 569d1602c6b..4ff1b8b19aa 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -71270,6 +71270,28 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { return CheckThrottlerRequest; })(); + /** + * CheckThrottlerResponseCode enum. + * @name tabletmanagerdata.CheckThrottlerResponseCode + * @enum {number} + * @property {number} UNDEFINED=0 UNDEFINED value + * @property {number} OK=1 OK value + * @property {number} THRESHOLD_EXCEEDED=2 THRESHOLD_EXCEEDED value + * @property {number} APP_DENIED=3 APP_DENIED value + * @property {number} UNKNOWN_METRIC=4 UNKNOWN_METRIC value + * @property {number} INTERNAL_ERROR=5 INTERNAL_ERROR value + */ + tabletmanagerdata.CheckThrottlerResponseCode = (function() { + const valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "UNDEFINED"] = 0; + values[valuesById[1] = "OK"] = 1; + values[valuesById[2] = "THRESHOLD_EXCEEDED"] = 2; + values[valuesById[3] = "APP_DENIED"] = 3; + values[valuesById[4] = "UNKNOWN_METRIC"] = 4; + values[valuesById[5] = "INTERNAL_ERROR"] = 5; + return values; + })(); + tabletmanagerdata.CheckThrottlerResponse = (function() { /** @@ -71285,6 +71307,7 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { * @property {Object.|null} [metrics] CheckThrottlerResponse metrics * @property {string|null} [app_name] CheckThrottlerResponse app_name * @property {string|null} [summary] CheckThrottlerResponse summary + * @property {tabletmanagerdata.CheckThrottlerResponseCode|null} [response_code] CheckThrottlerResponse response_code */ /** @@ -71375,6 +71398,14 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { */ CheckThrottlerResponse.prototype.summary = ""; + /** + * CheckThrottlerResponse response_code. + * @member {tabletmanagerdata.CheckThrottlerResponseCode} response_code + * @memberof tabletmanagerdata.CheckThrottlerResponse + * @instance + */ + CheckThrottlerResponse.prototype.response_code = 0; + /** * Creates a new CheckThrottlerResponse instance using the specified properties. * @function create @@ -71420,6 +71451,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { writer.uint32(/* id 8, wireType 2 =*/66).string(message.app_name); if (message.summary != null && Object.hasOwnProperty.call(message, "summary")) writer.uint32(/* id 9, wireType 2 =*/74).string(message.summary); + if (message.response_code != null && Object.hasOwnProperty.call(message, "response_code")) + writer.uint32(/* id 10, wireType 0 =*/80).int32(message.response_code); return writer; }; @@ -71509,6 +71542,10 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.summary = reader.string(); break; } + case 10: { + message.response_code = reader.int32(); + break; + } default: reader.skipType(tag & 7); break; @@ -71578,6 +71615,18 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { if (message.summary != null && message.hasOwnProperty("summary")) if (!$util.isString(message.summary)) return "summary: string expected"; + if (message.response_code != null && message.hasOwnProperty("response_code")) + switch (message.response_code) { + default: + return "response_code: enum value expected"; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + break; + } return null; }; @@ -71619,6 +71668,38 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.app_name = String(object.app_name); if (object.summary != null) message.summary = String(object.summary); + switch (object.response_code) { + default: + if (typeof object.response_code === "number") { + message.response_code = object.response_code; + break; + } + break; + case "UNDEFINED": + case 0: + message.response_code = 0; + break; + case "OK": + case 1: + message.response_code = 1; + break; + case "THRESHOLD_EXCEEDED": + case 2: + message.response_code = 2; + break; + case "APP_DENIED": + case 3: + message.response_code = 3; + break; + case "UNKNOWN_METRIC": + case 4: + message.response_code = 4; + break; + case "INTERNAL_ERROR": + case 5: + message.response_code = 5; + break; + } return message; }; @@ -71646,6 +71727,7 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { object.recently_checked = false; object.app_name = ""; object.summary = ""; + object.response_code = options.enums === String ? "UNDEFINED" : 0; } if (message.status_code != null && message.hasOwnProperty("status_code")) object.status_code = message.status_code; @@ -71669,6 +71751,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { object.app_name = message.app_name; if (message.summary != null && message.hasOwnProperty("summary")) object.summary = message.summary; + if (message.response_code != null && message.hasOwnProperty("response_code")) + object.response_code = options.enums === String ? $root.tabletmanagerdata.CheckThrottlerResponseCode[message.response_code] === undefined ? message.response_code : $root.tabletmanagerdata.CheckThrottlerResponseCode[message.response_code] : message.response_code; return object; }; @@ -71711,6 +71795,7 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { * @property {string|null} [error] Metric error * @property {string|null} [message] Metric message * @property {string|null} [scope] Metric scope + * @property {tabletmanagerdata.CheckThrottlerResponseCode|null} [response_code] Metric response_code */ /** @@ -71784,6 +71869,14 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { */ Metric.prototype.scope = ""; + /** + * Metric response_code. + * @member {tabletmanagerdata.CheckThrottlerResponseCode} response_code + * @memberof tabletmanagerdata.CheckThrottlerResponse.Metric + * @instance + */ + Metric.prototype.response_code = 0; + /** * Creates a new Metric instance using the specified properties. * @function create @@ -71822,6 +71915,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { writer.uint32(/* id 6, wireType 2 =*/50).string(message.message); if (message.scope != null && Object.hasOwnProperty.call(message, "scope")) writer.uint32(/* id 7, wireType 2 =*/58).string(message.scope); + if (message.response_code != null && Object.hasOwnProperty.call(message, "response_code")) + writer.uint32(/* id 8, wireType 0 =*/64).int32(message.response_code); return writer; }; @@ -71884,6 +71979,10 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.scope = reader.string(); break; } + case 8: { + message.response_code = reader.int32(); + break; + } default: reader.skipType(tag & 7); break; @@ -71940,6 +72039,18 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { if (message.scope != null && message.hasOwnProperty("scope")) if (!$util.isString(message.scope)) return "scope: string expected"; + if (message.response_code != null && message.hasOwnProperty("response_code")) + switch (message.response_code) { + default: + return "response_code: enum value expected"; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + break; + } return null; }; @@ -71969,6 +72080,38 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.message = String(object.message); if (object.scope != null) message.scope = String(object.scope); + switch (object.response_code) { + default: + if (typeof object.response_code === "number") { + message.response_code = object.response_code; + break; + } + break; + case "UNDEFINED": + case 0: + message.response_code = 0; + break; + case "OK": + case 1: + message.response_code = 1; + break; + case "THRESHOLD_EXCEEDED": + case 2: + message.response_code = 2; + break; + case "APP_DENIED": + case 3: + message.response_code = 3; + break; + case "UNKNOWN_METRIC": + case 4: + message.response_code = 4; + break; + case "INTERNAL_ERROR": + case 5: + message.response_code = 5; + break; + } return message; }; @@ -71993,6 +72136,7 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { object.error = ""; object.message = ""; object.scope = ""; + object.response_code = options.enums === String ? "UNDEFINED" : 0; } if (message.name != null && message.hasOwnProperty("name")) object.name = message.name; @@ -72008,6 +72152,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { object.message = message.message; if (message.scope != null && message.hasOwnProperty("scope")) object.scope = message.scope; + if (message.response_code != null && message.hasOwnProperty("response_code")) + object.response_code = options.enums === String ? $root.tabletmanagerdata.CheckThrottlerResponseCode[message.response_code] === undefined ? message.response_code : $root.tabletmanagerdata.CheckThrottlerResponseCode[message.response_code] : message.response_code; return object; }; @@ -73526,6 +73672,7 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { * @interface IRecentApp * @property {vttime.ITime|null} [checked_at] RecentApp checked_at * @property {number|null} [status_code] RecentApp status_code + * @property {tabletmanagerdata.CheckThrottlerResponseCode|null} [response_code] RecentApp response_code */ /** @@ -73559,6 +73706,14 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { */ RecentApp.prototype.status_code = 0; + /** + * RecentApp response_code. + * @member {tabletmanagerdata.CheckThrottlerResponseCode} response_code + * @memberof tabletmanagerdata.GetThrottlerStatusResponse.RecentApp + * @instance + */ + RecentApp.prototype.response_code = 0; + /** * Creates a new RecentApp instance using the specified properties. * @function create @@ -73587,6 +73742,8 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { $root.vttime.Time.encode(message.checked_at, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); if (message.status_code != null && Object.hasOwnProperty.call(message, "status_code")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status_code); + if (message.response_code != null && Object.hasOwnProperty.call(message, "response_code")) + writer.uint32(/* id 3, wireType 0 =*/24).int32(message.response_code); return writer; }; @@ -73629,6 +73786,10 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { message.status_code = reader.int32(); break; } + case 3: { + message.response_code = reader.int32(); + break; + } default: reader.skipType(tag & 7); break; @@ -73672,6 +73833,18 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { if (message.status_code != null && message.hasOwnProperty("status_code")) if (!$util.isInteger(message.status_code)) return "status_code: integer expected"; + if (message.response_code != null && message.hasOwnProperty("response_code")) + switch (message.response_code) { + default: + return "response_code: enum value expected"; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + break; + } return null; }; @@ -73694,6 +73867,38 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { } if (object.status_code != null) message.status_code = object.status_code | 0; + switch (object.response_code) { + default: + if (typeof object.response_code === "number") { + message.response_code = object.response_code; + break; + } + break; + case "UNDEFINED": + case 0: + message.response_code = 0; + break; + case "OK": + case 1: + message.response_code = 1; + break; + case "THRESHOLD_EXCEEDED": + case 2: + message.response_code = 2; + break; + case "APP_DENIED": + case 3: + message.response_code = 3; + break; + case "UNKNOWN_METRIC": + case 4: + message.response_code = 4; + break; + case "INTERNAL_ERROR": + case 5: + message.response_code = 5; + break; + } return message; }; @@ -73713,11 +73918,14 @@ export const tabletmanagerdata = $root.tabletmanagerdata = (() => { if (options.defaults) { object.checked_at = null; object.status_code = 0; + object.response_code = options.enums === String ? "UNDEFINED" : 0; } if (message.checked_at != null && message.hasOwnProperty("checked_at")) object.checked_at = $root.vttime.Time.toObject(message.checked_at, options); if (message.status_code != null && message.hasOwnProperty("status_code")) object.status_code = message.status_code; + if (message.response_code != null && message.hasOwnProperty("response_code")) + object.response_code = options.enums === String ? $root.tabletmanagerdata.CheckThrottlerResponseCode[message.response_code] === undefined ? message.response_code : $root.tabletmanagerdata.CheckThrottlerResponseCode[message.response_code] : message.response_code; return object; }; From 2e79d1641e71c48d66ef2f941a109b81b9bd8344 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:39:22 +0300 Subject: [PATCH 14/34] Throttler: `SelfMetric` interface, simplify adding new throttler metrics (#16469) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- ...rottle_metric_cache.go => metric_cache.go} | 0 .../tabletserver/throttle/base/metric_name.go | 128 +++++++++ ...tle_metric_test.go => metric_name_test.go} | 9 + .../throttle/base/metric_result.go | 121 ++++++++ .../throttle/base/metric_scope.go | 44 +++ .../tabletserver/throttle/base/self_metric.go | 91 ++++++ .../throttle/base/self_metric_custom_query.go | 48 ++++ .../throttle/base/self_metric_default.go | 51 ++++ .../throttle/base/self_metric_lag.go | 70 +++++ .../throttle/base/self_metric_loadavg.go | 81 ++++++ .../base/self_metric_threads_running.go | 52 ++++ .../throttle/base/throttle_metric.go | 260 ------------------ .../throttle/base/throttle_metric_app.go | 59 ---- .../base/throttler_metrics_publisher.go | 23 ++ .../throttle/config/mysql_config.go | 27 +- .../tabletserver/throttle/throttler.go | 203 +++----------- .../tabletserver/throttle/throttler_test.go | 6 +- 17 files changed, 770 insertions(+), 503 deletions(-) rename go/vt/vttablet/tabletserver/throttle/base/{throttle_metric_cache.go => metric_cache.go} (100%) create mode 100644 go/vt/vttablet/tabletserver/throttle/base/metric_name.go rename go/vt/vttablet/tabletserver/throttle/base/{throttle_metric_test.go => metric_name_test.go} (93%) create mode 100644 go/vt/vttablet/tabletserver/throttle/base/metric_result.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/metric_scope.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/self_metric.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/self_metric_custom_query.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/self_metric_default.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/self_metric_lag.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/self_metric_loadavg.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/self_metric_threads_running.go delete mode 100644 go/vt/vttablet/tabletserver/throttle/base/throttle_metric.go delete mode 100644 go/vt/vttablet/tabletserver/throttle/base/throttle_metric_app.go create mode 100644 go/vt/vttablet/tabletserver/throttle/base/throttler_metrics_publisher.go diff --git a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric_cache.go b/go/vt/vttablet/tabletserver/throttle/base/metric_cache.go similarity index 100% rename from go/vt/vttablet/tabletserver/throttle/base/throttle_metric_cache.go rename to go/vt/vttablet/tabletserver/throttle/base/metric_cache.go diff --git a/go/vt/vttablet/tabletserver/throttle/base/metric_name.go b/go/vt/vttablet/tabletserver/throttle/base/metric_name.go new file mode 100644 index 00000000000..98e1288fb23 --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/metric_name.go @@ -0,0 +1,128 @@ +/* +Copyright 2023 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 base + +import ( + "fmt" + "slices" + "strings" +) + +// MetricName is a formalized name for a metric, such as "lag" or "threads_running". A metric name +// may include a scope, such as "self/lag" or "shard/threads_running". It is possible to add a +// scope to a name, or to parse the scope out of a name, and there is also always a default scope +// associated with a metric name. +type MetricName string + +// MetricNames is a formalized list of metric names +type MetricNames []MetricName + +func (names MetricNames) Contains(name MetricName) bool { + return slices.Contains(names, name) +} + +func (names MetricNames) String() string { + s := make([]string, len(names)) + for i, name := range names { + s[i] = name.String() + } + return strings.Join(s, ",") +} + +// Unique returns a subset of unique metric names, in same order as the original names +func (names MetricNames) Unique() MetricNames { + if names == nil { + return nil + } + uniqueMetricNamesMap := map[MetricName]bool{} + uniqueMetricNames := MetricNames{} + for _, metricName := range names { + if _, ok := uniqueMetricNamesMap[metricName]; !ok { + uniqueMetricNames = append(uniqueMetricNames, metricName) + uniqueMetricNamesMap[metricName] = true + } + } + return uniqueMetricNames +} + +const ( + DefaultMetricName MetricName = "default" + LagMetricName MetricName = "lag" + ThreadsRunningMetricName MetricName = "threads_running" + CustomMetricName MetricName = "custom" + LoadAvgMetricName MetricName = "loadavg" +) + +func (metric MetricName) DefaultScope() Scope { + if selfMetric := RegisteredSelfMetrics[metric]; selfMetric != nil { + return selfMetric.DefaultScope() + } + return SelfScope +} + +func (metric MetricName) String() string { + return string(metric) +} + +// AggregatedName returns the string representation of this metric in the given scope, e.g.: +// - "self/loadavg" +// - "shard/lag" +func (metric MetricName) AggregatedName(scope Scope) string { + if metric == DefaultMetricName { + // backwards (v20) compatibility + return scope.String() + } + if scope == UndefinedScope { + scope = metric.DefaultScope() + } + return fmt.Sprintf("%s/%s", scope.String(), metric.String()) +} + +// Disaggregated returns a breakdown of this metric into scope + name. +func (metric MetricName) Disaggregated() (scope Scope, metricName MetricName, err error) { + return DisaggregateMetricName(metric.String()) +} + +type AggregatedMetricName struct { + Scope Scope + Metric MetricName +} + +var ( + KnownMetricNames = make(MetricNames, 0) + // aggregatedMetricNames precomputes the aggregated metric names for all known metric names, + // mapped to their breakdowns. e.g. "self/loadavg" -> {SelfScope, LoadAvgMetricName} + // This means: + // - no textual parsing is needed in the critical path + // - we can easily check if a metric name is valid + aggregatedMetricNames = make(map[string]AggregatedMetricName) +) + +// DisaggregateMetricName splits a metric name into its scope name and metric name +// aggregated metric name could be in the form: +// - loadavg +// - self +// - self/threads_running +// - shard +// - shard/lag +func DisaggregateMetricName(aggregatedMetricName string) (scope Scope, metricName MetricName, err error) { + breakdown, ok := aggregatedMetricNames[aggregatedMetricName] + if !ok { + return UndefinedScope, DefaultMetricName, ErrNoSuchMetric + } + return breakdown.Scope, breakdown.Metric, nil +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric_test.go b/go/vt/vttablet/tabletserver/throttle/base/metric_name_test.go similarity index 93% rename from go/vt/vttablet/tabletserver/throttle/base/throttle_metric_test.go rename to go/vt/vttablet/tabletserver/throttle/base/metric_name_test.go index 8a0f9b85a16..9867ca18db3 100644 --- a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric_test.go +++ b/go/vt/vttablet/tabletserver/throttle/base/metric_name_test.go @@ -230,3 +230,12 @@ func TestContains(t *testing.T) { }) } } + +func TestKnownMetricNames(t *testing.T) { + assert.NotEmpty(t, KnownMetricNames) + assert.Contains(t, KnownMetricNames, LagMetricName) + assert.Contains(t, KnownMetricNames, ThreadsRunningMetricName) + assert.Contains(t, KnownMetricNames, LoadAvgMetricName) + assert.Contains(t, KnownMetricNames, CustomMetricName) + assert.Contains(t, KnownMetricNames, DefaultMetricName) +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/metric_result.go b/go/vt/vttablet/tabletserver/throttle/base/metric_result.go new file mode 100644 index 00000000000..0fa48fe3240 --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/metric_result.go @@ -0,0 +1,121 @@ +/* +Copyright 2024 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 base + +import ( + "errors" + "net" +) + +// MetricResult is what we expect our probes to return. This can be a numeric result, or +// a special type of result indicating more meta-information +type MetricResult interface { + Get() (float64, error) +} + +// MetricResultFunc is a function that returns a metric result +type MetricResultFunc func() (metricResult MetricResult, threshold float64) + +type MetricResultMap map[MetricName]MetricResult + +func NewMetricResultMap() MetricResultMap { + result := make(MetricResultMap, len(KnownMetricNames)) + for _, metricName := range KnownMetricNames { + result[metricName] = nil + } + return result +} + +// ErrThresholdExceeded is the common error one may get checking on metric result +var ErrThresholdExceeded = errors.New("threshold exceeded") +var ErrNoResultYet = errors.New("metric not collected yet") + +// ErrNoSuchMetric is for when a user requests a metric by an unknown metric name +var ErrNoSuchMetric = errors.New("no such metric") + +// ErrAppDenied is seen when an app is denied access +var ErrAppDenied = errors.New("app denied") + +// ErrInvalidCheckType is an internal error indicating an unknown check type +var ErrInvalidCheckType = errors.New("unknown throttler check type") + +// IsDialTCPError sees if the given error indicates a TCP issue +func IsDialTCPError(err error) bool { + if err == nil { + return false + } + switch err := err.(type) { + case *net.OpError: + return err.Op == "dial" && err.Net == "tcp" + } + return false +} + +type noHostsMetricResult struct{} + +// Get implements MetricResult +func (metricResult *noHostsMetricResult) Get() (float64, error) { + return 0, nil +} + +// NoHostsMetricResult is a result indicating "no hosts" +var NoHostsMetricResult = &noHostsMetricResult{} + +type noMetricResultYet struct{} + +// Get implements MetricResult +func (metricResult *noMetricResultYet) Get() (float64, error) { + return 0, ErrNoResultYet +} + +// NoMetricResultYet is a result indicating "no data" +var NoMetricResultYet = &noMetricResultYet{} + +type noSuchMetric struct{} + +// Get implements MetricResult +func (metricResult *noSuchMetric) Get() (float64, error) { + return 0, ErrNoSuchMetric +} + +// NoSuchMetric is a metric results for an unknown metric name +var NoSuchMetric = &noSuchMetric{} + +// simpleMetricResult is a result with float value +type simpleMetricResult struct { + Value float64 +} + +// NewSimpleMetricResult creates a simpleMetricResult +func NewSimpleMetricResult(value float64) MetricResult { + return &simpleMetricResult{Value: value} +} + +// Get implements MetricResult +func (metricResult *simpleMetricResult) Get() (float64, error) { + return metricResult.Value, nil +} + +type appDeniedMetric struct{} + +// Get implements MetricResult +func (metricResult *appDeniedMetric) Get() (float64, error) { + return 0, ErrAppDenied +} + +// AppDeniedMetric is a special metric indicating a "denied" situation +var AppDeniedMetric = &appDeniedMetric{} diff --git a/go/vt/vttablet/tabletserver/throttle/base/metric_scope.go b/go/vt/vttablet/tabletserver/throttle/base/metric_scope.go new file mode 100644 index 00000000000..60d116861c3 --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/metric_scope.go @@ -0,0 +1,44 @@ +/* +Copyright 2024 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 base + +import ( + "fmt" +) + +// Scope defines the tablet range from which a metric is collected. This can be the local tablet +// ("self") or the entire shard ("shard") +type Scope string + +const ( + UndefinedScope Scope = "" + ShardScope Scope = "shard" + SelfScope Scope = "self" +) + +func (s Scope) String() string { + return string(s) +} + +func ScopeFromString(s string) (Scope, error) { + switch scope := Scope(s); scope { + case UndefinedScope, ShardScope, SelfScope: + return scope, nil + default: + return "", fmt.Errorf("unknown scope: %s", s) + } +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/self_metric.go b/go/vt/vttablet/tabletserver/throttle/base/self_metric.go new file mode 100644 index 00000000000..220dfa6bf60 --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/self_metric.go @@ -0,0 +1,91 @@ +/* +Copyright 2024 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 base + +import ( + "context" + "fmt" + "strconv" + + "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" +) + +type SelfMetric interface { + Name() MetricName + DefaultScope() Scope + DefaultThreshold() float64 + RequiresConn() bool + Read(ctx context.Context, throttler ThrottlerMetricsPublisher, conn *connpool.Conn) *ThrottleMetric +} + +var ( + RegisteredSelfMetrics = make(map[MetricName]SelfMetric) +) + +func registerSelfMetric(selfMetric SelfMetric) SelfMetric { + RegisteredSelfMetrics[selfMetric.Name()] = selfMetric + KnownMetricNames = append(KnownMetricNames, selfMetric.Name()) + aggregatedMetricNames[selfMetric.Name().String()] = AggregatedMetricName{ + Scope: selfMetric.DefaultScope(), + Metric: selfMetric.Name(), + } + for _, scope := range []Scope{ShardScope, SelfScope} { + aggregatedName := selfMetric.Name().AggregatedName(scope) + aggregatedMetricNames[aggregatedName] = AggregatedMetricName{ + Scope: scope, + Metric: selfMetric.Name(), + } + } + return selfMetric +} + +// ReadSelfMySQLThrottleMetric reads a metric using a given MySQL connection and a query. +func ReadSelfMySQLThrottleMetric(ctx context.Context, conn *connpool.Conn, query string) *ThrottleMetric { + metric := &ThrottleMetric{ + Scope: SelfScope, + } + if query == "" { + return metric + } + if conn == nil { + return metric.WithError(fmt.Errorf("conn is nil")) + } + + tm, err := conn.Exec(ctx, query, 1, true) + if err != nil { + return metric.WithError(err) + } + if len(tm.Rows) == 0 { + return metric.WithError(fmt.Errorf("no results in ReadSelfMySQLThrottleMetric for query %s", query)) + } + if len(tm.Rows) > 1 { + return metric.WithError(fmt.Errorf("expecting single row in ReadSelfMySQLThrottleMetric for query %s", query)) + } + + metricsQueryType := GetMetricsQueryType(query) + switch metricsQueryType { + case MetricsQueryTypeSelect: + metric.Value, metric.Err = tm.Rows[0][0].ToFloat64() + case MetricsQueryTypeShowGlobal: + // Columns are [Variable_name, Value] + metric.Value, metric.Err = strconv.ParseFloat(tm.Rows[0][1].ToString(), 64) + default: + metric.Err = fmt.Errorf("unsupported metrics query type for query: %s", query) + } + + return metric +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/self_metric_custom_query.go b/go/vt/vttablet/tabletserver/throttle/base/self_metric_custom_query.go new file mode 100644 index 00000000000..585e63ea285 --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/self_metric_custom_query.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 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 base + +import ( + "context" + + "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" +) + +var _ SelfMetric = registerSelfMetric(&CustomQuerySelfMetric{}) + +type CustomQuerySelfMetric struct { +} + +func (m *CustomQuerySelfMetric) Name() MetricName { + return CustomMetricName +} + +func (m *CustomQuerySelfMetric) DefaultScope() Scope { + return SelfScope +} + +func (m *CustomQuerySelfMetric) DefaultThreshold() float64 { + return 0 +} + +func (m *CustomQuerySelfMetric) RequiresConn() bool { + return true +} + +func (m *CustomQuerySelfMetric) Read(ctx context.Context, throttler ThrottlerMetricsPublisher, conn *connpool.Conn) *ThrottleMetric { + return ReadSelfMySQLThrottleMetric(ctx, conn, throttler.GetCustomMetricsQuery()) +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/self_metric_default.go b/go/vt/vttablet/tabletserver/throttle/base/self_metric_default.go new file mode 100644 index 00000000000..8bce295da7c --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/self_metric_default.go @@ -0,0 +1,51 @@ +/* +Copyright 2024 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 base + +import ( + "context" + "fmt" + + "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" +) + +var _ SelfMetric = registerSelfMetric(&DefaultSelfMetric{}) + +type DefaultSelfMetric struct { +} + +func (m *DefaultSelfMetric) Name() MetricName { + return DefaultMetricName +} + +func (m *DefaultSelfMetric) DefaultScope() Scope { + return SelfScope +} + +func (m *DefaultSelfMetric) DefaultThreshold() float64 { + return 0 +} + +func (m *DefaultSelfMetric) RequiresConn() bool { + return false +} + +func (m *DefaultSelfMetric) Read(ctx context.Context, throttler ThrottlerMetricsPublisher, conn *connpool.Conn) *ThrottleMetric { + return &ThrottleMetric{ + Err: fmt.Errorf("unexpected direct call to DefaultSelfMetric.Read"), + } +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/self_metric_lag.go b/go/vt/vttablet/tabletserver/throttle/base/self_metric_lag.go new file mode 100644 index 00000000000..dc25ee5622a --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/self_metric_lag.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 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 base + +import ( + "context" + "sync/atomic" + "time" + + "vitess.io/vitess/go/constants/sidecar" + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" +) + +var ( + lagSelfMetricQueryBase = "select unix_timestamp(now(6))-max(ts/1000000000) as replication_lag from %s.heartbeat" + lagSelfDefaultThreshold = 5 * time.Second +) + +var _ SelfMetric = registerSelfMetric(&LagSelfMetric{}) + +type LagSelfMetric struct { + lagSelfMetricQuery atomic.Value +} + +// SetQuery is only used by unit tests to override the query. +func (m *LagSelfMetric) SetQuery(query string) { + m.lagSelfMetricQuery.Store(query) +} + +func (m *LagSelfMetric) GetQuery() string { + if query := m.lagSelfMetricQuery.Load(); query == nil { + m.lagSelfMetricQuery.Store(sqlparser.BuildParsedQuery(lagSelfMetricQueryBase, sidecar.GetIdentifier()).Query) + } + return m.lagSelfMetricQuery.Load().(string) +} + +func (m *LagSelfMetric) Name() MetricName { + return LagMetricName +} + +func (m *LagSelfMetric) DefaultScope() Scope { + return ShardScope +} + +func (m *LagSelfMetric) DefaultThreshold() float64 { + return lagSelfDefaultThreshold.Seconds() +} + +func (m *LagSelfMetric) RequiresConn() bool { + return true +} + +func (m *LagSelfMetric) Read(ctx context.Context, throttler ThrottlerMetricsPublisher, conn *connpool.Conn) *ThrottleMetric { + return ReadSelfMySQLThrottleMetric(ctx, conn, m.GetQuery()) +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/self_metric_loadavg.go b/go/vt/vttablet/tabletserver/throttle/base/self_metric_loadavg.go new file mode 100644 index 00000000000..40a2878421a --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/self_metric_loadavg.go @@ -0,0 +1,81 @@ +/* +Copyright 2024 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 base + +import ( + "context" + "fmt" + "os" + "runtime" + "strconv" + "strings" + + "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" +) + +var ( + loadavgOnlyAvailableOnLinuxMetric = &ThrottleMetric{ + Scope: SelfScope, + Err: fmt.Errorf("loadavg metric is only available on Linux"), + } +) + +var _ SelfMetric = registerSelfMetric(&LoadAvgSelfMetric{}) + +type LoadAvgSelfMetric struct { +} + +func (m *LoadAvgSelfMetric) Name() MetricName { + return LoadAvgMetricName +} + +func (m *LoadAvgSelfMetric) DefaultScope() Scope { + return SelfScope +} + +func (m *LoadAvgSelfMetric) DefaultThreshold() float64 { + return 1.0 +} + +func (m *LoadAvgSelfMetric) RequiresConn() bool { + return false +} + +func (m *LoadAvgSelfMetric) Read(ctx context.Context, throttler ThrottlerMetricsPublisher, conn *connpool.Conn) *ThrottleMetric { + if runtime.GOOS != "linux" { + return loadavgOnlyAvailableOnLinuxMetric + } + metric := &ThrottleMetric{ + Scope: SelfScope, + } + { + content, err := os.ReadFile("/proc/loadavg") + if err != nil { + return metric.WithError(err) + } + fields := strings.Fields(string(content)) + if len(fields) == 0 { + return metric.WithError(fmt.Errorf("unexpected /proc/loadavg content")) + } + loadAvg, err := strconv.ParseFloat(fields[0], 64) + if err != nil { + return metric.WithError(err) + } + metric.Value = loadAvg / float64(runtime.NumCPU()) + } + return metric +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/self_metric_threads_running.go b/go/vt/vttablet/tabletserver/throttle/base/self_metric_threads_running.go new file mode 100644 index 00000000000..08f7d408d1c --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/self_metric_threads_running.go @@ -0,0 +1,52 @@ +/* +Copyright 2024 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 base + +import ( + "context" + + "vitess.io/vitess/go/vt/vttablet/tabletserver/connpool" +) + +var ( + threadsRunningMetricQuery = "show global status like 'threads_running'" +) + +var _ SelfMetric = registerSelfMetric(&ThreadsRunningSelfMetric{}) + +type ThreadsRunningSelfMetric struct { +} + +func (m *ThreadsRunningSelfMetric) Name() MetricName { + return ThreadsRunningMetricName +} + +func (m *ThreadsRunningSelfMetric) DefaultScope() Scope { + return SelfScope +} + +func (m *ThreadsRunningSelfMetric) DefaultThreshold() float64 { + return 100 +} + +func (m *ThreadsRunningSelfMetric) RequiresConn() bool { + return true +} + +func (m *ThreadsRunningSelfMetric) Read(ctx context.Context, throttler ThrottlerMetricsPublisher, conn *connpool.Conn) *ThrottleMetric { + return ReadSelfMySQLThrottleMetric(ctx, conn, threadsRunningMetricQuery) +} diff --git a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric.go b/go/vt/vttablet/tabletserver/throttle/base/throttle_metric.go deleted file mode 100644 index 054687cdd3f..00000000000 --- a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric.go +++ /dev/null @@ -1,260 +0,0 @@ -/* -Copyright 2023 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 base - -import ( - "errors" - "fmt" - "slices" - "strings" -) - -// Scope defines the tablet range from which a metric is collected. This can be the local tablet -// ("self") or the entire shard ("shard") -type Scope string - -const ( - UndefinedScope Scope = "" - ShardScope Scope = "shard" - SelfScope Scope = "self" -) - -func (s Scope) String() string { - return string(s) -} - -func ScopeFromString(s string) (Scope, error) { - switch scope := Scope(s); scope { - case UndefinedScope, ShardScope, SelfScope: - return scope, nil - default: - return "", fmt.Errorf("unknown scope: %s", s) - } -} - -// MetricName is a formalized name for a metric, such as "lag" or "threads_running". A metric name -// may include a scope, such as "self/lag" or "shard/threads_running". It is possible to add a -// scope to a name, or to parse the scope out of a name, and there is also always a default scope -// associated with a metric name. -type MetricName string - -// MetricNames is a formalized list of metric names -type MetricNames []MetricName - -func (names MetricNames) Contains(name MetricName) bool { - return slices.Contains(names, name) -} - -func (names MetricNames) String() string { - s := make([]string, len(names)) - for i, name := range names { - s[i] = name.String() - } - return strings.Join(s, ",") -} - -// Unique returns a subset of unique metric names, in same order as the original names -func (names MetricNames) Unique() MetricNames { - if names == nil { - return nil - } - uniqueMetricNamesMap := map[MetricName]bool{} - uniqueMetricNames := MetricNames{} - for _, metricName := range names { - if _, ok := uniqueMetricNamesMap[metricName]; !ok { - uniqueMetricNames = append(uniqueMetricNames, metricName) - uniqueMetricNamesMap[metricName] = true - } - } - return uniqueMetricNames -} - -const ( - DefaultMetricName MetricName = "default" - LagMetricName MetricName = "lag" - ThreadsRunningMetricName MetricName = "threads_running" - CustomMetricName MetricName = "custom" - LoadAvgMetricName MetricName = "loadavg" -) - -func (metric MetricName) DefaultScope() Scope { - switch metric { - case LagMetricName: - return ShardScope - default: - return SelfScope - } -} - -func (metric MetricName) String() string { - return string(metric) -} - -// AggregatedName returns the string representation of this metric in the given scope, e.g.: -// - "self/loadavg" -// - "shard/lag" -func (metric MetricName) AggregatedName(scope Scope) string { - if metric == DefaultMetricName { - // backwards (v20) compatibility - return scope.String() - } - if scope == UndefinedScope { - scope = metric.DefaultScope() - } - return fmt.Sprintf("%s/%s", scope.String(), metric.String()) -} - -// Disaggregated returns a breakdown of this metric into scope + name. -func (metric MetricName) Disaggregated() (scope Scope, metricName MetricName, err error) { - return DisaggregateMetricName(metric.String()) -} - -var KnownMetricNames = MetricNames{ - DefaultMetricName, - LagMetricName, - ThreadsRunningMetricName, - CustomMetricName, - LoadAvgMetricName, -} - -type AggregatedMetricName struct { - Scope Scope - Metric MetricName -} - -var ( - // aggregatedMetricNames precomputes the aggregated metric names for all known metric names, - // mapped to their breakdowns. e.g. "self/loadavg" -> {SelfScope, LoadAvgMetricName} - // This means: - // - no textual parsing is needed in the critical path - // - we can easily check if a metric name is valid - aggregatedMetricNames map[string]AggregatedMetricName -) - -func init() { - aggregatedMetricNames = make(map[string]AggregatedMetricName, 3*len(KnownMetricNames)) - for _, metricName := range KnownMetricNames { - aggregatedMetricNames[metricName.String()] = AggregatedMetricName{ - Scope: metricName.DefaultScope(), - Metric: metricName, - } - for _, scope := range []Scope{ShardScope, SelfScope} { - aggregatedName := metricName.AggregatedName(scope) - aggregatedMetricNames[aggregatedName] = AggregatedMetricName{ - Scope: scope, - Metric: metricName, - } - } - } -} - -// DisaggregateMetricName splits a metric name into its scope name and metric name -// aggregated metric name could be in the form: -// - loadavg -// - self -// - self/threads_running -// - shard -// - shard/lag -func DisaggregateMetricName(aggregatedMetricName string) (scope Scope, metricName MetricName, err error) { - breakdown, ok := aggregatedMetricNames[aggregatedMetricName] - if !ok { - return UndefinedScope, DefaultMetricName, ErrNoSuchMetric - } - return breakdown.Scope, breakdown.Metric, nil -} - -// MetricResult is what we expect our probes to return. This can be a numeric result, or -// a special type of result indicating more meta-information -type MetricResult interface { - Get() (float64, error) -} - -// MetricResultFunc is a function that returns a metric result -type MetricResultFunc func() (metricResult MetricResult, threshold float64) - -type MetricResultMap map[MetricName]MetricResult - -func NewMetricResultMap() MetricResultMap { - result := make(MetricResultMap, len(KnownMetricNames)) - for _, metricName := range KnownMetricNames { - result[metricName] = nil - } - return result -} - -// ErrThresholdExceeded is the common error one may get checking on metric result -var ErrThresholdExceeded = errors.New("threshold exceeded") -var ErrNoResultYet = errors.New("metric not collected yet") - -// ErrNoSuchMetric is for when a user requests a metric by an unknown metric name -var ErrNoSuchMetric = errors.New("no such metric") - -// ErrInvalidCheckType is an internal error indicating an unknown check type -var ErrInvalidCheckType = errors.New("unknown throttler check type") - -// IsDialTCPError sees if the given error indicates a TCP issue -func IsDialTCPError(e error) bool { - if e == nil { - return false - } - return strings.HasPrefix(e.Error(), "dial tcp") -} - -type noHostsMetricResult struct{} - -// Get implements MetricResult -func (metricResult *noHostsMetricResult) Get() (float64, error) { - return 0, nil -} - -// NoHostsMetricResult is a result indicating "no hosts" -var NoHostsMetricResult = &noHostsMetricResult{} - -type noMetricResultYet struct{} - -// Get implements MetricResult -func (metricResult *noMetricResultYet) Get() (float64, error) { - return 0, ErrNoResultYet -} - -// NoMetricResultYet is a result indicating "no data" -var NoMetricResultYet = &noMetricResultYet{} - -type noSuchMetric struct{} - -// Get implements MetricResult -func (metricResult *noSuchMetric) Get() (float64, error) { - return 0, ErrNoSuchMetric -} - -// NoSuchMetric is a metric results for an unknown metric name -var NoSuchMetric = &noSuchMetric{} - -// simpleMetricResult is a result with float value -type simpleMetricResult struct { - Value float64 -} - -// NewSimpleMetricResult creates a simpleMetricResult -func NewSimpleMetricResult(value float64) MetricResult { - return &simpleMetricResult{Value: value} -} - -// Get implements MetricResult -func (metricResult *simpleMetricResult) Get() (float64, error) { - return metricResult.Value, nil -} diff --git a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric_app.go b/go/vt/vttablet/tabletserver/throttle/base/throttle_metric_app.go deleted file mode 100644 index 482f319365f..00000000000 --- a/go/vt/vttablet/tabletserver/throttle/base/throttle_metric_app.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2023 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. -*/ - -// This codebase originates from https://github.com/github/freno, See https://github.com/github/freno/blob/master/LICENSE -/* - MIT License - - Copyright (c) 2017 GitHub - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -package base - -import ( - "errors" -) - -// ErrAppDenied is seen when an app is denied access -var ErrAppDenied = errors.New("App denied") - -type appDeniedMetric struct{} - -// Get implements MetricResult -func (metricResult *appDeniedMetric) Get() (float64, error) { - return 0, ErrAppDenied -} - -// AppDeniedMetric is a special metric indicating a "denied" situation -var AppDeniedMetric = &appDeniedMetric{} diff --git a/go/vt/vttablet/tabletserver/throttle/base/throttler_metrics_publisher.go b/go/vt/vttablet/tabletserver/throttle/base/throttler_metrics_publisher.go new file mode 100644 index 00000000000..1d2d4d0652c --- /dev/null +++ b/go/vt/vttablet/tabletserver/throttle/base/throttler_metrics_publisher.go @@ -0,0 +1,23 @@ +/* +Copyright 2024 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 base + +// ThrottlerMetricsPublisher is implemented by throttler.Throttler and is used by SelfMetric +// implementations to query the throttler. +type ThrottlerMetricsPublisher interface { + GetCustomMetricsQuery() string +} diff --git a/go/vt/vttablet/tabletserver/throttle/config/mysql_config.go b/go/vt/vttablet/tabletserver/throttle/config/mysql_config.go index d4beb40deb4..76d5f2dd298 100644 --- a/go/vt/vttablet/tabletserver/throttle/config/mysql_config.go +++ b/go/vt/vttablet/tabletserver/throttle/config/mysql_config.go @@ -41,32 +41,15 @@ limitations under the License. package config -import ( - "sync/atomic" - - "vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/base" -) - // // MySQL-specific configuration // -type MySQLMetricConfigurationSettings struct { - Name base.MetricName - CustomQuery string - Threshold atomic.Uint64 -} - // MySQLConfigurationSettings has the general configuration for all MySQL clusters type MySQLConfigurationSettings struct { - CacheMillis int // optional, if defined then probe result will be cached, and future probes may use cached value - Port int // Specify if different than 3306; applies to all clusters - IgnoreDialTCPErrors bool // Skip hosts where a metric cannot be retrieved due to TCP dial errors - IgnoreHostsCount int // Number of hosts that can be skipped/ignored even on error or on exceeding thresholds - IgnoreHostsThreshold float64 // Threshold beyond which IgnoreHostsCount applies (default: 0) - HTTPCheckPort int // port for HTTP check. -1 to disable. - HTTPCheckPath string // If non-empty, requires HTTPCheckPort - IgnoreHosts []string // If non empty, substrings to indicate hosts to be ignored/skipped - - Metrics map[base.MetricName]*MySQLMetricConfigurationSettings + CacheMillis int // optional, if defined then probe result will be cached, and future probes may use cached value + Port int // Specify if different than 3306; applies to all clusters + IgnoreDialTCPErrors bool // Skip hosts where a metric cannot be retrieved due to TCP dial errors + IgnoreHostsCount int // Number of hosts that can be skipped/ignored even on error or on exceeding thresholds + IgnoreHosts []string // If non empty, substrings to indicate hosts to be ignored/skipped } diff --git a/go/vt/vttablet/tabletserver/throttle/throttler.go b/go/vt/vttablet/tabletserver/throttle/throttler.go index 7fb83769a96..382d0a12a77 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler.go @@ -42,15 +42,12 @@ limitations under the License. package throttle import ( - "bufio" "context" "errors" "fmt" "math" "math/rand/v2" "net/http" - "os" - "strconv" "strings" "sync" "sync/atomic" @@ -59,7 +56,6 @@ import ( "github.com/patrickmn/go-cache" "github.com/spf13/pflag" - "vitess.io/vitess/go/constants/sidecar" "vitess.io/vitess/go/protoutil" "vitess.io/vitess/go/stats" @@ -67,7 +63,6 @@ import ( "vitess.io/vitess/go/timer" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/servenv" - "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/srvtopo" "vitess.io/vitess/go/vt/topo" "vitess.io/vitess/go/vt/topo/topoproto" @@ -108,14 +103,6 @@ const ( var ( throttleTabletTypes = "replica" - - defaultThresholds = map[base.MetricName]float64{ - base.DefaultMetricName: 5 * time.Second.Seconds(), - base.LagMetricName: 5 * time.Second.Seconds(), - base.ThreadsRunningMetricName: 100, - base.CustomMetricName: 0, - base.LoadAvgMetricName: 1.0, - } ) var ( @@ -187,7 +174,6 @@ type Throttler struct { inventory *base.Inventory - metricsQuery atomic.Value customMetricsQuery atomic.Value MetricsThreshold atomic.Uint64 checkAsCheckSelf atomic.Bool @@ -206,8 +192,6 @@ type Throttler struct { throttledAppsMutex sync.Mutex readSelfThrottleMetrics func(context.Context) base.ThrottleMetrics // overwritten by unit test - - hostCpuCoreCount atomic.Int32 } // ThrottlerStatus published some status values from the throttler @@ -278,11 +262,10 @@ func NewThrottler(env tabletenv.Env, srvTopoServer srvtopo.Server, ts *topo.Serv throttler.recentCheckDiff = 1 } - throttler.StoreMetricsThreshold(defaultThresholds[base.LagMetricName]) + throttler.StoreMetricsThreshold(base.RegisteredSelfMetrics[base.LagMetricName].DefaultThreshold()) throttler.readSelfThrottleMetrics = func(ctx context.Context) base.ThrottleMetrics { return throttler.readSelfThrottleMetricsInternal(ctx) } - return throttler } @@ -313,7 +296,14 @@ func (throttler *Throttler) InitDBConfig(keyspace, shard string) { } func (throttler *Throttler) GetMetricsQuery() string { - return throttler.metricsQuery.Load().(string) + if customQuery := throttler.GetCustomMetricsQuery(); customQuery != "" { + return customQuery + } + lagSelfMetric, ok := base.RegisteredSelfMetrics[base.LagMetricName].(*base.LagSelfMetric) + if !ok { + return "" + } + return lagSelfMetric.GetQuery() } func (throttler *Throttler) GetCustomMetricsQuery() string { @@ -337,27 +327,6 @@ func (throttler *Throttler) initConfig() { IgnoreDialTCPErrors: true, }, } - metrics := make(map[base.MetricName]*config.MySQLMetricConfigurationSettings) - for _, metricsName := range base.KnownMetricNames { - metrics[metricsName] = &config.MySQLMetricConfigurationSettings{ - Name: metricsName, - } - } - metrics[base.DefaultMetricName].CustomQuery = "" - metrics[base.DefaultMetricName].Threshold.Store(throttler.MetricsThreshold.Load()) - - metrics[base.LagMetricName].CustomQuery = sqlparser.BuildParsedQuery(defaultReplicationLagQuery, sidecar.GetIdentifier()).Query - metrics[base.LagMetricName].Threshold.Store(throttler.MetricsThreshold.Load()) - - metrics[base.ThreadsRunningMetricName].CustomQuery = threadsRunningQuery - metrics[base.ThreadsRunningMetricName].Threshold.Store(math.Float64bits(defaultThresholds[base.ThreadsRunningMetricName])) - - metrics[base.CustomMetricName].CustomQuery = "" - metrics[base.CustomMetricName].Threshold.Store(math.Float64bits(defaultThresholds[base.CustomMetricName])) - - metrics[base.LoadAvgMetricName].Threshold.Store(math.Float64bits(defaultThresholds[base.LoadAvgMetricName])) - - throttler.configSettings.MySQLStore.Metrics = metrics } // readThrottlerConfig proactively reads the throttler's config from SrvKeyspace in local topo @@ -386,7 +355,7 @@ func (throttler *Throttler) normalizeThrottlerConfig(throttlerConfig *topodatapb if throttlerConfig.CustomQuery == "" { // no custom query; we check replication lag if throttlerConfig.Threshold == 0 { - throttlerConfig.Threshold = defaultThresholds[base.LagMetricName] + throttlerConfig.Threshold = base.RegisteredSelfMetrics[base.LagMetricName].DefaultThreshold() } } return throttlerConfig @@ -440,11 +409,6 @@ func (throttler *Throttler) convergeMetricThresholds() { // Note: you should be holding the initMutex when calling this function. func (throttler *Throttler) applyThrottlerConfig(ctx context.Context, throttlerConfig *topodatapb.ThrottlerConfig) { log.Infof("Throttler: applying topo config: %+v", throttlerConfig) - if throttlerConfig.CustomQuery == "" { - throttler.metricsQuery.Store(sqlparser.BuildParsedQuery(defaultReplicationLagQuery, sidecar.GetIdentifier()).Query) - } else { - throttler.metricsQuery.Store(throttlerConfig.CustomQuery) - } throttler.customMetricsQuery.Store(throttlerConfig.CustomQuery) if throttlerConfig.Threshold > 0 || throttlerConfig.CustomQuery != "" { // We do not allow Threshold=0, unless there is a custom query. @@ -644,10 +608,6 @@ func (throttler *Throttler) Open() error { log.Infof("Throttler: opening") var ctx context.Context ctx, throttler.cancelOpenContext = context.WithCancel(context.Background()) - // The query needs to be dynamically built because the sidecar database name - // is not known when the TabletServer is created, which in turn creates the - // Throttler. - throttler.metricsQuery.Store(sqlparser.BuildParsedQuery(defaultReplicationLagQuery, sidecar.GetIdentifier()).Query) // default throttler.customMetricsQuery.Store("") throttler.initConfig() throttler.pool.Open(throttler.env.Config().DB.AppWithDB(), throttler.env.Config().DB.DbaWithDB(), throttler.env.Config().DB.AppDebugWithDB()) @@ -724,99 +684,6 @@ func (throttler *Throttler) stimulatePrimaryThrottler(ctx context.Context, tmCli return nil } -func (throttler *Throttler) readSelfLoadAvgPerCore(ctx context.Context) *base.ThrottleMetric { - metric := &base.ThrottleMetric{ - Scope: base.SelfScope, - Alias: throttler.tabletAlias, - } - - coreCount := throttler.hostCpuCoreCount.Load() - if coreCount == 0 { - // Count cores. This number is not going to change in the lifetime of this tablet, - // hence it makes sense to read it once then cache it. - - // We choose to read /proc/cpuinfo over executing "nproc" or similar commands. - var coreCount int32 - f, err := os.Open("/proc/cpuinfo") - if err != nil { - return metric.WithError(err) - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - if strings.HasPrefix(scanner.Text(), "processor") { - coreCount++ - } - } - - if err := scanner.Err(); err != nil { - return metric.WithError(err) - } - throttler.hostCpuCoreCount.Store(coreCount) - } - if coreCount == 0 { - return metric.WithError(fmt.Errorf("could not determine number of cores")) - } - { - content, err := os.ReadFile("/proc/loadavg") - if err != nil { - return metric.WithError(err) - } - fields := strings.Fields(string(content)) - if len(fields) == 0 { - return metric.WithError(fmt.Errorf("unexpected /proc/loadavg content")) - } - loadAvg, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return metric.WithError(err) - } - metric.Value = loadAvg / float64(throttler.hostCpuCoreCount.Load()) - } - return metric -} - -// readSelfMySQLThrottleMetric reads the metric from this very tablet or from its backend mysql. -func (throttler *Throttler) readSelfMySQLThrottleMetric(ctx context.Context, query string) *base.ThrottleMetric { - metric := &base.ThrottleMetric{ - Scope: base.SelfScope, - Alias: throttler.tabletAlias, - } - if query == "" { - return metric - } - conn, err := throttler.pool.Get(ctx, nil) - if err != nil { - return metric.WithError(err) - } - defer conn.Recycle() - - tm, err := conn.Conn.Exec(ctx, query, 1, true) - if err != nil { - return metric.WithError(err) - } - row := tm.Named().Row() - if row == nil { - return metric.WithError(fmt.Errorf("no results for readSelfThrottleMetric")) - } - - metricsQueryType := base.GetMetricsQueryType(query) - switch metricsQueryType { - case base.MetricsQueryTypeSelect: - // We expect a single row, single column result. - // The "for" iteration below is just a way to get first result without knowing column name - for k := range row { - metric.Value, metric.Err = row.ToFloat64(k) - } - case base.MetricsQueryTypeShowGlobal: - metric.Value, metric.Err = strconv.ParseFloat(row["Value"].ToString(), 64) - default: - metric.Err = fmt.Errorf("Unsupported metrics query type for query: %s", throttler.GetMetricsQuery()) - } - - return metric -} - // throttledAppsSnapshot returns a snapshot (a copy) of current throttled apps func (throttler *Throttler) throttledAppsSnapshot() map[string]cache.Item { return throttler.throttledApps.Items() @@ -1063,22 +930,40 @@ func (throttler *Throttler) generateTabletProbeFunction(scope base.Scope, tmClie } } +// readSelfThrottleMetricsInternal rreads all registsred self metrics on this tablet (or backend MySQL server). +// This is the actual place where metrics are read, to be later aggregated and/or propagated to other tablets. func (throttler *Throttler) readSelfThrottleMetricsInternal(ctx context.Context) base.ThrottleMetrics { - - writeMetric := func(metricName base.MetricName, metric *base.ThrottleMetric) { - metric.Name = metricName + result := make(base.ThrottleMetrics, len(base.RegisteredSelfMetrics)) + writeMetric := func(metric *base.ThrottleMetric) { select { case <-ctx.Done(): return case throttler.throttleMetricChan <- metric: } } + readMetric := func(selfMetric base.SelfMetric) *base.ThrottleMetric { + if !selfMetric.RequiresConn() { + return selfMetric.Read(ctx, throttler, nil) + } + conn, err := throttler.pool.Get(ctx, nil) + if err != nil { + return &base.ThrottleMetric{Err: err} + } + defer conn.Recycle() + return selfMetric.Read(ctx, throttler, conn.Conn) + } + for metricName, selfMetric := range base.RegisteredSelfMetrics { + if metricName == base.DefaultMetricName { + continue + } + metric := readMetric(selfMetric) + metric.Name = metricName + metric.Alias = throttler.tabletAlias - go writeMetric(base.LagMetricName, throttler.readSelfMySQLThrottleMetric(ctx, sqlparser.BuildParsedQuery(defaultReplicationLagQuery, sidecar.GetIdentifier()).Query)) - go writeMetric(base.ThreadsRunningMetricName, throttler.readSelfMySQLThrottleMetric(ctx, threadsRunningQuery)) - go writeMetric(base.CustomMetricName, throttler.readSelfMySQLThrottleMetric(ctx, throttler.GetCustomMetricsQuery())) - go writeMetric(base.LoadAvgMetricName, throttler.readSelfLoadAvgPerCore(ctx)) - return nil + go writeMetric(metric) + result[metricName] = metric + } + return result } func (throttler *Throttler) collectSelfMetrics(ctx context.Context) { @@ -1170,21 +1055,19 @@ func (throttler *Throttler) refreshInventory(ctx context.Context) error { } } - metricsThreshold := throttler.MetricsThreshold.Load() metricNameUsedAsDefault := throttler.metricNameUsedAsDefault() - mysqlSettings := &throttler.configSettings.MySQLStore - mysqlSettings.Metrics[base.DefaultMetricName].Threshold.Store(metricsThreshold) - for metricName, metricConfig := range mysqlSettings.Metrics { - threshold := metricConfig.Threshold.Load() - if metricName == metricNameUsedAsDefault && metricsThreshold != 0 { + metricsThreshold := throttler.GetMetricsThreshold() + for metricName, selfMetric := range base.RegisteredSelfMetrics { + threshold := selfMetric.DefaultThreshold() + if (metricName == metricNameUsedAsDefault || metricName == base.DefaultMetricName) && metricsThreshold != 0 { // backwards compatibility to v20: threshold = metricsThreshold } - - throttler.metricThresholds.Set(inventoryPrefix+metricName.String(), math.Float64frombits(threshold), cache.DefaultExpiration) + throttler.metricThresholds.Set(inventoryPrefix+metricName.String(), threshold, cache.DefaultExpiration) } throttler.convergeMetricThresholds() - clusterSettingsCopy := *mysqlSettings + + var clusterSettingsCopy config.MySQLConfigurationSettings = throttler.configSettings.MySQLStore // config may dynamically change, but internal structure (config.Settings().MySQLStore.Clusters in our case) // is immutable and can only be _replaced_. Hence, it's safe to read in a goroutine: collect := func() error { diff --git a/go/vt/vttablet/tabletserver/throttle/throttler_test.go b/go/vt/vttablet/tabletserver/throttle/throttler_test.go index 9ea75090515..e095378926c 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler_test.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler_test.go @@ -247,7 +247,8 @@ func newTestThrottler() *Throttler { tabletTypeFunc: func() topodatapb.TabletType { return topodatapb.TabletType_PRIMARY }, overrideTmClient: &fakeTMClient{}, } - throttler.metricsQuery.Store(metricsQuery) + lagSelfMetric := base.RegisteredSelfMetrics[base.LagMetricName].(*base.LagSelfMetric) + lagSelfMetric.SetQuery(metricsQuery) throttler.MetricsThreshold.Store(math.Float64bits(0.75)) throttler.configSettings = config.NewConfigurationSettings() throttler.initConfig() @@ -1143,7 +1144,8 @@ func TestRefreshInventory(t *testing.T) { ts: &FakeTopoServer{}, inventory: base.NewInventory(), } - throttler.metricsQuery.Store(metricsQuery) + lagSelfMetric := base.RegisteredSelfMetrics[base.LagMetricName].(*base.LagSelfMetric) + lagSelfMetric.SetQuery(metricsQuery) throttler.configSettings = configSettings throttler.initConfig() throttler.initThrottleTabletTypes() From 304237d6fdeaa5980afa9c1479d2a087e98bd457 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:54:36 +0300 Subject: [PATCH 15/34] Deprecate `UpdateThrottlerConfig`'s `--check-as-check-self` and `--check-as-check-shard` flags (#16507) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- go/cmd/vtctldclient/command/throttler.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go/cmd/vtctldclient/command/throttler.go b/go/cmd/vtctldclient/command/throttler.go index 7a4f6c92653..da8b0763b0d 100644 --- a/go/cmd/vtctldclient/command/throttler.go +++ b/go/cmd/vtctldclient/command/throttler.go @@ -37,7 +37,7 @@ import ( var ( // UpdateThrottlerConfig makes a UpdateThrottlerConfig gRPC call to a vtctld. UpdateThrottlerConfig = &cobra.Command{ - Use: "UpdateThrottlerConfig [--enable|--disable] [--threshold=] [--custom-query=] [--check-as-check-self|--check-as-check-shard] [--throttle-app|unthrottle-app=] [--throttle-app-ratio=] [--throttle-app-duration=] ", + Use: "UpdateThrottlerConfig [--enable|--disable] [--metric-name=] [--threshold=] [--custom-query=] [--throttle-app|unthrottle-app=] [--throttle-app-ratio=] [--throttle-app-duration=] [--throttle-app-exempt=] [--app-name= --app-metrics=] ", Short: "Update the tablet throttler configuration for all tablets in the given keyspace (across all cells)", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), @@ -171,6 +171,8 @@ func init() { UpdateThrottlerConfig.Flags().StringVar(&updateThrottlerConfigOptions.CustomQuery, "custom-query", "", "custom throttler check query") UpdateThrottlerConfig.Flags().BoolVar(&updateThrottlerConfigOptions.CheckAsCheckSelf, "check-as-check-self", false, "/throttler/check requests behave as is /throttler/check-self was called") UpdateThrottlerConfig.Flags().BoolVar(&updateThrottlerConfigOptions.CheckAsCheckShard, "check-as-check-shard", false, "use standard behavior for /throttler/check requests") + UpdateThrottlerConfig.Flags().MarkDeprecated("check-as-check-self", "specify metric with scope in --app-metrics to apply to all checks, or use --scope in CheckThrottler for a specific check") + UpdateThrottlerConfig.Flags().MarkDeprecated("check-as-check-shard", "specify metric with scope in --app-metrics to apply to all checks, or use --scope in CheckThrottler for a specific check") UpdateThrottlerConfig.Flags().StringVar(&unthrottledAppRule.Name, "unthrottle-app", "", "an app name to unthrottle") UpdateThrottlerConfig.Flags().StringVar(&throttledAppRule.Name, "throttle-app", "", "an app name to throttle") @@ -178,7 +180,7 @@ func init() { UpdateThrottlerConfig.Flags().DurationVar(&throttledAppDuration, "throttle-app-duration", throttle.DefaultAppThrottleDuration, "duration after which throttled app rule expires (app specififed in --throttled-app)") UpdateThrottlerConfig.Flags().BoolVar(&throttledAppRule.Exempt, "throttle-app-exempt", throttledAppRule.Exempt, "exempt this app from being at all throttled. WARNING: use with extreme care, as this is likely to push metrics beyond the throttler's threshold, and starve other apps") UpdateThrottlerConfig.Flags().StringVar(&updateThrottlerConfigOptions.AppName, "app-name", "", "app name for which to assign metrics (requires --app-metrics)") - UpdateThrottlerConfig.Flags().StringSliceVar(&updateThrottlerConfigOptions.AppCheckedMetrics, "app-metrics", nil, "metrics to be used when checking the throttler for the app (requires --app-name). Empty to restore to default metrics") + UpdateThrottlerConfig.Flags().StringSliceVar(&updateThrottlerConfigOptions.AppCheckedMetrics, "app-metrics", nil, "metrics to be used when checking the throttler for the app (requires --app-name). Empty to restore to default metrics. Example: --app-metrics=lag,custom,shard/loadavg") UpdateThrottlerConfig.MarkFlagsMutuallyExclusive("unthrottle-app", "throttle-app") UpdateThrottlerConfig.MarkFlagsRequiredTogether("app-name", "app-metrics") From cc64418ef9f6f267cee2384976d43e35618dd367 Mon Sep 17 00:00:00 2001 From: Rohit Nayak <57520317+rohit-nayak-ps@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:00:02 +0200 Subject: [PATCH 16/34] Multi-tenant Movetables: add e2e test for vdiff (#16309) Signed-off-by: Rohit Nayak --- go/test/endtoend/vreplication/multi_tenant_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go/test/endtoend/vreplication/multi_tenant_test.go b/go/test/endtoend/vreplication/multi_tenant_test.go index eda245ee597..b10395b08c8 100644 --- a/go/test/endtoend/vreplication/multi_tenant_test.go +++ b/go/test/endtoend/vreplication/multi_tenant_test.go @@ -203,6 +203,7 @@ func TestMultiTenantSimple(t *testing.T) { lastIndex = insertRows(lastIndex, sourceKeyspace) waitForWorkflowState(t, vc, fmt.Sprintf("%s.%s", targetKeyspace, mt.workflowName), binlogdatapb.VReplicationWorkflowState_Running.String()) + vdiff(t, targetKeyspace, workflowName, defaultCellName, false, true, nil) mt.SwitchReads() confirmOnlyReadsSwitched(t) @@ -362,6 +363,7 @@ func TestMultiTenantSharded(t *testing.T) { // Note: we cannot insert into the target keyspace since that is never routed to the source keyspace. lastIndex = insertRows(lastIndex, sourceKeyspace) waitForWorkflowState(t, vc, fmt.Sprintf("%s.%s", targetKeyspace, mt.workflowName), binlogdatapb.VReplicationWorkflowState_Running.String()) + vdiff(t, targetKeyspace, workflowName, defaultCellName, false, true, nil) mt.SwitchReadsAndWrites() // Note: here we have already switched, and we can insert into the target keyspace, and it should get reverse // replicated to the source keyspace. The source keyspace is routed to the target keyspace at this point. @@ -558,6 +560,7 @@ func (mtm *multiTenantMigration) switchTraffic(tenantId int64) { mt := mtm.getActiveMoveTables(tenantId) ksWorkflow := fmt.Sprintf("%s.%s", mtm.targetKeyspace, mt.workflowName) waitForWorkflowState(t, vc, ksWorkflow, binlogdatapb.VReplicationWorkflowState_Running.String()) + vdiff(t, mt.targetKeyspace, mt.workflowName, defaultCellName, false, true, nil) mtm.insertSomeData(t, tenantId, sourceKeyspaceName, numAdditionalRowsPerTenant) mt.SwitchReadsAndWrites() mtm.insertSomeData(t, tenantId, sourceKeyspaceName, numAdditionalRowsPerTenant) From 43e828049cc5e46ef425b180e4af494edcf65a48 Mon Sep 17 00:00:00 2001 From: Matt Lord Date: Wed, 31 Jul 2024 17:09:53 -0400 Subject: [PATCH 17/34] VTAdmin: Upgrade websockets js package (#16504) --- web/vtadmin/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/vtadmin/package-lock.json b/web/vtadmin/package-lock.json index a14da6bf0ee..928217e9d4a 100644 --- a/web/vtadmin/package-lock.json +++ b/web/vtadmin/package-lock.json @@ -10339,7 +10339,7 @@ "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^12.0.1", - "ws": "^8.13.0", + "ws": "^8.17.1", "xml-name-validator": "^4.0.0" }, "engines": { @@ -18182,9 +18182,9 @@ } }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" From d4d64e844908a971a5ae8b554baa9c6ac42e81bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Taylor?= Date: Thu, 1 Aug 2024 14:55:51 +0200 Subject: [PATCH 18/34] bugfix: Allow cross-keyspace joins (#16520) Signed-off-by: Andres Taylor --- .github/workflows/vitess_tester_vtgate.yml | 6 +- .../two_sharded_keyspaces/queries.test | 26 +++++++ .../two_sharded_keyspaces/vschema.json | 72 +++++++++++++++++++ .../planbuilder/operators/join_merging.go | 2 +- .../planbuilder/operators/sharded_routing.go | 8 ++- .../operators/subquery_planning.go | 2 +- .../planbuilder/testdata/from_cases.json | 49 +++++++++++++ .../testdata/unsupported_cases.json | 5 ++ test/templates/cluster_vitess_tester.tpl | 6 +- 9 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test create mode 100644 go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/vschema.json diff --git a/.github/workflows/vitess_tester_vtgate.yml b/.github/workflows/vitess_tester_vtgate.yml index f2a669289bc..3aa2450ecbb 100644 --- a/.github/workflows/vitess_tester_vtgate.yml +++ b/.github/workflows/vitess_tester_vtgate.yml @@ -112,7 +112,7 @@ jobs: go install github.com/vitessio/go-junit-report@HEAD # install vitess tester - go install github.com/vitessio/vitess-tester@eb953122baba163ed8ccaa6642458ee984f5d7e4 + go install github.com/vitessio/vitess-tester@89dd933a9ea0e15f69ca58b9c8ea09a358762cca - name: Setup launchable dependencies if: steps.skip-workflow.outputs.is_draft == 'false' && steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' && github.base_ref == 'main' @@ -144,9 +144,9 @@ jobs: # We go over all the directories in the given path. # If there is a vschema file there, we use it, otherwise we let vitess-tester autogenerate it. if [ -f $dir/vschema.json ]; then - vitess-tester --sharded --xunit --test-dir $dir --vschema "$dir"vschema.json + vitess-tester --xunit --vschema "$dir"vschema.json $dir/*.test else - vitess-tester --sharded --xunit --test-dir $dir + vitess-tester --sharded --xunit $dir/*.test fi # Number the reports by changing their file names. mv report.xml report"$i".xml diff --git a/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test b/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test new file mode 100644 index 00000000000..f625333313a --- /dev/null +++ b/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test @@ -0,0 +1,26 @@ +use customer; +create table if not exists customer( + customer_id bigint not null, + email varbinary(128), + primary key(customer_id) +) ENGINE=InnoDB; +insert into customer.customer(customer_id, email) values(1, '[alice@domain.com](mailto:alice@domain.com)'); +insert into customer.customer(customer_id, email) values(2, '[bob@domain.com](mailto:bob@domain.com)'); +insert into customer.customer(customer_id, email) values(3, '[charlie@domain.com](mailto:charlie@domain.com)'); +insert into customer.customer(customer_id, email) values(4, '[dan@domain.com](mailto:dan@domain.com)'); +insert into customer.customer(customer_id, email) values(5, '[eve@domain.com](mailto:eve@domain.com)'); +use corder; +create table if not exists corder( + order_id bigint not null, + customer_id bigint, + sku varbinary(128), + price bigint, + primary key(order_id) +) ENGINE=InnoDB; +insert into corder.corder(order_id, customer_id, sku, price) values(1, 1, 'SKU-1001', 100); +insert into corder.corder(order_id, customer_id, sku, price) values(2, 2, 'SKU-1002', 30); +insert into corder.corder(order_id, customer_id, sku, price) values(3, 3, 'SKU-1002', 30); +insert into corder.corder(order_id, customer_id, sku, price) values(4, 4, 'SKU-1002', 30); +insert into corder.corder(order_id, customer_id, sku, price) values(5, 5, 'SKU-1002', 30); + +select co.order_id, co.customer_id, co.price from corder.corder co left join customer.customer cu on co.customer_id=cu.customer_id where cu.customer_id=1; diff --git a/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/vschema.json b/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/vschema.json new file mode 100644 index 00000000000..5672042bace --- /dev/null +++ b/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/vschema.json @@ -0,0 +1,72 @@ +{ + "keyspaces": { + "customer": { + "sharded": true, + "vindexes": { + "hash": { + "type": "hash", + "params": {}, + "owner": "" + } + }, + "tables": { + "customer": { + "type": "", + "column_vindexes": [ + { + "column": "customer_id", + "name": "hash", + "columns": [] + } + ], + "columns": [], + "pinned": "", + "column_list_authoritative": false, + "source": "" + } + }, + "require_explicit_routing": false, + "foreign_key_mode": 0, + "multi_tenant_spec": null + }, + "corder": { + "sharded": true, + "vindexes": { + "hash": { + "type": "hash", + "params": {}, + "owner": "" + } + }, + "tables": { + "corder": { + "type": "", + "column_vindexes": [ + { + "column": "customer_id", + "name": "hash", + "columns": [] + } + ], + "columns": [], + "pinned": "", + "column_list_authoritative": false, + "source": "" + } + }, + "require_explicit_routing": false, + "foreign_key_mode": 0, + "multi_tenant_spec": null + } + }, + "routing_rules": { + "rules": [] + }, + "shard_routing_rules": { + "rules": [] + }, + "keyspace_routing_rules": null, + "mirror_rules": { + "rules": [] + } +} \ No newline at end of file diff --git a/go/vt/vtgate/planbuilder/operators/join_merging.go b/go/vt/vtgate/planbuilder/operators/join_merging.go index 34ad2c5adbc..7508a2034ce 100644 --- a/go/vt/vtgate/planbuilder/operators/join_merging.go +++ b/go/vt/vtgate/planbuilder/operators/join_merging.go @@ -76,7 +76,7 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr // sharded routing is complex, so we handle it in a separate method case a == sharded && b == sharded: - return tryMergeJoinShardedRouting(ctx, lhsRoute, rhsRoute, m, joinPredicates) + return tryMergeShardedRouting(ctx, lhsRoute, rhsRoute, m, joinPredicates, false /*isSubquery*/) default: return nil diff --git a/go/vt/vtgate/planbuilder/operators/sharded_routing.go b/go/vt/vtgate/planbuilder/operators/sharded_routing.go index 1319b76f040..40532f729c3 100644 --- a/go/vt/vtgate/planbuilder/operators/sharded_routing.go +++ b/go/vt/vtgate/planbuilder/operators/sharded_routing.go @@ -634,11 +634,12 @@ func (tr *ShardedRouting) extraInfo() string { ) } -func tryMergeJoinShardedRouting( +func tryMergeShardedRouting( ctx *plancontext.PlanningContext, routeA, routeB *Route, m merger, joinPredicates []sqlparser.Expr, + isSubquery bool, ) *Route { sameKeyspace := routeA.Routing.Keyspace() == routeB.Routing.Keyspace() tblA := routeA.Routing.(*ShardedRouting) @@ -670,7 +671,10 @@ func tryMergeJoinShardedRouting( } if !sameKeyspace { - panic(vterrors.VT12001("cross-shard correlated subquery")) + if isSubquery { + panic(vterrors.VT12001("cross-shard correlated subquery")) + } + return nil } canMerge := canMergeOnFilters(ctx, routeA, routeB, joinPredicates) diff --git a/go/vt/vtgate/planbuilder/operators/subquery_planning.go b/go/vt/vtgate/planbuilder/operators/subquery_planning.go index aef54bd8c41..d8427fce09f 100644 --- a/go/vt/vtgate/planbuilder/operators/subquery_planning.go +++ b/go/vt/vtgate/planbuilder/operators/subquery_planning.go @@ -758,7 +758,7 @@ func mergeSubqueryInputs(ctx *plancontext.PlanningContext, in, out Operator, joi // sharded routing is complex, so we handle it in a separate method case inner == sharded && outer == sharded: - return tryMergeJoinShardedRouting(ctx, inRoute, outRoute, m, joinPredicates) + return tryMergeShardedRouting(ctx, inRoute, outRoute, m, joinPredicates, true /*isSubquery*/) default: return nil diff --git a/go/vt/vtgate/planbuilder/testdata/from_cases.json b/go/vt/vtgate/planbuilder/testdata/from_cases.json index 7b2cda26ff6..74a8b91430d 100644 --- a/go/vt/vtgate/planbuilder/testdata/from_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/from_cases.json @@ -4619,6 +4619,55 @@ ] } }, + { + "comment": "Cross keyspace join", + "query": "select 1 from user join t1 on user.id = t1.id", + "plan": { + "QueryType": "SELECT", + "Original": "select 1 from user join t1 on user.id = t1.id", + "Instructions": { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0", + "JoinVars": { + "t1_id": 1 + }, + "TableName": "t1_`user`", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "zlookup_unique", + "Sharded": true + }, + "FieldQuery": "select 1, t1.id from t1 where 1 != 1", + "Query": "select 1, t1.id from t1", + "Table": "t1" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from `user` where 1 != 1", + "Query": "select 1 from `user` where `user`.id = :t1_id", + "Table": "`user`", + "Values": [ + ":t1_id" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.user", + "zlookup_unique.t1" + ] + } + }, { "comment": "Select everything from a derived table having a cross-shard join", "query": "select * from (select u.foo * ue.bar from user u join user_extra ue) as dt", diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index e1618d91efb..287e5cc992c 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -294,6 +294,11 @@ "query": "select 1 from music union (select id from user union all select name from unsharded)", "plan": "VT12001: unsupported: nesting of UNIONs on the right-hand side" }, + { + "comment": "Cross keyspace query with subquery", + "query": "select 1 from user where id in (select id from t1)", + "plan": "VT12001: unsupported: cross-shard correlated subquery" + }, { "comment": "multi-shard union", "query": "select 1 from music union (select id from user union select name from unsharded)", diff --git a/test/templates/cluster_vitess_tester.tpl b/test/templates/cluster_vitess_tester.tpl index 7660cd05f14..ead64a909d2 100644 --- a/test/templates/cluster_vitess_tester.tpl +++ b/test/templates/cluster_vitess_tester.tpl @@ -110,7 +110,7 @@ jobs: go install github.com/vitessio/go-junit-report@HEAD # install vitess tester - go install github.com/vitessio/vitess-tester@eb953122baba163ed8ccaa6642458ee984f5d7e4 + go install github.com/vitessio/vitess-tester@89dd933a9ea0e15f69ca58b9c8ea09a358762cca - name: Setup launchable dependencies if: steps.skip-workflow.outputs.is_draft == 'false' && steps.skip-workflow.outputs.skip-workflow == 'false' && steps.changes.outputs.end_to_end == 'true' && github.base_ref == 'main' @@ -142,9 +142,9 @@ jobs: # We go over all the directories in the given path. # If there is a vschema file there, we use it, otherwise we let vitess-tester autogenerate it. if [ -f $dir/vschema.json ]; then - vitess-tester --sharded --xunit --test-dir $dir --vschema "$dir"vschema.json + vitess-tester --xunit --vschema "$dir"vschema.json $dir/*.test else - vitess-tester --sharded --xunit --test-dir $dir + vitess-tester --sharded --xunit $dir/*.test fi # Number the reports by changing their file names. mv report.xml report"$i".xml From 498450f1e2ee943dea6aa0b3c076f8333d9da429 Mon Sep 17 00:00:00 2001 From: vitess-bot <139342327+vitess-bot@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:57:01 -0400 Subject: [PATCH 19/34] Upgrade the Golang Dependencies (#16514) Signed-off-by: GitHub Co-authored-by: frouioui --- go.mod | 48 ++++++++++++------------ go.sum | 113 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 80 insertions(+), 81 deletions(-) diff --git a/go.mod b/go.mod index 59d0ad29827..1e80e0b9f62 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.22.5 require ( cloud.google.com/go/storage v1.43.0 - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240716105424-66b64c4bb379 github.com/Azure/azure-pipeline-go v0.2.3 github.com/Azure/azure-storage-blob-go v0.15.0 github.com/HdrHistogram/hdrhistogram-go v0.9.0 // indirect github.com/aquarapid/vaultlib v0.5.1 github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go v1.54.19 + github.com/aws/aws-sdk-go v1.55.5 github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/corpix/uarand v0.1.1 // indirect @@ -40,7 +40,7 @@ require ( github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1 github.com/montanaflynn/stats v0.7.1 github.com/olekukonko/tablewriter v0.0.5 - github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e + github.com/opentracing-contrib/go-grpc v0.0.0-20240724223109-9dec25a38fa8 github.com/opentracing/opentracing-go v1.2.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect @@ -58,15 +58,15 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/tchap/go-patricia v2.3.0+incompatible - github.com/tidwall/gjson v1.17.1 + github.com/tidwall/gjson v1.17.3 github.com/tinylib/msgp v1.2.0 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 github.com/z-division/go-zookeeper v1.0.0 - go.etcd.io/etcd/api/v3 v3.5.14 - go.etcd.io/etcd/client/pkg/v3 v3.5.14 - go.etcd.io/etcd/client/v3 v3.5.14 + go.etcd.io/etcd/api/v3 v3.5.15 + go.etcd.io/etcd/client/pkg/v3 v3.5.15 + go.etcd.io/etcd/client/v3 v3.5.15 go.uber.org/mock v0.2.0 golang.org/x/crypto v0.25.0 // indirect golang.org/x/mod v0.19.0 // indirect @@ -77,13 +77,13 @@ require ( golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 golang.org/x/tools v0.23.0 - google.golang.org/api v0.188.0 - google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/api v0.189.0 + google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect google.golang.org/grpc v1.65.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 google.golang.org/grpc/examples v0.0.0-20210430044426-28078834f35b google.golang.org/protobuf v1.34.2 - gopkg.in/DataDog/dd-trace-go.v1 v1.65.1 + gopkg.in/DataDog/dd-trace-go.v1 v1.66.0 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/ldap.v2 v2.5.1 sigs.k8s.io/yaml v1.4.0 @@ -104,21 +104,21 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/xlab/treeprint v1.2.0 go.uber.org/goleak v1.3.0 - golang.org/x/exp v0.0.0-20240707233637-46b078467d37 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/sync v0.7.0 gonum.org/v1/gonum v0.14.0 - modernc.org/sqlite v1.30.2 + modernc.org/sqlite v1.31.1 ) require ( cloud.google.com/go v0.115.0 // indirect - cloud.google.com/go/auth v0.7.1 // indirect + cloud.google.com/go/auth v0.7.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect - cloud.google.com/go/iam v1.1.11 // indirect + cloud.google.com/go/iam v1.1.12 // indirect github.com/DataDog/appsec-internal-go v1.7.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.55.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.55.0 // indirect + github.com/DataDog/datadog-agent/pkg/obfuscate v0.55.2 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.55.2 // indirect github.com/DataDog/go-libddwaf/v3 v3.3.0 // indirect github.com/DataDog/go-sqllexer v0.0.12 // indirect github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect @@ -137,9 +137,9 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -152,7 +152,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-ieproxy v0.0.12 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect @@ -184,14 +184,14 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect - modernc.org/libc v1.55.1 // indirect + modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect + modernc.org/libc v1.55.7 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect diff --git a/go.sum b/go.sum index 3ff3456402a..2be032bcc92 100644 --- a/go.sum +++ b/go.sum @@ -2,20 +2,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/auth v0.7.1 h1:Iv1bbpzJ2OIg16m94XI9/tlzZZl3cdeR3nGVGj78N7s= -cloud.google.com/go/auth v0.7.1/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE= +cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw= -cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ= -cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k= -cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= +cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw= +cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg= +cloud.google.com/go/longrunning v0.5.11 h1:Havn1kGjz3whCfoD8dxMLP73Ph5w+ODyZB9RUsDxtGk= +cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4= cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240716105424-66b64c4bb379 h1:shYAfOpsleWVaSwGxQjmi+BBIwzj5jxB1FTCpVqs0N8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240716105424-66b64c4bb379/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -34,10 +34,10 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIbyBlfvLmAeb0= github.com/DataDog/appsec-internal-go v1.7.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.55.0 h1:q8n6qVTPATzBL02e0rxCOrLFWDNw4as0GcuKWkJENFk= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.55.0/go.mod h1:/C99KWKukVnTtIiYCQ55izSNDQceREb8vSPa3zUn6jc= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.55.0 h1:+T+3WXCFC9g8r4AVBaD3v1LOKSLyKAtl/LtXyCTcm7I= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.55.0/go.mod h1:3yFk56PJ57yS1GqI9HAsS4PSlAeGCC9RQA7jxKzYj6g= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.55.2 h1:Uc0V20r3BdVdPZ0AjDd8IpRKG9+8GBHW68Sg94aqRlU= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.55.2/go.mod h1:/C99KWKukVnTtIiYCQ55izSNDQceREb8vSPa3zUn6jc= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.55.2 h1:Q7aIwDE+aKXclYhHrKRQvEl5IdabmdaFw5+QBp5DlNA= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.55.2/go.mod h1:3yFk56PJ57yS1GqI9HAsS4PSlAeGCC9RQA7jxKzYj6g= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI6LDrKU= github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= @@ -73,8 +73,8 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= -github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -142,6 +142,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -206,8 +208,8 @@ github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9 github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8= github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -218,8 +220,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= @@ -229,7 +231,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hashicorp/consul/api v1.29.2 h1:aYyRn8EdE2mSfG14S1+L9Qkjtz8RzmaWh6AcNGRNwPw= github.com/hashicorp/consul/api v1.29.2/go.mod h1:0YObcaLNDSbtlgzIRtmRXI1ZkeuK0trCBxwZQ4MYnIk= github.com/hashicorp/consul/proto-public v0.6.2 h1:+DA/3g/IiKlJZb88NBn0ZgXrxJp2NlvCZdEyl+qxvL0= @@ -336,8 +337,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -378,8 +379,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= -github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= +github.com/opentracing-contrib/go-grpc v0.0.0-20240724223109-9dec25a38fa8 h1:gHTSPFezGeYzTWCvpPM6lBanwXfuksik5Hy5MEHtvUA= +github.com/opentracing-contrib/go-grpc v0.0.0-20240724223109-9dec25a38fa8/go.mod h1:z1k3YVSdAPSXtMUPS1TBWG5DaNWlT+VCbB0Qm3QJe74= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -502,8 +503,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs= github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= -github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -525,12 +526,12 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/z-division/go-zookeeper v1.0.0 h1:ULsCj0nP6+U1liDFWe+2oEF6o4amixoDcDlwEUghVUY= github.com/z-division/go-zookeeper v1.0.0/go.mod h1:6X4UioQXpvyezJJl4J9NHAJKsoffCwy5wCaaTktXjOA= -go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= -go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= -go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= -go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= -go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= -go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= +go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk= +go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM= +go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA= +go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= +go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= +go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= @@ -571,8 +572,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -593,7 +594,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -688,12 +688,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= -google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= +google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI= +google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -701,15 +701,14 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= -google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY= +google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M= +google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE= +google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= @@ -735,8 +734,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/DataDog/dd-trace-go.v1 v1.65.1 h1:Ne7kzWr/br/jwhUJR7CnqPl/mUpNxa6LfgZs0S4htZM= -gopkg.in/DataDog/dd-trace-go.v1 v1.65.1/go.mod h1:beNFIWd/H04d0k96cfltgiDH2+t0T5sDbyYLF3VTXqk= +gopkg.in/DataDog/dd-trace-go.v1 v1.66.0 h1:025+lLubGtpiDWrRmSOxoFBPIiVRVYRcqP9oLabVOeg= +gopkg.in/DataDog/dd-trace-go.v1 v1.66.0/go.mod h1:Av6AXGmQCQAbDnwNoPiuUz1k3GS8TwQjj+vEdwmEpmM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= @@ -771,16 +770,16 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= -modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/ccgo/v4 v4.20.5 h1:s04akhT2dysD0DFOlv9fkQ6oUTLPYgMnnDk9oaqjszM= +modernc.org/ccgo/v4 v4.20.5/go.mod h1:fYXClPUMWxWaz1Xj5sHbzW/ZENEFeuHLToqBxUk41nE= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= -modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.55.1 h1:2K/vMbMDGymj0CO4mcQybYW8SW3czB+u9rlghpMkTrI= -modernc.org/libc v1.55.1/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/gc/v2 v2.4.3 h1:Ik4ZcMbC7aY4ZDPUhzXVXi7GMub9QcXLTfXn3mWpNw8= +modernc.org/gc/v2 v2.4.3/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e h1:WPC4v0rNIFb2PY+nBBEEKyugPPRHPzUgyN3xZPpGK58= +modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.7 h1:/5PMGAF3tyZhK72WpoqeLNtgUUpYMrnhT+Gm/5tVDgs= +modernc.org/libc v1.55.7/go.mod h1:JXguUpMkbw1gknxspNE9XaG+kk9hDAAnBxpA6KGLiyA= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= @@ -789,8 +788,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk= -modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= +modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From d122ac6f1e3dab3da9813dec8c5f6b73fd553106 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Thu, 1 Aug 2024 20:29:36 +0530 Subject: [PATCH 20/34] fix: show tables to use any keyspace on system schema (#16521) Signed-off-by: Harshit Gangal --- go/test/endtoend/vtgate/misc_test.go | 9 ++++++--- go/vt/vtgate/executor_test.go | 9 +++++++++ go/vt/vtgate/vcursor_impl.go | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/go/test/endtoend/vtgate/misc_test.go b/go/test/endtoend/vtgate/misc_test.go index 128d930718c..bcb4f68a935 100644 --- a/go/test/endtoend/vtgate/misc_test.go +++ b/go/test/endtoend/vtgate/misc_test.go @@ -95,10 +95,13 @@ func TestShowTables(t *testing.T) { conn, closer := start(t) defer closer() - query := "show tables;" - qr := utils.Exec(t, conn, query) - + qr := utils.Exec(t, conn, "show tables") assert.Equal(t, "Tables_in_ks", qr.Fields[0].Name) + + // no error on executing `show tables` on system schema + utils.Exec(t, conn, `use mysql`) + utils.Exec(t, conn, "show tables") + utils.Exec(t, conn, "show tables from information_schema") } func TestCastConvert(t *testing.T) { diff --git a/go/vt/vtgate/executor_test.go b/go/vt/vtgate/executor_test.go index cce717674d6..b8e2b996780 100644 --- a/go/vt/vtgate/executor_test.go +++ b/go/vt/vtgate/executor_test.go @@ -1105,6 +1105,15 @@ func TestExecutorShowTargeted(t *testing.T) { } } +func TestExecutorShowFromSystemSchema(t *testing.T) { + executor, _, _, _, ctx := createExecutorEnv(t) + + session := NewSafeSession(&vtgatepb.Session{TargetString: "mysql"}) + + _, err := executor.Execute(ctx, nil, "TestExecutorShowFromSystemSchema", session, "show tables", nil) + require.NoError(t, err) +} + func TestExecutorUse(t *testing.T) { executor, _, _, _, ctx := createExecutorEnv(t) diff --git a/go/vt/vtgate/vcursor_impl.go b/go/vt/vtgate/vcursor_impl.go index ae9c073e123..ee000abed8f 100644 --- a/go/vt/vtgate/vcursor_impl.go +++ b/go/vt/vtgate/vcursor_impl.go @@ -819,7 +819,7 @@ func commentedShardQueries(shardQueries []*querypb.BoundQuery, marginComments sq // TargetDestination implements the ContextVSchema interface func (vc *vcursorImpl) TargetDestination(qualifier string) (key.Destination, *vindexes.Keyspace, topodatapb.TabletType, error) { - keyspaceName := vc.keyspace + keyspaceName := vc.getActualKeyspace() if vc.destination == nil && qualifier != "" { keyspaceName = qualifier } From 33030b7a3b269bf8d0d77ff7d0998cba5735dc49 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Thu, 1 Aug 2024 21:55:12 +0530 Subject: [PATCH 21/34] fix: sequence table next value acl permission to writer role (#16509) Signed-off-by: Harshit Gangal --- go/vt/vttablet/tabletserver/planbuilder/permission.go | 8 +++++++- .../vttablet/tabletserver/planbuilder/permission_test.go | 6 ++++++ .../tabletserver/planbuilder/testdata/exec_cases.txt | 8 ++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/go/vt/vttablet/tabletserver/planbuilder/permission.go b/go/vt/vttablet/tabletserver/planbuilder/permission.go index 79b2f9eb430..dbc6cfccdad 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/permission.go +++ b/go/vt/vttablet/tabletserver/planbuilder/permission.go @@ -36,7 +36,13 @@ func BuildPermissions(stmt sqlparser.Statement) []Permission { var permissions []Permission // All Statement types myst be covered here. switch node := stmt.(type) { - case *sqlparser.Union, *sqlparser.Select: + case *sqlparser.Select: + role := tableacl.READER + if _, ok := node.SelectExprs[0].(*sqlparser.Nextval); ok { + role = tableacl.WRITER + } + permissions = buildSubqueryPermissions(node, role, permissions) + case *sqlparser.Union: permissions = buildSubqueryPermissions(node, tableacl.READER, permissions) case *sqlparser.Insert: permissions = buildTableExprPermissions(node.Table, tableacl.WRITER, permissions) diff --git a/go/vt/vttablet/tabletserver/planbuilder/permission_test.go b/go/vt/vttablet/tabletserver/planbuilder/permission_test.go index 6d42118cb0b..0ece6ed19b2 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/permission_test.go +++ b/go/vt/vttablet/tabletserver/planbuilder/permission_test.go @@ -174,6 +174,12 @@ func TestBuildPermissions(t *testing.T) { }, { TableName: "t1", // derived table in update or delete needs reader permission as they cannot be modified. }}, + }, { + input: "select next 10 values from seq", + output: []Permission{{ + TableName: "seq", + Role: tableacl.WRITER, + }}, }} for _, tcase := range tcases { diff --git a/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt b/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt index 977b3822050..cafbe43231d 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt +++ b/go/vt/vttablet/tabletserver/planbuilder/testdata/exec_cases.txt @@ -140,7 +140,7 @@ "Permissions": [ { "TableName": "seq", - "Role": 0 + "Role": 1 } ], "NextCount": "1" @@ -154,7 +154,7 @@ "Permissions": [ { "TableName": "seq", - "Role": 0 + "Role": 1 } ], "NextCount": "10" @@ -169,7 +169,7 @@ "Permissions": [ { "TableName": "seq", - "Role": 0 + "Role": 1 } ], "NextCount": ":a" @@ -183,7 +183,7 @@ "Permissions": [ { "TableName": "seq", - "Role": 0 + "Role": 1 } ], "NextCount": "12345667852342342342323423423" From 4a897495f9e9db8c044d6bdfd7312457dbefb727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Taylor?= Date: Mon, 5 Aug 2024 08:34:00 +0200 Subject: [PATCH 22/34] simplify merging logic (#16525) Signed-off-by: Andres Taylor --- .github/workflows/vitess_tester_vtgate.yml | 2 +- .../two_sharded_keyspaces/queries.test | 59 +++++++++++-------- .../planbuilder/operators/join_merging.go | 2 +- .../planbuilder/operators/sharded_routing.go | 13 +--- .../operators/subquery_planning.go | 2 +- .../testdata/unsupported_cases.json | 4 +- test/templates/cluster_vitess_tester.tpl | 2 +- 7 files changed, 45 insertions(+), 39 deletions(-) diff --git a/.github/workflows/vitess_tester_vtgate.yml b/.github/workflows/vitess_tester_vtgate.yml index 3aa2450ecbb..9e11697f778 100644 --- a/.github/workflows/vitess_tester_vtgate.yml +++ b/.github/workflows/vitess_tester_vtgate.yml @@ -57,7 +57,7 @@ jobs: end_to_end: - 'go/**/*.go' - 'go/vt/sidecardb/**/*.sql' - - 'go/test/endtoend/onlineddl/vrepl_suite/**' + - 'go/test/endtoend/vtgate/vitess_tester/**' - 'test.go' - 'Makefile' - 'build.env' diff --git a/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test b/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test index f625333313a..28c55e559c9 100644 --- a/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test +++ b/go/test/endtoend/vtgate/vitess_tester/two_sharded_keyspaces/queries.test @@ -1,26 +1,39 @@ use customer; -create table if not exists customer( - customer_id bigint not null, - email varbinary(128), - primary key(customer_id) -) ENGINE=InnoDB; -insert into customer.customer(customer_id, email) values(1, '[alice@domain.com](mailto:alice@domain.com)'); -insert into customer.customer(customer_id, email) values(2, '[bob@domain.com](mailto:bob@domain.com)'); -insert into customer.customer(customer_id, email) values(3, '[charlie@domain.com](mailto:charlie@domain.com)'); -insert into customer.customer(customer_id, email) values(4, '[dan@domain.com](mailto:dan@domain.com)'); -insert into customer.customer(customer_id, email) values(5, '[eve@domain.com](mailto:eve@domain.com)'); +create table if not exists customer +( + customer_id bigint not null, + email varbinary(128), + primary key (customer_id) +) ENGINE = InnoDB; + +insert into customer.customer(customer_id, email) +values (1, '[alice@domain.com](mailto:alice@domain.com)'), + (2, '[bob@domain.com](mailto:bob@domain.com)'), + (3, '[charlie@domain.com](mailto:charlie@domain.com)'), + (4, '[dan@domain.com](mailto:dan@domain.com)'), + (5, '[eve@domain.com](mailto:eve@domain.com)'); use corder; -create table if not exists corder( - order_id bigint not null, - customer_id bigint, - sku varbinary(128), - price bigint, - primary key(order_id) -) ENGINE=InnoDB; -insert into corder.corder(order_id, customer_id, sku, price) values(1, 1, 'SKU-1001', 100); -insert into corder.corder(order_id, customer_id, sku, price) values(2, 2, 'SKU-1002', 30); -insert into corder.corder(order_id, customer_id, sku, price) values(3, 3, 'SKU-1002', 30); -insert into corder.corder(order_id, customer_id, sku, price) values(4, 4, 'SKU-1002', 30); -insert into corder.corder(order_id, customer_id, sku, price) values(5, 5, 'SKU-1002', 30); +create table if not exists corder +( + order_id bigint not null, + customer_id bigint, + sku varbinary(128), + price bigint, + primary key (order_id) +) ENGINE = InnoDB; +insert into corder.corder(order_id, customer_id, sku, price) +values (1, 1, 'SKU-1001', 100), + (2, 2, 'SKU-1002', 30), + (3, 3, 'SKU-1002', 30), + (4, 4, 'SKU-1002', 30), + (5, 5, 'SKU-1002', 30); + +select co.order_id, co.customer_id, co.price +from corder.corder co + left join customer.customer cu on co.customer_id = cu.customer_id +where cu.customer_id = 1; -select co.order_id, co.customer_id, co.price from corder.corder co left join customer.customer cu on co.customer_id=cu.customer_id where cu.customer_id=1; +# This query was accidentally disallowed by https://github.com/vitessio/vitess/pull/16520 +select 1 +from customer.customer +where customer_id in (select customer_id from corder.corder where price > 50); \ No newline at end of file diff --git a/go/vt/vtgate/planbuilder/operators/join_merging.go b/go/vt/vtgate/planbuilder/operators/join_merging.go index 7508a2034ce..6f2af8b5ff9 100644 --- a/go/vt/vtgate/planbuilder/operators/join_merging.go +++ b/go/vt/vtgate/planbuilder/operators/join_merging.go @@ -76,7 +76,7 @@ func mergeJoinInputs(ctx *plancontext.PlanningContext, lhs, rhs Operator, joinPr // sharded routing is complex, so we handle it in a separate method case a == sharded && b == sharded: - return tryMergeShardedRouting(ctx, lhsRoute, rhsRoute, m, joinPredicates, false /*isSubquery*/) + return tryMergeShardedRouting(ctx, lhsRoute, rhsRoute, m, joinPredicates) default: return nil diff --git a/go/vt/vtgate/planbuilder/operators/sharded_routing.go b/go/vt/vtgate/planbuilder/operators/sharded_routing.go index 40532f729c3..066cb47d9a9 100644 --- a/go/vt/vtgate/planbuilder/operators/sharded_routing.go +++ b/go/vt/vtgate/planbuilder/operators/sharded_routing.go @@ -23,7 +23,6 @@ import ( "vitess.io/vitess/go/mysql/collations" "vitess.io/vitess/go/slice" "vitess.io/vitess/go/vt/sqlparser" - "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/engine" "vitess.io/vitess/go/vt/vtgate/evalengine" "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" @@ -639,9 +638,10 @@ func tryMergeShardedRouting( routeA, routeB *Route, m merger, joinPredicates []sqlparser.Expr, - isSubquery bool, ) *Route { - sameKeyspace := routeA.Routing.Keyspace() == routeB.Routing.Keyspace() + if routeA.Routing.Keyspace() != routeB.Routing.Keyspace() { + return nil + } tblA := routeA.Routing.(*ShardedRouting) tblB := routeB.Routing.(*ShardedRouting) @@ -670,13 +670,6 @@ func tryMergeShardedRouting( return nil } - if !sameKeyspace { - if isSubquery { - panic(vterrors.VT12001("cross-shard correlated subquery")) - } - return nil - } - canMerge := canMergeOnFilters(ctx, routeA, routeB, joinPredicates) if !canMerge { return nil diff --git a/go/vt/vtgate/planbuilder/operators/subquery_planning.go b/go/vt/vtgate/planbuilder/operators/subquery_planning.go index d8427fce09f..0893afbeead 100644 --- a/go/vt/vtgate/planbuilder/operators/subquery_planning.go +++ b/go/vt/vtgate/planbuilder/operators/subquery_planning.go @@ -758,7 +758,7 @@ func mergeSubqueryInputs(ctx *plancontext.PlanningContext, in, out Operator, joi // sharded routing is complex, so we handle it in a separate method case inner == sharded && outer == sharded: - return tryMergeShardedRouting(ctx, inRoute, outRoute, m, joinPredicates, true /*isSubquery*/) + return tryMergeShardedRouting(ctx, inRoute, outRoute, m, joinPredicates) default: return nil diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index 287e5cc992c..0e230b3e44d 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -296,8 +296,8 @@ }, { "comment": "Cross keyspace query with subquery", - "query": "select 1 from user where id in (select id from t1)", - "plan": "VT12001: unsupported: cross-shard correlated subquery" + "query": "select 1 from user where id = (select id from t1 where user.foo = t1.bar)", + "plan": "VT12001: unsupported: correlated subquery is only supported for EXISTS" }, { "comment": "multi-shard union", diff --git a/test/templates/cluster_vitess_tester.tpl b/test/templates/cluster_vitess_tester.tpl index ead64a909d2..541bfd5c6a0 100644 --- a/test/templates/cluster_vitess_tester.tpl +++ b/test/templates/cluster_vitess_tester.tpl @@ -55,7 +55,7 @@ jobs: end_to_end: - 'go/**/*.go' - 'go/vt/sidecardb/**/*.sql' - - 'go/test/endtoend/onlineddl/vrepl_suite/**' + - 'go/test/endtoend/vtgate/vitess_tester/**' - 'test.go' - 'Makefile' - 'build.env' From 623e82075fc6f5cb866abf3ad93439498ad72150 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Tue, 6 Aug 2024 11:28:16 +0530 Subject: [PATCH 23/34] vindex function: error when keyspace not selected (#16534) Signed-off-by: Harshit Gangal --- go/vt/vtgate/engine/fake_vcursor_test.go | 2 +- go/vt/vtgate/engine/vindex_func.go | 3 +++ go/vt/vtgate/executor_select_test.go | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/go/vt/vtgate/engine/fake_vcursor_test.go b/go/vt/vtgate/engine/fake_vcursor_test.go index adc425c0632..653bcf64576 100644 --- a/go/vt/vtgate/engine/fake_vcursor_test.go +++ b/go/vt/vtgate/engine/fake_vcursor_test.go @@ -342,7 +342,7 @@ func (t *noopVCursor) ExceedsMaxMemoryRows(numRows int) bool { } func (t *noopVCursor) GetKeyspace() string { - return "" + return "test_ks" } func (t *noopVCursor) RecordWarning(warning *querypb.QueryWarning) { diff --git a/go/vt/vtgate/engine/vindex_func.go b/go/vt/vtgate/engine/vindex_func.go index ecd83baeaad..13507631716 100644 --- a/go/vt/vtgate/engine/vindex_func.go +++ b/go/vt/vtgate/engine/vindex_func.go @@ -153,6 +153,9 @@ func (vf *VindexFunc) mapVindex(ctx context.Context, vcursor VCursor, bindVars m case key.DestinationKeyspaceID: if len(d) > 0 { if vcursor != nil { + if vcursor.GetKeyspace() == "" { + return nil, vterrors.VT09005() + } resolvedShards, _, err := vcursor.ResolveDestinations(ctx, vcursor.GetKeyspace(), nil, []key.Destination{d}) if err != nil { return nil, err diff --git a/go/vt/vtgate/executor_select_test.go b/go/vt/vtgate/executor_select_test.go index bd24907af9b..fa448092550 100644 --- a/go/vt/vtgate/executor_select_test.go +++ b/go/vt/vtgate/executor_select_test.go @@ -427,6 +427,19 @@ func TestSetSystemVariablesWithReservedConnection(t *testing.T) { sbc1.Queries = nil } +func TestSelectVindexFunc(t *testing.T) { + executor, _, _, _, _ := createExecutorEnv(t) + + query := "select * from hash_index where id = 1" + session := NewAutocommitSession(&vtgatepb.Session{}) + _, err := executor.Execute(context.Background(), nil, "TestSelectVindexFunc", session, query, nil) + require.ErrorContains(t, err, "VT09005: no database selected") + + session.TargetString = KsTestSharded + _, err = executor.Execute(context.Background(), nil, "TestSelectVindexFunc", session, query, nil) + require.NoError(t, err) +} + func TestCreateTableValidTimestamp(t *testing.T) { executor, sbc1, _, _, _ := createExecutorEnv(t) executor.normalize = true From d042d7c22c672d3823f6f914303f142d7957a0ed Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Wed, 7 Aug 2024 11:10:12 +0530 Subject: [PATCH 24/34] vttablet api distributed transaction changes (#16506) Signed-off-by: Harshit Gangal --- go/vt/vterrors/code.go | 2 + go/vt/vtgate/tx_conn.go | 8 +- go/vt/vttablet/tabletserver/dt_executor.go | 40 ++++++--- .../vttablet/tabletserver/dt_executor_test.go | 85 +++++++++++++++++-- go/vt/vttablet/tabletserver/tx_prep_pool.go | 4 +- .../tabletserver/tx_prep_pool_test.go | 33 +++---- 6 files changed, 125 insertions(+), 47 deletions(-) diff --git a/go/vt/vterrors/code.go b/go/vt/vterrors/code.go index d485c930b77..857ba538ebe 100644 --- a/go/vt/vterrors/code.go +++ b/go/vt/vterrors/code.go @@ -98,6 +98,7 @@ var ( VT09024 = errorWithoutState("VT09024", vtrpcpb.Code_FAILED_PRECONDITION, "could not map %v to a unique keyspace id: %v", "Unable to determine the shard for the given row.") VT10001 = errorWithoutState("VT10001", vtrpcpb.Code_ABORTED, "foreign key constraints are not allowed", "Foreign key constraints are not allowed, see https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/.") + VT10002 = errorWithoutState("VT10002", vtrpcpb.Code_ABORTED, "atomic distributed transaction not allowed: %s", "The distributed transaction cannot be committed. A rollback decision is taken.") VT12001 = errorWithoutState("VT12001", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: %s", "This statement is unsupported by Vitess. Please rewrite your query to use supported syntax.") VT12002 = errorWithoutState("VT12002", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: cross-shard foreign keys", "Vitess does not support cross shard foreign keys.") @@ -182,6 +183,7 @@ var ( VT09023, VT09024, VT10001, + VT10002, VT12001, VT12002, VT13001, diff --git a/go/vt/vtgate/tx_conn.go b/go/vt/vtgate/tx_conn.go index f8b08def10c..e388740ee6a 100644 --- a/go/vt/vtgate/tx_conn.go +++ b/go/vt/vtgate/tx_conn.go @@ -458,21 +458,21 @@ func (txc *TxConn) resolveTx(ctx context.Context, target *querypb.Target, transa case querypb.TransactionState_PREPARE: // If state is PREPARE, make a decision to rollback and // fallthrough to the rollback workflow. - if err := txc.tabletGateway.SetRollback(ctx, target, transaction.Dtid, mmShard.TransactionId); err != nil { + if err = txc.tabletGateway.SetRollback(ctx, target, transaction.Dtid, mmShard.TransactionId); err != nil { return err } fallthrough case querypb.TransactionState_ROLLBACK: - if err := txc.resumeRollback(ctx, target, transaction); err != nil { + if err = txc.resumeRollback(ctx, target, transaction); err != nil { return err } case querypb.TransactionState_COMMIT: - if err := txc.resumeCommit(ctx, target, transaction); err != nil { + if err = txc.resumeCommit(ctx, target, transaction); err != nil { return err } default: // Should never happen. - return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "invalid state: %v", transaction.State) + return vterrors.VT13001(fmt.Sprintf("invalid state: %v", transaction.State)) } return nil } diff --git a/go/vt/vttablet/tabletserver/dt_executor.go b/go/vt/vttablet/tabletserver/dt_executor.go index edf4438b8b2..5f4e7644766 100644 --- a/go/vt/vttablet/tabletserver/dt_executor.go +++ b/go/vt/vttablet/tabletserver/dt_executor.go @@ -63,14 +63,14 @@ func (dte *DTExecutor) Prepare(transactionID int64, dtid string) error { // If no queries were executed, we just rollback. if len(conn.TxProperties().Queries) == 0 { - conn.Release(tx.TxRollback) + dte.te.txPool.RollbackAndRelease(dte.ctx, conn) return nil } // If the connection is tainted, we cannot prepare it. As there could be temporary tables involved. if conn.IsTainted() { - conn.Release(tx.TxRollback) - return vterrors.VT12001("cannot prepare the transaction on a reserved connection") + dte.te.txPool.RollbackAndRelease(dte.ctx, conn) + return vterrors.VT10002("cannot prepare the transaction on a reserved connection") } err = dte.te.preparedPool.Put(conn, dtid) @@ -88,30 +88,34 @@ func (dte *DTExecutor) Prepare(transactionID int64, dtid string) error { // CommitPrepared commits a prepared transaction. If the operation // fails, an error counter is incremented and the transaction is // marked as failed in the redo log. -func (dte *DTExecutor) CommitPrepared(dtid string) error { +func (dte *DTExecutor) CommitPrepared(dtid string) (err error) { if !dte.te.twopcEnabled { return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") } defer dte.te.env.Stats().QueryTimings.Record("COMMIT_PREPARED", time.Now()) - conn, err := dte.te.preparedPool.FetchForCommit(dtid) + var conn *StatefulConnection + conn, err = dte.te.preparedPool.FetchForCommit(dtid) if err != nil { return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "cannot commit dtid %s, state: %v", dtid, err) } + // No connection means the transaction was already committed. if conn == nil { return nil } // We have to use a context that will never give up, // even if the original context expires. ctx := trace.CopySpan(context.Background(), dte.ctx) - defer dte.te.txPool.RollbackAndRelease(ctx, conn) - err = dte.te.twoPC.DeleteRedo(ctx, conn, dtid) - if err != nil { - dte.markFailed(ctx, dtid) + defer func() { + if err != nil { + dte.markFailed(ctx, dtid) + log.Warningf("failed to commit the prepared transaction '%s' with error: %v", dtid, err) + } + dte.te.txPool.RollbackAndRelease(ctx, conn) + }() + if err = dte.te.twoPC.DeleteRedo(ctx, conn, dtid); err != nil { return err } - _, err = dte.te.txPool.Commit(ctx, conn) - if err != nil { - dte.markFailed(ctx, dtid) + if _, err = dte.te.txPool.Commit(ctx, conn); err != nil { return err } dte.te.preparedPool.Forget(dtid) @@ -207,6 +211,15 @@ func (dte *DTExecutor) StartCommit(transactionID int64, dtid string) error { } defer dte.te.txPool.RollbackAndRelease(dte.ctx, conn) + // If the connection is tainted, we cannot take a commit decision on it. + if conn.IsTainted() { + dte.inTransaction(func(conn *StatefulConnection) error { + return dte.te.twoPC.Transition(dte.ctx, conn, dtid, querypb.TransactionState_ROLLBACK) + }) + // return the error, defer call above will roll back the transaction. + return vterrors.VT10002("cannot commit the transaction on a reserved connection") + } + err = dte.te.twoPC.Transition(dte.ctx, conn, dtid, querypb.TransactionState_COMMIT) if err != nil { return err @@ -228,6 +241,9 @@ func (dte *DTExecutor) SetRollback(dtid string, transactionID int64) error { // If the transaction is still open, it will be rolled back. // Otherwise, it would have been rolled back by other means, like a timeout or vttablet/mysql restart. dte.te.Rollback(dte.ctx, transactionID) + } else { + // This is a warning because it should not happen in normal operation. + log.Warningf("SetRollback called with no transactionID for dtid %s", dtid) } return dte.inTransaction(func(conn *StatefulConnection) error { diff --git a/go/vt/vttablet/tabletserver/dt_executor_test.go b/go/vt/vttablet/tabletserver/dt_executor_test.go index 448dd63bf5a..045496eb4b8 100644 --- a/go/vt/vttablet/tabletserver/dt_executor_test.go +++ b/go/vt/vttablet/tabletserver/dt_executor_test.go @@ -21,9 +21,11 @@ import ( "errors" "fmt" "reflect" + "strings" "testing" "time" + "vitess.io/vitess/go/event/syslogger" "vitess.io/vitess/go/vt/vttablet/tabletserver/tx" "github.com/stretchr/testify/require" @@ -43,11 +45,42 @@ func TestTxExecutorEmptyPrepare(t *testing.T) { txe, tsv, db := newTestTxExecutor(t, ctx) defer db.Close() defer tsv.StopService() + + // start a transaction. txid := newTransaction(tsv, nil) - err := txe.Prepare(txid, "aa") + + // taint the connection. + sc, err := tsv.te.txPool.GetAndLock(txid, "taint") + require.NoError(t, err) + sc.Taint(ctx, nil) + sc.Unlock() + + err = txe.Prepare(txid, "aa") require.NoError(t, err) // Nothing should be prepared. require.Empty(t, txe.te.preparedPool.conns, "txe.te.preparedPool.conns") + require.False(t, sc.IsInTransaction(), "transaction should be roll back before returning the connection to the pool") +} + +func TestExecutorPrepareFailure(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + txe, tsv, db := newTestTxExecutor(t, ctx) + defer db.Close() + defer tsv.StopService() + + // start a transaction + txid := newTxForPrep(ctx, tsv) + + // taint the connection. + sc, err := tsv.te.txPool.GetAndLock(txid, "taint") + require.NoError(t, err) + sc.Taint(ctx, nil) + sc.Unlock() + + // try 2pc commit of Metadata Manager. + err = txe.Prepare(txid, "aa") + require.EqualError(t, err, "VT10002: atomic distributed transaction not allowed: cannot prepare the transaction on a reserved connection") } func TestTxExecutorPrepare(t *testing.T) { @@ -82,7 +115,7 @@ func TestDTExecutorPrepareResevedConn(t *testing.T) { txe.te.Reserve(ctx, nil, txid, nil) err := txe.Prepare(txid, "aa") - require.ErrorContains(t, err, "VT12001: unsupported: cannot prepare the transaction on a reserved connection") + require.ErrorContains(t, err, "VT10002: atomic distributed transaction not allowed: cannot prepare the transaction on a reserved connection") } func TestTxExecutorPrepareNotInTx(t *testing.T) { @@ -174,20 +207,31 @@ func TestTxExecutorCommitRedoFail(t *testing.T) { txe, tsv, db := newTestTxExecutor(t, ctx) defer db.Close() defer tsv.StopService() + + tl := syslogger.NewTestLogger() + defer tl.Close() + + // start a transaction. txid := newTxForPrep(ctx, tsv) - // Allow all additions to redo logs to succeed + + // prepare the transaction db.AddQueryPattern("insert into _vt\\.redo_state.*", &sqltypes.Result{}) err := txe.Prepare(txid, "bb") require.NoError(t, err) - defer txe.RollbackPrepared("bb", 0) - db.AddQuery("update _vt.redo_state set state = 'Failed' where dtid = 'bb'", &sqltypes.Result{}) + + // fail commit prepare as the delete redo query is in rejected query. + db.AddRejectedQuery("delete from _vt.redo_state where dtid = 'bb'", errors.New("delete redo log fail")) + db.AddQuery("update _vt.redo_state set state = 0 where dtid = 'bb'", sqltypes.MakeTestResult(nil)) err = txe.CommitPrepared("bb") - require.Error(t, err) - require.Contains(t, err.Error(), "is not supported") - // A retry should fail differently. + require.ErrorContains(t, err, "delete redo log fail") + + // A retry should fail differently as the prepared transaction is marked as failed. err = txe.CommitPrepared("bb") require.Error(t, err) require.Contains(t, err.Error(), "cannot commit dtid bb, state: failed") + + require.Contains(t, strings.Join(tl.GetAllLogs(), "|"), + "failed to commit the prepared transaction 'bb' with error: unknown error: delete redo log fail") } func TestTxExecutorCommitRedoCommitFail(t *testing.T) { @@ -273,6 +317,31 @@ func TestExecutorStartCommit(t *testing.T) { require.Contains(t, err.Error(), "could not transition to COMMIT: aa") } +func TestExecutorStartCommitFailure(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + txe, tsv, db := newTestTxExecutor(t, ctx) + defer db.Close() + defer tsv.StopService() + + // start a transaction + txid := newTxForPrep(ctx, tsv) + + // taint the connection. + sc, err := tsv.te.txPool.GetAndLock(txid, "taint") + require.NoError(t, err) + sc.Taint(ctx, nil) + sc.Unlock() + + // add rollback state update expectation + rollbackTransition := fmt.Sprintf("update _vt.dt_state set state = %d where dtid = 'aa' and state = %d", int(querypb.TransactionState_ROLLBACK), int(querypb.TransactionState_PREPARE)) + db.AddQuery(rollbackTransition, sqltypes.MakeTestResult(nil)) + + // try 2pc commit of Metadata Manager. + err = txe.StartCommit(txid, "aa") + require.EqualError(t, err, "VT10002: atomic distributed transaction not allowed: cannot commit the transaction on a reserved connection") +} + func TestExecutorSetRollback(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/go/vt/vttablet/tabletserver/tx_prep_pool.go b/go/vt/vttablet/tabletserver/tx_prep_pool.go index 89547570cfc..22e0ce295c0 100644 --- a/go/vt/vttablet/tabletserver/tx_prep_pool.go +++ b/go/vt/vttablet/tabletserver/tx_prep_pool.go @@ -23,8 +23,8 @@ import ( ) var ( - errPrepCommitting = errors.New("committing") - errPrepFailed = errors.New("failed") + errPrepCommitting = errors.New("locked for committing") + errPrepFailed = errors.New("failed to commit") ) // TxPreparedPool manages connections for prepared transactions. diff --git a/go/vt/vttablet/tabletserver/tx_prep_pool_test.go b/go/vt/vttablet/tabletserver/tx_prep_pool_test.go index a1cf50edb56..cd2b5a180c1 100644 --- a/go/vt/vttablet/tabletserver/tx_prep_pool_test.go +++ b/go/vt/vttablet/tabletserver/tx_prep_pool_test.go @@ -19,6 +19,7 @@ package tabletserver import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -82,38 +83,28 @@ func TestPrepFetchForCommit(t *testing.T) { conn := &StatefulConnection{} got, err := pp.FetchForCommit("aa") require.NoError(t, err) - if got != nil { - t.Errorf("Get(aa): %v, want nil", got) - } + assert.Nil(t, got) + pp.Put(conn, "aa") got, err = pp.FetchForCommit("aa") require.NoError(t, err) - if got != conn { - t.Errorf("pp.Get(aa): %p, want %p", got, conn) - } + assert.Equal(t, conn, got) + _, err = pp.FetchForCommit("aa") - want := "committing" - if err == nil || err.Error() != want { - t.Errorf("FetchForCommit err: %v, want %s", err, want) - } + assert.ErrorContains(t, err, "locked for committing") + pp.SetFailed("aa") _, err = pp.FetchForCommit("aa") - want = "failed" - if err == nil || err.Error() != want { - t.Errorf("FetchForCommit err: %v, want %s", err, want) - } + assert.ErrorContains(t, err, "failed to commit") + pp.SetFailed("bb") _, err = pp.FetchForCommit("bb") - want = "failed" - if err == nil || err.Error() != want { - t.Errorf("FetchForCommit err: %v, want %s", err, want) - } + assert.ErrorContains(t, err, "failed to commit") + pp.Forget("aa") got, err = pp.FetchForCommit("aa") require.NoError(t, err) - if got != nil { - t.Errorf("Get(aa): %v, want nil", got) - } + assert.Nil(t, got) } func TestPrepFetchAll(t *testing.T) { From 27f2d736463364ead0bea817c0e73ed79be47883 Mon Sep 17 00:00:00 2001 From: Noble Mittal <62551163+beingnoble03@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:59:37 +0530 Subject: [PATCH 25/34] evalengine: Implement `PERIOD_ADD` (#16492) --- go/mysql/datetime/mydate.go | 45 ++++++++++++++++ go/mysql/datetime/mydate_test.go | 54 +++++++++++++++++++ go/vt/vtgate/evalengine/cached_size.go | 12 +++++ go/vt/vtgate/evalengine/compiler_asm.go | 20 +++++++ go/vt/vtgate/evalengine/fn_time.go | 57 ++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 22 ++++++++ go/vt/vtgate/evalengine/testcases/inputs.go | 5 ++ go/vt/vtgate/evalengine/translate_builtin.go | 7 +++ 8 files changed, 222 insertions(+) diff --git a/go/mysql/datetime/mydate.go b/go/mysql/datetime/mydate.go index 62cbb3f2524..5b77082055a 100644 --- a/go/mysql/datetime/mydate.go +++ b/go/mysql/datetime/mydate.go @@ -89,3 +89,48 @@ func DateFromDayNumber(daynr int) Date { d.year, d.month, d.day = mysqlDateFromDayNumber(daynr) return d } + +// ValidatePeriod validates the MySQL period. +// Returns false if period is non-positive or contains incorrect month value. +func ValidatePeriod(period int64) bool { + if period <= 0 { + return false + } + month := period % 100 + if month == 0 || month > 12 { + return false + } + return true +} + +// PeriodToMonths converts a MySQL period into number of months. +// This is an algorithm that has been reverse engineered from MySQL. +func PeriodToMonths(period int64) int64 { + p := uint64(period) + if p == 0 { + return 0 + } + y := p / 100 + if y < 70 { + y += 2000 + } else if y < 100 { + y += 1900 + } + return int64(y*12 + p%100 - 1) +} + +// MonthsToPeriod converts number of months into MySQL period. +// This is an algorithm that has been reverse engineered from MySQL. +func MonthsToPeriod(months int64) int64 { + m := uint64(months) + if m == 0 { + return 0 + } + y := m / 12 + if y < 70 { + y += 2000 + } else if y < 100 { + y += 1900 + } + return int64(y*100 + m%12 + 1) +} diff --git a/go/mysql/datetime/mydate_test.go b/go/mysql/datetime/mydate_test.go index bb5073b8ff8..a743db60709 100644 --- a/go/mysql/datetime/mydate_test.go +++ b/go/mysql/datetime/mydate_test.go @@ -65,3 +65,57 @@ func TestDayNumberFields(t *testing.T) { assert.Equal(t, wantDate, got) } } + +func TestValidatePeriod(t *testing.T) { + testCases := []struct { + period int64 + want bool + }{ + {110112, true}, + {101122, false}, + {-1112212, false}, + {7110, true}, + } + + for _, tc := range testCases { + got := ValidatePeriod(tc.period) + assert.Equal(t, tc.want, got) + } +} + +func TestPeriodToMonths(t *testing.T) { + testCases := []struct { + period int64 + want int64 + }{ + {0, 0}, + {110112, 13223}, + {100112, 12023}, + {7112, 23663}, + {200112, 24023}, + {112, 24023}, + } + + for _, tc := range testCases { + got := PeriodToMonths(tc.period) + assert.Equal(t, tc.want, got) + } +} + +func TestMonthsToPeriod(t *testing.T) { + testCases := []struct { + months int64 + want int64 + }{ + {0, 0}, + {13223, 110112}, + {12023, 100112}, + {23663, 197112}, + {24023, 200112}, + } + + for _, tc := range testCases { + got := MonthsToPeriod(tc.months) + assert.Equal(t, tc.want, got) + } +} diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 6f447f0d1c1..9009b069f5a 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1397,6 +1397,18 @@ func (cached *builtinPad) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinPeriodAdd) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(48) + } + // field CallExpr vitess.io/vitess/go/vt/vtgate/evalengine.CallExpr + size += cached.CallExpr.CachedSize(false) + return size +} func (cached *builtinPi) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 0cac66d9e5e..93781ed077b 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -4299,6 +4299,26 @@ func (asm *assembler) Fn_YEARWEEK() { }, "FN YEARWEEK DATE(SP-1)") } +func (asm *assembler) Fn_PERIOD_ADD() { + asm.adjustStack(-1) + asm.emit(func(env *ExpressionEnv) int { + if env.vm.stack[env.vm.sp-2] == nil { + env.vm.sp-- + return 1 + } + period := env.vm.stack[env.vm.sp-2].(*evalInt64).i + months := env.vm.stack[env.vm.sp-1].(*evalInt64).i + res, err := periodAdd(period, months) + if err != nil { + env.vm.err = err + return 0 + } + env.vm.stack[env.vm.sp-2] = res + env.vm.sp-- + return 1 + }, "FN PERIOD_ADD INT64(SP-2) INT64(SP-1)") +} + func (asm *assembler) Interval(l int) { asm.adjustStack(-l) asm.emit(func(env *ExpressionEnv) int { diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 8d920e9e135..90fcda2c32a 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -26,6 +26,9 @@ import ( "vitess.io/vitess/go/mysql/datetime" "vitess.io/vitess/go/mysql/decimal" "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/vterrors" + + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" ) var SystemTime = time.Now @@ -174,6 +177,10 @@ type ( CallExpr } + builtinPeriodAdd struct { + CallExpr + } + builtinDateMath struct { CallExpr sub bool @@ -214,6 +221,7 @@ var _ IR = (*builtinWeekDay)(nil) var _ IR = (*builtinWeekOfYear)(nil) var _ IR = (*builtinYear)(nil) var _ IR = (*builtinYearWeek)(nil) +var _ IR = (*builtinPeriodAdd)(nil) func (call *builtinNow) eval(env *ExpressionEnv) (eval, error) { now := env.time(call.utc) @@ -1964,6 +1972,55 @@ func (call *builtinYearWeek) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Int64, Col: collationNumeric, Flag: arg.Flag | flagNullable}, nil } +func periodAdd(period, months int64) (*evalInt64, error) { + if !datetime.ValidatePeriod(period) { + return nil, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongArguments, "Incorrect arguments to period_add") + } + return newEvalInt64(datetime.MonthsToPeriod(datetime.PeriodToMonths(period) + months)), nil +} + +func (b *builtinPeriodAdd) eval(env *ExpressionEnv) (eval, error) { + p, m, err := b.arg2(env) + if err != nil { + return nil, err + } + if p == nil || m == nil { + return nil, nil + } + period := evalToInt64(p) + months := evalToInt64(m) + return periodAdd(period.i, months.i) +} + +func (call *builtinPeriodAdd) compile(c *compiler) (ctype, error) { + period, err := call.Arguments[0].compile(c) + if err != nil { + return ctype{}, err + } + months, err := call.Arguments[1].compile(c) + if err != nil { + return ctype{}, err + } + + skip := c.compileNullCheck2(period, months) + + switch period.Type { + case sqltypes.Int64: + default: + c.asm.Convert_xi(2) + } + + switch months.Type { + case sqltypes.Int64: + default: + c.asm.Convert_xi(1) + } + + c.asm.Fn_PERIOD_ADD() + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Int64, Flag: period.Flag | months.Flag | flagNullable}, nil +} + func evalToInterval(itv eval, unit datetime.IntervalType, negate bool) *datetime.Interval { switch itv := itv.(type) { case *evalBytes: diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 003eb45c0a3..7d5305b21f7 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -155,6 +155,7 @@ var Cases = []TestCase{ {Run: FnWeekOfYear}, {Run: FnYear}, {Run: FnYearWeek}, + {Run: FnPeriodAdd}, {Run: FnInetAton}, {Run: FnInetNtoa}, {Run: FnInet6Aton}, @@ -2223,6 +2224,27 @@ func FnYearWeek(yield Query) { } } +func FnPeriodAdd(yield Query) { + for _, p := range inputBitwise { + for _, m := range inputBitwise { + yield(fmt.Sprintf("PERIOD_ADD(%s, %s)", p, m), nil) + } + } + for _, p := range inputPeriods { + for _, m := range inputBitwise { + yield(fmt.Sprintf("PERIOD_ADD(%s, %s)", p, m), nil) + } + } + + mysqlDocSamples := []string{ + `PERIOD_ADD(200801,2)`, + } + + for _, q := range mysqlDocSamples { + yield(q, nil) + } +} + func FnInetAton(yield Query) { for _, d := range ipInputs { yield(fmt.Sprintf("INET_ATON(%s)", d), nil) diff --git a/go/vt/vtgate/evalengine/testcases/inputs.go b/go/vt/vtgate/evalengine/testcases/inputs.go index eb94235d9b4..ac23281fd54 100644 --- a/go/vt/vtgate/evalengine/testcases/inputs.go +++ b/go/vt/vtgate/evalengine/testcases/inputs.go @@ -59,6 +59,11 @@ var inputBitwise = []string{ "64", "'64'", "_binary '64'", "X'40'", "_binary X'40'", } +var inputPeriods = []string{ + "110192", "'119812'", "2703", "7111", "200103", "200309", "0309", "-110102", "0", + "'032'", "223", "'-119812'", "-2703", "99999999999999999999999911", "'-0309'", +} + var radianInputs = []string{ "0", "1", diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go index d4c6bcdae5a..2c4d887ff19 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -528,6 +528,13 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) { default: return nil, argError(method) } + case "period_add": + switch len(args) { + case 2: + return &builtinPeriodAdd{CallExpr: call}, nil + default: + return nil, argError(method) + } case "inet_aton": if len(args) != 1 { return nil, argError(method) From 0b7c0e4989d65feeff06dc0aab0d029f8934a671 Mon Sep 17 00:00:00 2001 From: Manan Gupta <35839558+GuptaManan100@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:45:00 +0530 Subject: [PATCH 26/34] Fix: Offset planning in hash joins (#16540) Signed-off-by: Manan Gupta --- .../vtgate/vitess_tester/join/join.test | 32 ++++- .../vtgate/vitess_tester/join/vschema.json | 8 ++ .../planbuilder/operators/apply_join.go | 2 +- .../planbuilder/operators/dml_with_input.go | 2 +- .../vtgate/planbuilder/operators/hash_join.go | 23 +--- .../planbuilder/operators/offset_planning.go | 9 +- .../planbuilder/testdata/aggr_cases.json | 93 ++++++++------ .../planbuilder/testdata/from_cases.json | 113 ++++++++++++++++++ .../planbuilder/testdata/vschemas/schema.json | 6 + 9 files changed, 222 insertions(+), 66 deletions(-) diff --git a/go/test/endtoend/vtgate/vitess_tester/join/join.test b/go/test/endtoend/vtgate/vitess_tester/join/join.test index cffd3a1b3aa..72d79a1206e 100644 --- a/go/test/endtoend/vtgate/vitess_tester/join/join.test +++ b/go/test/endtoend/vtgate/vitess_tester/join/join.test @@ -25,6 +25,15 @@ CREATE TABLE `t3` CHARSET utf8mb4, COLLATE utf8mb4_unicode_ci; +CREATE TABLE `t4` +( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `col` int unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE InnoDB, + CHARSET utf8mb4, + COLLATE utf8mb4_unicode_ci; + insert into t1 (id, name) values (1, 'A'), (2, 'B'), @@ -43,7 +52,28 @@ values (1, 'A'), (4, 'B'), (5, 'B'); +insert into t4 (id, col) +values (1, 1), + (2, 2), + (3, 3); + -- wait_authoritative t1 -- wait_authoritative t2 -- wait_authoritative t3 -select 42 from t1 join t2 on t1.id = t2.t1_id join t3 on t1.id = t3.id where t1.name or t2.id or t3.name; +select 42 +from t1 + join t2 on t1.id = t2.t1_id + join t3 on t1.id = t3.id +where t1.name + or t2.id + or t3.name; + +# Complex query that requires hash join underneath a memory sort and ordered aggregate +select 1 +from t1 + join t2 on t1.id = t2.t1_id + join t4 on t4.col = t2.id + left join (select t4.col, count(*) as count from t4 group by t4.col) t3 on t3.col = t2.id +where t1.id IN (1, 2) +group by t2.id, t4.col; + diff --git a/go/test/endtoend/vtgate/vitess_tester/join/vschema.json b/go/test/endtoend/vtgate/vitess_tester/join/vschema.json index b922d3f760c..1105b951e61 100644 --- a/go/test/endtoend/vtgate/vitess_tester/join/vschema.json +++ b/go/test/endtoend/vtgate/vitess_tester/join/vschema.json @@ -31,6 +31,14 @@ "name": "hash" } ] + }, + "t4": { + "column_vindexes": [ + { + "column": "id", + "name": "hash" + } + ] } } } diff --git a/go/vt/vtgate/planbuilder/operators/apply_join.go b/go/vt/vtgate/planbuilder/operators/apply_join.go index d87fb529caf..ef36f6a6765 100644 --- a/go/vt/vtgate/planbuilder/operators/apply_join.go +++ b/go/vt/vtgate/planbuilder/operators/apply_join.go @@ -293,7 +293,7 @@ func (aj *ApplyJoin) AddWSColumn(ctx *plancontext.PlanningContext, offset int, u func (aj *ApplyJoin) planOffsets(ctx *plancontext.PlanningContext) Operator { if len(aj.Columns) > 0 { // we've already done offset planning - return aj + return nil } for _, col := range aj.JoinColumns.columns { // Read the type description for applyJoinColumn to understand the following code diff --git a/go/vt/vtgate/planbuilder/operators/dml_with_input.go b/go/vt/vtgate/planbuilder/operators/dml_with_input.go index 09859b90bac..3843e2f3fa8 100644 --- a/go/vt/vtgate/planbuilder/operators/dml_with_input.go +++ b/go/vt/vtgate/planbuilder/operators/dml_with_input.go @@ -114,7 +114,7 @@ func (d *DMLWithInput) planOffsets(ctx *plancontext.PlanningContext) Operator { } } d.BvList = bvList - return d + return nil } var _ Operator = (*DMLWithInput)(nil) diff --git a/go/vt/vtgate/planbuilder/operators/hash_join.go b/go/vt/vtgate/planbuilder/operators/hash_join.go index 1928f4dda9e..23d0d061e21 100644 --- a/go/vt/vtgate/planbuilder/operators/hash_join.go +++ b/go/vt/vtgate/planbuilder/operators/hash_join.go @@ -326,20 +326,9 @@ func (hj *HashJoin) addColumn(ctx *plancontext.PlanningContext, in sqlparser.Exp inOffset = op.AddColumn(ctx, false, false, aeWrap(expr)) } - // we turn the + // we have to turn the incoming offset to an outgoing offset of the columns this operator is exposing internalOffset := offsetter(inOffset) - - // ok, we have an offset from the input operator. Let's check if we already have it - // in our list of incoming columns - - for idx, offset := range hj.ColumnOffsets { - if internalOffset == offset { - return idx - } - } - hj.ColumnOffsets = append(hj.ColumnOffsets, internalOffset) - return len(hj.ColumnOffsets) - 1 } @@ -434,17 +423,7 @@ func (hj *HashJoin) addSingleSidedColumn( // we have to turn the incoming offset to an outgoing offset of the columns this operator is exposing internalOffset := offsetter(inOffset) - - // ok, we have an offset from the input operator. Let's check if we already have it - // in our list of incoming columns - for idx, offset := range hj.ColumnOffsets { - if internalOffset == offset { - return idx - } - } - hj.ColumnOffsets = append(hj.ColumnOffsets, internalOffset) - return len(hj.ColumnOffsets) - 1 } diff --git a/go/vt/vtgate/planbuilder/operators/offset_planning.go b/go/vt/vtgate/planbuilder/operators/offset_planning.go index 7a9cedccb89..e8301c18823 100644 --- a/go/vt/vtgate/planbuilder/operators/offset_planning.go +++ b/go/vt/vtgate/planbuilder/operators/offset_planning.go @@ -38,7 +38,6 @@ func planOffsets(ctx *plancontext.PlanningContext, root Operator) Operator { panic(vterrors.VT13001(fmt.Sprintf("should not see %T here", in))) case offsettable: newOp := op.planOffsets(ctx) - if newOp == nil { newOp = op } @@ -47,7 +46,13 @@ func planOffsets(ctx *plancontext.PlanningContext, root Operator) Operator { fmt.Println("Planned offsets for:") fmt.Println(ToTree(newOp)) } - return newOp, nil + + if newOp == op { + return newOp, nil + } else { + // We got a new operator from plan offsets. We should return that something has changed. + return newOp, Rewrote("planning offsets introduced a new operator") + } } return in, NoRewrite } diff --git a/go/vt/vtgate/planbuilder/testdata/aggr_cases.json b/go/vt/vtgate/planbuilder/testdata/aggr_cases.json index eca27d81213..628a959af1d 100644 --- a/go/vt/vtgate/planbuilder/testdata/aggr_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/aggr_cases.json @@ -6663,55 +6663,70 @@ "OrderBy": "(4|6) ASC, (5|7) ASC", "Inputs": [ { - "OperatorType": "Join", - "Variant": "HashLeftJoin", - "Collation": "binary", - "ComparisonType": "INT16", - "JoinColumnIndexes": "-1,1,-2,2,-3,3", - "Predicate": "`user`.col = ue.col", - "TableName": "`user`_user_extra", + "OperatorType": "Projection", + "Expressions": [ + "count(*) as count(*)", + "count(*) as count(*)", + "`user`.col as col", + "ue.col as col", + "`user`.foo as foo", + "ue.bar as bar", + "weight_string(`user`.foo) as weight_string(`user`.foo)", + "weight_string(ue.bar) as weight_string(ue.bar)" + ], "Inputs": [ { - "OperatorType": "Route", - "Variant": "Scatter", - "Keyspace": { - "Name": "user", - "Sharded": true - }, - "FieldQuery": "select count(*), `user`.col, `user`.foo from `user` where 1 != 1 group by `user`.col, `user`.foo", - "Query": "select count(*), `user`.col, `user`.foo from `user` group by `user`.col, `user`.foo", - "Table": "`user`" - }, - { - "OperatorType": "Aggregate", - "Variant": "Ordered", - "Aggregates": "count_star(0)", - "GroupBy": "1, (2|3)", - "ResultColumns": 3, + "OperatorType": "Join", + "Variant": "HashLeftJoin", + "Collation": "binary", + "ComparisonType": "INT16", + "JoinColumnIndexes": "-1,1,-2,2,-3,3,-3,3", + "Predicate": "`user`.col = ue.col", + "TableName": "`user`_user_extra", "Inputs": [ { - "OperatorType": "SimpleProjection", - "Columns": "2,0,1,3", + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select count(*), `user`.col, `user`.foo from `user` where 1 != 1 group by `user`.col, `user`.foo", + "Query": "select count(*), `user`.col, `user`.foo from `user` group by `user`.col, `user`.foo", + "Table": "`user`" + }, + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "count_star(0)", + "GroupBy": "1, (2|3)", + "ResultColumns": 3, "Inputs": [ { - "OperatorType": "Sort", - "Variant": "Memory", - "OrderBy": "0 ASC, (1|3) ASC", + "OperatorType": "SimpleProjection", + "Columns": "2,0,1,3", "Inputs": [ { - "OperatorType": "Limit", - "Count": "10", + "OperatorType": "Sort", + "Variant": "Memory", + "OrderBy": "0 ASC, (1|3) ASC", "Inputs": [ { - "OperatorType": "Route", - "Variant": "Scatter", - "Keyspace": { - "Name": "user", - "Sharded": true - }, - "FieldQuery": "select ue.col, ue.bar, 1, weight_string(ue.bar) from (select col, bar from user_extra where 1 != 1) as ue where 1 != 1", - "Query": "select ue.col, ue.bar, 1, weight_string(ue.bar) from (select col, bar from user_extra) as ue limit 10", - "Table": "user_extra" + "OperatorType": "Limit", + "Count": "10", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select ue.col, ue.bar, 1, weight_string(ue.bar) from (select col, bar from user_extra where 1 != 1) as ue where 1 != 1", + "Query": "select ue.col, ue.bar, 1, weight_string(ue.bar) from (select col, bar from user_extra) as ue limit 10", + "Table": "user_extra" + } + ] } ] } diff --git a/go/vt/vtgate/planbuilder/testdata/from_cases.json b/go/vt/vtgate/planbuilder/testdata/from_cases.json index 74a8b91430d..ca94f4ee866 100644 --- a/go/vt/vtgate/planbuilder/testdata/from_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/from_cases.json @@ -720,6 +720,119 @@ ] } }, + { + "comment": "Complex query that has hash left join underneath a memory sort and ordered aggregation", + "query": "select 1 from user join user_extra on user.id = user_extra.user_id join music on music.intcol = user_extra.col left join (select user_metadata.col, count(*) as count from user_metadata group by user_metadata.col) um on um.col = user_extra.col where user.id IN (103) group by user_extra.col, music.intcol", + "plan": { + "QueryType": "SELECT", + "Original": "select 1 from user join user_extra on user.id = user_extra.user_id join music on music.intcol = user_extra.col left join (select user_metadata.col, count(*) as count from user_metadata group by user_metadata.col) um on um.col = user_extra.col where user.id IN (103) group by user_extra.col, music.intcol", + "Instructions": { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "any_value(0) AS 1", + "GroupBy": "1, 4", + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "Sort", + "Variant": "Memory", + "OrderBy": "1 ASC, 4 ASC", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "HashLeftJoin", + "Collation": "binary", + "ComparisonType": "FLOAT64", + "JoinColumnIndexes": "-1,-2,1,-2,-4,-1", + "Predicate": "user_extra.col = um.col", + "TableName": "music_`user`, user_extra_user_metadata", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0,R:0,R:0,L:1", + "JoinVars": { + "music_intcol": 1 + }, + "TableName": "music_`user`, user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1, music.intcol from music where 1 != 1 group by music.intcol", + "Query": "select 1, music.intcol from music group by music.intcol", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_extra.col, user_extra.col from `user`, user_extra where 1 != 1 group by user_extra.col", + "Query": "select user_extra.col, user_extra.col from `user`, user_extra where `user`.id in (103) and user_extra.col = :music_intcol /* INT16 */ and `user`.id = user_extra.user_id group by user_extra.col", + "Table": "`user`, user_extra", + "Values": [ + "103" + ], + "Vindex": "user_index" + } + ] + }, + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "GroupBy": "(0|1)", + "ResultColumns": 1, + "Inputs": [ + { + "OperatorType": "SimpleProjection", + "Columns": "0,2", + "Inputs": [ + { + "OperatorType": "Aggregate", + "Variant": "Ordered", + "Aggregates": "sum_count_star(1) AS count", + "GroupBy": "(0|2)", + "ResultColumns": 3, + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select user_metadata.col, count(*) as `count`, weight_string(user_metadata.col) from user_metadata where 1 != 1 group by user_metadata.col, weight_string(user_metadata.col)", + "OrderBy": "(0|2) ASC", + "Query": "select user_metadata.col, count(*) as `count`, weight_string(user_metadata.col) from user_metadata group by user_metadata.col, weight_string(user_metadata.col) order by user_metadata.col asc", + "Table": "user_metadata" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user", + "user.user_extra", + "user.user_metadata" + ] + } + }, { "comment": "Straight-join (ignores the straight_join hint)", "query": "select m1.col from unsharded as m1 straight_join unsharded as m2", diff --git a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json index a8fe91e5d49..4fe275f2398 100644 --- a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json +++ b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json @@ -282,6 +282,12 @@ "column": "id", "name": "music_user_map" } + ], + "columns": [ + { + "name": "intcol", + "type": "INT16" + } ] }, "authoritative": { From cf8f5d1285ee4c861bd7b78dbd58d68ebacd1458 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 7 Aug 2024 16:31:05 +0100 Subject: [PATCH 27/34] Remove unused `formatRelativeTime` import (#16549) Signed-off-by: Graham Campbell --- web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx index 2d86e1141a6..ef521406e3d 100644 --- a/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx +++ b/web/vtadmin/src/components/routes/workflow/WorkflowStreams.tsx @@ -20,7 +20,7 @@ import { Link } from 'react-router-dom'; import { useWorkflow } from '../../../hooks/api'; import { formatAlias } from '../../../util/tablets'; -import { formatDateTime, formatRelativeTime } from '../../../util/time'; +import { formatDateTime } from '../../../util/time'; import { formatStreamKey, getStreams, getStreamSource, getStreamTarget } from '../../../util/workflows'; import { DataCell } from '../../dataTable/DataCell'; import { DataTable } from '../../dataTable/DataTable'; From 8f0d2d44deb7b067ad129197a38c5efa166be218 Mon Sep 17 00:00:00 2001 From: Matt Lord Date: Thu, 8 Aug 2024 09:25:24 -0400 Subject: [PATCH 28/34] VReplication: Properly ignore errors from trying to drop tables that don't exist (#16505) Signed-off-by: Matt Lord --- .../endtoend/vreplication/materialize_test.go | 3 +- .../vreplication/vreplication_test.go | 12 ++++ go/vt/vtctl/workflow/framework_test.go | 3 +- go/vt/vtctl/workflow/server.go | 3 +- go/vt/vtctl/workflow/server_test.go | 61 ++++++++++++++++--- go/vt/vtctl/workflow/traffic_switcher.go | 17 +++--- go/vt/vtctl/workflow/utils.go | 18 ++++-- 7 files changed, 91 insertions(+), 26 deletions(-) diff --git a/go/test/endtoend/vreplication/materialize_test.go b/go/test/endtoend/vreplication/materialize_test.go index 486692a58ba..3f2e3451a64 100644 --- a/go/test/endtoend/vreplication/materialize_test.go +++ b/go/test/endtoend/vreplication/materialize_test.go @@ -108,7 +108,6 @@ const smMaterializeSchemaSource = ` const smMaterializeVSchemaSource = ` { - "sharded": true, "tables": { "mat": { "column_vindexes": [ @@ -197,6 +196,8 @@ func testMaterialize(t *testing.T, useVtctldClient bool) { _, err = ks2Primary.QueryTablet(customFunc, targetKs, true) require.NoError(t, err) + testMaterializeWithNonExistentTable(t) + materialize(t, smMaterializeSpec2, useVtctldClient) catchup(t, ks2Primary, "wf1", "Materialize") diff --git a/go/test/endtoend/vreplication/vreplication_test.go b/go/test/endtoend/vreplication/vreplication_test.go index 52874b5839c..c3f3e4e6557 100644 --- a/go/test/endtoend/vreplication/vreplication_test.go +++ b/go/test/endtoend/vreplication/vreplication_test.go @@ -1183,6 +1183,18 @@ func materialize(t *testing.T, spec string, useVtctldClient bool) { } } +func testMaterializeWithNonExistentTable(t *testing.T) { + t.Run("vtctldclient materialize with nonexistent table", func(t *testing.T) { + tableSettings := `[{"target_table": "table_that_doesnt_exist", "create_ddl": "create table mat_val_counts (mat_val varbinary(10), cnt int unsigned, primary key (mat_val))", "source_expression": "select val, count(*) as cnt from mat group by val"}]` + output, err := vc.VtctldClient.ExecuteCommandWithOutput("materialize", "--workflow=tablenogood", "--target-keyspace=source", + "create", "--source-keyspace=source", "--table-settings", tableSettings) + require.NoError(t, err, "Materialize create failed, err: %v, output: %s", err, output) + waitForWorkflowState(t, vc, "source.tablenogood", binlogdatapb.VReplicationWorkflowState_Stopped.String()) + output, err = vc.VtctldClient.ExecuteCommandWithOutput("materialize", "--workflow=tablenogood", "--target-keyspace=source", "cancel") + require.NoError(t, err, "Materialize cancel failed, err: %v, output: %s", err, output) + }) +} + func materializeProduct(t *testing.T, useVtctldClient bool) { t.Run("materializeProduct", func(t *testing.T) { // Materializing from "product" keyspace to "customer" keyspace. diff --git a/go/vt/vtctl/workflow/framework_test.go b/go/vt/vtctl/workflow/framework_test.go index e2ccde0a0e7..56feeee0860 100644 --- a/go/vt/vtctl/workflow/framework_test.go +++ b/go/vt/vtctl/workflow/framework_test.go @@ -66,6 +66,7 @@ type testKeyspace struct { type queryResult struct { query string result *querypb.QueryResult + err error } func TestMain(m *testing.M) { @@ -389,7 +390,7 @@ func (tmc *testTMClient) VReplicationExec(ctx context.Context, tablet *topodatap return nil, fmt.Errorf("tablet %v:\nunexpected query\n%s\nwant:\n%s", tablet, query, qrs[0].query) } tmc.vrQueries[int(tablet.Alias.Uid)] = qrs[1:] - return qrs[0].result, nil + return qrs[0].result, qrs[0].err } func (tmc *testTMClient) ExecuteFetchAsDba(ctx context.Context, tablet *topodatapb.Tablet, usePool bool, req *tabletmanagerdatapb.ExecuteFetchAsDbaRequest) (*querypb.QueryResult, error) { diff --git a/go/vt/vtctl/workflow/server.go b/go/vt/vtctl/workflow/server.go index 5b6c3f05343..3601ed2d1a1 100644 --- a/go/vt/vtctl/workflow/server.go +++ b/go/vt/vtctl/workflow/server.go @@ -35,7 +35,6 @@ import ( "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" - "vitess.io/vitess/go/mysql/sqlerror" "vitess.io/vitess/go/protoutil" "vitess.io/vitess/go/sets" "vitess.io/vitess/go/sqlescape" @@ -2548,7 +2547,7 @@ func (s *Server) optimizeCopyStateTable(tablet *topodatapb.Tablet) { Query: []byte(sqlOptimizeTable), MaxRows: uint64(100), // always produces 1+rows with notes and status }); err != nil { - if sqlErr, ok := err.(*sqlerror.SQLError); ok && sqlErr.Num == sqlerror.ERNoSuchTable { // the table may not exist + if IsTableDidNotExistError(err) { return } log.Warningf("Failed to optimize the copy_state table on %q: %v", tablet.Alias.String(), err) diff --git a/go/vt/vtctl/workflow/server_test.go b/go/vt/vtctl/workflow/server_test.go index c67d45bb9e6..542361a1571 100644 --- a/go/vt/vtctl/workflow/server_test.go +++ b/go/vt/vtctl/workflow/server_test.go @@ -213,15 +213,34 @@ func TestWorkflowDelete(t *testing.T) { defer cancel() workflowName := "wf1" - tableName := "t1" + table1Name := "t1" + table2Name := "t1_2" + table3Name := "t1_3" + tableTemplate := "CREATE TABLE %s (id BIGINT, name VARCHAR(64), PRIMARY KEY (id))" sourceKeyspaceName := "sourceks" targetKeyspaceName := "targetks" schema := map[string]*tabletmanagerdatapb.SchemaDefinition{ - "t1": { + table1Name: { TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ { - Name: tableName, - Schema: fmt.Sprintf("CREATE TABLE %s (id BIGINT, name VARCHAR(64), PRIMARY KEY (id))", tableName), + Name: table1Name, + Schema: fmt.Sprintf(tableTemplate, table1Name), + }, + }, + }, + table2Name: { + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: table2Name, + Schema: fmt.Sprintf(tableTemplate, table2Name), + }, + }, + }, + table3Name: { + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: table3Name, + Schema: fmt.Sprintf(tableTemplate, table3Name), }, }, }, @@ -239,7 +258,7 @@ func TestWorkflowDelete(t *testing.T) { postFunc func(t *testing.T, env *testEnv) }{ { - name: "basic", + name: "missing table", sourceKeyspace: &testKeyspace{ KeyspaceName: sourceKeyspaceName, ShardNames: []string{"0"}, @@ -261,7 +280,21 @@ func TestWorkflowDelete(t *testing.T) { }, expectedTargetQueries: []*queryResult{ { - query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, tableName), + query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, table1Name), + result: &querypb.QueryResult{}, + }, + { + query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, table2Name), + result: &querypb.QueryResult{}, + // We don't care that the cell and tablet info is off in the error message, only that + // it contains the expected SQL error we'd encounter when attempting to drop a table + // that doesn't exist. That will then cause this error to be non-fatal and the workflow + // delete work will continue. + err: fmt.Errorf("rpc error: code = Unknown desc = TabletManager.ExecuteFetchAsDba on cell-01: rpc error: code = Unknown desc = Unknown table 'vt_%s.%s' (errno 1051) (sqlstate 42S02) during query: drop table `vt_%s`.`%s`", + targetKeyspaceName, table2Name, targetKeyspaceName, table2Name), + }, + { + query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, table3Name), result: &querypb.QueryResult{}, }, }, @@ -281,7 +314,7 @@ func TestWorkflowDelete(t *testing.T) { }, }, { - name: "basic with existing denied table entries", + name: "missing denied table entries", sourceKeyspace: &testKeyspace{ KeyspaceName: sourceKeyspaceName, ShardNames: []string{"0"}, @@ -298,7 +331,9 @@ func TestWorkflowDelete(t *testing.T) { defer targetUnlock(&err) for _, shard := range env.targetKeyspace.ShardNames { _, err := env.ts.UpdateShardFields(lockCtx, targetKeyspaceName, shard, func(si *topo.ShardInfo) error { - err := si.UpdateDeniedTables(lockCtx, topodatapb.TabletType_PRIMARY, nil, false, []string{tableName, "t2", "t3"}) + // So t1_2 and t1_3 do not exist in the denied table list when we go + // to remove t1, t1_2, and t1_3. + err := si.UpdateDeniedTables(lockCtx, topodatapb.TabletType_PRIMARY, nil, false, []string{table1Name, "t2", "t3"}) return err }) require.NoError(t, err) @@ -317,7 +352,15 @@ func TestWorkflowDelete(t *testing.T) { }, expectedTargetQueries: []*queryResult{ { - query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, tableName), + query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, table1Name), + result: &querypb.QueryResult{}, + }, + { + query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, table2Name), + result: &querypb.QueryResult{}, + }, + { + query: fmt.Sprintf("drop table `vt_%s`.`%s`", targetKeyspaceName, table3Name), result: &querypb.QueryResult{}, }, }, diff --git a/go/vt/vtctl/workflow/traffic_switcher.go b/go/vt/vtctl/workflow/traffic_switcher.go index c9d9952ef8f..bcc42d13ce9 100644 --- a/go/vt/vtctl/workflow/traffic_switcher.go +++ b/go/vt/vtctl/workflow/traffic_switcher.go @@ -30,7 +30,6 @@ import ( "golang.org/x/sync/errgroup" "vitess.io/vitess/go/json2" - "vitess.io/vitess/go/mysql/sqlerror" "vitess.io/vitess/go/sqlescape" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/binlog/binlogplayer" @@ -548,12 +547,12 @@ func (ts *trafficSwitcher) removeSourceTables(ctx context.Context, removalType T DisableForeignKeyChecks: true, }) if err != nil { - if mysqlErr, ok := err.(*sqlerror.SQLError); ok && mysqlErr.Num == sqlerror.ERNoSuchTable { + if IsTableDidNotExistError(err) { ts.Logger().Warningf("%s: Table %s did not exist when attempting to remove it", topoproto.TabletAliasString(source.GetPrimary().GetAlias()), tableName) - return nil + } else { + ts.Logger().Errorf("%s: Error removing table %s: %v", topoproto.TabletAliasString(source.GetPrimary().GetAlias()), tableName, err) + return err } - ts.Logger().Errorf("%s: Error removing table %s: %v", topoproto.TabletAliasString(source.GetPrimary().GetAlias()), tableName, err) - return err } ts.Logger().Infof("%s: Removed table %s.%s\n", topoproto.TabletAliasString(source.GetPrimary().GetAlias()), source.GetPrimary().DbName(), tableName) @@ -1179,13 +1178,13 @@ func (ts *trafficSwitcher) removeTargetTables(ctx context.Context) error { }) log.Infof("Removed target table with result: %+v", res) if err != nil { - if mysqlErr, ok := err.(*sqlerror.SQLError); ok && mysqlErr.Num == sqlerror.ERNoSuchTable { + if IsTableDidNotExistError(err) { // The table was already gone, so we can ignore the error. ts.Logger().Warningf("%s: Table %s did not exist when attempting to remove it", topoproto.TabletAliasString(target.GetPrimary().GetAlias()), tableName) - return nil + } else { + ts.Logger().Errorf("%s: Error removing table %s: %v", topoproto.TabletAliasString(target.GetPrimary().GetAlias()), tableName, err) + return err } - ts.Logger().Errorf("%s: Error removing table %s: %v", topoproto.TabletAliasString(target.GetPrimary().GetAlias()), tableName, err) - return err } ts.Logger().Infof("%s: Removed table %s.%s\n", topoproto.TabletAliasString(target.GetPrimary().GetAlias()), target.GetPrimary().DbName(), tableName) diff --git a/go/vt/vtctl/workflow/utils.go b/go/vt/vtctl/workflow/utils.go index d4e8d7b4ec0..9cedf01733e 100644 --- a/go/vt/vtctl/workflow/utils.go +++ b/go/vt/vtctl/workflow/utils.go @@ -28,12 +28,9 @@ import ( "strings" "sync" - querypb "vitess.io/vitess/go/vt/proto/query" - - "vitess.io/vitess/go/vt/vtgate/vindexes" - "google.golang.org/protobuf/encoding/prototext" + "vitess.io/vitess/go/mysql/sqlerror" "vitess.io/vitess/go/sets" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/concurrency" @@ -46,9 +43,11 @@ import ( "vitess.io/vitess/go/vt/topo/topoproto" "vitess.io/vitess/go/vt/topotools" "vitess.io/vitess/go/vt/vterrors" + "vitess.io/vitess/go/vt/vtgate/vindexes" "vitess.io/vitess/go/vt/vttablet/tmclient" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" + querypb "vitess.io/vitess/go/vt/proto/query" tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" @@ -949,3 +948,14 @@ func getTabletTypeSuffix(tabletType topodatapb.TabletType) string { } return "" } + +// IsTableDidNotExistError will convert the given error to an sqlerror.SQLError and if +// the error code is ERNoSuchTable or ERBadTable, it will return true. This is helpful +// when e.g. processing a gRPC error which will be a status.Error that needs to be +// converted to an sqlerror.SQLError before we can examine the error code. +func IsTableDidNotExistError(err error) bool { + if sqlErr, ok := sqlerror.NewSQLErrorFromError(err).(*sqlerror.SQLError); ok { + return sqlErr.Num == sqlerror.ERNoSuchTable || sqlErr.Num == sqlerror.ERBadTable + } + return false +} From bf0c5f82e7d2b15de7afa9c9c7a9456dd25ab62a Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Fri, 9 Aug 2024 09:45:55 +0200 Subject: [PATCH 29/34] Fix `RemoveTablet` during `TabletExternallyReparented` causing connection issues (#16371) Signed-off-by: Arthur Schreiber --- go/vt/discovery/healthcheck.go | 22 ++++- go/vt/discovery/healthcheck_test.go | 121 ++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go index 70799b0f6bc..2a467301eaf 100644 --- a/go/vt/discovery/healthcheck.go +++ b/go/vt/discovery/healthcheck.go @@ -470,7 +470,20 @@ func (hc *HealthCheckImpl) deleteTablet(tablet *topodata.Tablet) { // delete from healthy list healthy, ok := hc.healthy[key] if ok && len(healthy) > 0 { - hc.recomputeHealthy(key) + if tabletType == topodata.TabletType_PRIMARY { + // If the deleted tablet was a primary, + // and it matches what we think is the current active primary, + // clear the healthy list for the primary. + // + // See the logic in `updateHealth` for more details. + alias := tabletAliasString(topoproto.TabletAliasString(healthy[0].Tablet.Alias)) + if alias == tabletAlias { + hc.healthy[key] = []*TabletHealth{} + } + } else { + // Simply recompute the list of healthy tablets for all other tablet types. + hc.recomputeHealthy(key) + } } } }() @@ -586,6 +599,13 @@ func (hc *HealthCheckImpl) updateHealth(th *TabletHealth, prevTarget *query.Targ hc.broadcast(th) } +// recomputeHealthy recomputes the healthy tablets for the given key. +// +// This filters out tablets that might be healthy, but are not part of the current +// cell or cell alias. It also performs filtering of tablets based on replication lag, +// if configured to do so. +// +// This should not be called for primary tablets. func (hc *HealthCheckImpl) recomputeHealthy(key KeyspaceShardTabletType) { all := hc.healthData[key] allArray := make([]*TabletHealth, 0, len(all)) diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go index c87ba699234..35c55354fb7 100644 --- a/go/vt/discovery/healthcheck_test.go +++ b/go/vt/discovery/healthcheck_test.go @@ -784,6 +784,127 @@ func TestRemoveTablet(t *testing.T) { assert.Empty(t, a, "wrong result, expected empty list") } +// When an external primary failover is performed, +// the demoted primary will advertise itself as a `PRIMARY` +// tablet until it recognizes that it was demoted, +// and until all in-flight operations have either finished +// (successfully or unsuccessfully, see `--shutdown_grace_period` flag). +// +// During this time, operations like `RemoveTablet` should not lead +// to multiple tablets becoming valid targets for `PRIMARY`. +func TestRemoveTabletDuringExternalReparenting(t *testing.T) { + ctx := utils.LeakCheckContext(t) + + // reset error counters + hcErrorCounters.ResetAll() + ts := memorytopo.NewServer(ctx, "cell") + defer ts.Close() + hc := createTestHc(ctx, ts) + // close healthcheck + defer hc.Close() + + firstTablet := createTestTablet(0, "cell", "a") + firstTablet.Type = topodatapb.TabletType_PRIMARY + + secondTablet := createTestTablet(1, "cell", "b") + secondTablet.Type = topodatapb.TabletType_REPLICA + + thirdTablet := createTestTablet(2, "cell", "c") + thirdTablet.Type = topodatapb.TabletType_REPLICA + + firstTabletHealthStream := make(chan *querypb.StreamHealthResponse) + firstTabletConn := createFakeConn(firstTablet, firstTabletHealthStream) + firstTabletConn.errCh = make(chan error) + + secondTabletHealthStream := make(chan *querypb.StreamHealthResponse) + secondTabletConn := createFakeConn(secondTablet, secondTabletHealthStream) + secondTabletConn.errCh = make(chan error) + + thirdTabletHealthStream := make(chan *querypb.StreamHealthResponse) + thirdTabletConn := createFakeConn(thirdTablet, thirdTabletHealthStream) + thirdTabletConn.errCh = make(chan error) + + resultChan := hc.Subscribe() + + hc.AddTablet(firstTablet) + <-resultChan + + hc.AddTablet(secondTablet) + <-resultChan + + hc.AddTablet(thirdTablet) + <-resultChan + + firstTabletPrimaryTermStartTimestamp := time.Now().Unix() - 10 + + firstTabletHealthStream <- &querypb.StreamHealthResponse{ + TabletAlias: firstTablet.Alias, + Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_PRIMARY}, + Serving: true, + + PrimaryTermStartTimestamp: firstTabletPrimaryTermStartTimestamp, + RealtimeStats: &querypb.RealtimeStats{ReplicationLagSeconds: 0, CpuUsage: 0.5}, + } + <-resultChan + + secondTabletHealthStream <- &querypb.StreamHealthResponse{ + TabletAlias: secondTablet.Alias, + Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_REPLICA}, + Serving: true, + + PrimaryTermStartTimestamp: 0, + RealtimeStats: &querypb.RealtimeStats{ReplicationLagSeconds: 1, CpuUsage: 0.5}, + } + <-resultChan + + thirdTabletHealthStream <- &querypb.StreamHealthResponse{ + TabletAlias: thirdTablet.Alias, + Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_REPLICA}, + Serving: true, + + PrimaryTermStartTimestamp: 0, + RealtimeStats: &querypb.RealtimeStats{ReplicationLagSeconds: 1, CpuUsage: 0.5}, + } + <-resultChan + + secondTabletPrimaryTermStartTimestamp := time.Now().Unix() + + // Simulate a failover + firstTabletHealthStream <- &querypb.StreamHealthResponse{ + TabletAlias: firstTablet.Alias, + Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_PRIMARY}, + Serving: true, + + PrimaryTermStartTimestamp: firstTabletPrimaryTermStartTimestamp, + RealtimeStats: &querypb.RealtimeStats{ReplicationLagSeconds: 0, CpuUsage: 0.5}, + } + <-resultChan + + secondTabletHealthStream <- &querypb.StreamHealthResponse{ + TabletAlias: secondTablet.Alias, + Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_PRIMARY}, + Serving: true, + + PrimaryTermStartTimestamp: secondTabletPrimaryTermStartTimestamp, + RealtimeStats: &querypb.RealtimeStats{ReplicationLagSeconds: 0, CpuUsage: 0.5}, + } + <-resultChan + + hc.RemoveTablet(thirdTablet) + + // `secondTablet` should be the primary now + expectedTabletStats := []*TabletHealth{{ + Tablet: secondTablet, + Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_PRIMARY}, + Serving: true, + Stats: &querypb.RealtimeStats{ReplicationLagSeconds: 0, CpuUsage: 0.5}, + PrimaryTermStartTime: secondTabletPrimaryTermStartTimestamp, + }} + + actualTabletStats := hc.GetHealthyTabletStats(&querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_PRIMARY}) + mustMatch(t, expectedTabletStats, actualTabletStats, "unexpected result") +} + // TestGetHealthyTablets tests the functionality of GetHealthyTabletStats. func TestGetHealthyTablets(t *testing.T) { ctx := utils.LeakCheckContext(t) From f68e62d107e4072180f57dfc3500f9f2bd601755 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:26:54 +0300 Subject: [PATCH 30/34] Throttler/vreplication: fix app name used by VPlayer (#16578) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- .../tabletmanager/vreplication/vplayer.go | 2 +- .../vreplication/vreplicator_test.go | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/go/vt/vttablet/tabletmanager/vreplication/vplayer.go b/go/vt/vttablet/tabletmanager/vreplication/vplayer.go index 31e26c30e88..2b8b8130f89 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/vplayer.go +++ b/go/vt/vttablet/tabletmanager/vreplication/vplayer.go @@ -176,7 +176,7 @@ func newVPlayer(vr *vreplicator, settings binlogplayer.VRSettings, copyState map timeLastSaved: time.Now(), tablePlans: make(map[string]*TablePlan), phase: phase, - throttlerAppName: throttlerapp.VCopierName.ConcatenateString(vr.throttlerAppName()), + throttlerAppName: throttlerapp.VPlayerName.ConcatenateString(vr.throttlerAppName()), query: queryFunc, commit: commitFunc, batchMode: batchMode, diff --git a/go/vt/vttablet/tabletmanager/vreplication/vreplicator_test.go b/go/vt/vttablet/tabletmanager/vreplication/vreplicator_test.go index f6eb3ac5958..20a5450741d 100644 --- a/go/vt/vttablet/tabletmanager/vreplication/vreplicator_test.go +++ b/go/vt/vttablet/tabletmanager/vreplication/vreplicator_test.go @@ -31,6 +31,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "vitess.io/vitess/go/mysql/replication" "vitess.io/vitess/go/vt/binlog/binlogplayer" "vitess.io/vitess/go/vt/dbconfigs" "vitess.io/vitess/go/vt/mysqlctl" @@ -815,3 +816,59 @@ func waitForQueryResult(t *testing.T, dbc binlogplayer.DBClient, query, val stri } } } + +func TestThrottlerAppNames(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tablet := addTablet(100) + defer deleteTablet(tablet) + filter := &binlogdatapb.Filter{ + Rules: []*binlogdatapb.Rule{{ + Match: "t1", + }}, + } + bls := &binlogdatapb.BinlogSource{ + Keyspace: env.KeyspaceName, + Shard: env.ShardName, + Filter: filter, + } + id := int32(1) + vsclient := newTabletConnector(tablet) + stats := binlogplayer.NewStats() + defer stats.Stop() + dbClient := playerEngine.dbClientFactoryFiltered() + err := dbClient.Connect() + require.NoError(t, err) + defer dbClient.Close() + dbName := dbClient.DBName() + // Ensure there's a dummy vreplication workflow record + _, err = dbClient.ExecuteFetch(fmt.Sprintf("insert into _vt.vreplication (id, workflow, source, pos, max_tps, max_replication_lag, time_updated, transaction_timestamp, state, db_name, options) values (%d, 'test_workflow', '', '', 99999, 99999, 0, 0, 'Running', '%s', '{}') on duplicate key update workflow='test', source='', pos='', max_tps=99999, max_replication_lag=99999, time_updated=0, transaction_timestamp=0, state='Running', db_name='%s'", + id, dbName, dbName), 1) + require.NoError(t, err) + defer func() { + _, err = dbClient.ExecuteFetch(fmt.Sprintf("delete from _vt.vreplication where id = %d", id), 1) + require.NoError(t, err) + }() + vr := newVReplicator(id, bls, vsclient, stats, dbClient, env.Mysqld, playerEngine) + settings, _, err := vr.loadSettings(ctx, newVDBClient(dbClient, stats)) + require.NoError(t, err) + + throttlerAppName := vr.throttlerAppName() + assert.Contains(t, throttlerAppName, "test_workflow") + assert.Contains(t, throttlerAppName, "vreplication") + assert.NotContains(t, throttlerAppName, "vcopier") + assert.NotContains(t, throttlerAppName, "vplayer") + + vp := newVPlayer(vr, settings, nil, replication.Position{}, "") + assert.Contains(t, vp.throttlerAppName, "test_workflow") + assert.Contains(t, vp.throttlerAppName, "vreplication") + assert.Contains(t, vp.throttlerAppName, "vplayer") + assert.NotContains(t, vp.throttlerAppName, "vcopier") + + vc := newVCopier(vr) + assert.Contains(t, vc.throttlerAppName, "test_workflow") + assert.Contains(t, vc.throttlerAppName, "vreplication") + assert.Contains(t, vc.throttlerAppName, "vcopier") + assert.NotContains(t, vc.throttlerAppName, "vplayer") +} From 9018fef4643a5dd7b93bf359e0d5bc8fa2104def Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:17:04 +0300 Subject: [PATCH 31/34] Tablet throttler: remove cached metric associated with removed tablet (#16555) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- .../tabletserver/throttle/throttler.go | 20 ++++- .../tabletserver/throttle/throttler_test.go | 81 +++++++++++++++++-- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/go/vt/vttablet/tabletserver/throttle/throttler.go b/go/vt/vttablet/tabletserver/throttle/throttler.go index 382d0a12a77..5cd56460713 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler.go @@ -743,6 +743,7 @@ func (throttler *Throttler) Operate(ctx context.Context, wg *sync.WaitGroup) { primaryStimulatorRateLimiter.Stop() throttler.aggregatedMetrics.Flush() throttler.recentApps.Flush() + clear(throttler.inventory.TabletMetrics) }() // we do not flush throttler.throttledApps because this is data submitted by the user; the user expects the data to survive a disable+enable @@ -842,7 +843,7 @@ func (throttler *Throttler) Operate(ctx context.Context, wg *sync.WaitGroup) { } case probes := <-throttler.clusterProbesChan: // incoming structural update, sparse, as result of refreshInventory() - throttler.updateClusterProbes(ctx, probes) + throttler.updateClusterProbes(probes) case <-metricsAggregateTicker.C: if throttler.IsOpen() { throttler.aggregateMetrics() @@ -1116,10 +1117,25 @@ func (throttler *Throttler) refreshInventory(ctx context.Context) error { } // synchronous update of inventory -func (throttler *Throttler) updateClusterProbes(ctx context.Context, clusterProbes *base.ClusterProbes) error { +func (throttler *Throttler) updateClusterProbes(clusterProbes *base.ClusterProbes) error { throttler.inventory.ClustersProbes = clusterProbes.TabletProbes throttler.inventory.IgnoreHostsCount = clusterProbes.IgnoreHostsCount throttler.inventory.IgnoreHostsThreshold = clusterProbes.IgnoreHostsThreshold + + for alias := range throttler.inventory.TabletMetrics { + if alias == "" { + // *this* tablet uses the empty alias to identify itself. + continue + } + if _, found := clusterProbes.TabletProbes[alias]; !found { + // There seems to be a metric stored for some alias, say zone1-0000000102, + // but there is no alias for this probe in the new clusterProbes. This + // suggests that the corresponding tablet has been removed, or its type was changed + // (e.g. from REPLICA to RDONLY). We should therefore remove this cached metric. + delete(throttler.inventory.TabletMetrics, alias) + } + } + return nil } diff --git a/go/vt/vttablet/tabletserver/throttle/throttler_test.go b/go/vt/vttablet/tabletserver/throttle/throttler_test.go index e095378926c..41036620a60 100644 --- a/go/vt/vttablet/tabletserver/throttle/throttler_test.go +++ b/go/vt/vttablet/tabletserver/throttle/throttler_test.go @@ -94,6 +94,7 @@ var ( Value: 5.1, }, } + nonPrimaryTabletType atomic.Int32 ) const ( @@ -151,7 +152,11 @@ type FakeTopoServer struct { func (ts *FakeTopoServer) GetTablet(ctx context.Context, alias *topodatapb.TabletAlias) (*topo.TabletInfo, error) { tabletType := topodatapb.TabletType_PRIMARY if alias.Uid != 100 { - tabletType = topodatapb.TabletType_REPLICA + val := topodatapb.TabletType(nonPrimaryTabletType.Load()) + if val == topodatapb.TabletType_UNKNOWN { + val = topodatapb.TabletType_REPLICA + } + tabletType = val } tablet := &topo.TabletInfo{ Tablet: &topodatapb.Tablet{ @@ -1156,9 +1161,9 @@ func TestRefreshInventory(t *testing.T) { // validateProbesCount expects number of probes according to cluster name and throttler's leadership status validateProbesCount := func(t *testing.T, probes base.Probes) { if throttler.isLeader.Load() { - assert.Equal(t, 3, len(probes)) + assert.Len(t, probes, 3) } else { - assert.Equal(t, 1, len(probes)) + assert.Len(t, probes, 1) } } t.Run("waiting for probes", func(t *testing.T) { @@ -1171,7 +1176,7 @@ func TestRefreshInventory(t *testing.T) { // not run, and therefore there is none but us to both populate `clusterProbesChan` as well as // read from it. We do not compete here with any other goroutine. assert.NotNil(t, probes) - throttler.updateClusterProbes(ctx, probes) + throttler.updateClusterProbes(probes) validateProbesCount(t, probes.TabletProbes) // Achieved our goal return @@ -1488,6 +1493,70 @@ func TestProbesWhileOperating(t *testing.T) { }) }) }) + + t.Run("metrics", func(t *testing.T) { + var results base.TabletResultMap + <-runSerialFunction(t, ctx, throttler, func(ctx context.Context) { + results = maps.Clone(throttler.inventory.TabletMetrics) + }) + assert.Len(t, results, 3) // 1 self tablet + 2 shard tablets + assert.Contains(t, results, "", "TabletMetrics: %+v", results) // primary self identifies with empty alias + assert.Contains(t, results, "fakezone1-0000000101", "TabletMetrics: %+v", results) + assert.Contains(t, results, "fakezone2-0000000102", "TabletMetrics: %+v", results) + }) + + t.Run("no REPLICA probes", func(t *testing.T) { + nonPrimaryTabletType.Store(int32(topodatapb.TabletType_RDONLY)) + defer nonPrimaryTabletType.Store(int32(topodatapb.TabletType_REPLICA)) + + t.Run("waiting for inventory metrics", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, waitForProbesTimeout) + defer cancel() + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + for { + var results base.TabletResultMap + <-runSerialFunction(t, ctx, throttler, func(ctx context.Context) { + results = maps.Clone(throttler.inventory.TabletMetrics) + }) + if len(results) == 1 { + // That's what we were waiting for. Good. + assert.Contains(t, results, "", "TabletMetrics: %+v", results) // primary self identifies with empty alias + return + } + + select { + case <-ticker.C: + case <-ctx.Done(): + assert.FailNowf(t, ctx.Err().Error(), "waiting for inventory metrics") + } + } + }) + }) + t.Run("again with probes", func(t *testing.T) { + t.Run("waiting for inventory metrics", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, waitForProbesTimeout) + defer cancel() + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + for { + var results base.TabletResultMap + <-runSerialFunction(t, ctx, throttler, func(ctx context.Context) { + results = maps.Clone(throttler.inventory.TabletMetrics) + }) + if len(results) == 3 { + // That's what we were waiting for. Good. + return + } + + select { + case <-ticker.C: + case <-ctx.Done(): + assert.FailNowf(t, ctx.Err().Error(), "waiting for inventory metrics") + } + } + }) + }) }) } @@ -1603,7 +1672,7 @@ func TestProbesPostDisable(t *testing.T) { }) t.Run("metrics", func(t *testing.T) { - assert.Equal(t, 3, len(throttler.inventory.TabletMetrics)) // 1 self tablet + 2 shard tablets + assert.Empty(t, throttler.inventory.TabletMetrics) // map has been cleared }) t.Run("aggregated", func(t *testing.T) { @@ -2103,7 +2172,7 @@ func TestReplica(t *testing.T) { defer throttler.appCheckedMetrics.Delete(testAppName.String()) checkResult := throttler.Check(ctx, testAppName.String(), nil, flags) require.NotNil(t, checkResult) - assert.Equal(t, 3, len(checkResult.Metrics)) + assert.Len(t, checkResult.Metrics, 3) }) t.Run("client, OK", func(t *testing.T) { client := NewBackgroundClient(throttler, throttlerapp.TestingName, base.UndefinedScope) From 3c2e8f94153f7e2ad697d52fdf42bab8673f69aa Mon Sep 17 00:00:00 2001 From: Manan Gupta <35839558+GuptaManan100@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:00:47 +0530 Subject: [PATCH 32/34] Atomic Transaction bug fix with PRS disruption (#16576) Signed-off-by: Manan Gupta --- go/test/endtoend/cluster/cluster_process.go | 24 ++-- go/test/endtoend/cluster/vtctl_process.go | 16 +-- .../encrypted_replication_test.go | 2 +- .../encrypted_transport_test.go | 2 +- go/test/endtoend/mysqlctl/mysqlctl_test.go | 2 +- go/test/endtoend/mysqlctld/mysqlctld_test.go | 2 +- .../endtoend/sharded/sharded_keyspace_test.go | 2 +- .../transaction/twopc/fuzzer/main_test.go | 28 +--- .../endtoend/transaction/twopc/main_test.go | 22 ++- go/test/endtoend/transaction/twopc/schema.sql | 6 + .../endtoend/transaction/twopc/twopc_test.go | 126 ++++++++++++++++++ .../endtoend/transaction/twopc/utils/utils.go | 59 ++++++++ .../endtoend/transaction/twopc/vschema.json | 11 ++ go/vt/vterrors/code.go | 1 + go/vt/vttablet/tabletserver/debug_2pc.go | 48 +++++++ go/vt/vttablet/tabletserver/dt_executor.go | 2 +- .../vttablet/tabletserver/dt_executor_test.go | 2 +- go/vt/vttablet/tabletserver/production.go | 30 +++++ go/vt/vttablet/tabletserver/tabletserver.go | 3 + go/vt/vttablet/tabletserver/tx_engine.go | 4 +- go/vt/vttablet/tabletserver/tx_prep_pool.go | 30 +++-- .../tabletserver/tx_prep_pool_test.go | 33 ++--- 22 files changed, 359 insertions(+), 96 deletions(-) create mode 100644 go/test/endtoend/transaction/twopc/utils/utils.go create mode 100644 go/vt/vttablet/tabletserver/debug_2pc.go create mode 100644 go/vt/vttablet/tabletserver/production.go diff --git a/go/test/endtoend/cluster/cluster_process.go b/go/test/endtoend/cluster/cluster_process.go index 44636b3cdb6..95995903a83 100644 --- a/go/test/endtoend/cluster/cluster_process.go +++ b/go/test/endtoend/cluster/cluster_process.go @@ -151,11 +151,12 @@ type Vttablet struct { // Keyspace : Cluster accepts keyspace to launch it type Keyspace struct { - Name string - SchemaSQL string - VSchema string - SidecarDBName string - Shards []Shard + Name string + SchemaSQL string + VSchema string + SidecarDBName string + DurabilityPolicy string + Shards []Shard } // Shard with associated vttablets @@ -284,9 +285,10 @@ func (cluster *LocalProcessCluster) startPartialKeyspace(keyspace Keyspace, shar cluster.HasPartialKeyspaces = true routedKeyspace := &Keyspace{ - Name: fmt.Sprintf("%s_routed", keyspace.Name), - SchemaSQL: keyspace.SchemaSQL, - VSchema: keyspace.VSchema, + Name: fmt.Sprintf("%s_routed", keyspace.Name), + SchemaSQL: keyspace.SchemaSQL, + VSchema: keyspace.VSchema, + DurabilityPolicy: keyspace.DurabilityPolicy, } err = cluster.startKeyspace(*routedKeyspace, shardNames, replicaCount, rdonly, customizers...) @@ -374,7 +376,7 @@ func (cluster *LocalProcessCluster) startKeyspace(keyspace Keyspace, shardNames keyspace.SidecarDBName = sidecar.DefaultName } // Create the keyspace if it doesn't already exist. - _ = cluster.VtctlProcess.CreateKeyspace(keyspace.Name, keyspace.SidecarDBName) + _ = cluster.VtctlProcess.CreateKeyspace(keyspace.Name, keyspace.SidecarDBName, keyspace.DurabilityPolicy) for _, shardName := range shardNames { shard := &Shard{ Name: shardName, @@ -538,7 +540,7 @@ func (cluster *LocalProcessCluster) StartKeyspaceLegacy(keyspace Keyspace, shard keyspace.SidecarDBName = sidecar.DefaultName } // Create the keyspace if it doesn't already exist. - _ = cluster.VtctlProcess.CreateKeyspace(keyspace.Name, keyspace.SidecarDBName) + _ = cluster.VtctlProcess.CreateKeyspace(keyspace.Name, keyspace.SidecarDBName, keyspace.DurabilityPolicy) var mysqlctlProcessList []*exec.Cmd for _, shardName := range shardNames { shard := &Shard{ @@ -681,7 +683,7 @@ func (cluster *LocalProcessCluster) SetupCluster(keyspace *Keyspace, shards []Sh if !cluster.ReusingVTDATAROOT { // Create Keyspace - err = cluster.VtctlProcess.CreateKeyspace(keyspace.Name, keyspace.SidecarDBName) + err = cluster.VtctlProcess.CreateKeyspace(keyspace.Name, keyspace.SidecarDBName, keyspace.DurabilityPolicy) if err != nil { log.Error(err) return diff --git a/go/test/endtoend/cluster/vtctl_process.go b/go/test/endtoend/cluster/vtctl_process.go index b9d8a5b46ce..185c3079d34 100644 --- a/go/test/endtoend/cluster/vtctl_process.go +++ b/go/test/endtoend/cluster/vtctl_process.go @@ -60,15 +60,15 @@ func (vtctl *VtctlProcess) AddCellInfo(Cell string) (err error) { } // CreateKeyspace executes vtctl command to create keyspace -func (vtctl *VtctlProcess) CreateKeyspace(keyspace, sidecarDBName string) (err error) { - var output string - // For upgrade/downgrade tests where an older version is also used. - if vtctl.VtctlMajorVersion < 17 { - log.Errorf("CreateKeyspace does not support the --sidecar-db-name flag in vtctl version %d; ignoring...", vtctl.VtctlMajorVersion) - output, err = vtctl.ExecuteCommandWithOutput("CreateKeyspace", keyspace) - } else { - output, err = vtctl.ExecuteCommandWithOutput("CreateKeyspace", keyspace, "--sidecar-db-name", sidecarDBName) +func (vtctl *VtctlProcess) CreateKeyspace(keyspace, sidecarDBName, durabilityPolicy string) error { + args := []string{ + "CreateKeyspace", keyspace, + "--sidecar-db-name", sidecarDBName, } + if durabilityPolicy != "" { + args = append(args, "--durability-policy", durabilityPolicy) + } + output, err := vtctl.ExecuteCommandWithOutput(args...) if err != nil { log.Errorf("CreateKeyspace returned err: %s, output: %s", err, output) } diff --git a/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go b/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go index 4c759ff577a..7dea6cf525f 100644 --- a/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go +++ b/go/test/endtoend/encryption/encryptedreplication/encrypted_replication_test.go @@ -131,7 +131,7 @@ func initializeCluster(t *testing.T) (int, error) { for _, keyspaceStr := range []string{keyspace} { KeyspacePtr := &cluster.Keyspace{Name: keyspaceStr} keyspace := *KeyspacePtr - if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspace.Name, sidecar.DefaultName); err != nil { + if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspace.Name, sidecar.DefaultName, ""); err != nil { return 1, err } shard := &cluster.Shard{ diff --git a/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go b/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go index 9147b7b9080..1363e07b2cd 100644 --- a/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go +++ b/go/test/endtoend/encryption/encryptedtransport/encrypted_transport_test.go @@ -350,7 +350,7 @@ func clusterSetUp(t *testing.T) (int, error) { for _, keyspaceStr := range []string{keyspace} { KeyspacePtr := &cluster.Keyspace{Name: keyspaceStr} keyspace := *KeyspacePtr - if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspace.Name, sidecar.DefaultName); err != nil { + if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspace.Name, sidecar.DefaultName, ""); err != nil { return 1, err } shard := &cluster.Shard{ diff --git a/go/test/endtoend/mysqlctl/mysqlctl_test.go b/go/test/endtoend/mysqlctl/mysqlctl_test.go index 6c3d65226e3..f93724fa4a8 100644 --- a/go/test/endtoend/mysqlctl/mysqlctl_test.go +++ b/go/test/endtoend/mysqlctl/mysqlctl_test.go @@ -53,7 +53,7 @@ func TestMain(m *testing.M) { return 1 } - if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspaceName, sidecar.DefaultName); err != nil { + if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspaceName, sidecar.DefaultName, ""); err != nil { return 1 } diff --git a/go/test/endtoend/mysqlctld/mysqlctld_test.go b/go/test/endtoend/mysqlctld/mysqlctld_test.go index 328bc563377..beb155830e2 100644 --- a/go/test/endtoend/mysqlctld/mysqlctld_test.go +++ b/go/test/endtoend/mysqlctld/mysqlctld_test.go @@ -57,7 +57,7 @@ func TestMain(m *testing.M) { return 1 } - if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspaceName, sidecar.DefaultName); err != nil { + if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspaceName, sidecar.DefaultName, ""); err != nil { return 1 } diff --git a/go/test/endtoend/sharded/sharded_keyspace_test.go b/go/test/endtoend/sharded/sharded_keyspace_test.go index f311404ad7e..192355fa6ef 100644 --- a/go/test/endtoend/sharded/sharded_keyspace_test.go +++ b/go/test/endtoend/sharded/sharded_keyspace_test.go @@ -84,7 +84,7 @@ func TestMain(m *testing.M) { if err := clusterInstance.StartTopo(); err != nil { return 1, err } - if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspaceName, sidecar.DefaultName); err != nil { + if err := clusterInstance.VtctlProcess.CreateKeyspace(keyspaceName, sidecar.DefaultName, ""); err != nil { return 1, err } diff --git a/go/test/endtoend/transaction/twopc/fuzzer/main_test.go b/go/test/endtoend/transaction/twopc/fuzzer/main_test.go index 5e1d14d77e4..e0affde186a 100644 --- a/go/test/endtoend/transaction/twopc/fuzzer/main_test.go +++ b/go/test/endtoend/transaction/twopc/fuzzer/main_test.go @@ -28,6 +28,7 @@ import ( "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/test/endtoend/cluster" + "vitess.io/vitess/go/test/endtoend/transaction/twopc/utils" ) var ( @@ -110,29 +111,6 @@ func start(t *testing.T) (*mysql.Conn, func()) { func cleanup(t *testing.T) { cluster.PanicHandler(t) - ctx := context.Background() - conn, err := mysql.Connect(ctx, &vtParams) - require.NoError(t, err) - defer conn.Close() - - clearOutTable(t, conn, "twopc_fuzzer_insert") - clearOutTable(t, conn, "twopc_fuzzer_update") -} - -// clearOutTable deletes everything from a table. Sometimes the table might have more rows than allowed in a single delete query, -// so we have to do the deletions iteratively. -func clearOutTable(t *testing.T, conn *mysql.Conn, tableName string) { - for { - res, err := conn.ExecuteFetch(fmt.Sprintf("SELECT count(*) FROM %v", tableName), 1, false) - require.NoError(t, err) - require.Len(t, res.Rows, 1) - require.Len(t, res.Rows[0], 1) - rowCount, err := res.Rows[0][0].ToInt() - require.NoError(t, err) - if rowCount == 0 { - return - } - _, err = conn.ExecuteFetch(fmt.Sprintf("DELETE FROM %v LIMIT 10000", tableName), 10000, false) - require.NoError(t, err) - } + utils.ClearOutTable(t, vtParams, "twopc_fuzzer_insert") + utils.ClearOutTable(t, vtParams, "twopc_fuzzer_update") } diff --git a/go/test/endtoend/transaction/twopc/main_test.go b/go/test/endtoend/transaction/twopc/main_test.go index 8ac7cfc1f21..4c5e2715563 100644 --- a/go/test/endtoend/transaction/twopc/main_test.go +++ b/go/test/endtoend/transaction/twopc/main_test.go @@ -33,7 +33,7 @@ import ( "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/test/endtoend/cluster" - "vitess.io/vitess/go/test/endtoend/utils" + "vitess.io/vitess/go/test/endtoend/transaction/twopc/utils" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" querypb "vitess.io/vitess/go/vt/proto/query" topodatapb "vitess.io/vitess/go/vt/proto/topodata" @@ -85,12 +85,13 @@ func TestMain(m *testing.M) { // Start keyspace keyspace := &cluster.Keyspace{ - Name: keyspaceName, - SchemaSQL: SchemaSQL, - VSchema: VSchema, - SidecarDBName: sidecarDBName, + Name: keyspaceName, + SchemaSQL: SchemaSQL, + VSchema: VSchema, + SidecarDBName: sidecarDBName, + DurabilityPolicy: "semi_sync", } - if err := clusterInstance.StartKeyspace(*keyspace, []string{"-40", "40-80", "80-"}, 0, false); err != nil { + if err := clusterInstance.StartKeyspace(*keyspace, []string{"-40", "40-80", "80-"}, 2, false); err != nil { return 1 } @@ -119,13 +120,8 @@ func start(t *testing.T) (*mysql.Conn, func()) { func cleanup(t *testing.T) { cluster.PanicHandler(t) - - ctx := context.Background() - conn, err := mysql.Connect(ctx, &vtParams) - require.NoError(t, err) - defer conn.Close() - - _, _ = utils.ExecAllowError(t, conn, "delete from twopc_user") + utils.ClearOutTable(t, vtParams, "twopc_user") + utils.ClearOutTable(t, vtParams, "twopc_t1") } type extractInterestingValues func(dtidMap map[string]string, vals []sqltypes.Value) []sqltypes.Value diff --git a/go/test/endtoend/transaction/twopc/schema.sql b/go/test/endtoend/transaction/twopc/schema.sql index 60a7c19837c..de9e3ef0656 100644 --- a/go/test/endtoend/transaction/twopc/schema.sql +++ b/go/test/endtoend/transaction/twopc/schema.sql @@ -9,4 +9,10 @@ create table twopc_music ( user_id bigint, title varchar(64), primary key (id) +) Engine=InnoDB; + +create table twopc_t1 ( + id bigint, + col bigint, + primary key (id, col) ) Engine=InnoDB; \ No newline at end of file diff --git a/go/test/endtoend/transaction/twopc/twopc_test.go b/go/test/endtoend/transaction/twopc/twopc_test.go index 98bc158c4da..53c1780f373 100644 --- a/go/test/endtoend/transaction/twopc/twopc_test.go +++ b/go/test/endtoend/transaction/twopc/twopc_test.go @@ -20,6 +20,8 @@ import ( "context" _ "embed" "fmt" + "os" + "path" "reflect" "sort" "strings" @@ -35,11 +37,17 @@ import ( "vitess.io/vitess/go/test/endtoend/cluster" "vitess.io/vitess/go/test/endtoend/utils" "vitess.io/vitess/go/vt/callerid" + "vitess.io/vitess/go/vt/log" binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" querypb "vitess.io/vitess/go/vt/proto/query" "vitess.io/vitess/go/vt/vtgate/vtgateconn" ) +const ( + DebugDelayCommitShard = "VT_DELAY_COMMIT_SHARD" + DebugDelayCommitTime = "VT_DELAY_COMMIT_TIME" +) + // TestDTCommit tests distributed transaction commit for insert, update and delete operations // It verifies the binlog events for the same with transaction state changes and redo statements. func TestDTCommit(t *testing.T) { @@ -955,3 +963,121 @@ func testWarningAndTransactionStatus(t *testing.T, conn *vtgateconn.VTGateSessio assert.Equal(t, txParticipants, tx.participants) } } + +// TestDisruptions tests that atomic transactions persevere through various disruptions. +func TestDisruptions(t *testing.T) { + testcases := []struct { + disruptionName string + commitDelayTime string + disruption func() error + }{ + { + disruptionName: "No Disruption", + commitDelayTime: "1", + disruption: func() error { + return nil + }, + }, + { + disruptionName: "PlannedReparentShard", + commitDelayTime: "5", + disruption: prsShard3, + }, + } + for _, tt := range testcases { + t.Run(fmt.Sprintf("%s-%ss timeout", tt.disruptionName, tt.commitDelayTime), func(t *testing.T) { + // Reparent all the shards to first tablet being the primary. + reparentToFistTablet(t) + // cleanup all the old data. + conn, closer := start(t) + defer closer() + // Start an atomic transaction. + utils.Exec(t, conn, "begin") + // Insert rows such that they go to all the three shards. Given that we have sharded the table `twopc_t1` on reverse_bits + // it is very easy to figure out what value will end up in which shard. + utils.Exec(t, conn, "insert into twopc_t1(id, col) values(4, 4)") + utils.Exec(t, conn, "insert into twopc_t1(id, col) values(6, 4)") + utils.Exec(t, conn, "insert into twopc_t1(id, col) values(9, 4)") + // We want to delay the commit on one of the shards to simulate slow commits on a shard. + writeTestCommunicationFile(t, DebugDelayCommitShard, "80-") + defer deleteFile(DebugDelayCommitShard) + writeTestCommunicationFile(t, DebugDelayCommitTime, tt.commitDelayTime) + defer deleteFile(DebugDelayCommitTime) + // We will execute a commit in a go routine, because we know it will take some time to complete. + // While the commit is ongoing, we would like to run the disruption. + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _, err := utils.ExecAllowError(t, conn, "commit") + if err != nil { + log.Errorf("Error in commit - %v", err) + } + }() + // Allow enough time for the commit to have started. + time.Sleep(1 * time.Second) + // Run the disruption. + err := tt.disruption() + require.NoError(t, err) + // Wait for the commit to have returned. We don't actually check for an error in the commit because the user might receive an error. + // But since we are waiting in CommitPrepared, the decision to commit the transaction should have already been taken. + wg.Wait() + // Check the data in the table. + waitForResults(t, "select id, col from twopc_t1 where col = 4 order by id", `[[INT64(4) INT64(4)] [INT64(6) INT64(4)] [INT64(9) INT64(4)]]`, 10*time.Second) + }) + } +} + +// reparentToFistTablet reparents all the shards to first tablet being the primary. +func reparentToFistTablet(t *testing.T) { + ks := clusterInstance.Keyspaces[0] + for _, shard := range ks.Shards { + primary := shard.Vttablets[0] + err := clusterInstance.VtctldClientProcess.PlannedReparentShard(keyspaceName, shard.Name, primary.Alias) + require.NoError(t, err) + } +} + +// writeTestCommunicationFile writes the content to the file with the given name. +// We use these files to coordinate with the vttablets running in the debug mode. +func writeTestCommunicationFile(t *testing.T, fileName string, content string) { + err := os.WriteFile(path.Join(os.Getenv("VTDATAROOT"), fileName), []byte(content), 0644) + require.NoError(t, err) +} + +// deleteFile deletes the file specified. +func deleteFile(fileName string) { + _ = os.Remove(path.Join(os.Getenv("VTDATAROOT"), fileName)) +} + +// waitForResults waits for the results of the query to be as expected. +func waitForResults(t *testing.T, query string, resultExpected string, waitTime time.Duration) { + timeout := time.After(waitTime) + for { + select { + case <-timeout: + t.Fatalf("didn't reach expected results for %s", query) + default: + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + res := utils.Exec(t, conn, query) + conn.Close() + if fmt.Sprintf("%v", res.Rows) == resultExpected { + return + } + time.Sleep(100 * time.Millisecond) + } + } +} + +/* +Cluster Level Disruptions for the fuzzer +*/ + +// prsShard3 runs a PRS in shard 3 of the keyspace. It promotes the second tablet to be the new primary. +func prsShard3() error { + shard := clusterInstance.Keyspaces[0].Shards[2] + newPrimary := shard.Vttablets[1] + return clusterInstance.VtctldClientProcess.PlannedReparentShard(keyspaceName, shard.Name, newPrimary.Alias) +} diff --git a/go/test/endtoend/transaction/twopc/utils/utils.go b/go/test/endtoend/transaction/twopc/utils/utils.go new file mode 100644 index 00000000000..7311375ee55 --- /dev/null +++ b/go/test/endtoend/transaction/twopc/utils/utils.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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 utils + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/vt/log" +) + +// ClearOutTable deletes everything from a table. Sometimes the table might have more rows than allowed in a single delete query, +// so we have to do the deletions iteratively. +func ClearOutTable(t *testing.T, vtParams mysql.ConnParams, tableName string) { + ctx := context.Background() + for { + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + + res, err := conn.ExecuteFetch(fmt.Sprintf("SELECT count(*) FROM %v", tableName), 1, false) + if err != nil { + log.Errorf("Error in selecting - %v", err) + conn.Close() + continue + } + require.Len(t, res.Rows, 1) + require.Len(t, res.Rows[0], 1) + rowCount, err := res.Rows[0][0].ToInt() + require.NoError(t, err) + if rowCount == 0 { + conn.Close() + return + } + _, err = conn.ExecuteFetch(fmt.Sprintf("DELETE FROM %v LIMIT 10000", tableName), 10000, false) + if err != nil { + log.Errorf("Error in cleanup deletion - %v", err) + conn.Close() + continue + } + } +} diff --git a/go/test/endtoend/transaction/twopc/vschema.json b/go/test/endtoend/transaction/twopc/vschema.json index 4ff62df6808..bca58b05c1e 100644 --- a/go/test/endtoend/transaction/twopc/vschema.json +++ b/go/test/endtoend/transaction/twopc/vschema.json @@ -3,6 +3,9 @@ "vindexes": { "xxhash": { "type": "xxhash" + }, + "reverse_bits": { + "type": "reverse_bits" } }, "tables": { @@ -21,6 +24,14 @@ "name": "xxhash" } ] + }, + "twopc_t1": { + "column_vindexes": [ + { + "column": "id", + "name": "reverse_bits" + } + ] } } } \ No newline at end of file diff --git a/go/vt/vterrors/code.go b/go/vt/vterrors/code.go index 857ba538ebe..83a87503265 100644 --- a/go/vt/vterrors/code.go +++ b/go/vt/vterrors/code.go @@ -96,6 +96,7 @@ var ( VT09022 = errorWithoutState("VT09022", vtrpcpb.Code_FAILED_PRECONDITION, "Destination does not have exactly one shard: %v", "Cannot send query to multiple shards.") VT09023 = errorWithoutState("VT09023", vtrpcpb.Code_FAILED_PRECONDITION, "could not map %v to a keyspace id", "Unable to determine the shard for the given row.") VT09024 = errorWithoutState("VT09024", vtrpcpb.Code_FAILED_PRECONDITION, "could not map %v to a unique keyspace id: %v", "Unable to determine the shard for the given row.") + VT09025 = errorWithoutState("VT09025", vtrpcpb.Code_FAILED_PRECONDITION, "atomic transaction error: %v", "Error in atomic transactions") VT10001 = errorWithoutState("VT10001", vtrpcpb.Code_ABORTED, "foreign key constraints are not allowed", "Foreign key constraints are not allowed, see https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/.") VT10002 = errorWithoutState("VT10002", vtrpcpb.Code_ABORTED, "atomic distributed transaction not allowed: %s", "The distributed transaction cannot be committed. A rollback decision is taken.") diff --git a/go/vt/vttablet/tabletserver/debug_2pc.go b/go/vt/vttablet/tabletserver/debug_2pc.go new file mode 100644 index 00000000000..a0de20104db --- /dev/null +++ b/go/vt/vttablet/tabletserver/debug_2pc.go @@ -0,0 +1,48 @@ +//go:build debug2PC + +/* +Copyright 2024 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 tabletserver + +import ( + "os" + "path" + "strconv" + "time" + + "vitess.io/vitess/go/vt/log" +) + +const DebugTwoPc = true + +// readFileForTestSynchronization is a test-only function that reads a file +// that we use for synchronizing some of the tests. +func readFileForTestSynchronization(fileName string) string { + res, _ := os.ReadFile(path.Join(os.Getenv("VTDATAROOT"), fileName)) + return string(res) +} + +// commitPreparedDelayForTest is a test-only function that delays the commit that have already been prepared. +func commitPreparedDelayForTest(tsv *TabletServer) { + sh := readFileForTestSynchronization("VT_DELAY_COMMIT_SHARD") + if tsv.sm.target.Shard == sh { + delay := readFileForTestSynchronization("VT_DELAY_COMMIT_TIME") + delVal, _ := strconv.Atoi(delay) + log.Infof("Delaying commit for shard %v for %d seconds", sh, delVal) + time.Sleep(time.Duration(delVal) * time.Second) + } +} diff --git a/go/vt/vttablet/tabletserver/dt_executor.go b/go/vt/vttablet/tabletserver/dt_executor.go index 5f4e7644766..9ddca3247a3 100644 --- a/go/vt/vttablet/tabletserver/dt_executor.go +++ b/go/vt/vttablet/tabletserver/dt_executor.go @@ -96,7 +96,7 @@ func (dte *DTExecutor) CommitPrepared(dtid string) (err error) { var conn *StatefulConnection conn, err = dte.te.preparedPool.FetchForCommit(dtid) if err != nil { - return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "cannot commit dtid %s, state: %v", dtid, err) + return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "cannot commit dtid %s, err: %v", dtid, err) } // No connection means the transaction was already committed. if conn == nil { diff --git a/go/vt/vttablet/tabletserver/dt_executor_test.go b/go/vt/vttablet/tabletserver/dt_executor_test.go index 045496eb4b8..fb45ab454fc 100644 --- a/go/vt/vttablet/tabletserver/dt_executor_test.go +++ b/go/vt/vttablet/tabletserver/dt_executor_test.go @@ -228,7 +228,7 @@ func TestTxExecutorCommitRedoFail(t *testing.T) { // A retry should fail differently as the prepared transaction is marked as failed. err = txe.CommitPrepared("bb") require.Error(t, err) - require.Contains(t, err.Error(), "cannot commit dtid bb, state: failed") + require.Contains(t, err.Error(), "cannot commit dtid bb, err: VT09025: atomic transaction error: failed to commit") require.Contains(t, strings.Join(tl.GetAllLogs(), "|"), "failed to commit the prepared transaction 'bb' with error: unknown error: delete redo log fail") diff --git a/go/vt/vttablet/tabletserver/production.go b/go/vt/vttablet/tabletserver/production.go new file mode 100644 index 00000000000..70cb8b092fa --- /dev/null +++ b/go/vt/vttablet/tabletserver/production.go @@ -0,0 +1,30 @@ +//go:build !debug2PC + +/* +Copyright 2024 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 tabletserver + +// This file defines debug constants that are always false. +// This file is used for building production code. +// We use go build directives to include a file that defines the constant to true +// when certain tags are provided while building binaries. +// This allows to have debugging code written in normal code flow without affecting +// production performance. + +const DebugTwoPc = false + +func commitPreparedDelayForTest(tsv *TabletServer) {} diff --git a/go/vt/vttablet/tabletserver/tabletserver.go b/go/vt/vttablet/tabletserver/tabletserver.go index 167d55a4e6f..e3e951892b7 100644 --- a/go/vt/vttablet/tabletserver/tabletserver.go +++ b/go/vt/vttablet/tabletserver/tabletserver.go @@ -660,6 +660,9 @@ func (tsv *TabletServer) CommitPrepared(ctx context.Context, target *querypb.Tar target, nil, true, /* allowOnShutdown */ func(ctx context.Context, logStats *tabletenv.LogStats) error { txe := NewDTExecutor(ctx, tsv.te, logStats) + if DebugTwoPc { + commitPreparedDelayForTest(tsv) + } return txe.CommitPrepared(dtid) }, ) diff --git a/go/vt/vttablet/tabletserver/tx_engine.go b/go/vt/vttablet/tabletserver/tx_engine.go index 57c6ff1fd64..33e22e321bc 100644 --- a/go/vt/vttablet/tabletserver/tx_engine.go +++ b/go/vt/vttablet/tabletserver/tx_engine.go @@ -160,6 +160,8 @@ func (te *TxEngine) transition(state txEngineState) { te.txPool.Open(te.env.Config().DB.AppWithDB(), te.env.Config().DB.DbaWithDB(), te.env.Config().DB.AppDebugWithDB()) if te.twopcEnabled && te.state == AcceptingReadAndWrite { + // Set the preparedPool to start accepting connections. + te.preparedPool.shutdown = false // If there are errors, we choose to raise an alert and // continue anyway. Serving traffic is considered more important // than blocking everything for the sake of a few transactions. @@ -442,7 +444,7 @@ func (te *TxEngine) shutdownTransactions() { func (te *TxEngine) rollbackPrepared() { ctx := tabletenv.LocalContext() - for _, conn := range te.preparedPool.FetchAll() { + for _, conn := range te.preparedPool.FetchAllForRollback() { te.txPool.Rollback(ctx, conn) conn.Release(tx.TxRollback) } diff --git a/go/vt/vttablet/tabletserver/tx_prep_pool.go b/go/vt/vttablet/tabletserver/tx_prep_pool.go index 22e0ce295c0..d5376172856 100644 --- a/go/vt/vttablet/tabletserver/tx_prep_pool.go +++ b/go/vt/vttablet/tabletserver/tx_prep_pool.go @@ -17,14 +17,16 @@ limitations under the License. package tabletserver import ( - "errors" "fmt" "sync" + + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/vterrors" ) var ( - errPrepCommitting = errors.New("locked for committing") - errPrepFailed = errors.New("failed to commit") + errPrepCommitting = vterrors.VT09025("locked for committing") + errPrepFailed = vterrors.VT09025("failed to commit") ) // TxPreparedPool manages connections for prepared transactions. @@ -34,6 +36,8 @@ type TxPreparedPool struct { mu sync.Mutex conns map[string]*StatefulConnection reserved map[string]error + // shutdown tells if the prepared pool has been drained and shutdown. + shutdown bool capacity int } @@ -55,14 +59,18 @@ func NewTxPreparedPool(capacity int) *TxPreparedPool { func (pp *TxPreparedPool) Put(c *StatefulConnection, dtid string) error { pp.mu.Lock() defer pp.mu.Unlock() + // If the pool is shutdown, we don't accept new prepared transactions. + if pp.shutdown { + return vterrors.VT09025("pool is shutdown") + } if _, ok := pp.reserved[dtid]; ok { - return errors.New("duplicate DTID in Prepare: " + dtid) + return vterrors.VT09025("duplicate DTID in Prepare: " + dtid) } if _, ok := pp.conns[dtid]; ok { - return errors.New("duplicate DTID in Prepare: " + dtid) + return vterrors.VT09025("duplicate DTID in Prepare: " + dtid) } if len(pp.conns) >= pp.capacity { - return fmt.Errorf("prepared transactions exceeded limit: %d", pp.capacity) + return vterrors.New(vtrpcpb.Code_RESOURCE_EXHAUSTED, fmt.Sprintf("prepared transactions exceeded limit: %d", pp.capacity)) } pp.conns[dtid] = c return nil @@ -95,6 +103,11 @@ func (pp *TxPreparedPool) FetchForRollback(dtid string) *StatefulConnection { func (pp *TxPreparedPool) FetchForCommit(dtid string) (*StatefulConnection, error) { pp.mu.Lock() defer pp.mu.Unlock() + // If the pool is shutdown, we don't have any connections to return. + // That however doesn't mean this transaction was committed, it could very well have been rollbacked. + if pp.shutdown { + return nil, vterrors.VT09025("pool is shutdown") + } if err, ok := pp.reserved[dtid]; ok { return nil, err } @@ -121,11 +134,12 @@ func (pp *TxPreparedPool) Forget(dtid string) { delete(pp.reserved, dtid) } -// FetchAll removes all connections and returns them as a list. +// FetchAllForRollback removes all connections and returns them as a list. // It also forgets all reserved dtids. -func (pp *TxPreparedPool) FetchAll() []*StatefulConnection { +func (pp *TxPreparedPool) FetchAllForRollback() []*StatefulConnection { pp.mu.Lock() defer pp.mu.Unlock() + pp.shutdown = true conns := make([]*StatefulConnection, 0, len(pp.conns)) for _, c := range pp.conns { conns = append(conns, c) diff --git a/go/vt/vttablet/tabletserver/tx_prep_pool_test.go b/go/vt/vttablet/tabletserver/tx_prep_pool_test.go index cd2b5a180c1..42e2b800e0e 100644 --- a/go/vt/vttablet/tabletserver/tx_prep_pool_test.go +++ b/go/vt/vttablet/tabletserver/tx_prep_pool_test.go @@ -25,11 +25,8 @@ import ( func TestEmptyPrep(t *testing.T) { pp := NewTxPreparedPool(0) - want := "prepared transactions exceeded limit: 0" err := pp.Put(nil, "aa") - if err == nil || err.Error() != want { - t.Errorf("Put err: %v, want %s", err, want) - } + require.ErrorContains(t, err, "prepared transactions exceeded limit: 0") } func TestPrepPut(t *testing.T) { @@ -38,23 +35,15 @@ func TestPrepPut(t *testing.T) { require.NoError(t, err) err = pp.Put(nil, "bb") require.NoError(t, err) - want := "prepared transactions exceeded limit: 2" err = pp.Put(nil, "cc") - if err == nil || err.Error() != want { - t.Errorf("Put err: %v, want %s", err, want) - } + require.ErrorContains(t, err, "prepared transactions exceeded limit: 2") err = pp.Put(nil, "aa") - want = "duplicate DTID in Prepare: aa" - if err == nil || err.Error() != want { - t.Errorf("Put err: %v, want %s", err, want) - } + require.ErrorContains(t, err, "duplicate DTID in Prepare: aa") + _, err = pp.FetchForCommit("aa") require.NoError(t, err) err = pp.Put(nil, "aa") - want = "duplicate DTID in Prepare: aa" - if err == nil || err.Error() != want { - t.Errorf("Put err: %v, want %s", err, want) - } + require.ErrorContains(t, err, "duplicate DTID in Prepare: aa") pp.Forget("aa") err = pp.Put(nil, "aa") require.NoError(t, err) @@ -113,11 +102,9 @@ func TestPrepFetchAll(t *testing.T) { conn2 := &StatefulConnection{} pp.Put(conn1, "aa") pp.Put(conn2, "bb") - got := pp.FetchAll() - if len(got) != 2 { - t.Errorf("FetchAll len: %d, want 2", len(got)) - } - if len(pp.conns) != 0 { - t.Errorf("len(pp.conns): %d, want 0", len(pp.conns)) - } + got := pp.FetchAllForRollback() + require.Len(t, got, 2) + require.Len(t, pp.conns, 0) + _, err := pp.FetchForCommit("aa") + require.ErrorContains(t, err, "pool is shutdown") } From 7f0980902178af0ccfd45795bc0c3ea0215c15d5 Mon Sep 17 00:00:00 2001 From: Noble Mittal <62551163+beingnoble03@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:34:35 +0530 Subject: [PATCH 33/34] evalengine: Implement `PERIOD_DIFF` (#16557) Signed-off-by: Noble Mittal --- go/vt/vtgate/evalengine/cached_size.go | 12 +++++ go/vt/vtgate/evalengine/compiler_asm.go | 20 +++++++ go/vt/vtgate/evalengine/fn_time.go | 55 ++++++++++++++++++++ go/vt/vtgate/evalengine/testcases/cases.go | 22 ++++++++ go/vt/vtgate/evalengine/translate_builtin.go | 7 +++ 5 files changed, 116 insertions(+) diff --git a/go/vt/vtgate/evalengine/cached_size.go b/go/vt/vtgate/evalengine/cached_size.go index 9009b069f5a..c1ed1f9475c 100644 --- a/go/vt/vtgate/evalengine/cached_size.go +++ b/go/vt/vtgate/evalengine/cached_size.go @@ -1409,6 +1409,18 @@ func (cached *builtinPeriodAdd) CachedSize(alloc bool) int64 { size += cached.CallExpr.CachedSize(false) return size } +func (cached *builtinPeriodDiff) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(48) + } + // field CallExpr vitess.io/vitess/go/vt/vtgate/evalengine.CallExpr + size += cached.CallExpr.CachedSize(false) + return size +} func (cached *builtinPi) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) diff --git a/go/vt/vtgate/evalengine/compiler_asm.go b/go/vt/vtgate/evalengine/compiler_asm.go index 93781ed077b..dfb1a30bffc 100644 --- a/go/vt/vtgate/evalengine/compiler_asm.go +++ b/go/vt/vtgate/evalengine/compiler_asm.go @@ -4319,6 +4319,26 @@ func (asm *assembler) Fn_PERIOD_ADD() { }, "FN PERIOD_ADD INT64(SP-2) INT64(SP-1)") } +func (asm *assembler) Fn_PERIOD_DIFF() { + asm.adjustStack(-1) + asm.emit(func(env *ExpressionEnv) int { + if env.vm.stack[env.vm.sp-2] == nil { + env.vm.sp-- + return 1 + } + period1 := env.vm.stack[env.vm.sp-2].(*evalInt64).i + period2 := env.vm.stack[env.vm.sp-1].(*evalInt64).i + res, err := periodDiff(period1, period2) + if err != nil { + env.vm.err = err + return 0 + } + env.vm.stack[env.vm.sp-2] = res + env.vm.sp-- + return 1 + }, "FN PERIOD_DIFF INT64(SP-2) INT64(SP-1)") +} + func (asm *assembler) Interval(l int) { asm.adjustStack(-l) asm.emit(func(env *ExpressionEnv) int { diff --git a/go/vt/vtgate/evalengine/fn_time.go b/go/vt/vtgate/evalengine/fn_time.go index 90fcda2c32a..2d5e12f518d 100644 --- a/go/vt/vtgate/evalengine/fn_time.go +++ b/go/vt/vtgate/evalengine/fn_time.go @@ -181,6 +181,10 @@ type ( CallExpr } + builtinPeriodDiff struct { + CallExpr + } + builtinDateMath struct { CallExpr sub bool @@ -222,6 +226,7 @@ var _ IR = (*builtinWeekOfYear)(nil) var _ IR = (*builtinYear)(nil) var _ IR = (*builtinYearWeek)(nil) var _ IR = (*builtinPeriodAdd)(nil) +var _ IR = (*builtinPeriodDiff)(nil) func (call *builtinNow) eval(env *ExpressionEnv) (eval, error) { now := env.time(call.utc) @@ -2021,6 +2026,56 @@ func (call *builtinPeriodAdd) compile(c *compiler) (ctype, error) { return ctype{Type: sqltypes.Int64, Flag: period.Flag | months.Flag | flagNullable}, nil } +func periodDiff(period1, period2 int64) (*evalInt64, error) { + if !datetime.ValidatePeriod(period1) || !datetime.ValidatePeriod(period2) { + return nil, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongArguments, "Incorrect arguments to period_diff") + } + res := datetime.PeriodToMonths(period1) - datetime.PeriodToMonths(period2) + return newEvalInt64(res), nil +} + +func (b *builtinPeriodDiff) eval(env *ExpressionEnv) (eval, error) { + p1, p2, err := b.arg2(env) + if err != nil { + return nil, err + } + if p1 == nil || p2 == nil { + return nil, nil + } + period1 := evalToInt64(p1) + period2 := evalToInt64(p2) + return periodDiff(period1.i, period2.i) +} + +func (call *builtinPeriodDiff) compile(c *compiler) (ctype, error) { + period1, err := call.Arguments[0].compile(c) + if err != nil { + return ctype{}, err + } + period2, err := call.Arguments[1].compile(c) + if err != nil { + return ctype{}, err + } + + skip := c.compileNullCheck2(period1, period2) + + switch period1.Type { + case sqltypes.Int64: + default: + c.asm.Convert_xi(2) + } + + switch period2.Type { + case sqltypes.Int64: + default: + c.asm.Convert_xi(1) + } + + c.asm.Fn_PERIOD_DIFF() + c.asm.jumpDestination(skip) + return ctype{Type: sqltypes.Int64, Flag: period1.Flag | period2.Flag | flagNullable}, nil +} + func evalToInterval(itv eval, unit datetime.IntervalType, negate bool) *datetime.Interval { switch itv := itv.(type) { case *evalBytes: diff --git a/go/vt/vtgate/evalengine/testcases/cases.go b/go/vt/vtgate/evalengine/testcases/cases.go index 7d5305b21f7..ed1c5ed1f76 100644 --- a/go/vt/vtgate/evalengine/testcases/cases.go +++ b/go/vt/vtgate/evalengine/testcases/cases.go @@ -156,6 +156,7 @@ var Cases = []TestCase{ {Run: FnYear}, {Run: FnYearWeek}, {Run: FnPeriodAdd}, + {Run: FnPeriodDiff}, {Run: FnInetAton}, {Run: FnInetNtoa}, {Run: FnInet6Aton}, @@ -2245,6 +2246,27 @@ func FnPeriodAdd(yield Query) { } } +func FnPeriodDiff(yield Query) { + for _, p1 := range inputBitwise { + for _, p2 := range inputBitwise { + yield(fmt.Sprintf("PERIOD_DIFF(%s, %s)", p1, p2), nil) + } + } + for _, p1 := range inputPeriods { + for _, p2 := range inputPeriods { + yield(fmt.Sprintf("PERIOD_DIFF(%s, %s)", p1, p2), nil) + } + } + + mysqlDocSamples := []string{ + `PERIOD_DIFF(200802,200703)`, + } + + for _, q := range mysqlDocSamples { + yield(q, nil) + } +} + func FnInetAton(yield Query) { for _, d := range ipInputs { yield(fmt.Sprintf("INET_ATON(%s)", d), nil) diff --git a/go/vt/vtgate/evalengine/translate_builtin.go b/go/vt/vtgate/evalengine/translate_builtin.go index 2c4d887ff19..476ee32483b 100644 --- a/go/vt/vtgate/evalengine/translate_builtin.go +++ b/go/vt/vtgate/evalengine/translate_builtin.go @@ -535,6 +535,13 @@ func (ast *astCompiler) translateFuncExpr(fn *sqlparser.FuncExpr) (IR, error) { default: return nil, argError(method) } + case "period_diff": + switch len(args) { + case 2: + return &builtinPeriodDiff{CallExpr: call}, nil + default: + return nil, argError(method) + } case "inet_aton": if len(args) != 1 { return nil, argError(method) From f2d5d1cf5438d861127b3a65984cb39843225901 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Mon, 12 Aug 2024 21:36:21 +0530 Subject: [PATCH 34/34] allow innodb_lock_wait_timeout as system variable (#16574) Signed-off-by: Harshit Gangal --- .../vtgate/reservedconn/sysvar_test.go | 32 +++++++++++++ go/vt/sqlparser/ast_rewriting.go | 4 +- go/vt/sysvars/sysvars.go | 2 +- go/vt/vtgate/executor_select_test.go | 47 +++++++++++++++++++ go/vt/vtgate/executor_set_test.go | 7 +++ 5 files changed, 90 insertions(+), 2 deletions(-) diff --git a/go/test/endtoend/vtgate/reservedconn/sysvar_test.go b/go/test/endtoend/vtgate/reservedconn/sysvar_test.go index 564cc671d5f..e7e0cfb0259 100644 --- a/go/test/endtoend/vtgate/reservedconn/sysvar_test.go +++ b/go/test/endtoend/vtgate/reservedconn/sysvar_test.go @@ -461,3 +461,35 @@ func TestSysVarTxIsolation(t *testing.T) { // second run, to ensuring the setting is applied on the session and not just on next query after settings. utils.AssertContains(t, conn, "select @@transaction_isolation, connection_id()", `SERIALIZABLE`) } + +// TestSysVarInnodbWaitTimeout tests the innodb_lock_wait_timeout system variable +func TestSysVarInnodbWaitTimeout(t *testing.T) { + conn, err := mysql.Connect(context.Background(), &vtParams) + require.NoError(t, err) + defer conn.Close() + + // default from mysql + utils.AssertMatches(t, conn, "select @@innodb_lock_wait_timeout", `[[UINT64(20)]]`) + utils.AssertMatches(t, conn, "select @@global.innodb_lock_wait_timeout", `[[UINT64(20)]]`) + // ensuring it goes to mysql + utils.AssertContains(t, conn, "select @@innodb_lock_wait_timeout", `UINT64(20)`) + utils.AssertContains(t, conn, "select @@global.innodb_lock_wait_timeout", `UINT64(20)`) + + // setting to different value. + utils.Exec(t, conn, "set @@innodb_lock_wait_timeout = 120") + utils.AssertMatches(t, conn, "select @@innodb_lock_wait_timeout", `[[INT64(120)]]`) + // ensuring it goes to mysql + utils.AssertContains(t, conn, "select @@global.innodb_lock_wait_timeout, connection_id()", `UINT64(20)`) + utils.AssertContains(t, conn, "select @@innodb_lock_wait_timeout, connection_id()", `INT64(120)`) + // second run, to ensuring the setting is applied on the session and not just on next query after settings. + utils.AssertContains(t, conn, "select @@innodb_lock_wait_timeout, connection_id()", `INT64(120)`) + + // changing setting to different value. + utils.Exec(t, conn, "set @@innodb_lock_wait_timeout = 240") + utils.AssertMatches(t, conn, "select @@innodb_lock_wait_timeout", `[[INT64(240)]]`) + // ensuring it goes to mysql + utils.AssertContains(t, conn, "select @@global.innodb_lock_wait_timeout, connection_id()", `UINT64(20)`) + utils.AssertContains(t, conn, "select @@innodb_lock_wait_timeout, connection_id()", `INT64(240)`) + // second run, to ensuring the setting is applied on the session and not just on next query after settings. + utils.AssertContains(t, conn, "select @@innodb_lock_wait_timeout, connection_id()", `INT64(240)`) +} diff --git a/go/vt/sqlparser/ast_rewriting.go b/go/vt/sqlparser/ast_rewriting.go index 64de1f9d920..ef46b124875 100644 --- a/go/vt/sqlparser/ast_rewriting.go +++ b/go/vt/sqlparser/ast_rewriting.go @@ -301,10 +301,12 @@ func (er *astRewriter) rewriteVariable(cursor *Cursor, node *Variable) { if v, isSet := cursor.Parent().(*SetExpr); isSet && v.Var == node { return } + // no rewriting for global scope variable. + // this should be returned from the underlying database. switch node.Scope { case VariableScope: er.udvRewrite(cursor, node) - case GlobalScope, SessionScope, NextTxScope: + case SessionScope, NextTxScope: er.sysVarRewrite(cursor, node) } } diff --git a/go/vt/sysvars/sysvars.go b/go/vt/sysvars/sysvars.go index c8037563ca1..297ed956bf8 100644 --- a/go/vt/sysvars/sysvars.go +++ b/go/vt/sysvars/sysvars.go @@ -191,6 +191,7 @@ var ( {Name: ForeignKeyChecks, IsBoolean: true, SupportSetVar: true}, {Name: "group_concat_max_len", SupportSetVar: true}, {Name: "information_schema_stats_expiry"}, + {Name: "innodb_lock_wait_timeout"}, {Name: "max_heap_table_size", SupportSetVar: true}, {Name: "max_seeks_for_key", SupportSetVar: true}, {Name: "max_tmp_tables"}, @@ -246,7 +247,6 @@ var ( {Name: "collation_server"}, {Name: "completion_type"}, {Name: "div_precision_increment", SupportSetVar: true}, - {Name: "innodb_lock_wait_timeout"}, {Name: "interactive_timeout"}, {Name: "lc_time_names"}, {Name: "lock_wait_timeout", SupportSetVar: true}, diff --git a/go/vt/vtgate/executor_select_test.go b/go/vt/vtgate/executor_select_test.go index fa448092550..6e3bcdd3eda 100644 --- a/go/vt/vtgate/executor_select_test.go +++ b/go/vt/vtgate/executor_select_test.go @@ -4348,3 +4348,50 @@ func TestStreamJoinQuery(t *testing.T) { utils.MustMatch(t, wantResult.Rows[idx], result.Rows[idx], "mismatched on: ", strconv.Itoa(idx)) } } + +// TestSysVarGlobalAndSession tests that global and session variables are set correctly. +// It also tests that setting a global variable does not affect the session variable and vice versa. +// Also, test what happens on running select @@global and select @@session for a system variable. +func TestSysVarGlobalAndSession(t *testing.T) { + executor, sbc1, _, _, _ := createExecutorEnv(t) + executor.normalize = true + session := NewAutocommitSession(&vtgatepb.Session{EnableSystemSettings: true, SystemVariables: map[string]string{}}) + + sbc1.SetResults([]*sqltypes.Result{ + sqltypes.MakeTestResult(sqltypes.MakeTestFields("innodb_lock_wait_timeout", "uint64"), "20"), + sqltypes.MakeTestResult(sqltypes.MakeTestFields("innodb_lock_wait_timeout", "uint64"), "20"), + sqltypes.MakeTestResult(sqltypes.MakeTestFields("1", "int64")), + sqltypes.MakeTestResult(sqltypes.MakeTestFields("new", "uint64"), "40"), + sqltypes.MakeTestResult(sqltypes.MakeTestFields("reserve_execute", "uint64")), + sqltypes.MakeTestResult(sqltypes.MakeTestFields("@@global.innodb_lock_wait_timeout", "uint64"), "20"), + }) + qr, err := executor.Execute(context.Background(), nil, "TestSetStmt", session, + "select @@innodb_lock_wait_timeout", nil) + require.NoError(t, err) + require.Equal(t, `[[UINT64(20)]]`, fmt.Sprintf("%v", qr.Rows)) + + qr, err = executor.Execute(context.Background(), nil, "TestSetStmt", session, + "select @@global.innodb_lock_wait_timeout", nil) + require.NoError(t, err) + require.Equal(t, `[[UINT64(20)]]`, fmt.Sprintf("%v", qr.Rows)) + + _, err = executor.Execute(context.Background(), nil, "TestSetStmt", session, + "set @@global.innodb_lock_wait_timeout = 120", nil) + require.NoError(t, err) + require.Empty(t, session.SystemVariables["innodb_lock_wait_timeout"]) + + _, err = executor.Execute(context.Background(), nil, "TestSetStmt", session, + "set @@innodb_lock_wait_timeout = 40", nil) + require.NoError(t, err) + require.EqualValues(t, "40", session.SystemVariables["innodb_lock_wait_timeout"]) + + qr, err = executor.Execute(context.Background(), nil, "TestSetStmt", session, + "select @@innodb_lock_wait_timeout", nil) + require.NoError(t, err) + require.Equal(t, `[[INT64(40)]]`, fmt.Sprintf("%v", qr.Rows)) + + qr, err = executor.Execute(context.Background(), nil, "TestSetStmt", session, + "select @@global.innodb_lock_wait_timeout", nil) + require.NoError(t, err) + require.Equal(t, `[[UINT64(20)]]`, fmt.Sprintf("%v", qr.Rows)) +} diff --git a/go/vt/vtgate/executor_set_test.go b/go/vt/vtgate/executor_set_test.go index 5e66899db44..2792c957edd 100644 --- a/go/vt/vtgate/executor_set_test.go +++ b/go/vt/vtgate/executor_set_test.go @@ -364,6 +364,13 @@ func TestExecutorSetOp(t *testing.T) { in: "set tx_isolation = 'read-committed'", sysVars: map[string]string{"tx_isolation": "'read-committed'"}, result: returnResult("tx_isolation", "varchar", "read-committed"), + }, { + in: "set @@innodb_lock_wait_timeout=120", + sysVars: map[string]string{"innodb_lock_wait_timeout": "120"}, + result: returnResult("innodb_lock_wait_timeout", "int64", "120"), + }, { + in: "set @@global.innodb_lock_wait_timeout=120", + result: returnResult("innodb_lock_wait_timeout", "int64", "120"), }} for _, tcase := range testcases { t.Run(tcase.in, func(t *testing.T) {