github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/changefeedccl/kvfeed/kv_feed_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package kvfeed 10 11 import ( 12 "context" 13 "math" 14 "sort" 15 "testing" 16 "time" 17 18 "github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/changefeedbase" 19 "github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/schemafeed" 20 "github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/schemafeed/schematestutils" 21 "github.com/cockroachdb/cockroach/pkg/keys" 22 "github.com/cockroachdb/cockroach/pkg/roachpb" 23 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 24 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 25 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 26 "github.com/cockroachdb/cockroach/pkg/util/ctxgroup" 27 "github.com/cockroachdb/cockroach/pkg/util/encoding" 28 "github.com/cockroachdb/cockroach/pkg/util/hlc" 29 "github.com/cockroachdb/cockroach/pkg/util/mon" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 ) 33 34 func TestKVFeed(t *testing.T) { 35 // We want to inject fake table events and data into the buffer 36 // and use that to assert that there are proper calls to the kvScanner and 37 // what not. 38 ts := func(seconds int) hlc.Timestamp { 39 return hlc.Timestamp{WallTime: (time.Duration(seconds) * time.Second).Nanoseconds()} 40 } 41 kv := func(tableID uint32, k, v string, ts hlc.Timestamp) roachpb.KeyValue { 42 vDatum := tree.DString(k) 43 key, err := sqlbase.EncodeTableKey(keys.SystemSQLCodec.TablePrefix(tableID), &vDatum, encoding.Ascending) 44 if err != nil { 45 panic(err) 46 } 47 return roachpb.KeyValue{ 48 Key: key, 49 Value: roachpb.Value{ 50 RawBytes: []byte(v), 51 Timestamp: ts, 52 }, 53 } 54 } 55 kvEvent := func(tableID uint32, k, v string, ts hlc.Timestamp) roachpb.RangeFeedEvent { 56 keyVal := kv(tableID, k, v, ts) 57 return roachpb.RangeFeedEvent{ 58 Val: &roachpb.RangeFeedValue{ 59 Key: keyVal.Key, 60 Value: keyVal.Value, 61 }, 62 Checkpoint: nil, 63 Error: nil, 64 } 65 } 66 checkpointEvent := func(span roachpb.Span, ts hlc.Timestamp) roachpb.RangeFeedEvent { 67 return roachpb.RangeFeedEvent{ 68 Checkpoint: &roachpb.RangeFeedCheckpoint{ 69 Span: span, 70 ResolvedTS: ts, 71 }, 72 } 73 } 74 75 type testCase struct { 76 name string 77 needsInitialScan bool 78 withDiff bool 79 schemaChangeEvents changefeedbase.SchemaChangeEventClass 80 schemaChangePolicy changefeedbase.SchemaChangePolicy 81 initialHighWater hlc.Timestamp 82 spans []roachpb.Span 83 events []roachpb.RangeFeedEvent 84 85 descs []*sqlbase.TableDescriptor 86 87 expScans []hlc.Timestamp 88 expEvents int 89 expErrRE string 90 } 91 runTest := func(t *testing.T, tc testCase) { 92 settings := cluster.MakeTestingClusterSettings() 93 buf := MakeChanBuffer() 94 mm := mon.MakeUnlimitedMonitor( 95 context.Background(), "test", mon.MemoryResource, 96 nil /* curCount */, nil /* maxHist */, math.MaxInt64, settings, 97 ) 98 metrics := MakeMetrics(time.Minute) 99 bufferFactory := func() EventBuffer { 100 return makeMemBuffer(mm.MakeBoundAccount(), &metrics) 101 } 102 scans := make(chan physicalConfig) 103 sf := scannerFunc(func(ctx context.Context, sink EventBufferWriter, cfg physicalConfig) error { 104 select { 105 case scans <- cfg: 106 return nil 107 case <-ctx.Done(): 108 return ctx.Err() 109 } 110 }) 111 ref := rawEventFeed(tc.events) 112 tf := newRawTableFeed(tc.descs, tc.initialHighWater) 113 f := newKVFeed(buf, tc.spans, 114 tc.schemaChangeEvents, tc.schemaChangePolicy, 115 tc.needsInitialScan, tc.withDiff, 116 tc.initialHighWater, 117 &tf, sf, rangefeedFactory(ref.run), bufferFactory) 118 ctx, cancel := context.WithCancel(context.Background()) 119 g := ctxgroup.WithContext(ctx) 120 g.GoCtx(func(ctx context.Context) error { 121 return f.run(ctx) 122 }) 123 testG := ctxgroup.WithContext(ctx) 124 testG.GoCtx(func(ctx context.Context) error { 125 for expScans := tc.expScans; len(expScans) > 0; expScans = expScans[1:] { 126 scan := <-scans 127 assert.Equal(t, expScans[0], scan.Timestamp) 128 assert.Equal(t, tc.withDiff, scan.WithDiff) 129 } 130 return nil 131 }) 132 testG.GoCtx(func(ctx context.Context) error { 133 for events := 0; events < tc.expEvents; events++ { 134 _, err := buf.Get(ctx) 135 assert.NoError(t, err) 136 } 137 return nil 138 }) 139 // Wait for the feed to fail rather than canceling it. 140 if tc.schemaChangePolicy == changefeedbase.OptSchemaChangePolicyStop { 141 testG.Go(func() error { 142 _ = g.Wait() 143 return nil 144 }) 145 } 146 require.NoError(t, testG.Wait()) 147 cancel() 148 if runErr := g.Wait(); tc.expErrRE != "" { 149 require.Regexp(t, tc.expErrRE, runErr) 150 } else { 151 require.Regexp(t, context.Canceled, runErr) 152 } 153 } 154 makeTableDesc := schematestutils.MakeTableDesc 155 addColumnDropBackfillMutation := schematestutils.AddColumnDropBackfillMutation 156 for _, tc := range []testCase{ 157 { 158 name: "no events - backfill", 159 schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault, 160 schemaChangePolicy: changefeedbase.OptSchemaChangePolicyBackfill, 161 needsInitialScan: true, 162 initialHighWater: ts(2), 163 spans: []roachpb.Span{ 164 tableSpan(42), 165 }, 166 events: []roachpb.RangeFeedEvent{ 167 kvEvent(42, "a", "b", ts(3)), 168 }, 169 expScans: []hlc.Timestamp{ 170 ts(2), 171 }, 172 expEvents: 1, 173 }, 174 { 175 name: "one table event - backfill", 176 schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault, 177 schemaChangePolicy: changefeedbase.OptSchemaChangePolicyBackfill, 178 needsInitialScan: true, 179 initialHighWater: ts(2), 180 spans: []roachpb.Span{ 181 tableSpan(42), 182 }, 183 events: []roachpb.RangeFeedEvent{ 184 kvEvent(42, "a", "b", ts(3)), 185 checkpointEvent(tableSpan(42), ts(4)), 186 kvEvent(42, "a", "b", ts(5)), 187 checkpointEvent(tableSpan(42), ts(2)), // ensure that events are filtered 188 checkpointEvent(tableSpan(42), ts(5)), 189 }, 190 expScans: []hlc.Timestamp{ 191 ts(2), 192 ts(3), 193 }, 194 descs: []*sqlbase.TableDescriptor{ 195 makeTableDesc(42, 1, ts(1), 2), 196 addColumnDropBackfillMutation(makeTableDesc(42, 2, ts(3), 1)), 197 }, 198 expEvents: 2, 199 }, 200 { 201 name: "one table event - skip", 202 schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault, 203 schemaChangePolicy: changefeedbase.OptSchemaChangePolicyNoBackfill, 204 needsInitialScan: true, 205 initialHighWater: ts(2), 206 spans: []roachpb.Span{ 207 tableSpan(42), 208 }, 209 events: []roachpb.RangeFeedEvent{ 210 kvEvent(42, "a", "b", ts(3)), 211 checkpointEvent(tableSpan(42), ts(4)), 212 kvEvent(42, "a", "b", ts(5)), 213 checkpointEvent(tableSpan(42), ts(6)), 214 }, 215 expScans: []hlc.Timestamp{ 216 ts(2), 217 }, 218 descs: []*sqlbase.TableDescriptor{ 219 makeTableDesc(42, 1, ts(1), 2), 220 addColumnDropBackfillMutation(makeTableDesc(42, 2, ts(3), 1)), 221 }, 222 expEvents: 4, 223 }, 224 { 225 name: "one table event - stop", 226 schemaChangeEvents: changefeedbase.OptSchemaChangeEventClassDefault, 227 schemaChangePolicy: changefeedbase.OptSchemaChangePolicyStop, 228 needsInitialScan: true, 229 initialHighWater: ts(2), 230 spans: []roachpb.Span{ 231 tableSpan(42), 232 }, 233 events: []roachpb.RangeFeedEvent{ 234 kvEvent(42, "a", "b", ts(3)), 235 checkpointEvent(tableSpan(42), ts(4)), 236 kvEvent(42, "a", "b", ts(5)), 237 checkpointEvent(tableSpan(42), ts(2)), // ensure that events are filtered 238 checkpointEvent(tableSpan(42), ts(5)), 239 }, 240 expScans: []hlc.Timestamp{ 241 ts(2), 242 }, 243 descs: []*sqlbase.TableDescriptor{ 244 makeTableDesc(42, 1, ts(1), 2), 245 addColumnDropBackfillMutation(makeTableDesc(42, 2, ts(4), 1)), 246 }, 247 expEvents: 2, 248 expErrRE: "schema change ...", 249 }, 250 } { 251 t.Run(tc.name, func(t *testing.T) { 252 runTest(t, tc) 253 }) 254 } 255 } 256 257 type scannerFunc func(ctx context.Context, sink EventBufferWriter, cfg physicalConfig) error 258 259 func (s scannerFunc) Scan(ctx context.Context, sink EventBufferWriter, cfg physicalConfig) error { 260 return s(ctx, sink, cfg) 261 } 262 263 var _ kvScanner = (scannerFunc)(nil) 264 265 type rawTableFeed struct { 266 events []schemafeed.TableEvent 267 } 268 269 func newRawTableFeed( 270 descs []*sqlbase.TableDescriptor, initialHighWater hlc.Timestamp, 271 ) rawTableFeed { 272 sort.Slice(descs, func(i, j int) bool { 273 if descs[i].ID != descs[j].ID { 274 return descs[i].ID < descs[j].ID 275 } 276 return descs[i].ModificationTime.Less(descs[j].ModificationTime) 277 }) 278 f := rawTableFeed{} 279 curID := sqlbase.ID(math.MaxUint32) 280 for i, d := range descs { 281 if d.ID != curID { 282 curID = d.ID 283 continue 284 } 285 if d.ModificationTime.Less(initialHighWater) { 286 continue 287 } 288 f.events = append(f.events, schemafeed.TableEvent{ 289 Before: descs[i-1], 290 After: d, 291 }) 292 } 293 return f 294 } 295 296 func (r *rawTableFeed) Peek( 297 ctx context.Context, atOrBefore hlc.Timestamp, 298 ) (events []schemafeed.TableEvent, err error) { 299 return r.peekOrPop(ctx, atOrBefore, false /* pop */) 300 } 301 302 func (r *rawTableFeed) Pop( 303 ctx context.Context, atOrBefore hlc.Timestamp, 304 ) (events []schemafeed.TableEvent, err error) { 305 return r.peekOrPop(ctx, atOrBefore, true /* pop */) 306 } 307 308 func (r *rawTableFeed) peekOrPop( 309 ctx context.Context, atOrBefore hlc.Timestamp, pop bool, 310 ) (events []schemafeed.TableEvent, err error) { 311 i := sort.Search(len(r.events), func(i int) bool { 312 return !r.events[i].Timestamp().LessEq(atOrBefore) 313 }) 314 if i == -1 { 315 i = 0 316 } 317 events = r.events[:i] 318 if pop { 319 r.events = r.events[i:] 320 } 321 return events, nil 322 } 323 324 type rawEventFeed []roachpb.RangeFeedEvent 325 326 func (f rawEventFeed) run( 327 ctx context.Context, 328 span roachpb.Span, 329 startFrom hlc.Timestamp, 330 withDiff bool, 331 eventC chan<- *roachpb.RangeFeedEvent, 332 ) error { 333 // We can't use binary search because the errors don't have timestamps. 334 // Instead we just search for the first event which comes after the start time. 335 var i int 336 for i = range f { 337 ev := f[i] 338 if ev.Val != nil && startFrom.Less(ev.Val.Value.Timestamp) || 339 ev.Checkpoint != nil && startFrom.Less(ev.Checkpoint.ResolvedTS) { 340 break 341 } 342 343 } 344 f = f[i:] 345 for i := range f { 346 select { 347 case eventC <- &f[i]: 348 case <-ctx.Done(): 349 return ctx.Err() 350 } 351 } 352 return nil 353 } 354 355 var _ schemaFeed = (*rawTableFeed)(nil) 356 357 func tableSpan(tableID uint32) roachpb.Span { 358 return roachpb.Span{ 359 Key: keys.SystemSQLCodec.TablePrefix(tableID), 360 EndKey: keys.SystemSQLCodec.TablePrefix(tableID).PrefixEnd(), 361 } 362 }