Skip to content

Commit 1e6a5b0

Browse files
committed
zero: add Equal and ValueOrZero methods
1 parent cc180c7 commit 1e6a5b0

File tree

10 files changed

+431
-19
lines changed

10 files changed

+431
-19
lines changed

zero/bool.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ func BoolFromPtr(b *bool) Bool {
3838
return NewBool(*b, true)
3939
}
4040

41+
// ValueOrZero returns the inner value if valid, otherwise false.
42+
func (b Bool) ValueOrZero() bool {
43+
return b.Valid && b.Bool
44+
}
45+
4146
// UnmarshalJSON implements json.Unmarshaler.
4247
// "false" will be considered a null Bool.
4348
// It also supports unmarshalling a sql.NullBool.
@@ -119,3 +124,8 @@ func (b Bool) Ptr() *bool {
119124
func (b Bool) IsZero() bool {
120125
return !b.Valid || !b.Bool
121126
}
127+
128+
// Equal returns true if both booleans are true and valid, or if both booleans are either false or invalid.
129+
func (b Bool) Equal(other Bool) bool {
130+
return b.ValueOrZero() == other.ValueOrZero()
131+
}

zero/bool_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,48 @@ func TestBoolScan(t *testing.T) {
171171
assertNullBool(t, null, "scanned null")
172172
}
173173

174+
func TestBoolValueOrZero(t *testing.T) {
175+
valid := NewBool(true, true)
176+
if valid.ValueOrZero() != true {
177+
t.Error("unexpected ValueOrZero", valid.ValueOrZero())
178+
}
179+
180+
invalid := NewBool(true, false)
181+
if invalid.ValueOrZero() != false {
182+
t.Error("unexpected ValueOrZero", invalid.ValueOrZero())
183+
}
184+
}
185+
186+
func TestBoolEqual(t *testing.T) {
187+
b1 := NewBool(true, false)
188+
b2 := NewBool(true, false)
189+
assertBoolEqualIsTrue(t, b1, b2)
190+
191+
b1 = NewBool(true, false)
192+
b2 = NewBool(false, false)
193+
assertBoolEqualIsTrue(t, b1, b2)
194+
195+
b1 = NewBool(true, true)
196+
b2 = NewBool(true, true)
197+
assertBoolEqualIsTrue(t, b1, b2)
198+
199+
b1 = NewBool(true, false)
200+
b2 = NewBool(false, true)
201+
assertBoolEqualIsTrue(t, b1, b2)
202+
203+
b1 = NewBool(true, true)
204+
b2 = NewBool(true, false)
205+
assertBoolEqualIsFalse(t, b1, b2)
206+
207+
b1 = NewBool(true, false)
208+
b2 = NewBool(true, true)
209+
assertBoolEqualIsFalse(t, b1, b2)
210+
211+
b1 = NewBool(true, true)
212+
b2 = NewBool(false, true)
213+
assertBoolEqualIsFalse(t, b1, b2)
214+
}
215+
174216
func assertBool(t *testing.T, b Bool, from string) {
175217
if b.Bool != true {
176218
t.Errorf("bad %s bool: %v ≠ %v\n", from, b.Bool, true)
@@ -185,3 +227,17 @@ func assertNullBool(t *testing.T, b Bool, from string) {
185227
t.Error(from, "is valid, but should be invalid")
186228
}
187229
}
230+
231+
func assertBoolEqualIsTrue(t *testing.T, a, b Bool) {
232+
t.Helper()
233+
if !a.Equal(b) {
234+
t.Errorf("Equal() of Bool{%t, Valid:%t} and Bool{%t, Valid:%t} should return true", a.Bool, a.Valid, b.Bool, b.Valid)
235+
}
236+
}
237+
238+
func assertBoolEqualIsFalse(t *testing.T, a, b Bool) {
239+
t.Helper()
240+
if a.Equal(b) {
241+
t.Errorf("Equal() of Bool{%t, Valid:%t} and Bool{%t, Valid:%t} should return false", a.Bool, a.Valid, b.Bool, b.Valid)
242+
}
243+
}

zero/float.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ func FloatFromPtr(f *float64) Float {
3939
return NewFloat(*f, true)
4040
}
4141

42+
// ValueOrZero returns the inner value if valid, otherwise zero.
43+
func (f Float) ValueOrZero() float64 {
44+
if !f.Valid {
45+
return 0
46+
}
47+
return f.Float64
48+
}
49+
4250
// UnmarshalJSON implements json.Unmarshaler.
4351
// It supports number and null input.
4452
// 0 will be considered a null Float.
@@ -130,3 +138,12 @@ func (f Float) Ptr() *float64 {
130138
func (f Float) IsZero() bool {
131139
return !f.Valid || f.Float64 == 0
132140
}
141+
142+
// Equal returns true if both floats have the same value or are both either null or zero.
143+
// Warning: calculations using floating point numbers can result in different ways
144+
// the numbers are stored in memory. Therefore, this function is not suitable to
145+
// compare the result of a calculation. Use this method only to check if the value
146+
// has changed in comparison to some previous value.
147+
func (f Float) Equal(other Float) bool {
148+
return f.ValueOrZero() == other.ValueOrZero()
149+
}

zero/float_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,48 @@ func TestFloatInfNaN(t *testing.T) {
191191
}
192192
}
193193

194+
func TestFloatValueOrZero(t *testing.T) {
195+
valid := NewFloat(1.2345, true)
196+
if valid.ValueOrZero() != 1.2345 {
197+
t.Error("unexpected ValueOrZero", valid.ValueOrZero())
198+
}
199+
200+
invalid := NewFloat(1.2345, false)
201+
if invalid.ValueOrZero() != 0 {
202+
t.Error("unexpected ValueOrZero", invalid.ValueOrZero())
203+
}
204+
}
205+
206+
func TestFloatEqual(t *testing.T) {
207+
f1 := NewFloat(10, false)
208+
f2 := NewFloat(10, false)
209+
assertFloatEqualIsTrue(t, f1, f2)
210+
211+
f1 = NewFloat(10, false)
212+
f2 = NewFloat(20, false)
213+
assertFloatEqualIsTrue(t, f1, f2)
214+
215+
f1 = NewFloat(10, true)
216+
f2 = NewFloat(10, true)
217+
assertFloatEqualIsTrue(t, f1, f2)
218+
219+
f1 = NewFloat(10, false)
220+
f2 = NewFloat(0, true)
221+
assertFloatEqualIsTrue(t, f1, f2)
222+
223+
f1 = NewFloat(10, true)
224+
f2 = NewFloat(10, false)
225+
assertFloatEqualIsFalse(t, f1, f2)
226+
227+
f1 = NewFloat(10, false)
228+
f2 = NewFloat(10, true)
229+
assertFloatEqualIsFalse(t, f1, f2)
230+
231+
f1 = NewFloat(10, true)
232+
f2 = NewFloat(20, true)
233+
assertFloatEqualIsFalse(t, f1, f2)
234+
}
235+
194236
func assertFloat(t *testing.T, f Float, from string) {
195237
if f.Float64 != 1.2345 {
196238
t.Errorf("bad %s float: %f ≠ %f\n", from, f.Float64, 1.2345)
@@ -205,3 +247,17 @@ func assertNullFloat(t *testing.T, f Float, from string) {
205247
t.Error(from, "is valid, but should be invalid")
206248
}
207249
}
250+
251+
func assertFloatEqualIsTrue(t *testing.T, a, b Float) {
252+
t.Helper()
253+
if !a.Equal(b) {
254+
t.Errorf("Equal() of Float{%v, Valid:%t} and Float{%v, Valid:%t} should return true", a.Float64, a.Valid, b.Float64, b.Valid)
255+
}
256+
}
257+
258+
func assertFloatEqualIsFalse(t *testing.T, a, b Float) {
259+
t.Helper()
260+
if a.Equal(b) {
261+
t.Errorf("Equal() of Float{%v, Valid:%t} and Float{%v, Valid:%t} should return false", a.Float64, a.Valid, b.Float64, b.Valid)
262+
}
263+
}

zero/int.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ func IntFromPtr(i *int64) Int {
3939
return n
4040
}
4141

42+
// ValueOrZero returns the inner value if valid, otherwise zero.
43+
func (i Int) ValueOrZero() int64 {
44+
if !i.Valid {
45+
return 0
46+
}
47+
return i.Int64
48+
}
49+
4250
// UnmarshalJSON implements json.Unmarshaler.
4351
// It supports number and null input.
4452
// 0 will be considered a null Int.
@@ -125,3 +133,8 @@ func (i Int) Ptr() *int64 {
125133
func (i Int) IsZero() bool {
126134
return !i.Valid || i.Int64 == 0
127135
}
136+
137+
// Equal returns true if both ints have the same value or are both either null or zero.
138+
func (i Int) Equal(other Int) bool {
139+
return i.ValueOrZero() == other.ValueOrZero()
140+
}

zero/int_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,48 @@ func TestIntSetValid(t *testing.T) {
202202
assertInt(t, change, "SetValid()")
203203
}
204204

205+
func TestIntValueOrZero(t *testing.T) {
206+
valid := NewInt(12345, true)
207+
if valid.ValueOrZero() != 12345 {
208+
t.Error("unexpected ValueOrZero", valid.ValueOrZero())
209+
}
210+
211+
invalid := NewInt(12345, false)
212+
if invalid.ValueOrZero() != 0 {
213+
t.Error("unexpected ValueOrZero", invalid.ValueOrZero())
214+
}
215+
}
216+
217+
func TestIntEqual(t *testing.T) {
218+
int1 := NewInt(10, false)
219+
int2 := NewInt(10, false)
220+
assertIntEqualIsTrue(t, int1, int2)
221+
222+
int1 = NewInt(10, false)
223+
int2 = NewInt(20, false)
224+
assertIntEqualIsTrue(t, int1, int2)
225+
226+
int1 = NewInt(10, true)
227+
int2 = NewInt(10, true)
228+
assertIntEqualIsTrue(t, int1, int2)
229+
230+
int1 = NewInt(0, true)
231+
int2 = NewInt(10, false)
232+
assertIntEqualIsTrue(t, int1, int2)
233+
234+
int1 = NewInt(10, true)
235+
int2 = NewInt(10, false)
236+
assertIntEqualIsFalse(t, int1, int2)
237+
238+
int1 = NewInt(10, false)
239+
int2 = NewInt(10, true)
240+
assertIntEqualIsFalse(t, int1, int2)
241+
242+
int1 = NewInt(10, true)
243+
int2 = NewInt(20, true)
244+
assertIntEqualIsFalse(t, int1, int2)
245+
}
246+
205247
func assertInt(t *testing.T, i Int, from string) {
206248
if i.Int64 != 12345 {
207249
t.Errorf("bad %s int: %d ≠ %d\n", from, i.Int64, 12345)
@@ -216,3 +258,17 @@ func assertNullInt(t *testing.T, i Int, from string) {
216258
t.Error(from, "is valid, but should be invalid")
217259
}
218260
}
261+
262+
func assertIntEqualIsTrue(t *testing.T, a, b Int) {
263+
t.Helper()
264+
if !a.Equal(b) {
265+
t.Errorf("Equal() of Int{%v, Valid:%t} and Int{%v, Valid:%t} should return true", a.Int64, a.Valid, b.Int64, b.Valid)
266+
}
267+
}
268+
269+
func assertIntEqualIsFalse(t *testing.T, a, b Int) {
270+
t.Helper()
271+
if a.Equal(b) {
272+
t.Errorf("Equal() of Int{%v, Valid:%t} and Int{%v, Valid:%t} should return false", a.Int64, a.Valid, b.Int64, b.Valid)
273+
}
274+
}

zero/string.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ func StringFromPtr(s *string) String {
4242
return NewString(*s, *s != "")
4343
}
4444

45+
// ValueOrZero returns the inner value if valid, otherwise zero.
46+
func (s String) ValueOrZero() string {
47+
if !s.Valid {
48+
return ""
49+
}
50+
return s.String
51+
}
52+
4553
// UnmarshalJSON implements json.Unmarshaler.
4654
// It supports string and null input. Blank string input produces a null String.
4755
// It also supports unmarshalling a sql.NullString.
@@ -101,3 +109,8 @@ func (s String) Ptr() *string {
101109
func (s String) IsZero() bool {
102110
return !s.Valid || s.String == ""
103111
}
112+
113+
// Equal returns true if both strings have the same value or are both either null or empty.
114+
func (s String) Equal(other String) bool {
115+
return s.ValueOrZero() == other.ValueOrZero()
116+
}

zero/string_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,48 @@ func TestStringSetValid(t *testing.T) {
161161
assertStr(t, change, "SetValid()")
162162
}
163163

164+
func TestStringValueOrZero(t *testing.T) {
165+
valid := NewString("test", true)
166+
if valid.ValueOrZero() != "test" {
167+
t.Error("unexpected ValueOrZero", valid.ValueOrZero())
168+
}
169+
170+
invalid := NewString("test", false)
171+
if invalid.ValueOrZero() != "" {
172+
t.Error("unexpected ValueOrZero", invalid.ValueOrZero())
173+
}
174+
}
175+
176+
func TestStringEqual(t *testing.T) {
177+
str1 := NewString("foo", false)
178+
str2 := NewString("foo", false)
179+
assertStringEqualIsTrue(t, str1, str2)
180+
181+
str1 = NewString("foo", false)
182+
str2 = NewString("bar", false)
183+
assertStringEqualIsTrue(t, str1, str2)
184+
185+
str1 = NewString("foo", true)
186+
str2 = NewString("foo", true)
187+
assertStringEqualIsTrue(t, str1, str2)
188+
189+
str1 = NewString("", true)
190+
str2 = NewString("foo", false)
191+
assertStringEqualIsTrue(t, str1, str2)
192+
193+
str1 = NewString("foo", true)
194+
str2 = NewString("foo", false)
195+
assertStringEqualIsFalse(t, str1, str2)
196+
197+
str1 = NewString("foo", false)
198+
str2 = NewString("foo", true)
199+
assertStringEqualIsFalse(t, str1, str2)
200+
201+
str1 = NewString("foo", true)
202+
str2 = NewString("bar", true)
203+
assertStringEqualIsFalse(t, str1, str2)
204+
}
205+
164206
func maybePanic(err error) {
165207
if err != nil {
166208
panic(err)
@@ -187,3 +229,17 @@ func assertJSONEquals(t *testing.T, data []byte, cmp string, from string) {
187229
t.Errorf("bad %s data: %s ≠ %s\n", from, data, cmp)
188230
}
189231
}
232+
233+
func assertStringEqualIsTrue(t *testing.T, a, b String) {
234+
t.Helper()
235+
if !a.Equal(b) {
236+
t.Errorf("Equal() of String{\"%v\", Valid:%t} and String{\"%v\", Valid:%t} should return true", a.String, a.Valid, b.String, b.Valid)
237+
}
238+
}
239+
240+
func assertStringEqualIsFalse(t *testing.T, a, b String) {
241+
t.Helper()
242+
if a.Equal(b) {
243+
t.Errorf("Equal() of String{\"%v\", Valid:%t} and String{\"%v\", Valid:%t} should return false", a.String, a.Valid, b.String, b.Valid)
244+
}
245+
}

zero/time.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ func TimeFromPtr(t *time.Time) Time {
6363
return TimeFrom(*t)
6464
}
6565

66+
// ValueOrZero returns the inner value if valid, otherwise zero.
67+
func (t Time) ValueOrZero() time.Time {
68+
if !t.Valid {
69+
return time.Time{}
70+
}
71+
return t.Time
72+
}
73+
6674
// MarshalJSON implements json.Marshaler.
6775
// It will encode the zero value of time.Time
6876
// if this time is invalid.
@@ -148,3 +156,17 @@ func (t Time) Ptr() *time.Time {
148156
func (t Time) IsZero() bool {
149157
return !t.Valid || t.Time.IsZero()
150158
}
159+
160+
// Equal returns true if both Time objects encode the same time or are both are either null or zero.
161+
// Two times can be equal even if they are in different locations.
162+
// For example, 6:00 +0200 CEST and 4:00 UTC are Equal.
163+
func (t Time) Equal(other Time) bool {
164+
return t.ValueOrZero().Equal(other.ValueOrZero())
165+
}
166+
167+
// ExactEqual returns true if both Time objects are equal or both are either null or zero.
168+
// ExactEqual returns false for times that are in different locations or
169+
// have a different monotonic clock reading.
170+
func (t Time) ExactEqual(other Time) bool {
171+
return t.ValueOrZero() == other.ValueOrZero()
172+
}

0 commit comments

Comments
 (0)