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

     1  //go:build integration
     2  // +build integration
     3  
     4  package integration
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  	"os"
    11  	"path"
    12  	"runtime/debug"
    13  	"strings"
    14  	"sync/atomic"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/stretchr/testify/require"
    19  	"google.golang.org/grpc"
    20  	grpcCodes "google.golang.org/grpc/codes"
    21  	"google.golang.org/grpc/metadata"
    22  
    23  	"github.com/ydb-platform/ydb-go-sdk/v3"
    24  	"github.com/ydb-platform/ydb-go-sdk/v3/balancers"
    25  	"github.com/ydb-platform/ydb-go-sdk/v3/config"
    26  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest"
    27  	"github.com/ydb-platform/ydb-go-sdk/v3/log"
    28  	"github.com/ydb-platform/ydb-go-sdk/v3/meta"
    29  	"github.com/ydb-platform/ydb-go-sdk/v3/sugar"
    30  	"github.com/ydb-platform/ydb-go-sdk/v3/table"
    31  	"github.com/ydb-platform/ydb-go-sdk/v3/table/options"
    32  	"github.com/ydb-platform/ydb-go-sdk/v3/table/result"
    33  	"github.com/ydb-platform/ydb-go-sdk/v3/table/result/named"
    34  	"github.com/ydb-platform/ydb-go-sdk/v3/table/types"
    35  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    36  )
    37  
    38  func TestBasicExampleNative(sourceTest *testing.T) { //nolint:gocyclo
    39  	t := xtest.MakeSyncedTest(sourceTest)
    40  	folder := t.Name()
    41  
    42  	ctx, cancel := context.WithTimeout(context.Background(), 42*time.Second)
    43  	defer cancel()
    44  
    45  	var totalConsumedUnits atomic.Uint64
    46  	defer func() {
    47  		t.Logf("total consumed units: %d", totalConsumedUnits.Load())
    48  	}()
    49  
    50  	ctx = meta.WithTrailerCallback(ctx, func(md metadata.MD) {
    51  		totalConsumedUnits.Add(meta.ConsumedUnits(md))
    52  	})
    53  
    54  	var (
    55  		limit      = 50
    56  		shutdowned atomic.Bool
    57  	)
    58  
    59  	db, err := ydb.Open(ctx,
    60  		os.Getenv("YDB_CONNECTION_STRING"),
    61  		ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")),
    62  		ydb.WithApplicationName("table/e2e"),
    63  		withMetrics(t, trace.DetailsAll, time.Second),
    64  		ydb.With(
    65  			config.WithOperationTimeout(time.Second*5),
    66  			config.WithOperationCancelAfter(time.Second*5),
    67  			config.ExcludeGRPCCodesForPessimization(grpcCodes.DeadlineExceeded),
    68  			config.WithGrpcOptions(
    69  				grpc.WithUnaryInterceptor(func(
    70  					ctx context.Context,
    71  					method string,
    72  					req, reply interface{},
    73  					cc *grpc.ClientConn,
    74  					invoker grpc.UnaryInvoker,
    75  					opts ...grpc.CallOption,
    76  				) error {
    77  					return invoker(ctx, method, req, reply, cc, opts...)
    78  				}),
    79  				grpc.WithStreamInterceptor(func(
    80  					ctx context.Context,
    81  					desc *grpc.StreamDesc,
    82  					cc *grpc.ClientConn,
    83  					method string,
    84  					streamer grpc.Streamer,
    85  					opts ...grpc.CallOption,
    86  				) (grpc.ClientStream, error) {
    87  					return streamer(ctx, desc, cc, method, opts...)
    88  				}),
    89  			),
    90  		),
    91  		ydb.WithBalancer(balancers.RandomChoice()),
    92  		ydb.WithDialTimeout(5*time.Second),
    93  		ydb.WithSessionPoolSizeLimit(limit),
    94  		ydb.WithConnectionTTL(5*time.Second),
    95  		ydb.WithLogger(
    96  			newLoggerWithMinLevel(t, log.FromString(os.Getenv("YDB_LOG_SEVERITY_LEVEL"))),
    97  			trace.MatchDetails(`ydb\.(driver|table|discovery|retry|scheme).*`),
    98  		),
    99  		ydb.WithPanicCallback(func(e interface{}) {
   100  			t.Fatalf("panic recovered:%v:\n%s", e, debug.Stack())
   101  		}),
   102  	)
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  
   107  	defer func() {
   108  		// cleanup
   109  		_ = db.Close(ctx)
   110  	}()
   111  
   112  	if err = db.Table().Do(ctx, func(ctx context.Context, _ table.Session) error {
   113  		// hack for wait pool initializing
   114  		return nil
   115  	}); err != nil {
   116  		t.Fatalf("pool not initialized: %+v", err)
   117  	}
   118  
   119  	// prepare scheme
   120  	err = sugar.RemoveRecursive(ctx, db, folder)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	err = sugar.MakeRecursive(ctx, db, folder)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  
   129  	t.Run("prepare", func(t *testing.T) {
   130  		t.Run("scheme", func(t *testing.T) {
   131  			t.Run("series", func(t *testing.T) {
   132  				err := db.Table().Do(ctx,
   133  					func(ctx context.Context, session table.Session) (err error) {
   134  						if _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "series")); err == nil {
   135  							_ = session.DropTable(ctx, path.Join(db.Name(), folder, "series"))
   136  						}
   137  						return session.CreateTable(ctx, path.Join(db.Name(), folder, "series"),
   138  							options.WithColumn("series_id", types.Optional(types.TypeUint64)),
   139  							options.WithColumn("title", types.Optional(types.TypeText)),
   140  							options.WithColumn("series_info", types.Optional(types.TypeText)),
   141  							options.WithColumn("release_date", types.Optional(types.TypeDate)),
   142  							options.WithColumn("comment", types.Optional(types.TypeText)),
   143  							options.WithPrimaryKeyColumn("series_id"),
   144  						)
   145  					},
   146  					table.WithIdempotent(),
   147  				)
   148  				require.NoError(t, err)
   149  			})
   150  			t.Run("seasons", func(t *testing.T) {
   151  				err := db.Table().Do(ctx,
   152  					func(ctx context.Context, session table.Session) (err error) {
   153  						if _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "seasons")); err == nil {
   154  							_ = session.DropTable(ctx, path.Join(db.Name(), folder, "seasons"))
   155  						}
   156  						return session.CreateTable(ctx, path.Join(db.Name(), folder, "seasons"),
   157  							options.WithColumn("series_id", types.Optional(types.TypeUint64)),
   158  							options.WithColumn("season_id", types.Optional(types.TypeUint64)),
   159  							options.WithColumn("title", types.Optional(types.TypeText)),
   160  							options.WithColumn("first_aired", types.Optional(types.TypeDate)),
   161  							options.WithColumn("last_aired", types.Optional(types.TypeDate)),
   162  							options.WithPrimaryKeyColumn("series_id", "season_id"),
   163  						)
   164  					},
   165  					table.WithIdempotent(),
   166  				)
   167  				require.NoError(t, err)
   168  			})
   169  			t.Run("episodes", func(t *testing.T) {
   170  				err := db.Table().Do(ctx,
   171  					func(ctx context.Context, session table.Session) (err error) {
   172  						if _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "episodes")); err == nil {
   173  							_ = session.DropTable(ctx, path.Join(db.Name(), folder, "episodes"))
   174  						}
   175  						return session.CreateTable(ctx, path.Join(db.Name(), folder, "episodes"),
   176  							options.WithColumn("series_id", types.Optional(types.TypeUint64)),
   177  							options.WithColumn("season_id", types.Optional(types.TypeUint64)),
   178  							options.WithColumn("episode_id", types.Optional(types.TypeUint64)),
   179  							options.WithColumn("title", types.Optional(types.TypeText)),
   180  							options.WithColumn("air_date", types.Optional(types.TypeDate)),
   181  							options.WithColumn("views", types.Optional(types.TypeUint64)),
   182  							options.WithPrimaryKeyColumn("series_id", "season_id", "episode_id"),
   183  						)
   184  					},
   185  					table.WithIdempotent(),
   186  				)
   187  				require.NoError(t, err)
   188  			})
   189  		})
   190  	})
   191  
   192  	t.Run("describe", func(t *testing.T) {
   193  		t.Run("table", func(t *testing.T) {
   194  			t.Run("series", func(t *testing.T) {
   195  				err := db.Table().Do(ctx,
   196  					func(ctx context.Context, session table.Session) (err error) {
   197  						_, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "series"))
   198  						if err != nil {
   199  							return
   200  						}
   201  						return err
   202  					},
   203  					table.WithIdempotent(),
   204  				)
   205  				require.NoError(t, err)
   206  			})
   207  			t.Run("seasons", func(t *testing.T) {
   208  				err := db.Table().Do(ctx,
   209  					func(ctx context.Context, session table.Session) (err error) {
   210  						_, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "seasons"))
   211  						if err != nil {
   212  							return
   213  						}
   214  						return err
   215  					},
   216  					table.WithIdempotent(),
   217  				)
   218  				require.NoError(t, err)
   219  			})
   220  			t.Run("episodes", func(t *testing.T) {
   221  				err := db.Table().Do(ctx,
   222  					func(ctx context.Context, session table.Session) (err error) {
   223  						_, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "episodes"))
   224  						if err != nil {
   225  							return
   226  						}
   227  						return err
   228  					},
   229  					table.WithIdempotent(),
   230  				)
   231  				require.NoError(t, err)
   232  			})
   233  		})
   234  	})
   235  
   236  	t.Run("upsert", func(t *testing.T) {
   237  		t.Run("data", func(t *testing.T) {
   238  			writeTx := table.TxControl(
   239  				table.BeginTx(
   240  					table.WithSerializableReadWrite(),
   241  				),
   242  				table.CommitTx(),
   243  			)
   244  			err := db.Table().Do(ctx,
   245  				func(ctx context.Context, session table.Session) (err error) {
   246  					stmt, err := session.Prepare(ctx, `
   247  						PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`");
   248  
   249  						DECLARE $seriesData AS List<Struct<
   250  							series_id: Uint64,
   251  							title: Text,
   252  							series_info: Text,
   253  							release_date: Date,
   254  							comment: Optional<Text>>>;
   255  		
   256  						DECLARE $seasonsData AS List<Struct<
   257  							series_id: Uint64,
   258  							season_id: Uint64,
   259  							title: Text,
   260  							first_aired: Date,
   261  							last_aired: Date>>;
   262  		
   263  						DECLARE $episodesData AS List<Struct<
   264  							series_id: Uint64,
   265  							season_id: Uint64,
   266  							episode_id: Uint64,
   267  							title: Text,
   268  							air_date: Date>>;
   269  		
   270  						REPLACE INTO series
   271  						SELECT
   272  							series_id,
   273  							title,
   274  							series_info,
   275  							release_date,
   276  							comment
   277  						FROM AS_TABLE($seriesData);
   278  		
   279  						REPLACE INTO seasons
   280  						SELECT
   281  							series_id,
   282  							season_id,
   283  							title,
   284  							first_aired,
   285  							last_aired
   286  						FROM AS_TABLE($seasonsData);
   287  		
   288  						REPLACE INTO episodes
   289  						SELECT
   290  							series_id,
   291  							season_id,
   292  							episode_id,
   293  							title,
   294  							air_date
   295  						FROM AS_TABLE($episodesData);`,
   296  					)
   297  					if err != nil {
   298  						return err
   299  					}
   300  
   301  					_, _, err = stmt.Execute(ctx, writeTx, table.NewQueryParameters(
   302  						table.ValueParam("$seriesData", getSeriesData()),
   303  						table.ValueParam("$seasonsData", getSeasonsData()),
   304  						table.ValueParam("$episodesData", getEpisodesData()),
   305  					))
   306  					return err
   307  				},
   308  				table.WithIdempotent(),
   309  			)
   310  			require.NoError(t, err)
   311  		})
   312  	})
   313  
   314  	t.Run("increment", func(t *testing.T) {
   315  		t.Run("views", func(t *testing.T) {
   316  			err := db.Table().DoTx(ctx,
   317  				func(ctx context.Context, tx table.TransactionActor) (err error) {
   318  					var (
   319  						res   result.Result
   320  						views uint64
   321  					)
   322  					// select current value of `views`
   323  					res, err = tx.Execute(ctx, `
   324  						PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`");
   325  
   326  						DECLARE $seriesID AS Uint64;
   327  						DECLARE $seasonID AS Uint64;
   328  						DECLARE $episodeID AS Uint64;
   329  
   330  						SELECT
   331  							views
   332  						FROM
   333  							episodes
   334  						WHERE
   335  							series_id = $seriesID AND 
   336  							season_id = $seasonID AND 
   337  							episode_id = $episodeID;`,
   338  						table.NewQueryParameters(
   339  							table.ValueParam("$seriesID", types.Uint64Value(1)),
   340  							table.ValueParam("$seasonID", types.Uint64Value(1)),
   341  							table.ValueParam("$episodeID", types.Uint64Value(1)),
   342  						),
   343  					)
   344  					if err != nil {
   345  						return err
   346  					}
   347  					if err = res.NextResultSetErr(ctx); err != nil {
   348  						return err
   349  					}
   350  					if !res.NextRow() {
   351  						return fmt.Errorf("nothing rows")
   352  					}
   353  					if err = res.ScanNamed(
   354  						named.OptionalWithDefault("views", &views),
   355  					); err != nil {
   356  						return err
   357  					}
   358  					if err = res.Err(); err != nil {
   359  						return err
   360  					}
   361  					if err = res.Close(); err != nil {
   362  						return err
   363  					}
   364  					// increment `views`
   365  					res, err = tx.Execute(ctx, `
   366  						PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`");
   367  
   368  						DECLARE $seriesID AS Uint64;
   369  						DECLARE $seasonID AS Uint64;
   370  						DECLARE $episodeID AS Uint64;
   371  						DECLARE $views AS Uint64;
   372  
   373  						UPSERT INTO episodes ( series_id, season_id, episode_id, views )
   374  						VALUES ( $seriesID, $seasonID, $episodeID, $views );`,
   375  						table.NewQueryParameters(
   376  							table.ValueParam("$seriesID", types.Uint64Value(1)),
   377  							table.ValueParam("$seasonID", types.Uint64Value(1)),
   378  							table.ValueParam("$episodeID", types.Uint64Value(1)),
   379  							table.ValueParam("$views", types.Uint64Value(views+1)), // increment views
   380  						),
   381  					)
   382  					if err != nil {
   383  						return err
   384  					}
   385  					if err = res.Err(); err != nil {
   386  						return err
   387  					}
   388  					return res.Close()
   389  				},
   390  				table.WithIdempotent(),
   391  			)
   392  			require.NoError(t, err)
   393  		})
   394  	})
   395  
   396  	t.Run("lookup", func(t *testing.T) {
   397  		t.Run("views", func(t *testing.T) {
   398  			err = db.Table().Do(ctx,
   399  				func(ctx context.Context, s table.Session) (err error) {
   400  					var (
   401  						res   result.Result
   402  						views uint64
   403  					)
   404  					// select current value of `views`
   405  					_, res, err = s.Execute(ctx,
   406  						table.TxControl(
   407  							table.BeginTx(
   408  								table.WithOnlineReadOnly(),
   409  							),
   410  							table.CommitTx(),
   411  						), `
   412  						PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`");
   413  
   414  						DECLARE $seriesID AS Uint64;
   415  						DECLARE $seasonID AS Uint64;
   416  						DECLARE $episodeID AS Uint64;
   417  
   418  						SELECT
   419  							views
   420  						FROM
   421  							episodes
   422  						WHERE
   423  							series_id = $seriesID AND 
   424  							season_id = $seasonID AND 
   425  							episode_id = $episodeID;`,
   426  						table.NewQueryParameters(
   427  							table.ValueParam("$seriesID", types.Uint64Value(1)),
   428  							table.ValueParam("$seasonID", types.Uint64Value(1)),
   429  							table.ValueParam("$episodeID", types.Uint64Value(1)),
   430  						),
   431  					)
   432  					if err != nil {
   433  						return err
   434  					}
   435  					if !res.NextResultSet(ctx, "views") {
   436  						return fmt.Errorf("nothing result sets")
   437  					}
   438  					if !res.NextRow() {
   439  						return fmt.Errorf("nothing result rows")
   440  					}
   441  					if err = res.ScanWithDefaults(&views); err != nil {
   442  						return err
   443  					}
   444  					if err = res.Err(); err != nil {
   445  						return err
   446  					}
   447  					if err = res.Close(); err != nil {
   448  						return err
   449  					}
   450  					if views != 1 {
   451  						return fmt.Errorf("unexpected views value: %d", views)
   452  					}
   453  					return nil
   454  				},
   455  				table.WithIdempotent(),
   456  			)
   457  			require.NoError(t, err)
   458  		})
   459  	})
   460  
   461  	t.Run("sessions", func(t *testing.T) {
   462  		t.Run("shutdown", func(t *testing.T) {
   463  			urls := os.Getenv("YDB_SESSIONS_SHUTDOWN_URLS")
   464  			if len(urls) > 0 {
   465  				for _, url := range strings.Split(urls, ",") {
   466  					//nolint:gosec
   467  					_, err = http.Get(url)
   468  					require.NoError(t, err)
   469  				}
   470  				shutdowned.Store(true)
   471  			}
   472  		})
   473  	})
   474  
   475  	t.Run("ExecuteDataQuery", func(t *testing.T) {
   476  		var (
   477  			query = `
   478  					PRAGMA TablePathPrefix("` + path.Join(db.Name(), folder) + `");
   479  
   480  					DECLARE $seriesID AS Uint64;
   481  
   482  					SELECT
   483  						series_id,
   484  						title,
   485  						release_date
   486  					FROM
   487  						series
   488  					WHERE
   489  						series_id = $seriesID;`
   490  			readTx = table.TxControl(
   491  				table.BeginTx(
   492  					table.WithOnlineReadOnly(),
   493  				),
   494  				table.CommitTx(),
   495  			)
   496  		)
   497  		err := db.Table().Do(ctx,
   498  			func(ctx context.Context, s table.Session) (err error) {
   499  				var (
   500  					res   result.Result
   501  					id    *uint64
   502  					title *string
   503  					date  *time.Time
   504  				)
   505  				_, res, err = s.Execute(ctx, readTx, query,
   506  					table.NewQueryParameters(
   507  						table.ValueParam("$seriesID", types.Uint64Value(1)),
   508  					),
   509  					options.WithCollectStatsModeBasic(),
   510  				)
   511  				if err != nil {
   512  					return err
   513  				}
   514  				defer func() {
   515  					_ = res.Close()
   516  				}()
   517  				t.Logf("> select_simple_transaction:\n")
   518  				for res.NextResultSet(ctx) {
   519  					for res.NextRow() {
   520  						err = res.ScanNamed(
   521  							named.Optional("series_id", &id),
   522  							named.Optional("title", &title),
   523  							named.Optional("release_date", &date),
   524  						)
   525  						if err != nil {
   526  							return err
   527  						}
   528  						t.Logf(
   529  							"  > %d %s %s\n",
   530  							*id, *title, *date,
   531  						)
   532  					}
   533  				}
   534  				return res.Err()
   535  			},
   536  			table.WithIdempotent(),
   537  		)
   538  		if err != nil && !ydb.IsTimeoutError(err) {
   539  			require.NoError(t, err)
   540  		}
   541  	})
   542  
   543  	t.Run("StreamExecuteScanQuery", func(t *testing.T) {
   544  		query := `
   545  			PRAGMA TablePathPrefix("` + path.Join(db.Name(), folder) + `");
   546  
   547  			DECLARE $series AS List<UInt64>;
   548  
   549  			SELECT series_id, season_id, title, first_aired
   550  			FROM seasons
   551  			WHERE series_id IN $series;`
   552  		err := db.Table().Do(ctx,
   553  			func(ctx context.Context, s table.Session) (err error) {
   554  				var (
   555  					res      result.StreamResult
   556  					seriesID uint64
   557  					seasonID uint64
   558  					title    string
   559  					date     time.Time
   560  				)
   561  				res, err = s.StreamExecuteScanQuery(ctx, query,
   562  					table.NewQueryParameters(
   563  						table.ValueParam("$series",
   564  							types.ListValue(
   565  								types.Uint64Value(1),
   566  								types.Uint64Value(10),
   567  							),
   568  						),
   569  					),
   570  				)
   571  				if err != nil {
   572  					return err
   573  				}
   574  				defer func() {
   575  					_ = res.Close()
   576  				}()
   577  				t.Logf("> scan_query_select:\n")
   578  				for res.NextResultSet(ctx) {
   579  					for res.NextRow() {
   580  						err = res.ScanWithDefaults(&seriesID, &seasonID, &title, &date)
   581  						if err != nil {
   582  							return err
   583  						}
   584  						t.Logf("  > SeriesId: %d, SeasonId: %d, Title: %s, Air date: %s\n", seriesID, seasonID, title, date)
   585  					}
   586  				}
   587  				return res.Err()
   588  			},
   589  			table.WithIdempotent(),
   590  		)
   591  		require.NoError(t, err)
   592  	})
   593  
   594  	t.Run("StreamReadTable", func(t *testing.T) {
   595  		err := db.Table().Do(ctx,
   596  			func(ctx context.Context, s table.Session) (err error) {
   597  				var (
   598  					res   result.StreamResult
   599  					id    *uint64
   600  					title *string
   601  					date  *time.Time
   602  				)
   603  				res, err = s.StreamReadTable(ctx, path.Join(db.Name(), folder, "series"),
   604  					options.ReadOrdered(),
   605  					options.ReadColumn("series_id"),
   606  					options.ReadColumn("title"),
   607  					options.ReadColumn("release_date"),
   608  				)
   609  				if err != nil {
   610  					return err
   611  				}
   612  				defer func() {
   613  					_ = res.Close()
   614  				}()
   615  				for res.NextResultSet(ctx, "series_id", "title", "release_date") {
   616  					for res.NextRow() {
   617  						err = res.Scan(&id, &title, &date)
   618  						if err != nil {
   619  							return err
   620  						}
   621  						// t.Logf("  > %d %s %s\n", *id, *title, date.String())
   622  					}
   623  				}
   624  				if err = res.Err(); err != nil {
   625  					return err
   626  				}
   627  
   628  				if stats := res.Stats(); stats != nil {
   629  					for i := 0; ; i++ {
   630  						phase, ok := stats.NextPhase()
   631  						if !ok {
   632  							break
   633  						}
   634  						for {
   635  							tbl, ok := phase.NextTableAccess()
   636  							if !ok {
   637  								break
   638  							}
   639  							t.Logf(
   640  								"#  accessed %s: read=(%drows, %dbytes)\n",
   641  								tbl.Name, tbl.Reads.Rows, tbl.Reads.Bytes,
   642  							)
   643  						}
   644  					}
   645  				}
   646  
   647  				return res.Err()
   648  			},
   649  			table.WithIdempotent(),
   650  		)
   651  		require.NoError(t, err)
   652  	})
   653  }