-
Notifications
You must be signed in to change notification settings - Fork 0
/
sqlb_text.go
253 lines (211 loc) · 6.13 KB
/
sqlb_text.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
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
package sqlb
import (
"encoding"
"fmt"
r "reflect"
"strconv"
)
/*
Tiny shortcut for encoding an `AppenderTo` implementation to a string by using its
`.AppendTo` method, without paying for a string-to-byte conversion. Used
internally by many `Expr` implementations. Exported because it's handy for
defining new types.
*/
func AppenderString(val AppenderTo) string {
if val != nil {
return bytesToMutableString(val.AppendTo(nil))
}
return ``
}
// Variant of `String` that panics on error.
func TryString(val any) string { return try1(String(val)) }
/*
Missing feature of the standard library: return a string representation of an
arbitrary value intended only for machine use, only for "intentionally"
encodable types, without swallowing errors. Differences from `fmt.Sprint`:
* Nil input = "" output.
* Returns errors separately, without encoding them into the output. This is
important when the output is intended to be passed to another system rather
than read by humans.
* Supports ONLY the following types, in this order of priority. For other
types, returns an error.
* `fmt.Stringer`
* `AppenderTo`
* `encoding.TextMarshaler`
* Built-in primitive types.
* Encodes floats without the scientific notation.
* Aliases of `[]byte`.
*/
func String(src any) (string, error) {
if src == nil {
return ``, nil
}
stringer, _ := src.(fmt.Stringer)
if stringer != nil {
return stringer.String(), nil
}
appender, _ := src.(AppenderTo)
if appender != nil {
return bytesToMutableString(appender.AppendTo(nil)), nil
}
marshaler, _ := src.(encoding.TextMarshaler)
if marshaler != nil {
chunk, err := marshaler.MarshalText()
str := bytesToMutableString(chunk)
if err != nil {
return ``, ErrInternal{Err{`generating string representation`, err}}
}
return str, nil
}
typ := typeOf(src)
val := valueOf(src)
switch typ.Kind() {
case r.Int8, r.Int16, r.Int32, r.Int64, r.Int:
if val.IsValid() {
return strconv.FormatInt(val.Int(), 10), nil
}
return ``, nil
case r.Uint8, r.Uint16, r.Uint32, r.Uint64, r.Uint:
if val.IsValid() {
return strconv.FormatUint(val.Uint(), 10), nil
}
return ``, nil
case r.Float32, r.Float64:
if val.IsValid() {
return strconv.FormatFloat(val.Float(), 'f', -1, 64), nil
}
return ``, nil
case r.Bool:
if val.IsValid() {
return strconv.FormatBool(val.Bool()), nil
}
return ``, nil
case r.String:
if val.IsValid() {
return val.String(), nil
}
return ``, nil
default:
if typ.ConvertibleTo(typeBytes) {
if val.IsValid() {
return bytesToMutableString(val.Bytes()), nil
}
return ``, nil
}
return ``, errUnsupportedType(`generating string representation`, typ)
}
}
// Variant of `AppendTo` that panics on error.
func TryAppend(buf []byte, src any) []byte { return try1(AppendTo(buf, src)) }
/*
Missing feature of the standard library: append the text representation of an
arbitrary value to the buffer, prioritizing "append"-style encoding functions
over "string"-style functions for efficiency, using only "intentional"
representations, and without swallowing errors.
Supports ONLY the following types, in this order of priority. For other types,
returns an error.
* `AppenderTo`
* `encoding.TextMarshaler`
* `fmt.Stringer`
* Built-in primitive types.
* Aliases of `[]byte`.
Special cases:
* Nil: append nothing, return buffer as-is.
* Integers: use `strconv.AppendInt` in base 10.
* Floats: use `strconv.AppendFloat` without scientific notation.
Used internally by `CommaAppender`, exported for advanced users.
*/
func AppendTo(buf []byte, src any) ([]byte, error) {
if src == nil {
return buf, nil
}
appender, _ := src.(AppenderTo)
if appender != nil {
return appender.AppendTo(buf), nil
}
marshaler, _ := src.(encoding.TextMarshaler)
if marshaler != nil {
chunk, err := marshaler.MarshalText()
if err != nil {
return buf, ErrInternal{Err{`appending string representation`, err}}
}
return append(buf, chunk...), nil
}
stringer, _ := src.(fmt.Stringer)
if stringer != nil {
return append(buf, stringer.String()...), nil
}
typ := typeOf(src)
val := valueOf(src)
switch typ.Kind() {
case r.Int8, r.Int16, r.Int32, r.Int64, r.Int:
if val.IsValid() {
return strconv.AppendInt(buf, val.Int(), 10), nil
}
return buf, nil
case r.Uint8, r.Uint16, r.Uint32, r.Uint64, r.Uint:
if val.IsValid() {
return strconv.AppendUint(buf, val.Uint(), 10), nil
}
return buf, nil
case r.Float32, r.Float64:
if val.IsValid() {
return strconv.AppendFloat(buf, val.Float(), 'f', -1, 64), nil
}
return buf, nil
case r.Bool:
if val.IsValid() {
return strconv.AppendBool(buf, val.Bool()), nil
}
return buf, nil
case r.String:
if val.IsValid() {
return append(buf, val.String()...), nil
}
return buf, nil
default:
if typ.ConvertibleTo(typeBytes) {
if val.IsValid() {
return append(buf, val.Bytes()...), nil
}
return buf, nil
}
return buf, errUnsupportedType(`appending string representation`, typ)
}
}
// Variant of `AppendWith` that panics on error.
func TryAppendWith(buf *[]byte, delim string, val any) bool {
return try1(AppendWith(buf, delim, val))
}
/*
Attempts to append the given delimiter and the text representation of the given
value, via `AppendTo`. If after delimiter non-zero amount of bytes was appended,
returns true. Otherwise reverts the buffer to the original length and returns
false. If the buffer got reallocated with increased capacity, preserves the new
capacity.
*/
func AppendWith(buf *[]byte, delim string, val any) (bool, error) {
if buf == nil {
return false, nil
}
pre := len(*buf)
*buf = append(*buf, delim...)
mid := len(*buf)
out, err := AppendTo(*buf, val)
if err != nil {
return false, err
}
*buf = out
/**
Note: there's a difference between snapshotting the length to reslice the
buffer after the append, versus naively snapshotting the slice itself. The
append may allocate additional capacity, perform a copy, and return a larger
buffer. Reslicing preserves the new capacity, which is important for
avoiding a "hot split" where the added capacity is repeatedly discarded.
*/
if mid == len(*buf) {
*buf = (*buf)[:pre]
return false, nil
}
return true, nil
}