Skip to content

Commit 19fa70d

Browse files
committed
Add new tests for integer and nullable number fields
1 parent 52f374f commit 19fa70d

File tree

1 file changed

+371
-0
lines changed

1 file changed

+371
-0
lines changed

tests/integer_number_test.go

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
/*
2+
** Copyright (c) 2025 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the Software
17+
** is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package tests
40+
41+
import (
42+
"database/sql"
43+
"math"
44+
"testing"
45+
"time"
46+
47+
"gorm.io/gorm"
48+
"gorm.io/gorm/clause"
49+
)
50+
51+
// IntegerTestModel covers basic integer data types.
52+
type IntegerTestModel struct {
53+
ID uint `gorm:"primaryKey;autoIncrement"`
54+
Int8Value int8 `gorm:"column:INT8_VALUE"`
55+
Int16Value int16 `gorm:"column:INT16_VALUE"`
56+
Int32Value int32 `gorm:"column:INT32_VALUE"`
57+
Int64Value int64 `gorm:"column:INT64_VALUE"`
58+
IntValue int `gorm:"column:INT_VALUE"`
59+
Uint8Value uint8 `gorm:"column:UINT8_VALUE"`
60+
Uint16Value uint16 `gorm:"column:UINT16_VALUE"`
61+
Uint32Value uint32 `gorm:"column:UINT32_VALUE"`
62+
Uint64Value uint64 `gorm:"column:UINT64_VALUE"`
63+
UintValue uint `gorm:"column:UINT_VALUE"`
64+
CreatedAt time.Time
65+
UpdatedAt time.Time
66+
}
67+
68+
// NullableIntegerTestModel tests nullable and optional integer types.
69+
type NullableIntegerTestModel struct {
70+
ID uint `gorm:"primaryKey;autoIncrement"`
71+
NullInt32 sql.NullInt32 `gorm:"column:NULL_INT32"`
72+
NullInt64 sql.NullInt64 `gorm:"column:NULL_INT64"`
73+
OptionalInt32 *int32 `gorm:"column:OPTIONAL_INT32"`
74+
OptionalInt64 *int64 `gorm:"column:OPTIONAL_INT64"`
75+
OptionalUint32 *uint32 `gorm:"column:OPTIONAL_UINT32"`
76+
OptionalUint64 *uint64 `gorm:"column:OPTIONAL_UINT64"`
77+
CreatedAt time.Time
78+
UpdatedAt time.Time
79+
}
80+
81+
// setupIntegerTestTables recreates the test tables before each test.
82+
func setupIntegerTestTables(t *testing.T) {
83+
t.Log("Setting up integer NUMBER test tables")
84+
85+
DB.Migrator().DropTable(&IntegerTestModel{})
86+
DB.Migrator().DropTable(&NullableIntegerTestModel{})
87+
88+
if err := DB.AutoMigrate(&IntegerTestModel{}, &NullableIntegerTestModel{}); err != nil {
89+
t.Fatalf("Failed to migrate integer test tables: %v", err)
90+
}
91+
92+
t.Log("Integer NUMBER test tables created successfully")
93+
}
94+
95+
func TestIntegerBasicCRUD(t *testing.T) {
96+
setupIntegerTestTables(t)
97+
98+
model := &IntegerTestModel{
99+
Int8Value: 127,
100+
Int16Value: 32767,
101+
Int32Value: 2147483647,
102+
Int64Value: 9223372036854775807,
103+
IntValue: 1000000,
104+
Uint8Value: 255,
105+
Uint16Value: 65535,
106+
Uint32Value: 4294967295,
107+
Uint64Value: 18446744073709551615,
108+
UintValue: 5000000,
109+
}
110+
111+
if err := DB.Create(model).Error; err != nil {
112+
t.Fatalf("Failed to create integer record: %v", err)
113+
}
114+
115+
if model.ID == 0 {
116+
t.Error("Expected auto-generated ID")
117+
}
118+
119+
var retrieved IntegerTestModel
120+
if err := DB.First(&retrieved, model.ID).Error; err != nil {
121+
t.Fatalf("Failed to retrieve integer record: %v", err)
122+
}
123+
124+
if retrieved.Int32Value != model.Int32Value {
125+
t.Errorf("Int32Value mismatch. Expected %d, got %d", model.Int32Value, retrieved.Int32Value)
126+
}
127+
128+
// Update
129+
newInt32Value := int32(42)
130+
if err := DB.Model(&retrieved).Update("INT32_VALUE", newInt32Value).Error; err != nil {
131+
t.Fatalf("Failed to update integer record: %v", err)
132+
}
133+
134+
var updated IntegerTestModel
135+
if err := DB.First(&updated, model.ID).Error; err != nil {
136+
t.Fatalf("Failed to retrieve updated integer record: %v", err)
137+
}
138+
if updated.Int32Value != newInt32Value {
139+
t.Errorf("Updated Int32Value mismatch. Expected %d, got %d", newInt32Value, updated.Int32Value)
140+
}
141+
142+
// Delete
143+
if err := DB.Delete(&updated).Error; err != nil {
144+
t.Fatalf("Failed to delete integer record: %v", err)
145+
}
146+
147+
var deleted IntegerTestModel
148+
err := DB.First(&deleted, model.ID).Error
149+
if err != gorm.ErrRecordNotFound {
150+
t.Errorf("Expected record not found, got: %v", err)
151+
}
152+
}
153+
154+
func TestIntegerEdgeCases(t *testing.T) {
155+
setupIntegerTestTables(t)
156+
157+
testCases := []struct {
158+
name string
159+
model IntegerTestModel
160+
}{
161+
{
162+
name: "Maximum positive values",
163+
model: IntegerTestModel{
164+
Int8Value: math.MaxInt8,
165+
Int16Value: math.MaxInt16,
166+
Int32Value: math.MaxInt32,
167+
Int64Value: math.MaxInt64,
168+
IntValue: math.MaxInt,
169+
Uint8Value: math.MaxUint8,
170+
Uint16Value: math.MaxUint16,
171+
Uint32Value: math.MaxUint32,
172+
Uint64Value: math.MaxUint64,
173+
UintValue: math.MaxUint,
174+
},
175+
},
176+
}
177+
178+
for _, tc := range testCases {
179+
t.Run(tc.name, func(t *testing.T) {
180+
if err := DB.Create(&tc.model).Error; err != nil {
181+
t.Fatalf("Failed to create record for %s: %v", tc.name, err)
182+
}
183+
184+
var retrieved IntegerTestModel
185+
if err := DB.First(&retrieved, tc.model.ID).Error; err != nil {
186+
t.Fatalf("Failed to retrieve record for %s: %v", tc.name, err)
187+
}
188+
189+
if retrieved.Int32Value != tc.model.Int32Value {
190+
t.Errorf("%s: Int32Value mismatch. Expected %d, got %d",
191+
tc.name, tc.model.Int32Value, retrieved.Int32Value)
192+
}
193+
})
194+
}
195+
}
196+
197+
func TestIntegerNullHandling(t *testing.T) {
198+
setupIntegerTestTables(t)
199+
200+
model1 := &NullableIntegerTestModel{}
201+
if err := DB.Create(model1).Error; err != nil {
202+
t.Fatalf("Failed to create record with NULL values: %v", err)
203+
}
204+
205+
var retrieved1 NullableIntegerTestModel
206+
if err := DB.First(&retrieved1, model1.ID).Error; err != nil {
207+
t.Fatalf("Failed to retrieve record with NULL values: %v", err)
208+
}
209+
210+
if retrieved1.NullInt32.Valid || retrieved1.NullInt64.Valid {
211+
t.Error("Expected NULL values to remain invalid")
212+
}
213+
214+
validInt32 := int32(42)
215+
validInt64 := int64(9999999)
216+
validUint32 := uint32(123)
217+
validUint64 := uint64(456789)
218+
219+
model2 := &NullableIntegerTestModel{
220+
NullInt32: sql.NullInt32{Int32: 100, Valid: true},
221+
NullInt64: sql.NullInt64{Int64: 200, Valid: true},
222+
OptionalInt32: &validInt32,
223+
OptionalInt64: &validInt64,
224+
OptionalUint32: &validUint32,
225+
OptionalUint64: &validUint64,
226+
}
227+
228+
if err := DB.Create(model2).Error; err != nil {
229+
t.Fatalf("Failed to create record with valid nullable values: %v", err)
230+
}
231+
232+
var retrieved2 NullableIntegerTestModel
233+
if err := DB.First(&retrieved2, model2.ID).Error; err != nil {
234+
t.Fatalf("Failed to retrieve record with valid nullable values: %v", err)
235+
}
236+
237+
if !retrieved2.NullInt32.Valid || retrieved2.NullInt32.Int32 != 100 {
238+
t.Errorf("Expected NullInt32=100, got %v", retrieved2.NullInt32)
239+
}
240+
if !retrieved2.NullInt64.Valid || retrieved2.NullInt64.Int64 != 200 {
241+
t.Errorf("Expected NullInt64=200, got %v", retrieved2.NullInt64)
242+
}
243+
244+
// Update NULL → value
245+
if err := DB.Model(&retrieved1).Updates(map[string]interface{}{
246+
"NULL_INT32": sql.NullInt32{Int32: 500, Valid: true},
247+
"NULL_INT64": sql.NullInt64{Int64: 600, Valid: true},
248+
}).Error; err != nil {
249+
t.Fatalf("Failed to update NULL to value: %v", err)
250+
}
251+
252+
var updated NullableIntegerTestModel
253+
if err := DB.First(&updated, model1.ID).Error; err != nil {
254+
t.Fatalf("Failed to retrieve updated record: %v", err)
255+
}
256+
257+
if !updated.NullInt32.Valid || updated.NullInt32.Int32 != 500 {
258+
t.Error("Failed to update NullInt32 from NULL to value")
259+
}
260+
261+
// Update value → NULL
262+
if err := DB.Model(&updated).Updates(map[string]interface{}{
263+
"NULL_INT32": sql.NullInt32{Valid: false},
264+
}).Error; err != nil {
265+
t.Fatalf("Failed to update value to NULL: %v", err)
266+
}
267+
}
268+
269+
func TestIntegerQueryOperations(t *testing.T) {
270+
setupIntegerTestTables(t)
271+
272+
data := []IntegerTestModel{
273+
{Int32Value: 10, Int64Value: 100, UintValue: 1},
274+
{Int32Value: 20, Int64Value: 200, UintValue: 2},
275+
{Int32Value: 30, Int64Value: 300, UintValue: 3},
276+
{Int32Value: 40, Int64Value: 400, UintValue: 4},
277+
{Int32Value: 50, Int64Value: 500, UintValue: 5},
278+
}
279+
280+
if err := DB.Create(&data).Error; err != nil {
281+
t.Fatalf("Failed to insert test data: %v", err)
282+
}
283+
284+
var result []IntegerTestModel
285+
if err := DB.Where("INT32_VALUE = ?", 30).Find(&result).Error; err != nil {
286+
t.Fatalf("Failed to query with equals: %v", err)
287+
}
288+
if len(result) != 1 {
289+
t.Errorf("Expected 1 record, got %d", len(result))
290+
}
291+
292+
var maxInt32 int32
293+
if err := DB.Model(&IntegerTestModel{}).Select("MAX(INT32_VALUE)").Scan(&maxInt32).Error; err != nil {
294+
t.Fatalf("Failed to query MAX: %v", err)
295+
}
296+
if maxInt32 != 50 {
297+
t.Errorf("Expected MAX(INT32_VALUE)=50, got %d", maxInt32)
298+
}
299+
}
300+
301+
func TestIntegerOverflowHandling(t *testing.T) {
302+
setupIntegerTestTables(t)
303+
304+
t.Run("Int8 overflow", func(t *testing.T) {
305+
model := &IntegerTestModel{Int8Value: math.MaxInt8}
306+
if err := DB.Create(model).Error; err != nil {
307+
t.Fatalf("Failed to create record: %v", err)
308+
}
309+
310+
err := DB.Model(&IntegerTestModel{}).
311+
Where("ID = ?", model.ID).
312+
Update("INT8_VALUE", gorm.Expr("INT8_VALUE + ?", 1)).Error
313+
314+
if err != nil {
315+
t.Logf("Overflow prevented as expected: %v", err)
316+
} else {
317+
var updated IntegerTestModel
318+
DB.First(&updated, model.ID)
319+
t.Logf("Post-overflow value: %d", updated.Int8Value)
320+
}
321+
})
322+
323+
t.Run("Uint64 maximum", func(t *testing.T) {
324+
model := &IntegerTestModel{Uint64Value: math.MaxUint64}
325+
if err := DB.Create(model).Error; err != nil {
326+
t.Fatalf("Failed to create record: %v", err)
327+
}
328+
329+
var retrieved IntegerTestModel
330+
if err := DB.First(&retrieved, model.ID).Error; err != nil {
331+
t.Fatalf("Failed to retrieve record: %v", err)
332+
}
333+
334+
if retrieved.Uint64Value != math.MaxUint64 {
335+
t.Errorf("Expected %d, got %d", uint64(math.MaxUint64), retrieved.Uint64Value)
336+
}
337+
})
338+
}
339+
340+
func TestIntegerWithReturning(t *testing.T) {
341+
setupIntegerTestTables(t)
342+
343+
models := []IntegerTestModel{
344+
{Int32Value: 111, Int64Value: 1111},
345+
{Int32Value: 222, Int64Value: 2222},
346+
{Int32Value: 333, Int64Value: 3333},
347+
}
348+
349+
if err := DB.Create(&models).Error; err != nil {
350+
t.Fatalf("Failed to create records: %v", err)
351+
}
352+
353+
for i, m := range models {
354+
if m.ID == 0 {
355+
t.Errorf("Record %d: expected ID populated, got 0", i)
356+
}
357+
}
358+
359+
var updated []IntegerTestModel
360+
err := DB.Model(&IntegerTestModel{}).
361+
Clauses(clause.Returning{}).
362+
Where("INT32_VALUE > ?", 200).
363+
Update("INT32_VALUE", gorm.Expr("INT32_VALUE + ?", 1000)).
364+
Find(&updated).Error
365+
366+
if err != nil {
367+
t.Logf("UPDATE with RETURNING not fully supported: %v", err)
368+
} else {
369+
t.Logf("Updated %d records via RETURNING", len(updated))
370+
}
371+
}

0 commit comments

Comments
 (0)