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+ )
46+
47+ const (
48+ // doubleComparisonEpsilon is used for direct value comparison
49+ doubleComparisonEpsilon = 1e-15
50+ // doubleAggregateEpsilon is used for aggregate operations (SUM, AVG, etc.)
51+ doubleAggregateEpsilon = 1e-10
52+ )
53+
54+ type BinaryDoubleTest struct {
55+ ID uint `gorm:"column:ID;primaryKey"`
56+ DoubleValue float64 `gorm:"column:DOUBLE_VALUE;type:BINARY_DOUBLE"`
57+ NullableDouble * float64 `gorm:"column:NULLABLE_DOUBLE;type:BINARY_DOUBLE"`
58+ SQLNullFloat sql.NullFloat64 `gorm:"column:SQL_NULL_FLOAT;type:BINARY_DOUBLE"`
59+ }
60+
61+ func (BinaryDoubleTest ) TableName () string {
62+ return "BINARY_DOUBLE_TESTS"
63+ }
64+
65+ func TestBinaryDoubleBasicCRUD (t * testing.T ) {
66+ DB .Migrator ().DropTable (& BinaryDoubleTest {})
67+ if err := DB .AutoMigrate (& BinaryDoubleTest {}); err != nil {
68+ t .Fatalf ("failed to migrate: %v" , err )
69+ }
70+
71+ // CREATE - Insert basic double value
72+ testValue := 3.141592653589793
73+ bd1 := BinaryDoubleTest {DoubleValue : testValue }
74+ if err := DB .Create (& bd1 ).Error ; err != nil {
75+ t .Fatalf ("insert failed: %v" , err )
76+ }
77+
78+ // READ - Fetch and verify
79+ var got BinaryDoubleTest
80+ if err := DB .First (& got , bd1 .ID ).Error ; err != nil {
81+ t .Fatalf ("fetch failed: %v" , err )
82+ }
83+
84+ if math .Abs (got .DoubleValue - testValue ) > doubleComparisonEpsilon {
85+ t .Errorf ("expected %v, got %v" , testValue , got .DoubleValue )
86+ }
87+
88+ // UPDATE - Modify the value
89+ newValue := 2.718281828459045
90+ if err := DB .Model (& got ).Update ("DoubleValue" , newValue ).Error ; err != nil {
91+ t .Fatalf ("update failed: %v" , err )
92+ }
93+
94+ // Verify update
95+ var updated BinaryDoubleTest
96+ if err := DB .First (& updated , bd1 .ID ).Error ; err != nil {
97+ t .Fatalf ("fetch after update failed: %v" , err )
98+ }
99+ if math .Abs (updated .DoubleValue - newValue ) > doubleComparisonEpsilon {
100+ t .Errorf ("expected %v after update, got %v" , newValue , updated .DoubleValue )
101+ }
102+
103+ // DELETE
104+ if err := DB .Delete (& updated ).Error ; err != nil {
105+ t .Fatalf ("delete failed: %v" , err )
106+ }
107+
108+ // Verify deletion
109+ var deleted BinaryDoubleTest
110+ err := DB .First (& deleted , bd1 .ID ).Error
111+ if err == nil {
112+ t .Error ("expected record to be deleted" )
113+ }
114+ }
115+
116+ func TestBinaryDoubleSpecialValues (t * testing.T ) {
117+ DB .Migrator ().DropTable (& BinaryDoubleTest {})
118+ DB .AutoMigrate (& BinaryDoubleTest {})
119+
120+ testCases := []struct {
121+ name string
122+ value float64
123+ }{
124+ {"Positive Infinity" , math .Inf (1 )},
125+ {"Negative Infinity" , math .Inf (- 1 )},
126+ {"NaN" , math .NaN ()},
127+ {"Max Float64" , math .MaxFloat64 },
128+ {"Min Float64" , - math .MaxFloat64 },
129+ {"Smallest Positive" , math .SmallestNonzeroFloat64 },
130+ {"Zero" , 0.0 },
131+ {"Negative Zero" , - 0.0 },
132+ }
133+
134+ for _ , tc := range testCases {
135+ t .Run (tc .name , func (t * testing.T ) {
136+ bd := BinaryDoubleTest {DoubleValue : tc .value }
137+ if err := DB .Create (& bd ).Error ; err != nil {
138+ t .Fatalf ("failed to insert %s: %v" , tc .name , err )
139+ }
140+
141+ var got BinaryDoubleTest
142+ if err := DB .First (& got , bd .ID ).Error ; err != nil {
143+ t .Fatalf ("failed to fetch %s: %v" , tc .name , err )
144+ }
145+
146+ if math .IsNaN (tc .value ) {
147+ if ! math .IsNaN (got .DoubleValue ) {
148+ t .Errorf ("expected NaN, got %v" , got .DoubleValue )
149+ }
150+ } else if math .IsInf (tc .value , 1 ) {
151+ if ! math .IsInf (got .DoubleValue , 1 ) {
152+ t .Errorf ("expected +Inf, got %v" , got .DoubleValue )
153+ }
154+ } else if math .IsInf (tc .value , - 1 ) {
155+ if ! math .IsInf (got .DoubleValue , - 1 ) {
156+ t .Errorf ("expected -Inf, got %v" , got .DoubleValue )
157+ }
158+ } else if tc .value == 0.0 || tc .value == - 0.0 {
159+ if got .DoubleValue != 0.0 {
160+ t .Errorf ("expected 0, got %v" , got .DoubleValue )
161+ }
162+ } else {
163+ if math .Abs (got .DoubleValue - tc .value ) > doubleComparisonEpsilon {
164+ t .Errorf ("expected %v, got %v" , tc .value , got .DoubleValue )
165+ }
166+ }
167+ })
168+ }
169+ }
170+
171+ func TestBinaryDoubleNullableColumn (t * testing.T ) {
172+ DB .Migrator ().DropTable (& BinaryDoubleTest {})
173+ DB .AutoMigrate (& BinaryDoubleTest {})
174+
175+ // Test NULL value
176+ bd1 := BinaryDoubleTest {
177+ DoubleValue : 1.23 ,
178+ NullableDouble : nil ,
179+ }
180+ if err := DB .Create (& bd1 ).Error ; err != nil {
181+ t .Fatalf ("failed to insert with NULL: %v" , err )
182+ }
183+
184+ var got1 BinaryDoubleTest
185+ if err := DB .First (& got1 , bd1 .ID ).Error ; err != nil {
186+ t .Fatal (err )
187+ }
188+ if got1 .NullableDouble != nil {
189+ t .Errorf ("expected NULL, got %v" , * got1 .NullableDouble )
190+ }
191+
192+ // Test with non-NULL value
193+ val := 456.789
194+ bd2 := BinaryDoubleTest {
195+ DoubleValue : 2.34 ,
196+ NullableDouble : & val ,
197+ }
198+ if err := DB .Create (& bd2 ).Error ; err != nil {
199+ t .Fatalf ("failed to insert with value: %v" , err )
200+ }
201+
202+ var got2 BinaryDoubleTest
203+ if err := DB .First (& got2 , bd2 .ID ).Error ; err != nil {
204+ t .Fatal (err )
205+ }
206+ if got2 .NullableDouble == nil {
207+ t .Error ("expected non-NULL value" )
208+ } else if math .Abs (* got2 .NullableDouble - val ) > doubleComparisonEpsilon {
209+ t .Errorf ("expected %v, got %v" , val , * got2 .NullableDouble )
210+ }
211+
212+ // Update to NULL
213+ if err := DB .Model (& got2 ).Update ("NullableDouble" , nil ).Error ; err != nil {
214+ t .Fatalf ("failed to update to NULL: %v" , err )
215+ }
216+
217+ var got3 BinaryDoubleTest
218+ if err := DB .First (& got3 , bd2 .ID ).Error ; err != nil {
219+ t .Fatal (err )
220+ }
221+ if got3 .NullableDouble != nil {
222+ t .Errorf ("expected NULL after update, got %v" , * got3 .NullableDouble )
223+ }
224+ }
225+
226+ func TestBinaryDoubleSQLNullFloat (t * testing.T ) {
227+ DB .Migrator ().DropTable (& BinaryDoubleTest {})
228+ DB .AutoMigrate (& BinaryDoubleTest {})
229+
230+ // Test with valid value
231+ bd1 := BinaryDoubleTest {
232+ DoubleValue : 1.0 ,
233+ SQLNullFloat : sql.NullFloat64 {Float64 : 123.456 , Valid : true },
234+ }
235+ if err := DB .Create (& bd1 ).Error ; err != nil {
236+ t .Fatalf ("failed to create with sql.NullFloat64: %v" , err )
237+ }
238+
239+ var got1 BinaryDoubleTest
240+ if err := DB .First (& got1 , bd1 .ID ).Error ; err != nil {
241+ t .Fatal (err )
242+ }
243+ if ! got1 .SQLNullFloat .Valid {
244+ t .Error ("expected Valid to be true" )
245+ }
246+ if math .Abs (got1 .SQLNullFloat .Float64 - 123.456 ) > doubleComparisonEpsilon {
247+ t .Errorf ("expected 123.456, got %v" , got1 .SQLNullFloat .Float64 )
248+ }
249+
250+ // Test with invalid (NULL) value
251+ bd2 := BinaryDoubleTest {
252+ DoubleValue : 2.0 ,
253+ SQLNullFloat : sql.NullFloat64 {Valid : false },
254+ }
255+ if err := DB .Create (& bd2 ).Error ; err != nil {
256+ t .Fatalf ("failed to create with NULL sql.NullFloat64: %v" , err )
257+ }
258+
259+ var got2 BinaryDoubleTest
260+ if err := DB .First (& got2 , bd2 .ID ).Error ; err != nil {
261+ t .Fatal (err )
262+ }
263+ if got2 .SQLNullFloat .Valid {
264+ t .Error ("expected Valid to be false for NULL" )
265+ }
266+ }
267+
268+ func TestBinaryDoubleArithmeticOperations (t * testing.T ) {
269+ DB .Migrator ().DropTable (& BinaryDoubleTest {})
270+ DB .AutoMigrate (& BinaryDoubleTest {})
271+
272+ // Insert test data
273+ values := []float64 {10.5 , 20.3 , 30.7 , 40.1 , 50.9 }
274+ for _ , v := range values {
275+ bd := BinaryDoubleTest {DoubleValue : v }
276+ if err := DB .Create (& bd ).Error ; err != nil {
277+ t .Fatalf ("failed to insert %v: %v" , v , err )
278+ }
279+ }
280+
281+ // Test SUM
282+ var sum float64
283+ if err := DB .Model (& BinaryDoubleTest {}).Select ("SUM(DOUBLE_VALUE)" ).Scan (& sum ).Error ; err != nil {
284+ t .Fatalf ("failed to calculate SUM: %v" , err )
285+ }
286+ expectedSum := 10.5 + 20.3 + 30.7 + 40.1 + 50.9
287+ if math .Abs (sum - expectedSum ) > doubleAggregateEpsilon {
288+ t .Errorf ("expected sum %v, got %v" , expectedSum , sum )
289+ }
290+
291+ // Test AVG
292+ var avg float64
293+ if err := DB .Model (& BinaryDoubleTest {}).Select ("AVG(DOUBLE_VALUE)" ).Scan (& avg ).Error ; err != nil {
294+ t .Fatalf ("failed to calculate AVG: %v" , err )
295+ }
296+ expectedAvg := expectedSum / 5
297+ if math .Abs (avg - expectedAvg ) > doubleAggregateEpsilon {
298+ t .Errorf ("expected avg %v, got %v" , expectedAvg , avg )
299+ }
300+
301+ // Test MIN/MAX
302+ var min , max float64
303+ if err := DB .Model (& BinaryDoubleTest {}).Select ("MIN(DOUBLE_VALUE)" ).Scan (& min ).Error ; err != nil {
304+ t .Fatalf ("failed to calculate MIN: %v" , err )
305+ }
306+ if err := DB .Model (& BinaryDoubleTest {}).Select ("MAX(DOUBLE_VALUE)" ).Scan (& max ).Error ; err != nil {
307+ t .Fatalf ("failed to calculate MAX: %v" , err )
308+ }
309+ if math .Abs (min - 10.5 ) > doubleAggregateEpsilon {
310+ t .Errorf ("expected min 10.5, got %v" , min )
311+ }
312+ if math .Abs (max - 50.9 ) > doubleAggregateEpsilon {
313+ t .Errorf ("expected max 50.9, got %v" , max )
314+ }
315+ }
316+
317+ func TestBinaryDoubleRangeQueries (t * testing.T ) {
318+ DB .Migrator ().DropTable (& BinaryDoubleTest {})
319+ DB .AutoMigrate (& BinaryDoubleTest {})
320+
321+ // Insert test data with various ranges
322+ testData := []float64 {- 100.5 , - 50.0 , 0.0 , 25.5 , 50.0 , 75.75 , 100.0 , 150.25 }
323+ for _ , v := range testData {
324+ bd := BinaryDoubleTest {DoubleValue : v }
325+ if err := DB .Create (& bd ).Error ; err != nil {
326+ t .Fatalf ("failed to insert %v: %v" , v , err )
327+ }
328+ }
329+
330+ // Test BETWEEN query
331+ var results []BinaryDoubleTest
332+ if err := DB .Where ("DOUBLE_VALUE BETWEEN ? AND ?" , 0.0 , 100.0 ).Find (& results ).Error ; err != nil {
333+ t .Fatalf ("BETWEEN query failed: %v" , err )
334+ }
335+ if len (results ) != 5 {
336+ t .Errorf ("expected 5 results, got %d" , len (results ))
337+ }
338+
339+ // Test greater than query
340+ var gtResults []BinaryDoubleTest
341+ if err := DB .Where ("DOUBLE_VALUE > ?" , 50.0 ).Find (& gtResults ).Error ; err != nil {
342+ t .Fatalf ("> query failed: %v" , err )
343+ }
344+ if len (gtResults ) != 3 {
345+ t .Errorf ("expected 3 results for > 50.0, got %d" , len (gtResults ))
346+ }
347+
348+ // Test less than or equal query
349+ var lteResults []BinaryDoubleTest
350+ if err := DB .Where ("DOUBLE_VALUE <= ?" , 0.0 ).Find (& lteResults ).Error ; err != nil {
351+ t .Fatalf ("<= query failed: %v" , err )
352+ }
353+ if len (lteResults ) != 3 {
354+ t .Errorf ("expected 3 results for <= 0.0, got %d" , len (lteResults ))
355+ }
356+
357+ // Test equality with floating point
358+ var eqResults []BinaryDoubleTest
359+ if err := DB .Where ("DOUBLE_VALUE = ?" , 25.5 ).Find (& eqResults ).Error ; err != nil {
360+ t .Fatalf ("= query failed: %v" , err )
361+ }
362+ if len (eqResults ) != 1 {
363+ t .Errorf ("expected 1 result for = 25.5, got %d" , len (eqResults ))
364+ }
365+ }
0 commit comments