github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/query/scanner/struct_test.go (about) 1 package scanner 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" 10 11 "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" 12 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 13 ) 14 15 func TestFieldName(t *testing.T) { 16 for _, tt := range []struct { 17 name string 18 in interface{} 19 out string 20 }{ 21 { 22 name: xtest.CurrentFileLine(), 23 in: struct { 24 Col0 string 25 }{}, 26 out: "Col0", 27 }, 28 { 29 name: xtest.CurrentFileLine(), 30 in: struct { 31 Col0 string `sql:"col0"` 32 }{}, 33 out: "col0", 34 }, 35 } { 36 t.Run(tt.name, func(t *testing.T) { 37 require.Equal(t, tt.out, fieldName(reflect.ValueOf(tt.in).Type().Field(0), "sql")) 38 }) 39 } 40 } 41 42 func TestStruct(t *testing.T) { 43 newScannerData := func(mapping map[*Ydb.Column]*Ydb.Value) *data { 44 data := &data{ 45 columns: make([]*Ydb.Column, 0, len(mapping)), 46 values: make([]*Ydb.Value, 0, len(mapping)), 47 } 48 for c, v := range mapping { 49 data.columns = append(data.columns, c) 50 data.values = append(data.values, v) 51 } 52 53 return data 54 } 55 56 type scanData struct { //nolint:maligned 57 Utf8String string 58 Utf8Bytes []byte 59 StringString string 60 StringBytes []byte 61 Uint64Uint64 uint64 62 Int64Int64 int64 63 Uint32Uint64 uint64 64 Uint32Int64 int64 65 Uint32Uint32 uint32 66 Int32Int64 int64 67 Int32Int32 int32 68 Uint16Uint64 uint64 69 Uint16Int64 int64 70 Uint16Uint32 uint32 71 Uint16Int32 int32 72 Uint16Uint16 uint16 73 Int16Int64 int64 74 Int16Int32 int32 75 Uint8Uint64 uint64 76 Uint8Int64 int64 77 Uint8Uint32 uint32 78 Uint8Int32 int32 79 Uint8Uint16 uint16 80 Int8Int64 int64 81 Int8Int32 int32 82 Int8Int16 int16 83 BoolBool bool 84 DateTime time.Time 85 DatetimeTime time.Time 86 TimestampTime time.Time 87 } 88 var dst scanData 89 err := Struct(newScannerData(map[*Ydb.Column]*Ydb.Value{ 90 { 91 Name: "Utf8String", 92 Type: &Ydb.Type{ 93 Type: &Ydb.Type_TypeId{ 94 TypeId: Ydb.Type_UTF8, 95 }, 96 }, 97 }: { 98 Value: &Ydb.Value_TextValue{ 99 TextValue: "A", 100 }, 101 }, 102 { 103 Name: "Utf8Bytes", 104 Type: &Ydb.Type{ 105 Type: &Ydb.Type_TypeId{ 106 TypeId: Ydb.Type_UTF8, 107 }, 108 }, 109 }: { 110 Value: &Ydb.Value_TextValue{ 111 TextValue: "A", 112 }, 113 }, 114 { 115 Name: "StringString", 116 Type: &Ydb.Type{ 117 Type: &Ydb.Type_TypeId{ 118 TypeId: Ydb.Type_STRING, 119 }, 120 }, 121 }: { 122 Value: &Ydb.Value_BytesValue{ 123 BytesValue: []byte("A"), 124 }, 125 }, 126 { 127 Name: "StringBytes", 128 Type: &Ydb.Type{ 129 Type: &Ydb.Type_TypeId{ 130 TypeId: Ydb.Type_STRING, 131 }, 132 }, 133 }: { 134 Value: &Ydb.Value_BytesValue{ 135 BytesValue: []byte("A"), 136 }, 137 }, 138 { 139 Name: "Uint64Uint64", 140 Type: &Ydb.Type{ 141 Type: &Ydb.Type_TypeId{ 142 TypeId: Ydb.Type_UINT64, 143 }, 144 }, 145 }: { 146 Value: &Ydb.Value_Uint64Value{ 147 Uint64Value: 123, 148 }, 149 }, 150 { 151 Name: "Int64Int64", 152 Type: &Ydb.Type{ 153 Type: &Ydb.Type_TypeId{ 154 TypeId: Ydb.Type_INT64, 155 }, 156 }, 157 }: { 158 Value: &Ydb.Value_Int64Value{ 159 Int64Value: 123, 160 }, 161 }, 162 { 163 Name: "Uint32Uint64", 164 Type: &Ydb.Type{ 165 Type: &Ydb.Type_TypeId{ 166 TypeId: Ydb.Type_UINT32, 167 }, 168 }, 169 }: { 170 Value: &Ydb.Value_Uint32Value{ 171 Uint32Value: 123, 172 }, 173 }, 174 { 175 Name: "Uint32Int64", 176 Type: &Ydb.Type{ 177 Type: &Ydb.Type_TypeId{ 178 TypeId: Ydb.Type_UINT32, 179 }, 180 }, 181 }: { 182 Value: &Ydb.Value_Uint32Value{ 183 Uint32Value: 123, 184 }, 185 }, 186 { 187 Name: "Uint32Uint32", 188 Type: &Ydb.Type{ 189 Type: &Ydb.Type_TypeId{ 190 TypeId: Ydb.Type_UINT32, 191 }, 192 }, 193 }: { 194 Value: &Ydb.Value_Uint32Value{ 195 Uint32Value: 123, 196 }, 197 }, 198 { 199 Name: "Int32Int64", 200 Type: &Ydb.Type{ 201 Type: &Ydb.Type_TypeId{ 202 TypeId: Ydb.Type_INT32, 203 }, 204 }, 205 }: { 206 Value: &Ydb.Value_Int32Value{ 207 Int32Value: 123, 208 }, 209 }, 210 { 211 Name: "Int32Int32", 212 Type: &Ydb.Type{ 213 Type: &Ydb.Type_TypeId{ 214 TypeId: Ydb.Type_INT32, 215 }, 216 }, 217 }: { 218 Value: &Ydb.Value_Int32Value{ 219 Int32Value: 123, 220 }, 221 }, 222 { 223 Name: "Uint16Uint64", 224 Type: &Ydb.Type{ 225 Type: &Ydb.Type_TypeId{ 226 TypeId: Ydb.Type_UINT16, 227 }, 228 }, 229 }: { 230 Value: &Ydb.Value_Uint32Value{ 231 Uint32Value: 123, 232 }, 233 }, 234 { 235 Name: "Uint16Int64", 236 Type: &Ydb.Type{ 237 Type: &Ydb.Type_TypeId{ 238 TypeId: Ydb.Type_UINT16, 239 }, 240 }, 241 }: { 242 Value: &Ydb.Value_Uint32Value{ 243 Uint32Value: 123, 244 }, 245 }, 246 { 247 Name: "Uint16Uint32", 248 Type: &Ydb.Type{ 249 Type: &Ydb.Type_TypeId{ 250 TypeId: Ydb.Type_UINT16, 251 }, 252 }, 253 }: { 254 Value: &Ydb.Value_Uint32Value{ 255 Uint32Value: 123, 256 }, 257 }, 258 { 259 Name: "Uint16Int32", 260 Type: &Ydb.Type{ 261 Type: &Ydb.Type_TypeId{ 262 TypeId: Ydb.Type_UINT16, 263 }, 264 }, 265 }: { 266 Value: &Ydb.Value_Uint32Value{ 267 Uint32Value: 123, 268 }, 269 }, 270 { 271 Name: "Uint16Uint16", 272 Type: &Ydb.Type{ 273 Type: &Ydb.Type_TypeId{ 274 TypeId: Ydb.Type_UINT16, 275 }, 276 }, 277 }: { 278 Value: &Ydb.Value_Uint32Value{ 279 Uint32Value: 123, 280 }, 281 }, 282 { 283 Name: "Int16Int64", 284 Type: &Ydb.Type{ 285 Type: &Ydb.Type_TypeId{ 286 TypeId: Ydb.Type_INT16, 287 }, 288 }, 289 }: { 290 Value: &Ydb.Value_Int32Value{ 291 Int32Value: 123, 292 }, 293 }, 294 { 295 Name: "Int16Int32", 296 Type: &Ydb.Type{ 297 Type: &Ydb.Type_TypeId{ 298 TypeId: Ydb.Type_INT16, 299 }, 300 }, 301 }: { 302 Value: &Ydb.Value_Int32Value{ 303 Int32Value: 123, 304 }, 305 }, 306 { 307 Name: "Uint8Uint64", 308 Type: &Ydb.Type{ 309 Type: &Ydb.Type_TypeId{ 310 TypeId: Ydb.Type_UINT16, 311 }, 312 }, 313 }: { 314 Value: &Ydb.Value_Uint32Value{ 315 Uint32Value: 123, 316 }, 317 }, 318 { 319 Name: "Uint8Int64", 320 Type: &Ydb.Type{ 321 Type: &Ydb.Type_TypeId{ 322 TypeId: Ydb.Type_UINT16, 323 }, 324 }, 325 }: { 326 Value: &Ydb.Value_Uint32Value{ 327 Uint32Value: 123, 328 }, 329 }, 330 { 331 Name: "Uint8Uint32", 332 Type: &Ydb.Type{ 333 Type: &Ydb.Type_TypeId{ 334 TypeId: Ydb.Type_UINT16, 335 }, 336 }, 337 }: { 338 Value: &Ydb.Value_Uint32Value{ 339 Uint32Value: 123, 340 }, 341 }, 342 { 343 Name: "Uint8Int32", 344 Type: &Ydb.Type{ 345 Type: &Ydb.Type_TypeId{ 346 TypeId: Ydb.Type_UINT16, 347 }, 348 }, 349 }: { 350 Value: &Ydb.Value_Uint32Value{ 351 Uint32Value: 123, 352 }, 353 }, 354 { 355 Name: "Uint8Uint16", 356 Type: &Ydb.Type{ 357 Type: &Ydb.Type_TypeId{ 358 TypeId: Ydb.Type_UINT16, 359 }, 360 }, 361 }: { 362 Value: &Ydb.Value_Uint32Value{ 363 Uint32Value: 123, 364 }, 365 }, 366 { 367 Name: "Int8Int64", 368 Type: &Ydb.Type{ 369 Type: &Ydb.Type_TypeId{ 370 TypeId: Ydb.Type_INT16, 371 }, 372 }, 373 }: { 374 Value: &Ydb.Value_Int32Value{ 375 Int32Value: 123, 376 }, 377 }, 378 { 379 Name: "Int8Int32", 380 Type: &Ydb.Type{ 381 Type: &Ydb.Type_TypeId{ 382 TypeId: Ydb.Type_INT16, 383 }, 384 }, 385 }: { 386 Value: &Ydb.Value_Int32Value{ 387 Int32Value: 123, 388 }, 389 }, 390 { 391 Name: "Int8Int16", 392 Type: &Ydb.Type{ 393 Type: &Ydb.Type_TypeId{ 394 TypeId: Ydb.Type_INT16, 395 }, 396 }, 397 }: { 398 Value: &Ydb.Value_Int32Value{ 399 Int32Value: 123, 400 }, 401 }, 402 { 403 Name: "BoolBool", 404 Type: &Ydb.Type{ 405 Type: &Ydb.Type_TypeId{ 406 TypeId: Ydb.Type_BOOL, 407 }, 408 }, 409 }: { 410 Value: &Ydb.Value_BoolValue{ 411 BoolValue: true, 412 }, 413 }, 414 { 415 Name: "DateTime", 416 Type: &Ydb.Type{ 417 Type: &Ydb.Type_TypeId{ 418 TypeId: Ydb.Type_DATE, 419 }, 420 }, 421 }: { 422 Value: &Ydb.Value_Uint32Value{ 423 Uint32Value: 100500, 424 }, 425 }, 426 { 427 Name: "DatetimeTime", 428 Type: &Ydb.Type{ 429 Type: &Ydb.Type_TypeId{ 430 TypeId: Ydb.Type_DATETIME, 431 }, 432 }, 433 }: { 434 Value: &Ydb.Value_Uint32Value{ 435 Uint32Value: 100500, 436 }, 437 }, 438 { 439 Name: "TimestampTime", 440 Type: &Ydb.Type{ 441 Type: &Ydb.Type_TypeId{ 442 TypeId: Ydb.Type_TIMESTAMP, 443 }, 444 }, 445 }: { 446 Value: &Ydb.Value_Uint64Value{ 447 Uint64Value: 12345678987654321, 448 }, 449 }, 450 })).ScanStruct(&dst) 451 require.NoError(t, err) 452 require.Equal(t, scanData{ 453 Utf8String: "A", 454 Utf8Bytes: []byte("A"), 455 StringString: "A", 456 StringBytes: []byte("A"), 457 Uint64Uint64: 123, 458 Int64Int64: 123, 459 Uint32Uint64: 123, 460 Uint32Int64: 123, 461 Uint32Uint32: 123, 462 Int32Int64: 123, 463 Int32Int32: 123, 464 Uint16Uint64: 123, 465 Uint16Int64: 123, 466 Uint16Uint32: 123, 467 Uint16Int32: 123, 468 Uint16Uint16: 123, 469 Int16Int64: 123, 470 Int16Int32: 123, 471 Uint8Uint64: 123, 472 Uint8Int64: 123, 473 Uint8Uint32: 123, 474 Uint8Int32: 123, 475 Uint8Uint16: 123, 476 Int8Int64: 123, 477 Int8Int32: 123, 478 Int8Int16: 123, 479 BoolBool: true, 480 DateTime: time.Unix(8683200000, 0).UTC(), 481 DatetimeTime: time.Unix(100500, 0), 482 TimestampTime: time.Unix(12345678987, 654321000), 483 }, dst) 484 } 485 486 func TestStructNotAPointer(t *testing.T) { 487 scanner := Struct(Data( 488 []*Ydb.Column{ 489 { 490 Name: "a", 491 Type: &Ydb.Type{ 492 Type: &Ydb.Type_TypeId{ 493 TypeId: Ydb.Type_UTF8, 494 }, 495 }, 496 }, 497 }, 498 []*Ydb.Value{ 499 { 500 Value: &Ydb.Value_TextValue{ 501 TextValue: "test", 502 }, 503 }, 504 }, 505 )) 506 var row struct { 507 B string 508 C string 509 } 510 err := scanner.ScanStruct(row) 511 require.ErrorIs(t, err, errDstTypeIsNotAPointer) 512 } 513 514 func TestStructNotAPointerToStruct(t *testing.T) { 515 scanner := Struct(Data( 516 []*Ydb.Column{ 517 { 518 Name: "a", 519 Type: &Ydb.Type{ 520 Type: &Ydb.Type_TypeId{ 521 TypeId: Ydb.Type_UTF8, 522 }, 523 }, 524 }, 525 }, 526 []*Ydb.Value{ 527 { 528 Value: &Ydb.Value_TextValue{ 529 TextValue: "test", 530 }, 531 }, 532 }, 533 )) 534 var row string 535 err := scanner.ScanStruct(&row) 536 require.ErrorIs(t, err, errDstTypeIsNotAPointerToStruct) 537 } 538 539 func TestStructCastFailed(t *testing.T) { 540 scanner := Struct(Data( 541 []*Ydb.Column{ 542 { 543 Name: "A", 544 Type: &Ydb.Type{ 545 Type: &Ydb.Type_TypeId{ 546 TypeId: Ydb.Type_UTF8, 547 }, 548 }, 549 }, 550 }, 551 []*Ydb.Value{ 552 { 553 Value: &Ydb.Value_TextValue{ 554 TextValue: "test", 555 }, 556 }, 557 }, 558 )) 559 var row struct { 560 A uint64 561 } 562 err := scanner.ScanStruct(&row) 563 require.ErrorIs(t, err, value.ErrCannotCast) 564 } 565 566 func TestStructCastFailedErrMsg(t *testing.T) { 567 scanner := Struct(Data( 568 []*Ydb.Column{ 569 { 570 Name: "A", 571 Type: &Ydb.Type{ 572 Type: &Ydb.Type_TypeId{ 573 TypeId: Ydb.Type_UTF8, 574 }, 575 }, 576 }, 577 }, 578 []*Ydb.Value{ 579 { 580 Value: &Ydb.Value_TextValue{ 581 TextValue: "test", 582 }, 583 }, 584 }, 585 )) 586 var row struct { 587 A uint64 588 } 589 err := scanner.ScanStruct(&row) 590 require.ErrorContains(t, err, "scan error on struct field name 'A': cast failed") 591 } 592 593 func TestStructNotFoundColumns(t *testing.T) { 594 scanner := Struct(Data( 595 []*Ydb.Column{ 596 { 597 Name: "a", 598 Type: &Ydb.Type{ 599 Type: &Ydb.Type_TypeId{ 600 TypeId: Ydb.Type_UTF8, 601 }, 602 }, 603 }, 604 }, 605 []*Ydb.Value{ 606 { 607 Value: &Ydb.Value_TextValue{ 608 TextValue: "test", 609 }, 610 }, 611 }, 612 )) 613 var row struct { 614 B string 615 C string 616 } 617 err := scanner.ScanStruct(&row) 618 require.ErrorIs(t, err, ErrColumnsNotFoundInRow) 619 } 620 621 func TestStructSkippedColumns(t *testing.T) { 622 scanner := Struct(Data( 623 []*Ydb.Column{ 624 { 625 Name: "A", 626 Type: &Ydb.Type{ 627 Type: &Ydb.Type_TypeId{ 628 TypeId: Ydb.Type_UTF8, 629 }, 630 }, 631 }, 632 }, 633 []*Ydb.Value{ 634 { 635 Value: &Ydb.Value_TextValue{ 636 TextValue: "test-a", 637 }, 638 }, 639 }, 640 )) 641 642 var row struct { 643 A string 644 C string `sql:"-"` 645 } 646 err := scanner.ScanStruct(&row) 647 require.NoError(t, err) 648 require.Equal(t, "test-a", row.A) 649 require.Empty(t, row.C) 650 } 651 652 func TestStructWithAllowMissingColumnsFromSelect(t *testing.T) { 653 scanner := Struct(Data( 654 []*Ydb.Column{ 655 { 656 Name: "A", 657 Type: &Ydb.Type{ 658 Type: &Ydb.Type_TypeId{ 659 TypeId: Ydb.Type_UTF8, 660 }, 661 }, 662 }, 663 }, 664 []*Ydb.Value{ 665 { 666 Value: &Ydb.Value_TextValue{ 667 TextValue: "test", 668 }, 669 }, 670 }, 671 )) 672 var row struct { 673 A string 674 B string 675 C string 676 } 677 err := scanner.ScanStruct(&row, 678 WithAllowMissingColumnsFromSelect(), 679 ) 680 require.NoError(t, err) 681 require.Equal(t, "test", row.A) 682 require.Equal(t, "", row.B) 683 require.Equal(t, "", row.C) 684 } 685 686 func TestStructNotFoundFields(t *testing.T) { 687 scanner := Struct(Data( 688 []*Ydb.Column{ 689 { 690 Name: "A", 691 Type: &Ydb.Type{ 692 Type: &Ydb.Type_TypeId{ 693 TypeId: Ydb.Type_UTF8, 694 }, 695 }, 696 }, 697 { 698 Name: "B", 699 Type: &Ydb.Type{ 700 Type: &Ydb.Type_TypeId{ 701 TypeId: Ydb.Type_UTF8, 702 }, 703 }, 704 }, 705 { 706 Name: "C", 707 Type: &Ydb.Type{ 708 Type: &Ydb.Type_TypeId{ 709 TypeId: Ydb.Type_UTF8, 710 }, 711 }, 712 }, 713 }, 714 []*Ydb.Value{ 715 { 716 Value: &Ydb.Value_TextValue{ 717 TextValue: "test", 718 }, 719 }, 720 { 721 Value: &Ydb.Value_TextValue{ 722 TextValue: "test", 723 }, 724 }, 725 { 726 Value: &Ydb.Value_TextValue{ 727 TextValue: "test", 728 }, 729 }, 730 }, 731 )) 732 var row struct { 733 A string 734 } 735 err := scanner.ScanStruct(&row) 736 require.ErrorIs(t, err, ErrFieldsNotFoundInStruct) 737 } 738 739 func TestStructWithAllowMissingFieldsInStruct(t *testing.T) { 740 scanner := Struct(Data( 741 []*Ydb.Column{ 742 { 743 Name: "A", 744 Type: &Ydb.Type{ 745 Type: &Ydb.Type_TypeId{ 746 TypeId: Ydb.Type_UTF8, 747 }, 748 }, 749 }, 750 { 751 Name: "B", 752 Type: &Ydb.Type{ 753 Type: &Ydb.Type_TypeId{ 754 TypeId: Ydb.Type_UTF8, 755 }, 756 }, 757 }, 758 { 759 Name: "C", 760 Type: &Ydb.Type{ 761 Type: &Ydb.Type_TypeId{ 762 TypeId: Ydb.Type_UTF8, 763 }, 764 }, 765 }, 766 }, 767 []*Ydb.Value{ 768 { 769 Value: &Ydb.Value_TextValue{ 770 TextValue: "test", 771 }, 772 }, 773 { 774 Value: &Ydb.Value_TextValue{ 775 TextValue: "test", 776 }, 777 }, 778 { 779 Value: &Ydb.Value_TextValue{ 780 TextValue: "test", 781 }, 782 }, 783 }, 784 )) 785 var row struct { 786 A string 787 } 788 err := scanner.ScanStruct(&row, 789 WithAllowMissingFieldsInStruct(), 790 ) 791 require.NoError(t, err) 792 require.Equal(t, "test", row.A) 793 } 794 795 func TestStructWithTagName(t *testing.T) { 796 scanner := Struct(Data( 797 []*Ydb.Column{ 798 { 799 Name: "A", 800 Type: &Ydb.Type{ 801 Type: &Ydb.Type_TypeId{ 802 TypeId: Ydb.Type_UTF8, 803 }, 804 }, 805 }, 806 { 807 Name: "B", 808 Type: &Ydb.Type{ 809 Type: &Ydb.Type_TypeId{ 810 TypeId: Ydb.Type_UTF8, 811 }, 812 }, 813 }, 814 { 815 Name: "C", 816 Type: &Ydb.Type{ 817 Type: &Ydb.Type_TypeId{ 818 TypeId: Ydb.Type_UTF8, 819 }, 820 }, 821 }, 822 }, 823 []*Ydb.Value{ 824 { 825 Value: &Ydb.Value_TextValue{ 826 TextValue: "AA", 827 }, 828 }, 829 { 830 Value: &Ydb.Value_TextValue{ 831 TextValue: "BB", 832 }, 833 }, 834 { 835 Value: &Ydb.Value_TextValue{ 836 TextValue: "CC", 837 }, 838 }, 839 }, 840 )) 841 var row struct { 842 A string `test:"A"` 843 B string `test:"B"` 844 C string `test:"C"` 845 } 846 err := scanner.ScanStruct(&row, 847 WithTagName("test"), 848 ) 849 require.NoError(t, err) 850 require.Equal(t, "AA", row.A) 851 require.Equal(t, "BB", row.B) 852 require.Equal(t, "CC", row.C) 853 } 854 855 func TestScannerStructOrdering(t *testing.T) { 856 scanner := Struct(Data( 857 []*Ydb.Column{ 858 { 859 Name: "B", 860 Type: &Ydb.Type{ 861 Type: &Ydb.Type_TypeId{ 862 TypeId: Ydb.Type_UTF8, 863 }, 864 }, 865 }, 866 { 867 Name: "A", 868 Type: &Ydb.Type{ 869 Type: &Ydb.Type_TypeId{ 870 TypeId: Ydb.Type_UTF8, 871 }, 872 }, 873 }, 874 { 875 Name: "C", 876 Type: &Ydb.Type{ 877 Type: &Ydb.Type_TypeId{ 878 TypeId: Ydb.Type_UTF8, 879 }, 880 }, 881 }, 882 }, 883 []*Ydb.Value{ 884 { 885 Value: &Ydb.Value_TextValue{ 886 TextValue: "B", 887 }, 888 }, 889 { 890 Value: &Ydb.Value_TextValue{ 891 TextValue: "A", 892 }, 893 }, 894 { 895 Value: &Ydb.Value_TextValue{ 896 TextValue: "C", 897 }, 898 }, 899 }, 900 )) 901 var row struct { 902 A string 903 B string 904 C string 905 } 906 err := scanner.ScanStruct(&row) 907 require.NoError(t, err) 908 require.Equal(t, "A", row.A) 909 require.Equal(t, "B", row.B) 910 require.Equal(t, "C", row.C) 911 }