diff --git a/internal/contentdata/repository.go b/internal/contentdata/repository.go index 7be8c3bc7..af39e360b 100644 --- a/internal/contentdata/repository.go +++ b/internal/contentdata/repository.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "math/big" "reflect" "strings" @@ -400,7 +401,9 @@ func (r *Repository) AddTableData(ctx context.Context, tx *connection.Tx, projec for _, column := range columns { if value, found := data[column.Name]; found { isTimestampColumn := column.Type == types.TIMESTAMP + isBigNumeric := column.Type == types.BIGNUMERIC inputString, isInputString := value.(string) + inputBytes, isInputBytes := value.([]uint8) if isInputString && isTimestampColumn { parsedTimestamp, err := zetasqlite.TimeFromTimestampValue(inputString) @@ -411,6 +414,14 @@ func (r *Repository) AddTableData(ctx context.Context, tx *connection.Tx, projec } } + if isBigNumeric && isInputBytes { + parsedString, err := BigNumericFromBytes(inputBytes) + if err == nil { + values = append(values, parsedString) + continue + } + } + values = append(values, value) } else { values = append(values, nil) @@ -425,6 +436,34 @@ func (r *Repository) AddTableData(ctx context.Context, tx *connection.Tx, projec return nil } +func BigNumericFromBytes(v []uint8) (string, error) { + if len(v) == 0 { + return "", fmt.Errorf("invalid input: empty byte array") + } + bytes := make([]byte, len(v)) + for i := 0; i < len(v); i++ { + bytes[i] = v[len(v)-1-i] + } + negative := bytes[0]&0x80 != 0 + n := new(big.Int) + if negative { + for i := 0; i < len(bytes); i++ { + bytes[i] = ^bytes[i] + } + n.SetBytes(bytes) + n.Add(n, big.NewInt(1)) + n.Neg(n) + } else { + n.SetBytes(bytes) + } + scaleFactor := new(big.Rat) + scaleFactor.SetString("100000000000000000000000000000000000000") + result := new(big.Rat).Quo(new(big.Rat).SetInt(n), scaleFactor).FloatString(10) + result = strings.TrimRight(result, "0") + result = strings.TrimRight(result, ".") + return result, nil +} + func (r *Repository) DeleteTables(ctx context.Context, tx *connection.Tx, projectID, datasetID string, tableIDs []string) error { tx.SetProjectAndDataset(projectID, datasetID) if err := tx.ContentRepoMode(); err != nil { diff --git a/internal/contentdata/repository_test.go b/internal/contentdata/repository_test.go new file mode 100644 index 000000000..a6514eb77 --- /dev/null +++ b/internal/contentdata/repository_test.go @@ -0,0 +1,28 @@ +package contentdata + +import "testing" + +func TestBigNumericFromBytes(t *testing.T) { + tests := []struct { + input []uint8 + expected string + }{ + {[]uint8{0, 0, 0, 0, 0, 54, 106, 188, 74, 131, 144, 97, 94, 142, 92, 32, 218, 254}, "-1000"}, + {[]uint8{0, 0, 0, 0, 0, 202, 149, 67, 181, 124, 111, 158, 161, 113, 163, 223, 37, 1}, "1000"}, + {[]uint8{0, 0, 0, 192, 5, 115, 196, 212, 216, 175, 10, 207, 166, 15, 226, 107, 175, 83, 170, 215, 0}, "12312314324.23423423"}, + {[]uint8{0, 0, 0, 0, 124, 113, 12, 44, 225, 164, 189, 0, 214, 22, 87, 236, 0}, "3.1415"}, + {[]uint8{0, 0, 0, 0, 224, 88, 126, 10, 83, 11, 97, 48, 185, 58, 193, 82}, "1.1"}, + {[]uint8{0, 0, 0, 0, 64, 34, 138, 9, 122, 196, 134, 90, 168, 76, 59, 75}, "1"}, + {[]uint8{0x00}, "0"}, + } + + for _, test := range tests { + result, err := BigNumericFromBytes(test.input) + if err != nil { + t.Errorf("BigNumericFromBytes(%v) returned error: %v", test.input, err) + } + if result != test.expected { + t.Errorf("BigNumericFromBytes(%v) = %v; want %v", test.input, result, test.expected) + } + } +}