github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/tests/integration/topic_cdc_reader_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package integration 5 6 import ( 7 "context" 8 "fmt" 9 "path" 10 "sync/atomic" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/require" 15 16 "github.com/ydb-platform/ydb-go-sdk/v3" 17 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 18 "github.com/ydb-platform/ydb-go-sdk/v3/table" 19 "github.com/ydb-platform/ydb-go-sdk/v3/table/types" 20 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicoptions" 21 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicreader" 22 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topictypes" 23 ) 24 25 const ( 26 consumerName = "test-consumer" 27 ) 28 29 func TestReadMessagesAndCommit(t *testing.T) { 30 ctx := xtest.Context(t) 31 32 db, reader := createFeedAndReader(ctx, t, topicoptions.WithCommitMode(topicoptions.CommitModeSync)) 33 34 sendCDCMessage(ctx, t, db) 35 36 msg, err := reader.ReadMessage(ctx) 37 require.NoError(t, err) 38 require.Equal(t, int64(1), msg.SeqNo) 39 40 require.NoError(t, reader.Commit(ctx, msg)) 41 require.NoError(t, reader.Close(ctx)) 42 43 sendCDCMessage(ctx, t, db) 44 sendCDCMessage(ctx, t, db) 45 reader = createFeedReader(t, db) 46 47 // read only no committed messages 48 for i := 0; i < 2; i++ { 49 msg, err = reader.ReadMessage(ctx) 50 require.NoError(t, err) 51 require.Equal(t, int64(i)+2, msg.SeqNo) 52 } 53 54 // and can't read more messages 55 ctxTimeout, cancel := context.WithTimeout(ctx, time.Second/10) 56 _, err = reader.ReadMessage(ctxTimeout) 57 cancel() 58 require.ErrorIs(t, err, context.DeadlineExceeded) 59 } 60 61 func TestCDCFeedSendTopicPathSameAsSubscribed(t *testing.T) { 62 ctx := xtest.Context(t) 63 64 db, reader := createFeedAndReader(ctx, t) 65 66 topicName := "feed" 67 topicPath := db.Name() + "/test/feed" 68 69 t.Run("ReceivedMessage", func(t *testing.T) { 70 sendCDCMessage(ctx, t, db) 71 72 msg, err := reader.ReadMessage(ctx) 73 require.NoError(t, err) 74 75 require.Equal(t, topicPath, msg.Topic()) 76 }) 77 t.Run("Describe", func(t *testing.T) { 78 res, err := db.Topic().Describe(ctx, topicPath) 79 require.NoError(t, err) 80 require.Equal(t, topicName, res.Path) 81 }) 82 } 83 84 func TestTopicPath(t *testing.T) { 85 ctx := xtest.Context(t) 86 db := connect(t) 87 88 topicPath := db.Name() + "/" + t.Name() 89 _ = db.Topic().Drop(ctx, topicPath) 90 91 err := db.Topic().Create(ctx, topicPath) 92 require.NoError(t, err) 93 } 94 95 func TestCDCInTableDescribe(t *testing.T) { 96 ctx := xtest.Context(t) 97 db := connect(t) 98 topicPath := createCDCFeed(ctx, t, db) 99 100 t.Run("SchemeDescribePath", func(t *testing.T) { 101 desc, err := db.Scheme().DescribePath(ctx, topicPath) 102 require.NoError(t, err) 103 require.True(t, desc.IsTopic()) 104 }) 105 106 t.Run("DescribeTable", func(t *testing.T) { 107 err := db.Table().Do(ctx, func(ctx context.Context, s table.Session) error { 108 tablePath := path.Dir(topicPath) 109 topicName := path.Base(topicPath) 110 desc, err := s.DescribeTable(ctx, tablePath) 111 if err != nil { 112 return err 113 } 114 if topicName != desc.Changefeeds[0].Name { 115 return fmt.Errorf("unexpected topic name: %s, epx: %s", desc.Changefeeds[0].Name, topicName) 116 } 117 return nil 118 }, table.WithIdempotent()) 119 require.NoError(t, err) 120 }) 121 } 122 123 func createCDCFeed(ctx context.Context, t *testing.T, db *ydb.Driver) string { 124 err := db.Table().Do(ctx, func(ctx context.Context, s table.Session) error { 125 _ = s.ExecuteSchemeQuery(ctx, "DROP TABLE test") 126 err := s.ExecuteSchemeQuery(ctx, ` 127 CREATE TABLE 128 test 129 ( 130 id Int64, 131 val Utf8, 132 PRIMARY KEY (id) 133 )`, 134 ) 135 if err != nil { 136 return fmt.Errorf("failed to create table: %w", err) 137 } 138 139 err = s.ExecuteSchemeQuery(ctx, ` 140 ALTER TABLE 141 test 142 ADD CHANGEFEED 143 feed 144 WITH ( 145 FORMAT = 'JSON', 146 MODE = 'UPDATES' 147 )`, 148 ) 149 if err != nil { 150 return fmt.Errorf("failed to add changefeed: %w", err) 151 } 152 153 return nil 154 }, table.WithIdempotent()) 155 require.NoError(t, err) 156 157 topicPath := testCDCFeedName(db) 158 159 require.NoError(t, err) 160 161 err = db.Topic().Alter( 162 ctx, 163 topicPath, 164 topicoptions.AlterWithAddConsumers(topictypes.Consumer{Name: consumerName}), 165 ) 166 require.NoError(t, err) 167 return topicPath 168 } 169 170 func createFeedReader(t *testing.T, db *ydb.Driver, opts ...topicoptions.ReaderOption) *topicreader.Reader { 171 topicPath := testCDCFeedName(db) 172 reader, err := db.Topic().StartReader(consumerName, []topicoptions.ReadSelector{ 173 { 174 Path: topicPath, 175 }, 176 }, opts...) 177 require.NoError(t, err) 178 return reader 179 } 180 181 func createFeedAndReader( 182 ctx context.Context, 183 t *testing.T, 184 opts ...topicoptions.ReaderOption, 185 ) (*ydb.Driver, *topicreader.Reader) { 186 db := connect(t) 187 createCDCFeed(ctx, t, db) 188 reader := createFeedReader(t, db, opts...) 189 return db, reader 190 } 191 192 var sendCDCCounter int64 193 194 func sendCDCMessage(ctx context.Context, t *testing.T, db *ydb.Driver) { 195 counter := atomic.AddInt64(&sendCDCCounter, 1) 196 err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error { 197 _, err := tx.Execute(ctx, 198 "DECLARE $id AS Int64; INSERT INTO test (id, val) VALUES($id, 'asd')", 199 table.NewQueryParameters(table.ValueParam("$id", types.Int64Value(counter)))) 200 return err 201 }) 202 require.NoError(t, err) 203 } 204 205 func testCDCFeedName(db *ydb.Driver) string { 206 return db.Name() + "/test/feed" 207 }