github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/tests/integration/table_bulk_upsert_test.go (about)

     1  //go:build integration
     2  // +build integration
     3  
     4  package integration
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/ydb-platform/ydb-go-sdk/v3"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/table"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/table/result/named"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/table/types"
    18  )
    19  
    20  func TestTableBulkUpsertSession(t *testing.T) {
    21  	var (
    22  		scope     = newScope(t)
    23  		driver    = scope.Driver()
    24  		tablePath = scope.TablePath()
    25  	)
    26  
    27  	// upsert
    28  	var rows []types.Value
    29  
    30  	for i := int64(0); i < 10; i++ {
    31  		val := fmt.Sprintf("value for %v", i)
    32  		rows = append(rows, types.StructValue(
    33  			types.StructFieldValue("id", types.Int64Value(i)),
    34  			types.StructFieldValue("val", types.TextValue(val)),
    35  		))
    36  	}
    37  
    38  	err := driver.Table().Do(scope.Ctx, func(ctx context.Context, s table.Session) error {
    39  		return s.BulkUpsert(ctx, tablePath, types.ListValue(rows...))
    40  	})
    41  	scope.Require.NoError(err)
    42  
    43  	for i := int64(0); i < 10; i++ {
    44  		val := fmt.Sprintf("value for %v", i)
    45  		assertIdValue(scope.Ctx, t, tablePath, i, val)
    46  	}
    47  }
    48  
    49  func TestTableBulkUpsert(t *testing.T) {
    50  	var (
    51  		scope     = newScope(t)
    52  		driver    = scope.Driver()
    53  		tablePath = scope.TablePath()
    54  	)
    55  
    56  	// upsert
    57  	var rows []types.Value
    58  
    59  	for i := int64(0); i < 10; i++ {
    60  		val := fmt.Sprintf("value for %v", i)
    61  		rows = append(rows, types.StructValue(
    62  			types.StructFieldValue("id", types.Int64Value(i)),
    63  			types.StructFieldValue("val", types.TextValue(val)),
    64  		))
    65  	}
    66  
    67  	err := driver.Table().BulkUpsert(scope.Ctx, tablePath, table.BulkUpsertDataRows(
    68  		types.ListValue(rows...),
    69  	))
    70  	scope.Require.NoError(err)
    71  
    72  	for i := int64(0); i < 10; i++ {
    73  		val := fmt.Sprintf("value for %v", i)
    74  		assertIdValue(scope.Ctx, t, tablePath, i, val)
    75  	}
    76  }
    77  
    78  func TestTableCsvBulkUpsert(t *testing.T) {
    79  	var (
    80  		scope     = newScope(t)
    81  		driver    = scope.Driver()
    82  		tablePath = scope.TablePath()
    83  	)
    84  
    85  	csv := `id,val
    86  42,"text42"
    87  43,"text43"`
    88  
    89  	err := driver.Table().BulkUpsert(scope.Ctx, tablePath, table.BulkUpsertDataCsv(
    90  		[]byte(csv),
    91  		table.WithCsvHeader(),
    92  	))
    93  	scope.Require.NoError(err)
    94  
    95  	assertIdValue(scope.Ctx, t, tablePath, 42, "text42")
    96  	assertIdValue(scope.Ctx, t, tablePath, 43, "text43")
    97  }
    98  
    99  func TestTableCsvBulkUpsertDelimiter(t *testing.T) {
   100  	var (
   101  		scope     = newScope(t)
   102  		driver    = scope.Driver()
   103  		tablePath = scope.TablePath()
   104  	)
   105  
   106  	csv := `id:val
   107  42:"text42"
   108  43:"text43"`
   109  
   110  	err := driver.Table().BulkUpsert(scope.Ctx, tablePath, table.BulkUpsertDataCsv(
   111  		[]byte(csv),
   112  		table.WithCsvHeader(),
   113  		table.WithCsvDelimiter([]byte(":")),
   114  	))
   115  	scope.Require.NoError(err)
   116  
   117  	assertIdValue(scope.Ctx, t, tablePath, 42, "text42")
   118  	assertIdValue(scope.Ctx, t, tablePath, 43, "text43")
   119  }
   120  
   121  func TestTableCsvBulkUpsertNullValue(t *testing.T) {
   122  	var (
   123  		scope     = newScope(t)
   124  		driver    = scope.Driver()
   125  		tablePath = scope.TablePath()
   126  	)
   127  
   128  	csv := `id,val
   129  42,hello
   130  43,hello world`
   131  
   132  	err := driver.Table().BulkUpsert(scope.Ctx, tablePath, table.BulkUpsertDataCsv(
   133  		[]byte(csv),
   134  		table.WithCsvHeader(),
   135  		table.WithCsvNullValue([]byte("hello")),
   136  	))
   137  	scope.Require.NoError(err)
   138  
   139  	assertIdValueNil(scope.Ctx, t, tablePath, 42)
   140  	assertIdValue(scope.Ctx, t, tablePath, 43, "hello world")
   141  }
   142  
   143  func TestTableCsvBulkUpsertSkipRows(t *testing.T) {
   144  	var (
   145  		scope     = newScope(t)
   146  		driver    = scope.Driver()
   147  		tablePath = scope.TablePath()
   148  	)
   149  
   150  	// Empty row are OK after skipped rows
   151  	csv := `First skip row
   152  			Second skip row
   153  
   154  id,val
   155  42,123
   156  43,456
   157  
   158  `
   159  
   160  	err := driver.Table().BulkUpsert(scope.Ctx, tablePath, table.BulkUpsertDataCsv(
   161  		[]byte(csv),
   162  		table.WithCsvHeader(),
   163  		table.WithCsvSkipRows(2),
   164  	))
   165  	scope.Require.NoError(err)
   166  
   167  	assertIdValue(scope.Ctx, t, tablePath, 42, "123")
   168  	assertIdValue(scope.Ctx, t, tablePath, 43, "456")
   169  }
   170  
   171  func TestTableArrowBulkUpsert(t *testing.T) {
   172  	var (
   173  		scope     = newScope(t)
   174  		driver    = scope.Driver()
   175  		tablePath = scope.TablePath()
   176  	)
   177  
   178  	// data & schema generated with make_test_arrow.py script
   179  	data, err := os.ReadFile("testdata/bulk_upsert_test_data.arrow")
   180  	scope.Require.NoError(err)
   181  
   182  	schema, err := os.ReadFile("testdata/bulk_upsert_test_schema.arrow")
   183  	scope.Require.NoError(err)
   184  
   185  	err = driver.Table().BulkUpsert(scope.Ctx, tablePath, table.BulkUpsertDataArrow(
   186  		[]byte(data),
   187  		table.WithArrowSchema(schema),
   188  	))
   189  	scope.Require.NoError(err)
   190  
   191  	assertIdValue(scope.Ctx, t, tablePath, 123, "data1")
   192  	assertIdValue(scope.Ctx, t, tablePath, 234, "data2")
   193  }
   194  
   195  func assertIdValueImpl(ctx context.Context, t *testing.T, tableName string, id int64, val *string) {
   196  	ctx, cancel := context.WithCancel(context.Background())
   197  	defer cancel()
   198  
   199  	db, err := ydb.Open(ctx,
   200  		os.Getenv("YDB_CONNECTION_STRING"),
   201  		// ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")),
   202  	)
   203  	require.NoError(t, err)
   204  	err = db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) (err error) {
   205  		res, err := tx.Execute(ctx, fmt.Sprintf("SELECT val FROM `%s` WHERE id = %d", tableName, id), nil)
   206  		if err != nil {
   207  			return err
   208  		}
   209  		err = res.NextResultSetErr(ctx)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		require.EqualValues(t, 1, res.ResultSetCount())
   214  		if !res.NextRow() {
   215  			if err = res.Err(); err != nil {
   216  				return err
   217  			}
   218  			return fmt.Errorf("unexpected empty result set")
   219  		}
   220  		var resultVal *string
   221  		err = res.ScanNamed(
   222  			named.Optional("val", &resultVal),
   223  		)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		if val != nil {
   228  			require.NotEmpty(t, resultVal)
   229  			require.EqualValues(t, *val, *resultVal)
   230  		} else {
   231  			require.Nil(t, resultVal)
   232  		}
   233  
   234  		return res.Err()
   235  	}, table.WithTxSettings(table.TxSettings(table.WithSnapshotReadOnly())), table.WithIdempotent())
   236  	require.NoError(t, err)
   237  }
   238  
   239  func assertIdValue(ctx context.Context, t *testing.T, tableName string, id int64, val string) {
   240  	assertIdValueImpl(ctx, t, tableName, id, &val)
   241  }
   242  
   243  func assertIdValueNil(ctx context.Context, t *testing.T, tableName string, id int64) {
   244  	assertIdValueImpl(ctx, t, tableName, id, nil)
   245  }