| 
 | 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