github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/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), 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 TestStructNotFoundColumns(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 B string 588 C string 589 } 590 err := scanner.ScanStruct(&row) 591 require.ErrorIs(t, err, errColumnsNotFoundInRow) 592 } 593 594 func TestStructWithAllowMissingColumnsFromSelect(t *testing.T) { 595 scanner := Struct(Data( 596 []*Ydb.Column{ 597 { 598 Name: "A", 599 Type: &Ydb.Type{ 600 Type: &Ydb.Type_TypeId{ 601 TypeId: Ydb.Type_UTF8, 602 }, 603 }, 604 }, 605 }, 606 []*Ydb.Value{ 607 { 608 Value: &Ydb.Value_TextValue{ 609 TextValue: "test", 610 }, 611 }, 612 }, 613 )) 614 var row struct { 615 A string 616 B string 617 C string 618 } 619 err := scanner.ScanStruct(&row, 620 WithAllowMissingColumnsFromSelect(), 621 ) 622 require.NoError(t, err) 623 require.Equal(t, "test", row.A) 624 require.Equal(t, "", row.B) 625 require.Equal(t, "", row.C) 626 } 627 628 func TestStructNotFoundFields(t *testing.T) { 629 scanner := Struct(Data( 630 []*Ydb.Column{ 631 { 632 Name: "A", 633 Type: &Ydb.Type{ 634 Type: &Ydb.Type_TypeId{ 635 TypeId: Ydb.Type_UTF8, 636 }, 637 }, 638 }, 639 { 640 Name: "B", 641 Type: &Ydb.Type{ 642 Type: &Ydb.Type_TypeId{ 643 TypeId: Ydb.Type_UTF8, 644 }, 645 }, 646 }, 647 { 648 Name: "C", 649 Type: &Ydb.Type{ 650 Type: &Ydb.Type_TypeId{ 651 TypeId: Ydb.Type_UTF8, 652 }, 653 }, 654 }, 655 }, 656 []*Ydb.Value{ 657 { 658 Value: &Ydb.Value_TextValue{ 659 TextValue: "test", 660 }, 661 }, 662 { 663 Value: &Ydb.Value_TextValue{ 664 TextValue: "test", 665 }, 666 }, 667 { 668 Value: &Ydb.Value_TextValue{ 669 TextValue: "test", 670 }, 671 }, 672 }, 673 )) 674 var row struct { 675 A string 676 } 677 err := scanner.ScanStruct(&row) 678 require.ErrorIs(t, err, errFieldsNotFoundInStruct) 679 } 680 681 func TestStructWithAllowMissingFieldsInStruct(t *testing.T) { 682 scanner := Struct(Data( 683 []*Ydb.Column{ 684 { 685 Name: "A", 686 Type: &Ydb.Type{ 687 Type: &Ydb.Type_TypeId{ 688 TypeId: Ydb.Type_UTF8, 689 }, 690 }, 691 }, 692 { 693 Name: "B", 694 Type: &Ydb.Type{ 695 Type: &Ydb.Type_TypeId{ 696 TypeId: Ydb.Type_UTF8, 697 }, 698 }, 699 }, 700 { 701 Name: "C", 702 Type: &Ydb.Type{ 703 Type: &Ydb.Type_TypeId{ 704 TypeId: Ydb.Type_UTF8, 705 }, 706 }, 707 }, 708 }, 709 []*Ydb.Value{ 710 { 711 Value: &Ydb.Value_TextValue{ 712 TextValue: "test", 713 }, 714 }, 715 { 716 Value: &Ydb.Value_TextValue{ 717 TextValue: "test", 718 }, 719 }, 720 { 721 Value: &Ydb.Value_TextValue{ 722 TextValue: "test", 723 }, 724 }, 725 }, 726 )) 727 var row struct { 728 A string 729 } 730 err := scanner.ScanStruct(&row, 731 WithAllowMissingFieldsInStruct(), 732 ) 733 require.NoError(t, err) 734 require.Equal(t, "test", row.A) 735 } 736 737 func TestStructWithTagName(t *testing.T) { 738 scanner := Struct(Data( 739 []*Ydb.Column{ 740 { 741 Name: "A", 742 Type: &Ydb.Type{ 743 Type: &Ydb.Type_TypeId{ 744 TypeId: Ydb.Type_UTF8, 745 }, 746 }, 747 }, 748 { 749 Name: "B", 750 Type: &Ydb.Type{ 751 Type: &Ydb.Type_TypeId{ 752 TypeId: Ydb.Type_UTF8, 753 }, 754 }, 755 }, 756 { 757 Name: "C", 758 Type: &Ydb.Type{ 759 Type: &Ydb.Type_TypeId{ 760 TypeId: Ydb.Type_UTF8, 761 }, 762 }, 763 }, 764 }, 765 []*Ydb.Value{ 766 { 767 Value: &Ydb.Value_TextValue{ 768 TextValue: "AA", 769 }, 770 }, 771 { 772 Value: &Ydb.Value_TextValue{ 773 TextValue: "BB", 774 }, 775 }, 776 { 777 Value: &Ydb.Value_TextValue{ 778 TextValue: "CC", 779 }, 780 }, 781 }, 782 )) 783 var row struct { 784 A string `test:"A"` 785 B string `test:"B"` 786 C string `test:"C"` 787 } 788 err := scanner.ScanStruct(&row, 789 WithTagName("test"), 790 ) 791 require.NoError(t, err) 792 require.Equal(t, "AA", row.A) 793 require.Equal(t, "BB", row.B) 794 require.Equal(t, "CC", row.C) 795 } 796 797 func TestScannerStructOrdering(t *testing.T) { 798 scanner := Struct(Data( 799 []*Ydb.Column{ 800 { 801 Name: "B", 802 Type: &Ydb.Type{ 803 Type: &Ydb.Type_TypeId{ 804 TypeId: Ydb.Type_UTF8, 805 }, 806 }, 807 }, 808 { 809 Name: "A", 810 Type: &Ydb.Type{ 811 Type: &Ydb.Type_TypeId{ 812 TypeId: Ydb.Type_UTF8, 813 }, 814 }, 815 }, 816 { 817 Name: "C", 818 Type: &Ydb.Type{ 819 Type: &Ydb.Type_TypeId{ 820 TypeId: Ydb.Type_UTF8, 821 }, 822 }, 823 }, 824 }, 825 []*Ydb.Value{ 826 { 827 Value: &Ydb.Value_TextValue{ 828 TextValue: "B", 829 }, 830 }, 831 { 832 Value: &Ydb.Value_TextValue{ 833 TextValue: "A", 834 }, 835 }, 836 { 837 Value: &Ydb.Value_TextValue{ 838 TextValue: "C", 839 }, 840 }, 841 }, 842 )) 843 var row struct { 844 A string 845 B string 846 C string 847 } 848 err := scanner.ScanStruct(&row) 849 require.NoError(t, err) 850 require.Equal(t, "A", row.A) 851 require.Equal(t, "B", row.B) 852 require.Equal(t, "C", row.C) 853 }