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 }