vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/vstream.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package engine 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 24 "vitess.io/vitess/go/vt/vterrors" 25 26 "vitess.io/vitess/go/sqltypes" 27 "vitess.io/vitess/go/vt/key" 28 "vitess.io/vitess/go/vt/vtgate/vindexes" 29 30 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 31 querypb "vitess.io/vitess/go/vt/proto/query" 32 ) 33 34 var _ Primitive = (*VStream)(nil) 35 36 // VStream is an operator for streaming specific keyspace, destination 37 type VStream struct { 38 Keyspace *vindexes.Keyspace 39 TargetDestination key.Destination 40 TableName string 41 Position string 42 Limit int 43 44 noTxNeeded 45 noInputs 46 } 47 48 // RouteType implements the Primitive interface 49 func (v *VStream) RouteType() string { 50 return "VStream" 51 } 52 53 // GetKeyspaceName implements the Primitive interface 54 func (v *VStream) GetKeyspaceName() string { 55 return v.Keyspace.Name 56 } 57 58 // GetTableName implements the Primitive interface 59 func (v *VStream) GetTableName() string { 60 return v.TableName 61 } 62 63 // TryExecute implements the Primitive interface 64 func (v *VStream) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) { 65 return nil, vterrors.VT13001("TryExecute is not supported for VStream") 66 } 67 68 // TryStreamExecute implements the Primitive interface 69 func (v *VStream) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error { 70 rss, _, err := vcursor.ResolveDestinations(ctx, v.Keyspace.Name, nil, []key.Destination{v.TargetDestination}) 71 if err != nil { 72 return err 73 } 74 filter := &binlogdatapb.Filter{ 75 Rules: []*binlogdatapb.Rule{{ 76 Match: v.TableName, 77 Filter: fmt.Sprintf("select * from %s", v.TableName), 78 }}, 79 } 80 var lastFields []*querypb.Field 81 numRows := 0 82 totalRows := 0 83 send := func(evs []*binlogdatapb.VEvent) error { 84 result := &sqltypes.Result{ 85 Fields: nil, 86 Rows: [][]sqltypes.Value{}, 87 } 88 for _, ev := range evs { 89 if totalRows+numRows >= v.Limit { 90 break 91 } 92 switch ev.Type { 93 case binlogdatapb.VEventType_FIELD: 94 lastFields = []*querypb.Field{{ 95 Name: "op", 96 Type: querypb.Type_VARCHAR, 97 }} 98 lastFields = append(lastFields, ev.FieldEvent.Fields...) 99 case binlogdatapb.VEventType_ROW: 100 result.Fields = lastFields 101 eventFields := lastFields[1:] 102 for _, change := range ev.RowEvent.RowChanges { 103 newVals := addRowChangeIndicatorColumn(change, eventFields) 104 result.Rows = append(result.Rows, newVals) 105 numRows++ 106 if totalRows+numRows >= v.Limit { 107 break 108 } 109 } 110 default: 111 } 112 } 113 if numRows > 0 { 114 err := callback(result) 115 if err != nil { 116 return err 117 } 118 totalRows += numRows 119 numRows = 0 120 if v.Limit > 0 && totalRows >= v.Limit { 121 return io.EOF 122 } 123 } 124 return nil 125 } 126 127 return vcursor.VStream(ctx, rss, filter, v.Position, send) 128 } 129 130 // for demo purposes we prefix the row with a column with a single char +/*/- to indicate why the row changed 131 // + => insert, - => delete, * => update. This will be removed/improved as we iterate over this functionality 132 const ( 133 RowChangeInsert string = "+" 134 RowChangeDelete string = "-" 135 RowChangeUpdate string = "*" 136 ) 137 138 func addRowChangeIndicatorColumn(change *binlogdatapb.RowChange, eventFields []*querypb.Field) []sqltypes.Value { 139 op := "" 140 var vals []sqltypes.Value 141 if change.After != nil && change.Before == nil { 142 op = RowChangeInsert 143 vals = sqltypes.MakeRowTrusted(eventFields, change.After) 144 } else if change.After != nil && change.Before != nil { 145 op = RowChangeUpdate 146 vals = sqltypes.MakeRowTrusted(eventFields, change.After) 147 } else { 148 op = RowChangeDelete 149 vals = sqltypes.MakeRowTrusted(eventFields, change.Before) 150 } 151 newVals := append([]sqltypes.Value{sqltypes.NewVarChar(op)}, vals...) 152 return newVals 153 } 154 155 // GetFields implements the Primitive interface 156 func (v *VStream) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) { 157 return nil, vterrors.VT13001("GetFields is not supported for VStream") 158 } 159 160 func (v *VStream) description() PrimitiveDescription { 161 other := map[string]any{ 162 "Table": v.TableName, 163 "Limit": v.Limit, 164 "Position": v.Position, 165 } 166 return PrimitiveDescription{ 167 OperatorType: "VStream", 168 Keyspace: v.Keyspace, 169 TargetDestination: v.TargetDestination, 170 Other: other, 171 } 172 }