Skip to content

Commit 36ebd33

Browse files
committed
Fix: support money/smallmoney types for bulkcopy
1 parent 1f11602 commit 36ebd33

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

bulkcopy.go

+68-1
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,74 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error)
542542
err = fmt.Errorf("mssql: invalid type for time column: %T %s", val, val)
543543
return
544544
}
545-
// case typeMoney, typeMoney4, typeMoneyN:
545+
case typeMoney, typeMoney4, typeMoneyN:
546+
var intvalue int64
547+
548+
string2Int64 := func(str string) (int64, error) {
549+
// Split on decimal point
550+
parts := strings.Split(str, ".")
551+
if len(parts) > 2 {
552+
return 0, fmt.Errorf("invalid money format")
553+
}
554+
555+
// Handle the decimal places
556+
if len(parts) == 2 {
557+
// Pad or truncate decimal places to exactly 4 digits
558+
decimal := parts[1]
559+
if len(decimal) > 4 {
560+
decimal = decimal[:4] // truncate to 4 decimal places
561+
} else {
562+
decimal = decimal + strings.Repeat("0", 4-len(decimal)) // pad with zeros
563+
}
564+
str = parts[0] + decimal
565+
} else {
566+
// No decimal point, append 4 zeros
567+
str = str + "0000"
568+
}
569+
570+
return strconv.ParseInt(str, 10, 64)
571+
}
572+
573+
switch val := val.(type) {
574+
case int:
575+
intvalue = int64(val)
576+
case int64:
577+
intvalue = val
578+
case []byte:
579+
intvalue, err = string2Int64(string(val))
580+
if err != nil {
581+
return res, fmt.Errorf("mssql: invalid money string format: %s", string(val))
582+
}
583+
case string:
584+
intvalue, err = string2Int64(val)
585+
if err != nil {
586+
return res, fmt.Errorf("mssql: invalid money string format: %s", val)
587+
}
588+
default:
589+
err = fmt.Errorf("mssql: invalid type for money column: %T %s", val, val)
590+
return
591+
}
592+
593+
res.buffer = make([]byte, res.ti.Size)
594+
595+
// smallmoney is a 4-byte integer stored as value * 10^4.
596+
// money is an 8-byte integer stored as value * 10^4.
597+
//
598+
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/1266679d-cd6e-492a-b2b2-3a9ba004196d
599+
switch col.ti.Size {
600+
case 4:
601+
binary.LittleEndian.PutUint32(res.buffer, uint32(intvalue))
602+
case 8:
603+
// The 8-byte signed integer is represented in the following sequence:
604+
// - One 4-byte integer that represents the more significant half.
605+
// - One 4-byte integer that represents the less significant half.
606+
//
607+
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/1266679d-cd6e-492a-b2b2-3a9ba004196d
608+
binary.LittleEndian.PutUint32(res.buffer[0:4], uint32(intvalue>>32))
609+
binary.LittleEndian.PutUint32(res.buffer[4:8], uint32(intvalue&0xFFFFFFFF))
610+
default:
611+
err = fmt.Errorf("mssql: invalid size of column %d", col.ti.Size)
612+
}
546613
case typeDecimal, typeDecimalN, typeNumeric, typeNumericN:
547614
prec := col.ti.Prec
548615
scale := col.ti.Scale

bulkcopy_test.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ func testBulkcopy(t *testing.T, guidConversion bool) {
176176
{"test_nullint32", sql.NullInt32{2147483647, true}, 2147483647},
177177
{"test_nullint16", sql.NullInt16{32767, true}, 32767},
178178
{"test_nulltime", sql.NullTime{time.Date(2010, 11, 12, 13, 14, 15, 120000000, time.UTC), true}, time.Date(2010, 11, 12, 13, 14, 15, 120000000, time.UTC)},
179-
// {"test_smallmoney", 1234.56, nil},
180-
// {"test_money", 1234.56, nil},
179+
{"test_smallmoney", []byte("1234.5600"), nil},
180+
{"test_smallmoneyn", nil, nil},
181+
{"test_money", []byte("1234.5600"), nil},
182+
{"test_moneyn", nil, nil},
181183
{"test_decimal_18_0", 1234.0001, "1234"},
182184
{"test_decimal_9_2", -1234.560001, "-1234.56"},
183185
{"test_decimal_20_0", 1234, "1234"},
@@ -407,8 +409,10 @@ func setupTable(ctx context.Context, t *testing.T, conn *sql.Conn, tableName str
407409
[test_date_2] [date] NULL,
408410
[test_time] [time](7) NULL,
409411
[test_time_2] [time](7) NULL,
410-
[test_smallmoney] [smallmoney] NULL,
411-
[test_money] [money] NULL,
412+
[test_smallmoney] [smallmoney] NOT NULL,
413+
[test_smallmoneyn] [smallmoney] NULL,
414+
[test_money] [money] NOT NULL,
415+
[test_moneyn] [money] NULL,
412416
[test_tinyint] [tinyint] NULL,
413417
[test_smallint] [smallint] NOT NULL,
414418
[test_smallintn] [smallint] NULL,

0 commit comments

Comments
 (0)