Skip to content

Commit 4a5f8ec

Browse files
easyCZroboquat
authored andcommitted
[usage] Handle empty value varchar time
1 parent fa36e39 commit 4a5f8ec

File tree

2 files changed

+118
-28
lines changed

2 files changed

+118
-28
lines changed

components/usage/pkg/db/types.go

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,37 +39,36 @@ func (n *VarcharTime) Scan(value interface{}) error {
3939

4040
switch s := value.(type) {
4141
case []uint8:
42-
// Null value - empty string mean value is not set
43-
if len(s) == 0 {
44-
n.valid = false
45-
return nil
46-
}
47-
48-
parsed, err := iso8601.ParseString(string(s))
49-
if err != nil {
50-
return fmt.Errorf("failed to parse %v into ISO8601: %w", string(s), err)
51-
}
52-
n.valid = true
53-
n.t = parsed.UTC()
54-
return nil
42+
return n.parseString(string(s))
5543
case string:
56-
if len(s) == 0 {
57-
n.valid = false
58-
return nil
59-
}
60-
61-
parsed, err := iso8601.ParseString(s)
62-
if err != nil {
63-
return fmt.Errorf("failed to parse %v into ISO8601: %w", s, err)
64-
}
65-
66-
n.valid = true
67-
n.t = parsed.UTC()
68-
return nil
44+
return n.parseString(s)
6945
}
7046
return fmt.Errorf("unknown scan value for VarcharTime with value: %v", value)
7147
}
7248

49+
func (n *VarcharTime) parseString(s string) error {
50+
// Null value - empty string mean value is not set
51+
if len(s) == 0 {
52+
n.valid = false
53+
return nil
54+
}
55+
56+
parsed, err := iso8601.ParseString(s)
57+
if err != nil {
58+
return fmt.Errorf("failed to parse %v into ISO8601: %w", s, err)
59+
}
60+
61+
if parsed.UTC().IsZero() {
62+
n.t = time.Time{}.UTC()
63+
n.valid = false
64+
return nil
65+
}
66+
67+
n.valid = true
68+
n.t = parsed.UTC()
69+
return nil
70+
}
71+
7372
func (n VarcharTime) Time() time.Time {
7473
return n.t
7574
}
@@ -80,9 +79,21 @@ func (n VarcharTime) IsSet() bool {
8079

8180
// Value implements the driver Valuer interface.
8281
func (n VarcharTime) Value() (driver.Value, error) {
83-
return n.t.UTC().Format(time.RFC3339Nano), nil
82+
if n.IsSet() {
83+
return TimeToISO8601(n.t), nil
84+
}
85+
return "", nil
8486
}
8587

8688
func (n VarcharTime) String() string {
87-
return n.t.Format(time.RFC3339Nano)
89+
if n.IsSet() {
90+
return TimeToISO8601(n.t)
91+
}
92+
return ""
93+
}
94+
95+
const ISO8601Format = "2006-01-02T15:04:05.000Z"
96+
97+
func TimeToISO8601(t time.Time) string {
98+
return t.UTC().Format(ISO8601Format)
8899
}

components/usage/pkg/db/types_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package db
66

77
import (
88
"github.com/stretchr/testify/require"
9+
"gorm.io/gorm"
910
"testing"
1011
"time"
1112
)
@@ -77,3 +78,81 @@ func TestVarcharTime_Scan(t *testing.T) {
7778
})
7879
}
7980
}
81+
82+
func TestVarcharTime_Value_ISO8601(t *testing.T) {
83+
for _, scenario := range []struct {
84+
Time VarcharTime
85+
Expected string
86+
}{
87+
{
88+
Time: NewVarcharTime(time.Date(2019, 05, 10, 9, 54, 28, 185000000, time.UTC)),
89+
Expected: "2019-05-10T09:54:28.185Z",
90+
},
91+
{
92+
Time: VarcharTime{},
93+
Expected: "",
94+
},
95+
} {
96+
wireFormat, err := scenario.Time.Value()
97+
require.NoError(t, err)
98+
require.Equal(t, scenario.Expected, wireFormat)
99+
}
100+
}
101+
102+
func TestVarcharTime_String_ISO8601(t *testing.T) {
103+
for _, scenario := range []struct {
104+
Time VarcharTime
105+
Expected string
106+
}{
107+
{
108+
Time: NewVarcharTime(time.Date(2019, 05, 10, 9, 54, 28, 185000000, time.UTC)),
109+
Expected: "2019-05-10T09:54:28.185Z",
110+
},
111+
{
112+
Time: VarcharTime{},
113+
Expected: "",
114+
},
115+
} {
116+
require.Equal(t, scenario.Expected, scenario.Time.String())
117+
}
118+
}
119+
120+
func TestVarcharTime_SerializeAndDeserialize(t *testing.T) {
121+
// Custom table to be able to exercise serialization easily, independent of other models
122+
type VarcharModel struct {
123+
ID int `gorm:"primaryKey"`
124+
Time VarcharTime `gorm:"column:time;type:varchar(255);"`
125+
}
126+
127+
conn := ConnectForTests(t)
128+
require.NoError(t, conn.AutoMigrate(&VarcharModel{}))
129+
130+
conn.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&VarcharModel{})
131+
132+
for _, scenario := range []struct {
133+
Description string
134+
Input VarcharModel
135+
Expected VarcharModel
136+
}{
137+
{
138+
Description: "empty value for VarcharTime",
139+
Input: VarcharModel{
140+
ID: 1,
141+
Time: VarcharTime{},
142+
},
143+
Expected: VarcharModel{
144+
ID: 1,
145+
Time: VarcharTime{},
146+
},
147+
},
148+
} {
149+
tx := conn.Create(scenario.Input)
150+
require.NoError(t, tx.Error)
151+
152+
var read VarcharModel
153+
tx = conn.First(&read, scenario.Input.ID)
154+
require.NoError(t, tx.Error)
155+
156+
require.Equal(t, scenario.Expected, read)
157+
}
158+
}

0 commit comments

Comments
 (0)