@@ -119,7 +119,7 @@ func TestCDCIterator_New(t *testing.T) {
119
119
}
120
120
}
121
121
122
- func TestCDCIterator_Next (t * testing.T ) {
122
+ func TestCDCIterator_Operation_NextN (t * testing.T ) {
123
123
ctx := test .Context (t )
124
124
is := is .New (t )
125
125
@@ -343,9 +343,11 @@ func TestCDCIterator_Next(t *testing.T) {
343
343
// fetch the change
344
344
nextCtx , cancel := context .WithTimeout (ctx , time .Second * 10 )
345
345
defer cancel ()
346
- got , err := i .Next (nextCtx )
346
+ records , err := i .NextN (nextCtx , 1 )
347
347
is .NoErr (err )
348
348
349
+ got := records [0 ]
350
+
349
351
readAt , err := got .Metadata .GetReadAt ()
350
352
is .NoErr (err )
351
353
is .True (readAt .After (now )) // ReadAt should be after now
@@ -359,40 +361,6 @@ func TestCDCIterator_Next(t *testing.T) {
359
361
}
360
362
}
361
363
362
- func TestCDCIterator_Next_Fail (t * testing.T ) {
363
- ctx := test .Context (t )
364
-
365
- pool := test .ConnectPool (ctx , t , test .RepmgrConnString )
366
- table := test .SetupTestTable (ctx , t , pool )
367
-
368
- t .Run ("fail when sub is done" , func (t * testing.T ) {
369
- is := is .New (t )
370
-
371
- i := testCDCIterator (ctx , t , pool , table , true )
372
- <- i .sub .Ready ()
373
-
374
- is .NoErr (i .Teardown (ctx ))
375
-
376
- _ , err := i .Next (ctx )
377
- expectErr := "logical replication error:"
378
-
379
- match := strings .Contains (err .Error (), expectErr )
380
- if ! match {
381
- t .Logf ("%s != %s" , err .Error (), expectErr )
382
- }
383
- is .True (match )
384
- })
385
-
386
- t .Run ("fail when subscriber is not started" , func (t * testing.T ) {
387
- is := is .New (t )
388
-
389
- i := testCDCIterator (ctx , t , pool , table , false )
390
-
391
- _ , nexterr := i .Next (ctx )
392
- is .Equal (nexterr .Error (), "logical replication has not been started" )
393
- })
394
- }
395
-
396
364
func TestCDCIterator_EnsureLSN (t * testing.T ) {
397
365
ctx := test .Context (t )
398
366
is := is .New (t )
@@ -407,8 +375,11 @@ func TestCDCIterator_EnsureLSN(t *testing.T) {
407
375
VALUES (6, 'bizz', 456, false, 12.3, 14)` , table ))
408
376
is .NoErr (err )
409
377
410
- r , err := i .Next (ctx )
378
+ rr , err := i .NextN (ctx , 1 )
411
379
is .NoErr (err )
380
+ is .True (len (rr ) > 0 )
381
+
382
+ r := rr [0 ]
412
383
413
384
p , err := position .ParseSDKPosition (r .Position )
414
385
is .NoErr (err )
@@ -485,6 +456,138 @@ func TestCDCIterator_Ack(t *testing.T) {
485
456
})
486
457
}
487
458
}
459
+ func TestCDCIterator_NextN (t * testing.T ) {
460
+ ctx := test .Context (t )
461
+ pool := test .ConnectPool (ctx , t , test .RepmgrConnString )
462
+ table := test .SetupTestTable (ctx , t , pool )
463
+
464
+ t .Run ("retrieve exact N records" , func (t * testing.T ) {
465
+ is := is .New (t )
466
+ i := testCDCIterator (ctx , t , pool , table , true )
467
+ <- i .sub .Ready ()
468
+
469
+ for j := 1 ; j <= 3 ; j ++ {
470
+ _ , err := pool .Exec (ctx , fmt .Sprintf (`INSERT INTO %s (id, column1, column2, column3, column4, column5)
471
+ VALUES (%d, 'test-%d', %d, false, 12.3, 14)` , table , j + 10 , j , j * 100 ))
472
+ is .NoErr (err )
473
+ }
474
+
475
+ var allRecords []opencdc.Record
476
+ attemptCtx , cancel := context .WithTimeout (ctx , 5 * time .Second )
477
+ defer cancel ()
478
+
479
+ // Collect records until we have all 3
480
+ for len (allRecords ) < 3 {
481
+ records , err := i .NextN (attemptCtx , 3 - len (allRecords ))
482
+ is .NoErr (err )
483
+ // Only proceed if we got at least one record
484
+ is .True (len (records ) > 0 )
485
+ allRecords = append (allRecords , records ... )
486
+ }
487
+
488
+ is .Equal (len (allRecords ), 3 )
489
+
490
+ for j , r := range allRecords {
491
+ is .Equal (r .Operation , opencdc .OperationCreate )
492
+ is .Equal (r .Key .(opencdc.StructuredData )["id" ], int64 (j + 11 ))
493
+ change := r .Payload
494
+ data := change .After .(opencdc.StructuredData )
495
+ is .Equal (data ["column1" ], fmt .Sprintf ("test-%d" , j + 1 ))
496
+ //nolint:gosec // no risk to overflow
497
+ is .Equal (data ["column2" ], (int32 (j )+ 1 )* 100 )
498
+ }
499
+ })
500
+
501
+ t .Run ("retrieve fewer records than requested" , func (t * testing.T ) {
502
+ is := is .New (t )
503
+ i := testCDCIterator (ctx , t , pool , table , true )
504
+ <- i .sub .Ready ()
505
+
506
+ for j := 1 ; j <= 2 ; j ++ {
507
+ _ , err := pool .Exec (ctx , fmt .Sprintf (`INSERT INTO %s (id, column1, column2, column3, column4, column5)
508
+ VALUES (%d, 'test-%d', %d, false, 12.3, 14)` , table , j + 20 , j , j * 100 ))
509
+ is .NoErr (err )
510
+ }
511
+
512
+ // Will keep calling NextN until all records are received
513
+ var records []opencdc.Record
514
+ for len (records ) < 2 {
515
+ recordsTmp , err := i .NextN (ctx , 5 )
516
+ is .NoErr (err )
517
+ records = append (records , recordsTmp ... )
518
+ }
519
+
520
+ // nothing else to fetch
521
+ ctxWithTimeout , cancel := context .WithTimeout (ctx , 500 * time .Millisecond )
522
+ defer cancel ()
523
+ _ , err := i .NextN (ctxWithTimeout , 5 )
524
+ is .True (errors .Is (err , context .DeadlineExceeded ))
525
+
526
+ for j , r := range records {
527
+ is .Equal (r .Operation , opencdc .OperationCreate )
528
+ is .Equal (r .Key .(opencdc.StructuredData )["id" ], int64 (j + 21 ))
529
+ change := r .Payload
530
+ data := change .After .(opencdc.StructuredData )
531
+ is .Equal (data ["column1" ], fmt .Sprintf ("test-%d" , j + 1 ))
532
+ //nolint:gosec // no risk to overflow
533
+ is .Equal (data ["column2" ], (int32 (j )+ 1 )* 100 )
534
+ }
535
+ })
536
+
537
+ t .Run ("context cancellation" , func (t * testing.T ) {
538
+ is := is .New (t )
539
+ i := testCDCIterator (ctx , t , pool , table , true )
540
+ <- i .sub .Ready ()
541
+
542
+ ctxTimeout , cancel := context .WithTimeout (ctx , 100 * time .Millisecond )
543
+ defer cancel ()
544
+
545
+ _ , err := i .NextN (ctxTimeout , 5 )
546
+ is .True (errors .Is (err , context .DeadlineExceeded ))
547
+ })
548
+
549
+ t .Run ("subscriber not started" , func (t * testing.T ) {
550
+ is := is .New (t )
551
+ i := testCDCIterator (ctx , t , pool , table , false )
552
+
553
+ _ , err := i .NextN (ctx , 5 )
554
+ is .Equal (err .Error (), "logical replication has not been started" )
555
+ })
556
+
557
+ t .Run ("invalid N values" , func (t * testing.T ) {
558
+ is := is .New (t )
559
+ i := testCDCIterator (ctx , t , pool , table , true )
560
+ <- i .sub .Ready ()
561
+
562
+ _ , err := i .NextN (ctx , 0 )
563
+ is .True (strings .Contains (err .Error (), "n must be greater than 0" ))
564
+
565
+ _ , err = i .NextN (ctx , - 1 )
566
+ is .True (strings .Contains (err .Error (), "n must be greater than 0" ))
567
+ })
568
+
569
+ t .Run ("subscription termination" , func (t * testing.T ) {
570
+ is := is .New (t )
571
+ i := testCDCIterator (ctx , t , pool , table , true )
572
+ <- i .sub .Ready ()
573
+
574
+ _ , err := pool .Exec (ctx , fmt .Sprintf (`INSERT INTO %s (id, column1, column2, column3, column4, column5)
575
+ VALUES (30, 'test-1', 100, false, 12.3, 14)` , table ))
576
+ is .NoErr (err )
577
+
578
+ go func () {
579
+ time .Sleep (100 * time .Millisecond )
580
+ is .NoErr (i .Teardown (ctx ))
581
+ }()
582
+
583
+ records , err := i .NextN (ctx , 5 )
584
+ if err != nil {
585
+ is .True (strings .Contains (err .Error (), "logical replication error" ))
586
+ } else {
587
+ is .True (len (records ) > 0 )
588
+ }
589
+ })
590
+ }
488
591
489
592
func testCDCIterator (ctx context.Context , t * testing.T , pool * pgxpool.Pool , table string , start bool ) * CDCIterator {
490
593
is := is .New (t )
@@ -560,8 +663,11 @@ func TestCDCIterator_Schema(t *testing.T) {
560
663
)
561
664
is .NoErr (err )
562
665
563
- r , err := i .Next (ctx )
666
+ rr , err := i .NextN (ctx , 1 )
564
667
is .NoErr (err )
668
+ is .True (len (rr ) > 0 )
669
+
670
+ r := rr [0 ]
565
671
566
672
assertPayloadSchemaOK (ctx , is , test .TestTableAvroSchemaV1 , table , r )
567
673
assertKeySchemaOK (ctx , is , table , r )
@@ -580,8 +686,11 @@ func TestCDCIterator_Schema(t *testing.T) {
580
686
)
581
687
is .NoErr (err )
582
688
583
- r , err := i .Next (ctx )
689
+ rr , err := i .NextN (ctx , 1 )
584
690
is .NoErr (err )
691
+ is .True (len (rr ) > 0 )
692
+
693
+ r := rr [0 ]
585
694
586
695
assertPayloadSchemaOK (ctx , is , test .TestTableAvroSchemaV2 , table , r )
587
696
assertKeySchemaOK (ctx , is , table , r )
@@ -600,8 +709,11 @@ func TestCDCIterator_Schema(t *testing.T) {
600
709
)
601
710
is .NoErr (err )
602
711
603
- r , err := i .Next (ctx )
712
+ rr , err := i .NextN (ctx , 1 )
604
713
is .NoErr (err )
714
+ is .True (len (rr ) > 0 )
715
+
716
+ r := rr [0 ]
605
717
606
718
assertPayloadSchemaOK (ctx , is , test .TestTableAvroSchemaV3 , table , r )
607
719
assertKeySchemaOK (ctx , is , table , r )
0 commit comments