github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/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 "math" 10 "net/http" 11 "os" 12 "path" 13 "runtime/debug" 14 "strings" 15 "sync" 16 "sync/atomic" 17 "testing" 18 "time" 19 20 "github.com/stretchr/testify/require" 21 "google.golang.org/grpc" 22 grpcCodes "google.golang.org/grpc/codes" 23 "google.golang.org/grpc/metadata" 24 25 "github.com/ydb-platform/ydb-go-sdk/v3" 26 "github.com/ydb-platform/ydb-go-sdk/v3/balancers" 27 "github.com/ydb-platform/ydb-go-sdk/v3/config" 28 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 29 "github.com/ydb-platform/ydb-go-sdk/v3/log" 30 "github.com/ydb-platform/ydb-go-sdk/v3/meta" 31 "github.com/ydb-platform/ydb-go-sdk/v3/sugar" 32 "github.com/ydb-platform/ydb-go-sdk/v3/table" 33 "github.com/ydb-platform/ydb-go-sdk/v3/table/options" 34 "github.com/ydb-platform/ydb-go-sdk/v3/table/result" 35 "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" 36 "github.com/ydb-platform/ydb-go-sdk/v3/table/types" 37 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 38 ) 39 40 type stats struct { 41 xsync.Mutex 42 43 inFlightSessions map[string]struct{} 44 openSessions map[string]struct{} 45 inPoolSessions map[string]struct{} 46 limit int 47 } 48 49 func (s *stats) print(t testing.TB) { 50 s.Lock() 51 defer s.Unlock() 52 t.Log("stats:") 53 t.Log(" - limit :", s.limit) 54 t.Log(" - open :", len(s.openSessions)) 55 t.Log(" - in-pool :", len(s.inPoolSessions)) 56 t.Log(" - in-flight :", len(s.inFlightSessions)) 57 } 58 59 func (s *stats) check(t testing.TB) { 60 s.Lock() 61 defer s.Unlock() 62 if s.limit < 0 { 63 t.Fatalf("negative limit: %d", s.limit) 64 } 65 if len(s.inFlightSessions) > len(s.inPoolSessions) { 66 t.Fatalf("len(in_flight) > len(pool) (%d > %d)", len(s.inFlightSessions), len(s.inPoolSessions)) 67 } 68 if len(s.inPoolSessions) > s.limit { 69 t.Fatalf("len(pool) > limit (%d > %d)", len(s.inPoolSessions), s.limit) 70 } 71 } 72 73 func (s *stats) max() int { 74 s.Lock() 75 defer s.Unlock() 76 return s.limit 77 } 78 79 func (s *stats) addToOpen(t testing.TB, id string) { 80 defer s.check(t) 81 82 s.Lock() 83 defer s.Unlock() 84 85 if _, ok := s.openSessions[id]; ok { 86 t.Fatalf("session '%s' add to open sessions twice", id) 87 } 88 89 s.openSessions[id] = struct{}{} 90 } 91 92 func (s *stats) removeFromOpen(t testing.TB, id string) { 93 defer s.check(t) 94 95 s.Lock() 96 defer s.Unlock() 97 98 if _, ok := s.openSessions[id]; !ok { 99 t.Fatalf("session '%s' already removed from open sessions", id) 100 } 101 102 delete(s.openSessions, id) 103 } 104 105 func (s *stats) addToPool(t testing.TB, id string) { 106 defer s.check(t) 107 108 s.Lock() 109 defer s.Unlock() 110 111 if _, ok := s.inPoolSessions[id]; ok { 112 t.Fatalf("session '%s' add to pool twice", id) 113 } 114 115 s.inPoolSessions[id] = struct{}{} 116 } 117 118 func (s *stats) removeFromPool(t testing.TB, id string) { 119 defer s.check(t) 120 121 s.Lock() 122 defer s.Unlock() 123 124 if _, ok := s.inPoolSessions[id]; !ok { 125 t.Fatalf("session '%s' already removed from pool", id) 126 } 127 128 delete(s.inPoolSessions, id) 129 } 130 131 func (s *stats) addToInFlight(t testing.TB, id string) { 132 defer s.check(t) 133 134 s.Lock() 135 defer s.Unlock() 136 137 if _, ok := s.inFlightSessions[id]; ok { 138 t.Fatalf("session '%s' add to in-flight twice", id) 139 } 140 141 s.inFlightSessions[id] = struct{}{} 142 } 143 144 func (s *stats) removeFromInFlight(t testing.TB, id string) { 145 defer s.check(t) 146 147 s.Lock() 148 defer s.Unlock() 149 150 if _, ok := s.inFlightSessions[id]; !ok { 151 return 152 } 153 154 delete(s.inFlightSessions, id) 155 } 156 157 func TestBasicExampleNative(t *testing.T) { //nolint:gocyclo 158 folder := t.Name() 159 160 ctx, cancel := context.WithTimeout(context.Background(), 42*time.Second) 161 defer cancel() 162 163 var totalConsumedUnits atomic.Uint64 164 defer func() { 165 t.Logf("total consumed units: %d", totalConsumedUnits.Load()) 166 }() 167 168 ctx = meta.WithTrailerCallback(ctx, func(md metadata.MD) { 169 totalConsumedUnits.Add(meta.ConsumedUnits(md)) 170 }) 171 172 s := &stats{ 173 limit: math.MaxInt32, 174 openSessions: make(map[string]struct{}), 175 inPoolSessions: make(map[string]struct{}), 176 inFlightSessions: make(map[string]struct{}), 177 } 178 defer func() { 179 s.Lock() 180 defer s.Unlock() 181 if len(s.inFlightSessions) != 0 { 182 t.Errorf("'in-flight' not a zero after closing table client: %v", s.inFlightSessions) 183 } 184 if len(s.openSessions) != 0 { 185 t.Errorf("'openSessions' not a zero after closing table client: %v", s.openSessions) 186 } 187 if len(s.inPoolSessions) != 0 { 188 t.Errorf("'inPoolSessions' not a zero after closing table client: %v", s.inPoolSessions) 189 } 190 }() 191 192 var ( 193 limit = 50 194 195 sessionsMtx sync.Mutex 196 sessions = make(map[string]struct{}, limit) 197 198 shutdowned atomic.Bool 199 200 shutdownTrace = trace.Table{ 201 OnPoolSessionAdd: func(info trace.TablePoolSessionAddInfo) { 202 sessionsMtx.Lock() 203 defer sessionsMtx.Unlock() 204 sessions[info.Session.ID()] = struct{}{} 205 }, 206 OnPoolGet: func( 207 info trace.TablePoolGetStartInfo, 208 ) func( 209 trace.TablePoolGetDoneInfo, 210 ) { 211 return func(info trace.TablePoolGetDoneInfo) { 212 if info.Session == nil { 213 return 214 } 215 if shutdowned.Load() { 216 return 217 } 218 if info.Session.Status() != table.SessionClosing { 219 return 220 } 221 sessionsMtx.Lock() 222 defer sessionsMtx.Unlock() 223 if _, has := sessions[info.Session.ID()]; !has { 224 return 225 } 226 t.Fatalf("old session returned from pool after shutdown") 227 } 228 }, 229 } 230 ) 231 232 db, err := ydb.Open(ctx, 233 os.Getenv("YDB_CONNECTION_STRING"), 234 ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), 235 ydb.WithUserAgent("table/e2e"), 236 withMetrics(t, trace.DetailsAll, time.Second), 237 ydb.With( 238 config.WithOperationTimeout(time.Second*5), 239 config.WithOperationCancelAfter(time.Second*5), 240 config.ExcludeGRPCCodesForPessimization(grpcCodes.DeadlineExceeded), 241 config.WithGrpcOptions( 242 grpc.WithUnaryInterceptor(func( 243 ctx context.Context, 244 method string, 245 req, reply interface{}, 246 cc *grpc.ClientConn, 247 invoker grpc.UnaryInvoker, 248 opts ...grpc.CallOption, 249 ) error { 250 return invoker(ctx, method, req, reply, cc, opts...) 251 }), 252 grpc.WithStreamInterceptor(func( 253 ctx context.Context, 254 desc *grpc.StreamDesc, 255 cc *grpc.ClientConn, 256 method string, 257 streamer grpc.Streamer, 258 opts ...grpc.CallOption, 259 ) (grpc.ClientStream, error) { 260 return streamer(ctx, desc, cc, method, opts...) 261 }), 262 ), 263 ), 264 ydb.WithBalancer(balancers.RandomChoice()), 265 ydb.WithDialTimeout(5*time.Second), 266 ydb.WithSessionPoolSizeLimit(limit), 267 ydb.WithConnectionTTL(5*time.Second), 268 ydb.WithLogger( 269 newLoggerWithMinLevel(t, log.FromString(os.Getenv("YDB_LOG_SEVERITY_LEVEL"))), 270 trace.MatchDetails(`ydb\.(driver|table|discovery|retry|scheme).*`), 271 ), 272 ydb.WithPanicCallback(func(e interface{}) { 273 t.Fatalf("panic recovered:%v:\n%s", e, debug.Stack()) 274 }), 275 ydb.WithTraceTable( 276 *shutdownTrace.Compose( 277 &trace.Table{ 278 OnInit: func( 279 info trace.TableInitStartInfo, 280 ) func( 281 trace.TableInitDoneInfo, 282 ) { 283 return func(info trace.TableInitDoneInfo) { 284 s.WithLock(func() { 285 s.limit = info.Limit 286 }) 287 } 288 }, 289 OnSessionNew: func( 290 info trace.TableSessionNewStartInfo, 291 ) func( 292 trace.TableSessionNewDoneInfo, 293 ) { 294 return func(info trace.TableSessionNewDoneInfo) { 295 if info.Error == nil { 296 s.addToOpen(t, info.Session.ID()) 297 } 298 } 299 }, 300 OnSessionDelete: func( 301 info trace.TableSessionDeleteStartInfo, 302 ) func( 303 trace.TableSessionDeleteDoneInfo, 304 ) { 305 s.removeFromOpen(t, info.Session.ID()) 306 return nil 307 }, 308 OnPoolSessionAdd: func(info trace.TablePoolSessionAddInfo) { 309 s.addToPool(t, info.Session.ID()) 310 }, 311 OnPoolSessionRemove: func(info trace.TablePoolSessionRemoveInfo) { 312 s.removeFromPool(t, info.Session.ID()) 313 }, 314 OnPoolGet: func( 315 info trace.TablePoolGetStartInfo, 316 ) func( 317 trace.TablePoolGetDoneInfo, 318 ) { 319 return func(info trace.TablePoolGetDoneInfo) { 320 if info.Error == nil { 321 s.addToInFlight(t, info.Session.ID()) 322 } 323 } 324 }, 325 OnPoolPut: func( 326 info trace.TablePoolPutStartInfo, 327 ) func( 328 trace.TablePoolPutDoneInfo, 329 ) { 330 s.removeFromInFlight(t, info.Session.ID()) 331 return nil 332 }, 333 }, 334 ), 335 ), 336 ) 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 defer func() { 342 // cleanup 343 _ = db.Close(ctx) 344 }() 345 346 if err = db.Table().Do(ctx, func(ctx context.Context, _ table.Session) error { 347 // hack for wait pool initializing 348 return nil 349 }); err != nil { 350 t.Fatalf("pool not initialized: %+v", err) 351 } else if s.max() != limit { 352 t.Fatalf("pool size not applied: %+v", s) 353 } 354 355 // prepare scheme 356 err = sugar.RemoveRecursive(ctx, db, folder) 357 if err != nil { 358 t.Fatal(err) 359 } 360 err = sugar.MakeRecursive(ctx, db, folder) 361 if err != nil { 362 t.Fatal(err) 363 } 364 365 t.Run("prepare", func(t *testing.T) { 366 t.Run("scheme", func(t *testing.T) { 367 t.Run("series", func(t *testing.T) { 368 err := db.Table().Do(ctx, 369 func(ctx context.Context, session table.Session) (err error) { 370 if _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "series")); err == nil { 371 _ = session.DropTable(ctx, path.Join(db.Name(), folder, "series")) 372 } 373 return session.CreateTable(ctx, path.Join(db.Name(), folder, "series"), 374 options.WithColumn("series_id", types.Optional(types.TypeUint64)), 375 options.WithColumn("title", types.Optional(types.TypeText)), 376 options.WithColumn("series_info", types.Optional(types.TypeText)), 377 options.WithColumn("release_date", types.Optional(types.TypeDate)), 378 options.WithColumn("comment", types.Optional(types.TypeText)), 379 options.WithPrimaryKeyColumn("series_id"), 380 ) 381 }, 382 table.WithIdempotent(), 383 ) 384 require.NoError(t, err) 385 }) 386 t.Run("seasons", func(t *testing.T) { 387 err := db.Table().Do(ctx, 388 func(ctx context.Context, session table.Session) (err error) { 389 if _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "seasons")); err == nil { 390 _ = session.DropTable(ctx, path.Join(db.Name(), folder, "seasons")) 391 } 392 return session.CreateTable(ctx, path.Join(db.Name(), folder, "seasons"), 393 options.WithColumn("series_id", types.Optional(types.TypeUint64)), 394 options.WithColumn("season_id", types.Optional(types.TypeUint64)), 395 options.WithColumn("title", types.Optional(types.TypeText)), 396 options.WithColumn("first_aired", types.Optional(types.TypeDate)), 397 options.WithColumn("last_aired", types.Optional(types.TypeDate)), 398 options.WithPrimaryKeyColumn("series_id", "season_id"), 399 ) 400 }, 401 table.WithIdempotent(), 402 ) 403 require.NoError(t, err) 404 }) 405 t.Run("episodes", func(t *testing.T) { 406 err := db.Table().Do(ctx, 407 func(ctx context.Context, session table.Session) (err error) { 408 if _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "episodes")); err == nil { 409 _ = session.DropTable(ctx, path.Join(db.Name(), folder, "episodes")) 410 } 411 return session.CreateTable(ctx, path.Join(db.Name(), folder, "episodes"), 412 options.WithColumn("series_id", types.Optional(types.TypeUint64)), 413 options.WithColumn("season_id", types.Optional(types.TypeUint64)), 414 options.WithColumn("episode_id", types.Optional(types.TypeUint64)), 415 options.WithColumn("title", types.Optional(types.TypeText)), 416 options.WithColumn("air_date", types.Optional(types.TypeDate)), 417 options.WithColumn("views", types.Optional(types.TypeUint64)), 418 options.WithPrimaryKeyColumn("series_id", "season_id", "episode_id"), 419 ) 420 }, 421 table.WithIdempotent(), 422 ) 423 require.NoError(t, err) 424 }) 425 }) 426 }) 427 428 t.Run("describe", func(t *testing.T) { 429 t.Run("table", func(t *testing.T) { 430 t.Run("series", func(t *testing.T) { 431 err := db.Table().Do(ctx, 432 func(ctx context.Context, session table.Session) (err error) { 433 _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "series")) 434 if err != nil { 435 return 436 } 437 return err 438 }, 439 table.WithIdempotent(), 440 ) 441 require.NoError(t, err) 442 }) 443 t.Run("seasons", func(t *testing.T) { 444 err := db.Table().Do(ctx, 445 func(ctx context.Context, session table.Session) (err error) { 446 _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "seasons")) 447 if err != nil { 448 return 449 } 450 return err 451 }, 452 table.WithIdempotent(), 453 ) 454 require.NoError(t, err) 455 }) 456 t.Run("episodes", func(t *testing.T) { 457 err := db.Table().Do(ctx, 458 func(ctx context.Context, session table.Session) (err error) { 459 _, err = session.DescribeTable(ctx, path.Join(db.Name(), folder, "episodes")) 460 if err != nil { 461 return 462 } 463 return err 464 }, 465 table.WithIdempotent(), 466 ) 467 require.NoError(t, err) 468 }) 469 }) 470 }) 471 472 t.Run("upsert", func(t *testing.T) { 473 t.Run("data", func(t *testing.T) { 474 writeTx := table.TxControl( 475 table.BeginTx( 476 table.WithSerializableReadWrite(), 477 ), 478 table.CommitTx(), 479 ) 480 err := db.Table().Do(ctx, 481 func(ctx context.Context, session table.Session) (err error) { 482 stmt, err := session.Prepare(ctx, ` 483 PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`"); 484 485 DECLARE $seriesData AS List<Struct< 486 series_id: Uint64, 487 title: Text, 488 series_info: Text, 489 release_date: Date, 490 comment: Optional<Text>>>; 491 492 DECLARE $seasonsData AS List<Struct< 493 series_id: Uint64, 494 season_id: Uint64, 495 title: Text, 496 first_aired: Date, 497 last_aired: Date>>; 498 499 DECLARE $episodesData AS List<Struct< 500 series_id: Uint64, 501 season_id: Uint64, 502 episode_id: Uint64, 503 title: Text, 504 air_date: Date>>; 505 506 REPLACE INTO series 507 SELECT 508 series_id, 509 title, 510 series_info, 511 release_date, 512 comment 513 FROM AS_TABLE($seriesData); 514 515 REPLACE INTO seasons 516 SELECT 517 series_id, 518 season_id, 519 title, 520 first_aired, 521 last_aired 522 FROM AS_TABLE($seasonsData); 523 524 REPLACE INTO episodes 525 SELECT 526 series_id, 527 season_id, 528 episode_id, 529 title, 530 air_date 531 FROM AS_TABLE($episodesData);`, 532 ) 533 if err != nil { 534 return err 535 } 536 537 _, _, err = stmt.Execute(ctx, writeTx, table.NewQueryParameters( 538 table.ValueParam("$seriesData", getSeriesData()), 539 table.ValueParam("$seasonsData", getSeasonsData()), 540 table.ValueParam("$episodesData", getEpisodesData()), 541 )) 542 return err 543 }, 544 table.WithIdempotent(), 545 ) 546 require.NoError(t, err) 547 }) 548 }) 549 550 t.Run("increment", func(t *testing.T) { 551 t.Run("views", func(t *testing.T) { 552 err := db.Table().DoTx(ctx, 553 func(ctx context.Context, tx table.TransactionActor) (err error) { 554 var ( 555 res result.Result 556 views uint64 557 ) 558 // select current value of `views` 559 res, err = tx.Execute(ctx, ` 560 PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`"); 561 562 DECLARE $seriesID AS Uint64; 563 DECLARE $seasonID AS Uint64; 564 DECLARE $episodeID AS Uint64; 565 566 SELECT 567 views 568 FROM 569 episodes 570 WHERE 571 series_id = $seriesID AND 572 season_id = $seasonID AND 573 episode_id = $episodeID;`, 574 table.NewQueryParameters( 575 table.ValueParam("$seriesID", types.Uint64Value(1)), 576 table.ValueParam("$seasonID", types.Uint64Value(1)), 577 table.ValueParam("$episodeID", types.Uint64Value(1)), 578 ), 579 ) 580 if err != nil { 581 return err 582 } 583 if err = res.NextResultSetErr(ctx); err != nil { 584 return err 585 } 586 if !res.NextRow() { 587 return fmt.Errorf("nothing rows") 588 } 589 if err = res.ScanNamed( 590 named.OptionalWithDefault("views", &views), 591 ); err != nil { 592 return err 593 } 594 if err = res.Err(); err != nil { 595 return err 596 } 597 if err = res.Close(); err != nil { 598 return err 599 } 600 // increment `views` 601 res, err = tx.Execute(ctx, ` 602 PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`"); 603 604 DECLARE $seriesID AS Uint64; 605 DECLARE $seasonID AS Uint64; 606 DECLARE $episodeID AS Uint64; 607 DECLARE $views AS Uint64; 608 609 UPSERT INTO episodes ( series_id, season_id, episode_id, views ) 610 VALUES ( $seriesID, $seasonID, $episodeID, $views );`, 611 table.NewQueryParameters( 612 table.ValueParam("$seriesID", types.Uint64Value(1)), 613 table.ValueParam("$seasonID", types.Uint64Value(1)), 614 table.ValueParam("$episodeID", types.Uint64Value(1)), 615 table.ValueParam("$views", types.Uint64Value(views+1)), // increment views 616 ), 617 ) 618 if err != nil { 619 return err 620 } 621 if err = res.Err(); err != nil { 622 return err 623 } 624 return res.Close() 625 }, 626 table.WithIdempotent(), 627 ) 628 require.NoError(t, err) 629 }) 630 }) 631 632 t.Run("lookup", func(t *testing.T) { 633 t.Run("views", func(t *testing.T) { 634 err = db.Table().Do(ctx, 635 func(ctx context.Context, s table.Session) (err error) { 636 var ( 637 res result.Result 638 views uint64 639 ) 640 // select current value of `views` 641 _, res, err = s.Execute(ctx, 642 table.TxControl( 643 table.BeginTx( 644 table.WithOnlineReadOnly(), 645 ), 646 table.CommitTx(), 647 ), ` 648 PRAGMA TablePathPrefix("`+path.Join(db.Name(), folder)+`"); 649 650 DECLARE $seriesID AS Uint64; 651 DECLARE $seasonID AS Uint64; 652 DECLARE $episodeID AS Uint64; 653 654 SELECT 655 views 656 FROM 657 episodes 658 WHERE 659 series_id = $seriesID AND 660 season_id = $seasonID AND 661 episode_id = $episodeID;`, 662 table.NewQueryParameters( 663 table.ValueParam("$seriesID", types.Uint64Value(1)), 664 table.ValueParam("$seasonID", types.Uint64Value(1)), 665 table.ValueParam("$episodeID", types.Uint64Value(1)), 666 ), 667 ) 668 if err != nil { 669 return err 670 } 671 if !res.NextResultSet(ctx, "views") { 672 return fmt.Errorf("nothing result sets") 673 } 674 if !res.NextRow() { 675 return fmt.Errorf("nothing result rows") 676 } 677 if err = res.ScanWithDefaults(&views); err != nil { 678 return err 679 } 680 if err = res.Err(); err != nil { 681 return err 682 } 683 if err = res.Close(); err != nil { 684 return err 685 } 686 if views != 1 { 687 return fmt.Errorf("unexpected views value: %d", views) 688 } 689 return nil 690 }, 691 table.WithIdempotent(), 692 ) 693 require.NoError(t, err) 694 }) 695 }) 696 697 t.Run("sessions", func(t *testing.T) { 698 t.Run("shutdown", func(t *testing.T) { 699 urls := os.Getenv("YDB_SESSIONS_SHUTDOWN_URLS") 700 if len(urls) > 0 { 701 for _, url := range strings.Split(urls, ",") { 702 //nolint:gosec 703 _, err = http.Get(url) 704 require.NoError(t, err) 705 } 706 shutdowned.Store(true) 707 } 708 }) 709 }) 710 711 t.Run("ExecuteDataQuery", func(t *testing.T) { 712 var ( 713 query = ` 714 PRAGMA TablePathPrefix("` + path.Join(db.Name(), folder) + `"); 715 716 DECLARE $seriesID AS Uint64; 717 718 SELECT 719 series_id, 720 title, 721 release_date 722 FROM 723 series 724 WHERE 725 series_id = $seriesID;` 726 readTx = table.TxControl( 727 table.BeginTx( 728 table.WithOnlineReadOnly(), 729 ), 730 table.CommitTx(), 731 ) 732 ) 733 err := db.Table().Do(ctx, 734 func(ctx context.Context, s table.Session) (err error) { 735 var ( 736 res result.Result 737 id *uint64 738 title *string 739 date *time.Time 740 ) 741 _, res, err = s.Execute(ctx, readTx, query, 742 table.NewQueryParameters( 743 table.ValueParam("$seriesID", types.Uint64Value(1)), 744 ), 745 options.WithCollectStatsModeBasic(), 746 ) 747 if err != nil { 748 return err 749 } 750 defer func() { 751 _ = res.Close() 752 }() 753 t.Logf("> select_simple_transaction:\n") 754 for res.NextResultSet(ctx) { 755 for res.NextRow() { 756 err = res.ScanNamed( 757 named.Optional("series_id", &id), 758 named.Optional("title", &title), 759 named.Optional("release_date", &date), 760 ) 761 if err != nil { 762 return err 763 } 764 t.Logf( 765 " > %d %s %s\n", 766 *id, *title, *date, 767 ) 768 } 769 } 770 return res.Err() 771 }, 772 table.WithIdempotent(), 773 ) 774 if err != nil && !ydb.IsTimeoutError(err) { 775 require.NoError(t, err) 776 } 777 }) 778 779 t.Run("StreamExecuteScanQuery", func(t *testing.T) { 780 query := ` 781 PRAGMA TablePathPrefix("` + path.Join(db.Name(), folder) + `"); 782 783 DECLARE $series AS List<UInt64>; 784 785 SELECT series_id, season_id, title, first_aired 786 FROM seasons 787 WHERE series_id IN $series;` 788 err := db.Table().Do(ctx, 789 func(ctx context.Context, s table.Session) (err error) { 790 var ( 791 res result.StreamResult 792 seriesID uint64 793 seasonID uint64 794 title string 795 date time.Time 796 ) 797 res, err = s.StreamExecuteScanQuery(ctx, query, 798 table.NewQueryParameters( 799 table.ValueParam("$series", 800 types.ListValue( 801 types.Uint64Value(1), 802 types.Uint64Value(10), 803 ), 804 ), 805 ), 806 ) 807 if err != nil { 808 return err 809 } 810 defer func() { 811 _ = res.Close() 812 }() 813 t.Logf("> scan_query_select:\n") 814 for res.NextResultSet(ctx) { 815 for res.NextRow() { 816 err = res.ScanWithDefaults(&seriesID, &seasonID, &title, &date) 817 if err != nil { 818 return err 819 } 820 t.Logf(" > SeriesId: %d, SeasonId: %d, Title: %s, Air date: %s\n", seriesID, seasonID, title, date) 821 } 822 } 823 return res.Err() 824 }, 825 table.WithIdempotent(), 826 ) 827 require.NoError(t, err) 828 }) 829 830 t.Run("StreamReadTable", func(t *testing.T) { 831 err := db.Table().Do(ctx, 832 func(ctx context.Context, s table.Session) (err error) { 833 var ( 834 res result.StreamResult 835 id *uint64 836 title *string 837 date *time.Time 838 ) 839 res, err = s.StreamReadTable(ctx, path.Join(db.Name(), folder, "series"), 840 options.ReadOrdered(), 841 options.ReadColumn("series_id"), 842 options.ReadColumn("title"), 843 options.ReadColumn("release_date"), 844 ) 845 if err != nil { 846 return err 847 } 848 defer func() { 849 _ = res.Close() 850 }() 851 for res.NextResultSet(ctx, "series_id", "title", "release_date") { 852 for res.NextRow() { 853 err = res.Scan(&id, &title, &date) 854 if err != nil { 855 return err 856 } 857 // t.Logf(" > %d %s %s\n", *id, *title, date.String()) 858 } 859 } 860 if err = res.Err(); err != nil { 861 return err 862 } 863 864 if stats := res.Stats(); stats != nil { 865 for i := 0; ; i++ { 866 phase, ok := stats.NextPhase() 867 if !ok { 868 break 869 } 870 for { 871 tbl, ok := phase.NextTableAccess() 872 if !ok { 873 break 874 } 875 t.Logf( 876 "# accessed %s: read=(%drows, %dbytes)\n", 877 tbl.Name, tbl.Reads.Rows, tbl.Reads.Bytes, 878 ) 879 } 880 } 881 } 882 883 return res.Err() 884 }, 885 table.WithIdempotent(), 886 ) 887 require.NoError(t, err) 888 }) 889 }