|
8 | 8 | "testing" |
9 | 9 | "time" |
10 | 10 |
|
| 11 | + "github.com/golang-sql/civil" |
11 | 12 | "github.com/microsoft/go-mssqldb/msdsn" |
12 | 13 | ) |
13 | 14 |
|
@@ -587,3 +588,166 @@ func TestTVP_encode_WithGuidConversion(t *testing.T) { |
587 | 588 | func TestTVP_encode(t *testing.T) { |
588 | 589 | testTVP_encode(t, false /*guidConversion*/) |
589 | 590 | } |
| 591 | + |
| 592 | +// TestTVPWithNullCivilTypes tests that nullable civil types work correctly in TVP operations |
| 593 | +func TestTVPWithNullCivilTypes(t *testing.T) { |
| 594 | + type tvpDataRowNullDateTime struct { |
| 595 | + T NullDateTime |
| 596 | + } |
| 597 | + |
| 598 | + type tvpDataRowNullDate struct { |
| 599 | + D NullDate |
| 600 | + } |
| 601 | + |
| 602 | + type tvpDataRowNullTime struct { |
| 603 | + T NullTime |
| 604 | + } |
| 605 | + |
| 606 | + type tvpDataRowMixed struct { |
| 607 | + Date NullDate `tvp:"date_col"` |
| 608 | + DateTime NullDateTime `tvp:"datetime_col"` |
| 609 | + Time NullTime `tvp:"time_col"` |
| 610 | + } |
| 611 | + |
| 612 | + tests := []struct { |
| 613 | + name string |
| 614 | + tvpData interface{} |
| 615 | + wantErr bool |
| 616 | + }{ |
| 617 | + { |
| 618 | + name: "NullDateTime with Valid=false", |
| 619 | + tvpData: []tvpDataRowNullDateTime{ |
| 620 | + {T: NullDateTime{Valid: false}}, |
| 621 | + }, |
| 622 | + wantErr: false, |
| 623 | + }, |
| 624 | + { |
| 625 | + name: "NullDateTime with Valid=true", |
| 626 | + tvpData: []tvpDataRowNullDateTime{ |
| 627 | + {T: NullDateTime{DateTime: civil.DateTime{Date: civil.Date{Year: 2025, Month: 10, Day: 2}, Time: civil.Time{Hour: 16, Minute: 10, Second: 55}}, Valid: true}}, |
| 628 | + }, |
| 629 | + wantErr: false, |
| 630 | + }, |
| 631 | + { |
| 632 | + name: "NullDate with Valid=false", |
| 633 | + tvpData: []tvpDataRowNullDate{ |
| 634 | + {D: NullDate{Valid: false}}, |
| 635 | + }, |
| 636 | + wantErr: false, |
| 637 | + }, |
| 638 | + { |
| 639 | + name: "NullDate with Valid=true", |
| 640 | + tvpData: []tvpDataRowNullDate{ |
| 641 | + {D: NullDate{Date: civil.Date{Year: 2025, Month: 10, Day: 2}, Valid: true}}, |
| 642 | + }, |
| 643 | + wantErr: false, |
| 644 | + }, |
| 645 | + { |
| 646 | + name: "NullTime with Valid=false", |
| 647 | + tvpData: []tvpDataRowNullTime{ |
| 648 | + {T: NullTime{Valid: false}}, |
| 649 | + }, |
| 650 | + wantErr: false, |
| 651 | + }, |
| 652 | + { |
| 653 | + name: "NullTime with Valid=true", |
| 654 | + tvpData: []tvpDataRowNullTime{ |
| 655 | + {T: NullTime{Time: civil.Time{Hour: 16, Minute: 10, Second: 55}, Valid: true}}, |
| 656 | + }, |
| 657 | + wantErr: false, |
| 658 | + }, |
| 659 | + { |
| 660 | + name: "Mixed nullable civil types with some null, some valid", |
| 661 | + tvpData: []tvpDataRowMixed{ |
| 662 | + { |
| 663 | + Date: NullDate{Valid: false}, |
| 664 | + DateTime: NullDateTime{DateTime: civil.DateTime{Date: civil.Date{Year: 2025, Month: 10, Day: 2}, Time: civil.Time{Hour: 16, Minute: 10, Second: 55}}, Valid: true}, |
| 665 | + Time: NullTime{Valid: false}, |
| 666 | + }, |
| 667 | + { |
| 668 | + Date: NullDate{Date: civil.Date{Year: 2025, Month: 12, Day: 25}, Valid: true}, |
| 669 | + DateTime: NullDateTime{Valid: false}, |
| 670 | + Time: NullTime{Time: civil.Time{Hour: 9, Minute: 30, Second: 0}, Valid: true}, |
| 671 | + }, |
| 672 | + }, |
| 673 | + wantErr: false, |
| 674 | + }, |
| 675 | + { |
| 676 | + name: "User example 1: Empty NullDateTime", |
| 677 | + tvpData: []tvpDataRowNullDateTime{ |
| 678 | + {T: NullDateTime{}}, // Valid defaults to false |
| 679 | + }, |
| 680 | + wantErr: false, |
| 681 | + }, |
| 682 | + { |
| 683 | + name: "User example 2: Valid NullDateTime", |
| 684 | + tvpData: func() []tvpDataRowNullDateTime { |
| 685 | + t1, _ := civil.ParseDateTime("2025-10-02T16:10:55") |
| 686 | + return []tvpDataRowNullDateTime{ |
| 687 | + {T: NullDateTime{DateTime: t1, Valid: true}}, |
| 688 | + } |
| 689 | + }(), |
| 690 | + wantErr: false, |
| 691 | + }, |
| 692 | + } |
| 693 | + |
| 694 | + for _, tt := range tests { |
| 695 | + t.Run(tt.name, func(t *testing.T) { |
| 696 | + tvp := TVP{ |
| 697 | + TypeName: "dbo.TestType", |
| 698 | + Value: tt.tvpData, |
| 699 | + } |
| 700 | + |
| 701 | + // Test columnTypes |
| 702 | + columnStr, tvpFieldIndexes, err := tvp.columnTypes() |
| 703 | + if (err != nil) != tt.wantErr { |
| 704 | + t.Errorf("TVP.columnTypes() error = %v, wantErr %v", err, tt.wantErr) |
| 705 | + return |
| 706 | + } |
| 707 | + |
| 708 | + if err != nil { |
| 709 | + return // Skip encoding test if columnTypes failed |
| 710 | + } |
| 711 | + |
| 712 | + // Test encode |
| 713 | + _, err = tvp.encode("dbo", "TestType", columnStr, tvpFieldIndexes, msdsn.EncodeParameters{}) |
| 714 | + if (err != nil) != tt.wantErr { |
| 715 | + t.Errorf("TVP.encode() error = %v, wantErr %v", err, tt.wantErr) |
| 716 | + return |
| 717 | + } |
| 718 | + }) |
| 719 | + } |
| 720 | +} |
| 721 | + |
| 722 | +// TestTVPNullCivilTypesCreateZeroType tests that nullable civil types are handled correctly |
| 723 | +// in the createZeroType method when building column type information |
| 724 | +func TestTVPNullCivilTypesCreateZeroType(t *testing.T) { |
| 725 | + type tvpDataRowMixed struct { |
| 726 | + Date NullDate `tvp:"date_col"` |
| 727 | + DateTime NullDateTime `tvp:"datetime_col"` |
| 728 | + Time NullTime `tvp:"time_col"` |
| 729 | + } |
| 730 | + |
| 731 | + tvp := TVP{ |
| 732 | + TypeName: "dbo.TestType", |
| 733 | + Value: []tvpDataRowMixed{ |
| 734 | + {}, // Empty struct to trigger createZeroType for all fields |
| 735 | + }, |
| 736 | + } |
| 737 | + |
| 738 | + // Test that we can get column types without error |
| 739 | + columnStr, tvpFieldIndexes, err := tvp.columnTypes() |
| 740 | + if err != nil { |
| 741 | + t.Errorf("TVP.columnTypes() with empty struct failed: %v", err) |
| 742 | + return |
| 743 | + } |
| 744 | + |
| 745 | + // Should have 3 columns for the 3 fields |
| 746 | + if len(columnStr) != 3 { |
| 747 | + t.Errorf("Expected 3 columns, got %d", len(columnStr)) |
| 748 | + } |
| 749 | + |
| 750 | + if len(tvpFieldIndexes) != 3 { |
| 751 | + t.Errorf("Expected 3 field indexes, got %d", len(tvpFieldIndexes)) |
| 752 | + } |
| 753 | +} |
0 commit comments