Repository navigation
Expand file tree
/
Copy pathencoding.go
More file actions
465 lines (405 loc) · 13.5 KB
/
Copy pathencoding.go
File metadata and controls
465 lines (405 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
package decimal
import (
"database/sql/driver"
"encoding/binary"
"fmt"
"math"
"math/big"
"reflect"
)
// UnmarshalJSON implements the json.Unmarshaler interface.
func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
if string(decimalBytes) == "null" {
return nil
}
decimal, err := NewFromString(unquoteIfQuoted(string(decimalBytes)))
*d = decimal
if err != nil {
return fmt.Errorf("error decoding string '%s': %s", string(decimalBytes), err)
}
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (d Decimal) MarshalJSON() ([]byte, error) {
str := d.String()
if MarshalJSONWithoutQuotes {
return []byte(str), nil
}
b := make([]byte, 0, len(str)+2)
b = append(b, '"')
b = append(b, str...)
return append(b, '"'), nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. As a string representation
// is already used when encoding to text, this method stores that string as []byte
func (d *Decimal) UnmarshalBinary(data []byte) error {
// Verify we have at least 4 bytes for the exponent. The GOB encoded value
// may be empty.
if len(data) < 4 {
return fmt.Errorf("error decoding binary %v: expected at least 4 bytes, got %d", data, len(data))
}
// Extract the exponent
exp := int32(binary.BigEndian.Uint32(data[:4]))
if int64(exp) > int64(MaxDecodeExponent) || int64(exp) < -int64(MaxDecodeExponent) {
return fmt.Errorf("error decoding binary: exponent %d exceeds MaxDecodeExponent (%d)", exp, MaxDecodeExponent)
}
d.exp = exp
// Extract the value
d.value = new(big.Int)
if err := d.value.GobDecode(data[4:]); err != nil {
return fmt.Errorf("error decoding binary %v: %s", data, err)
}
return nil
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (d Decimal) MarshalBinary() (data []byte, err error) {
// exp is written first, but encode value first to know output size
var valueData []byte
if valueData, err = d.getValue().GobEncode(); err != nil {
return nil, err
}
// Write the exponent in front, since it's a fixed size
expData := make([]byte, 4, len(valueData)+4)
binary.BigEndian.PutUint32(expData, uint32(d.exp))
// Return the byte array
return append(expData, valueData...), nil
}
// Scan implements the sql.Scanner interface for database deserialization.
func (d *Decimal) Scan(value interface{}) error {
// first try to see if the data is stored in database as a Numeric datatype
switch v := value.(type) {
case nil:
return fmt.Errorf("can't scan NULL into a Decimal, use NullDecimal or *Decimal")
case float32:
if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
return fmt.Errorf("could not convert value '%v' to decimal", v)
}
*d = NewFromFloat32(v)
return nil
case float64:
// numeric in sqlite3 sends us float64
if math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Errorf("could not convert value '%v' to decimal", v)
}
*d = NewFromFloat(v)
return nil
case int64:
// at least in sqlite3 when the value is 0 in db, the data is sent
// to us as an int64 instead of a float64 ...
*d = New(v, 0)
return nil
case uint64:
// while clickhouse may send 0 in db as uint64
*d = NewFromUint64(v)
return nil
case int, int8, int16, int32:
// clickhouse and test mocks send the other integer sizes
*d = New(reflect.ValueOf(v).Int(), 0)
return nil
case uint, uint8, uint16, uint32:
*d = NewFromUint64(reflect.ValueOf(v).Uint())
return nil
case big.Int:
// clickhouse sends Int128, UInt128 and Int256 through database/sql as big.Int
*d = NewFromBigInt(&v, 0)
return nil
case *big.Int:
if v != nil {
*d = NewFromBigInt(v, 0)
return nil
}
case Decimal:
// clickhouse sends Decimal columns through its native API as Decimal
*d = v
return nil
case string:
var err error
*d, err = NewFromString(unquoteIfQuoted(v))
return err
case []byte:
var err error
*d, err = NewFromString(unquoteIfQuoted(string(v)))
return err
}
return fmt.Errorf("could not convert value '%+v' to any known type", value)
}
// Value implements the driver.Valuer interface for database serialization.
func (d Decimal) Value() (driver.Value, error) {
return d.String(), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for XML
// deserialization.
func (d *Decimal) UnmarshalText(text []byte) error {
str := string(text)
dec, err := NewFromString(str)
*d = dec
if err != nil {
return fmt.Errorf("error decoding string '%s': %s", str, err)
}
return nil
}
// MarshalText implements the encoding.TextMarshaler interface for XML
// serialization.
func (d Decimal) MarshalText() (text []byte, err error) {
return []byte(d.String()), nil
}
// GobEncode implements the gob.GobEncoder interface for gob serialization.
func (d Decimal) GobEncode() ([]byte, error) {
return d.MarshalBinary()
}
// GobDecode implements the gob.GobDecoder interface for gob serialization.
func (d *Decimal) GobDecode(data []byte) error {
return d.UnmarshalBinary(data)
}
// DecodeSpanner decodes a Spanner value into a Decimal
func (d *Decimal) DecodeSpanner(val interface{}) error {
return d.Scan(val)
}
// EncodeSpanner encodes a Decimal into a Spanner value
func (d Decimal) EncodeSpanner() (interface{}, error) {
return d.String(), nil
}
// BSON type numbers, see https://bsonspec.org/spec.html
const (
bsonTypeDouble byte = 0x01
bsonTypeString byte = 0x02
bsonTypeUndefined byte = 0x06
bsonTypeNull byte = 0x0A
bsonTypeInt32 byte = 0x10
bsonTypeInt64 byte = 0x12
bsonTypeDecimal128 byte = 0x13
)
// MarshalBSONValue implements the bson.ValueMarshaler interface of the MongoDB Go driver v2. It stores
// the decimal as a BSON Decimal128, MongoDB's decimal type, which holds 34 significant digits and
// exponents from -6176 to 6111. Trailing zeros move between the coefficient and the exponent to fit, so
// Exponent() can change in a round trip but the value can't. Decimals that still don't fit return an
// error instead of being rounded.
//
// The v1 driver declares this interface with its own bsontype.Type, so it doesn't call this method.
func (d Decimal) MarshalBSONValue() (byte, []byte, error) {
coef := new(big.Int).Abs(d.getValue())
exp := d.exp
// Zero fits at any exponent, and the loops below could take 2^31 steps to get there.
if coef.Sign() == 0 {
if exp < decimal128MinExp {
exp = decimal128MinExp
} else if exp > decimal128MaxExp {
exp = decimal128MaxExp
}
}
quo, rem := new(big.Int), new(big.Int)
for (coef.Cmp(decimal128MaxCoef) > 0 || exp < decimal128MinExp) && exp < decimal128MaxExp {
if quo.QuoRem(coef, tenInt, rem); rem.Sign() != 0 {
break
}
coef, quo = quo, coef
exp++
}
for exp > decimal128MaxExp && coef.Cmp(decimal128MaxCoef) <= 0 {
coef.Mul(coef, tenInt)
exp--
}
if coef.Cmp(decimal128MaxCoef) > 0 || exp < decimal128MinExp {
return 0, nil, fmt.Errorf("can't encode %se%d as BSON Decimal128 without rounding: it holds 34 significant digits and exponents from %d to %d",
d.getValue(), d.exp, decimal128MinExp, decimal128MaxExp)
}
return bsonTypeDecimal128, encodeDecimal128(d.Sign() < 0, coef, exp), nil
}
// UnmarshalBSONValue implements the bson.ValueUnmarshaler interface of the MongoDB Go driver v2.
// It decodes BSON Decimal128, string, double, int32 and int64 values. BSON null and undefined decode
// to zero, as they do for the driver's own number types.
func (d *Decimal) UnmarshalBSONValue(typ byte, data []byte) error {
var err error
switch {
case typ == bsonTypeNull || typ == bsonTypeUndefined:
*d = Decimal{}
case typ == bsonTypeDecimal128 && len(data) == 16:
*d, err = decodeDecimal128(data)
case typ == bsonTypeString && len(data) >= 5:
// int32 length, the string, then a NUL byte
*d, err = NewFromString(string(data[4 : len(data)-1]))
case typ == bsonTypeDouble && len(data) == 8:
f := math.Float64frombits(binary.LittleEndian.Uint64(data))
if math.IsNaN(f) || math.IsInf(f, 0) {
return fmt.Errorf("can't convert BSON double %v to decimal", f)
}
*d = NewFromFloat(f)
case typ == bsonTypeInt32 && len(data) == 4:
*d = New(int64(int32(binary.LittleEndian.Uint32(data))), 0)
case typ == bsonTypeInt64 && len(data) == 8:
*d = New(int64(binary.LittleEndian.Uint64(data)), 0)
default:
return fmt.Errorf("can't convert BSON value of type 0x%02x and length %d to decimal", typ, len(data))
}
return err
}
// BSON stores a Decimal128 as a little-endian IEEE 754-2008 decimal128 in the binary integer decimal
// encoding: from the top bit down, a sign bit, a 14-bit exponent biased by 6176 and a 113-bit coefficient.
const (
decimal128MinExp = -6176
decimal128MaxExp = 6111
)
// decimal128MaxCoef is 10^34 - 1, the largest Decimal128 coefficient.
var decimal128MaxCoef = new(big.Int).Sub(new(big.Int).Exp(tenInt, big.NewInt(34), nil), oneInt)
// encodeDecimal128 expects a coefficient and exponent that are already in range.
func encodeDecimal128(neg bool, coef *big.Int, exp int32) []byte {
var buf [16]byte
b := coef.Bytes()
copy(buf[16-len(b):], b)
hi := binary.BigEndian.Uint64(buf[:8]) | uint64(exp-decimal128MinExp)<<49
if neg {
hi |= 1 << 63
}
data := make([]byte, 16)
binary.LittleEndian.PutUint64(data, binary.BigEndian.Uint64(buf[8:]))
binary.LittleEndian.PutUint64(data[8:], hi)
return data
}
func decodeDecimal128(data []byte) (Decimal, error) {
lo := binary.LittleEndian.Uint64(data)
hi := binary.LittleEndian.Uint64(data[8:])
neg := hi>>63 == 1
var exp int32
switch {
case hi>>59&0xF == 0xF:
return Decimal{}, fmt.Errorf("can't convert BSON Decimal128 NaN or Infinity to decimal")
case hi>>61&3 == 3:
// The exponent starts 2 bits lower, and the coefficient is always above 10^34 - 1.
exp, hi, lo = int32(hi>>47&0x3FFF), 0, 0
default:
exp, hi = int32(hi>>49&0x3FFF), hi&(1<<49-1)
}
exp += decimal128MinExp
if int64(exp) > int64(MaxDecodeExponent) || int64(exp) < -int64(MaxDecodeExponent) {
return Decimal{}, fmt.Errorf("can't convert BSON Decimal128 to decimal: exponent %d exceeds MaxDecodeExponent (%d)", exp, MaxDecodeExponent)
}
var buf [16]byte
binary.BigEndian.PutUint64(buf[:8], hi)
binary.BigEndian.PutUint64(buf[8:], lo)
value := new(big.Int).SetBytes(buf[:])
// IEEE 754 reads coefficients above 10^34 - 1 as zero.
if value.Cmp(decimal128MaxCoef) > 0 {
value.SetInt64(0)
}
if neg {
value.Neg(value)
}
return Decimal{value: value, exp: exp}, nil
}
func unquoteIfQuoted(value string) string {
// If the amount is quoted, strip the quotes
if len(value) > 2 && value[0] == '"' && value[len(value)-1] == '"' {
return value[1 : len(value)-1]
}
return value
}
// NullDecimal represents a nullable decimal with compatibility for
// scanning null values from the database.
type NullDecimal struct {
Decimal Decimal
Valid bool
}
// NewNullDecimal returns a valid NullDecimal holding d.
func NewNullDecimal(d Decimal) NullDecimal {
return NullDecimal{
Decimal: d,
Valid: true,
}
}
// Scan implements the sql.Scanner interface for database deserialization.
func (d *NullDecimal) Scan(value interface{}) error {
if value == nil {
d.Valid = false
return nil
}
err := d.Decimal.Scan(value)
d.Valid = err == nil
return err
}
// Value implements the driver.Valuer interface for database serialization.
func (d NullDecimal) Value() (driver.Value, error) {
if !d.Valid {
return nil, nil
}
return d.Decimal.Value()
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (d *NullDecimal) UnmarshalJSON(decimalBytes []byte) error {
if string(decimalBytes) == "null" {
d.Valid = false
return nil
}
err := d.Decimal.UnmarshalJSON(decimalBytes)
d.Valid = err == nil
return err
}
// MarshalJSON implements the json.Marshaler interface.
func (d NullDecimal) MarshalJSON() ([]byte, error) {
if !d.Valid {
return []byte("null"), nil
}
return d.Decimal.MarshalJSON()
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for XML
// deserialization
func (d *NullDecimal) UnmarshalText(text []byte) error {
str := string(text)
// check for empty XML or XML without body e.g., <tag></tag>
if str == "" {
d.Valid = false
return nil
}
if err := d.Decimal.UnmarshalText(text); err != nil {
d.Valid = false
return err
}
d.Valid = true
return nil
}
// MarshalText implements the encoding.TextMarshaler interface for XML
// serialization.
func (d NullDecimal) MarshalText() (text []byte, err error) {
if !d.Valid {
return []byte{}, nil
}
return d.Decimal.MarshalText()
}
// DecodeSpanner decodes a Spanner value into a Decimal
func (d *NullDecimal) DecodeSpanner(value interface{}) error {
switch t := value.(type) {
case nil:
d.Valid = false
return nil
case *string:
if t == nil {
d.Valid = false
return nil
}
value = *t
}
err := d.Decimal.Scan(value)
d.Valid = err == nil
return err
}
// EncodeSpanner encodes a Decimal into a Spanner value
func (d NullDecimal) EncodeSpanner() (interface{}, error) {
if !d.Valid {
return nil, nil
}
return d.Decimal.String(), nil
}
// MarshalBSONValue implements the bson.ValueMarshaler interface of the MongoDB Go driver v2.
// An invalid NullDecimal is stored as BSON null, see Decimal.MarshalBSONValue for the rest.
func (d NullDecimal) MarshalBSONValue() (byte, []byte, error) {
if !d.Valid {
return bsonTypeNull, nil, nil
}
return d.Decimal.MarshalBSONValue()
}
// UnmarshalBSONValue implements the bson.ValueUnmarshaler interface of the MongoDB Go driver v2.
// BSON null and undefined make the NullDecimal invalid, see Decimal.UnmarshalBSONValue for the rest.
func (d *NullDecimal) UnmarshalBSONValue(typ byte, data []byte) error {
err := d.Decimal.UnmarshalBSONValue(typ, data)
d.Valid = err == nil && typ != bsonTypeNull && typ != bsonTypeUndefined
return err
}