@@ -37,8 +37,23 @@ import (
3737 "github.com/stretchr/testify/assert"
3838)
3939
40+ const decimalTypeCode byte = 0x17
41+
4042type bufWriterFn func (b * qdb.Buffer ) error
4143
44+ type fakeShopspringDecimal struct {
45+ coeff * big.Int
46+ exp int32
47+ }
48+
49+ func (f fakeShopspringDecimal ) Coefficient () * big.Int {
50+ return f .coeff
51+ }
52+
53+ func (f fakeShopspringDecimal ) Exponent () int32 {
54+ return f .exp
55+ }
56+
4257func newTestBuffer () qdb.Buffer {
4358 return qdb .NewBuffer (128 * 1024 , 1024 * 1024 , 127 )
4459}
@@ -481,6 +496,132 @@ func TestFloat64ColumnBinary(t *testing.T) {
481496 }
482497}
483498
499+ func TestDecimalColumnText (t * testing.T ) {
500+ prefix := []byte (testTable + " price==" )
501+ testCases := []struct {
502+ name string
503+ value any
504+ expected []byte
505+ }{
506+ {
507+ name : "positive" ,
508+ value : qdb .NewDecimalFromInt64 (12345 , 2 ),
509+ expected : append (prefix , 0x17 , 0x02 , 0x02 , 0x30 , 0x39 , 0x0A ),
510+ },
511+ {
512+ name : "negative" ,
513+ value : qdb .NewDecimal (big .NewInt (- 12345 ), 3 ),
514+ expected : append (prefix , 0x17 , 0x03 , 0x02 , 0xCF , 0xC7 , 0x0A ),
515+ },
516+ {
517+ name : "zero with scale" ,
518+ value : qdb .NewDecimalFromInt64 (0 , 4 ),
519+ expected : append (prefix , 0x17 , 0x04 , 0x01 , 0x0 , 0x0A ),
520+ },
521+ {
522+ name : "null decimal" ,
523+ value : qdb .NullDecimal (),
524+ expected : append (prefix , 0x17 , 0x0 , 0x0 , 0x0A ),
525+ },
526+ {
527+ name : "shopspring compatible" ,
528+ value : fakeShopspringDecimal {coeff : big .NewInt (123456 ), exp : - 4 },
529+ expected : append (prefix , 0x17 , 0x04 , 0x03 , 0x01 , 0xE2 , 0x40 , 0x0A ),
530+ },
531+ {
532+ name : "nil pointer treated as null" ,
533+ value : (* fakeShopspringDecimal )(nil ),
534+ expected : append (prefix , 0x17 , 0x0 , 0x0 , 0x0A ),
535+ },
536+ }
537+
538+ for _ , tc := range testCases {
539+ t .Run (tc .name , func (t * testing.T ) {
540+ buf := newTestBuffer ()
541+ err := buf .Table (testTable ).DecimalColumn ("price" , tc .value ).At (time.Time {}, false )
542+ assert .NoError (t , err )
543+ assert .Equal (t , tc .expected , buf .Messages ())
544+ })
545+ }
546+ }
547+
548+ func TestDecimalColumnStringValidation (t * testing.T ) {
549+ t .Run ("valid strings" , func (t * testing.T ) {
550+ testCases := []struct {
551+ name string
552+ value string
553+ expected string
554+ }{
555+ {"integer" , "123" , "123d" },
556+ {"decimal" , "123.450" , "123.450d" },
557+ {"negative" , "-0.001" , "-0.001d" },
558+ {"exponent positive" , "1.2e3" , "1.2e3d" },
559+ {"exponent negative" , "-4.5E-2" , "-4.5E-2d" },
560+ {"nan token" , "NaN" , "NaNd" },
561+ {"infinity token" , "Infinity" , "Infinityd" },
562+ }
563+ for _ , tc := range testCases {
564+ t .Run (tc .name , func (t * testing.T ) {
565+ buf := newTestBuffer ()
566+ err := buf .Table (testTable ).DecimalColumn ("price" , tc .value ).At (time.Time {}, false )
567+ assert .NoError (t , err )
568+ expected := []byte (testTable + " price=" + tc .expected + "\n " )
569+ assert .Equal (t , expected , buf .Messages ())
570+ })
571+ }
572+ })
573+
574+ t .Run ("invalid strings" , func (t * testing.T ) {
575+ testCases := []struct {
576+ name string
577+ value string
578+ }{
579+ {"empty" , "" },
580+ {"sign only" , "+" },
581+ {"double dot" , "12.3.4" },
582+ {"invalid char" , "12a3" },
583+ {"exponent missing mantissa" , "e10" },
584+ {"exponent no digits" , "1.2e" },
585+ {"exponent sign no digits" , "1.2e+" },
586+ }
587+ for _ , tc := range testCases {
588+ t .Run (tc .name , func (t * testing.T ) {
589+ buf := newTestBuffer ()
590+ err := buf .Table (testTable ).DecimalColumn ("price" , tc .value ).At (time.Time {}, false )
591+ assert .Error (t , err )
592+ assert .Contains (t , err .Error (), "decimal" )
593+ assert .Empty (t , buf .Messages ())
594+ })
595+ }
596+ })
597+ }
598+
599+ func TestDecimalColumnErrors (t * testing.T ) {
600+ t .Run ("invalid scale" , func (t * testing.T ) {
601+ buf := newTestBuffer ()
602+ dec := qdb .NewDecimalFromInt64 (1 , 100 )
603+ err := buf .Table (testTable ).DecimalColumn ("price" , dec ).At (time.Time {}, false )
604+ assert .ErrorContains (t , err , "decimal scale" )
605+ assert .Empty (t , buf .Messages ())
606+ })
607+
608+ t .Run ("overflow" , func (t * testing.T ) {
609+ buf := newTestBuffer ()
610+ bigVal := new (big.Int ).Lsh (big .NewInt (1 ), 2100 )
611+ dec := qdb .NewDecimal (bigVal , 0 )
612+ err := buf .Table (testTable ).DecimalColumn ("price" , dec ).At (time.Time {}, false )
613+ assert .ErrorContains (t , err , "exceeds 256-bit range" )
614+ assert .Empty (t , buf .Messages ())
615+ })
616+
617+ t .Run ("unsupported type" , func (t * testing.T ) {
618+ buf := newTestBuffer ()
619+ err := buf .Table (testTable ).DecimalColumn ("price" , struct {}{}).At (time.Time {}, false )
620+ assert .ErrorContains (t , err , "unsupported decimal column value type" )
621+ assert .Empty (t , buf .Messages ())
622+ })
623+ }
624+
484625func TestFloat64Array1DColumn (t * testing.T ) {
485626 testCases := []struct {
486627 name string
0 commit comments