/
bytes.go
138 lines (118 loc) · 3.8 KB
/
bytes.go
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
// SPDX-FileCopyrightText: 2020 SAP SE
// SPDX-FileCopyrightText: 2021 SAP SE
// SPDX-FileCopyrightText: 2022 SAP SE
// SPDX-FileCopyrightText: 2023 SAP SE
//
// SPDX-License-Identifier: Apache-2.0
package asetypes
import (
"bytes"
"encoding/binary"
"fmt"
"time"
"unicode/utf16"
"github.com/SAP/go-dblib/asetime"
)
// Bytes returns a byte slice based on a given value-interface and depending
// on the ASE data type.
// TODO: Instead of parameter 'length', one could use a struct to store
// additional optional parameters, e.g. 'length', 'datastatus', ...
// (Problem: golang mod import cycle)
func (t DataType) Bytes(endian binary.ByteOrder, value interface{}, length int64) ([]byte, error) {
// If value is nil, then immediately return an empty byteslice
if value == nil {
bs := make([]byte, 0)
return bs, nil
}
switch t {
case MONEY, SHORTMONEY, MONEYN:
dec, ok := value.(*Decimal)
if !ok {
return nil, fmt.Errorf("expected *asetypes.Decimal for %s, received %T", t, value)
}
deci := dec.Int()
bs := make([]byte, length)
switch length {
case 4: // SHORTMONEY, MONEYN(4)
endian.PutUint32(bs, uint32(deci.Int64()))
case 8: // MONEY, MONEYN(8)
endian.PutUint32(bs[:4], uint32(deci.Int64()>>32))
endian.PutUint32(bs[4:], uint32(deci.Int64()))
}
return bs, nil
case DECN, NUMN:
dec, ok := value.(*Decimal)
if !ok {
return nil, fmt.Errorf("expected *asetypes.Decimal for %s, received %T", t, value)
}
bs := make([]byte, dec.ByteSize())
copy(bs[dec.ByteSize()-len(dec.Bytes()):], dec.Bytes())
if dec.IsNegative() {
bs[0] = 0x1
}
return bs, nil
case DATE, DATEN:
t := asetime.DurationFromDateTime(value.(time.Time))
t -= asetime.DurationFromDateTime(asetime.Epoch1900())
bs := make([]byte, length)
endian.PutUint32(bs, uint32(t.Days()))
return bs, nil
case TIME, TIMEN:
dur := asetime.DurationFromTime(value.(time.Time))
fract := asetime.MillisecondToFractionalSecond(dur.Microseconds())
bs := make([]byte, length)
endian.PutUint32(bs, uint32(fract))
return bs, nil
case SHORTDATE, DATETIME, DATETIMEN:
t := asetime.DurationFromDateTime(value.(time.Time))
t -= asetime.DurationFromDateTime(asetime.Epoch1900())
days := t.Days()
bs := make([]byte, length)
switch length {
case 4: // SHORTDATE/DATETIME4, DATETIMEN(4)
s := asetime.ASEDuration(t.Microseconds() - days*int(asetime.Day))
binary.LittleEndian.PutUint16(bs[:2], uint16(days))
binary.LittleEndian.PutUint16(bs[2:], uint16(s.Minutes()))
case 8: // DATETIME, DATETIMEN(8)
s := t.Microseconds() - days*int(asetime.Day)
s = asetime.MillisecondToFractionalSecond(s)
binary.LittleEndian.PutUint32(bs[:4], uint32(days))
binary.LittleEndian.PutUint32(bs[4:], uint32(s))
}
return bs, nil
case BIGDATETIMEN:
dur := asetime.DurationFromDateTime(value.(time.Time))
bs := make([]byte, length)
binary.LittleEndian.PutUint64(bs, uint64(dur))
return bs, nil
case BIGTIMEN:
dur := asetime.DurationFromTime(value.(time.Time))
bs := make([]byte, length)
binary.LittleEndian.PutUint64(bs, uint64(dur))
return bs, nil
case UNITEXT:
// convert go string to utf16 code points
runes := []rune(value.(string))
utf16bytes := utf16.Encode(runes)
// convert utf16 code points to bytes
bs := make([]byte, len(utf16bytes)*2)
for i := 0; i < len(utf16bytes); i++ {
binary.LittleEndian.PutUint16(bs[i:], utf16bytes[i])
}
return bs, nil
}
switch typed := value.(type) {
case string:
value = []byte(typed)
}
buf := &bytes.Buffer{}
if err := binary.Write(buf, endian, value); err != nil {
return nil, fmt.Errorf("error writing value: %w", err)
}
bs := buf.Bytes()
if t.ByteSize() != -1 && t.ByteSize() != len(bs) {
return nil, fmt.Errorf("binary.Write returned a byteslice of length %d, expected %d for datatype %s",
len(bs), t.ByteSize(), t)
}
return bs, nil
}