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  }