1 package pgtype
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "fmt"
7 "io"
8 "strconv"
9 "strings"
10 "unicode"
11
12 "github.com/jackc/pgx/v5/internal/pgio"
13 )
14
15
16
17
18
19 type arrayHeader struct {
20 ContainsNull bool
21 ElementOID uint32
22 Dimensions []ArrayDimension
23 }
24
25 type ArrayDimension struct {
26 Length int32
27 LowerBound int32
28 }
29
30
31 func cardinality(dimensions []ArrayDimension) int {
32 if len(dimensions) == 0 {
33 return 0
34 }
35
36 elementCount := int(dimensions[0].Length)
37 for _, d := range dimensions[1:] {
38 elementCount *= int(d.Length)
39 }
40
41 return elementCount
42 }
43
44 func (dst *arrayHeader) DecodeBinary(m *Map, src []byte) (int, error) {
45 if len(src) < 12 {
46 return 0, fmt.Errorf("array header too short: %d", len(src))
47 }
48
49 rp := 0
50
51 numDims := int(binary.BigEndian.Uint32(src[rp:]))
52 rp += 4
53
54 dst.ContainsNull = binary.BigEndian.Uint32(src[rp:]) == 1
55 rp += 4
56
57 dst.ElementOID = binary.BigEndian.Uint32(src[rp:])
58 rp += 4
59
60 dst.Dimensions = make([]ArrayDimension, numDims)
61 if len(src) < 12+numDims*8 {
62 return 0, fmt.Errorf("array header too short for %d dimensions: %d", numDims, len(src))
63 }
64 for i := range dst.Dimensions {
65 dst.Dimensions[i].Length = int32(binary.BigEndian.Uint32(src[rp:]))
66 rp += 4
67
68 dst.Dimensions[i].LowerBound = int32(binary.BigEndian.Uint32(src[rp:]))
69 rp += 4
70 }
71
72 return rp, nil
73 }
74
75 func (src arrayHeader) EncodeBinary(buf []byte) []byte {
76 buf = pgio.AppendInt32(buf, int32(len(src.Dimensions)))
77
78 var containsNull int32
79 if src.ContainsNull {
80 containsNull = 1
81 }
82 buf = pgio.AppendInt32(buf, containsNull)
83
84 buf = pgio.AppendUint32(buf, src.ElementOID)
85
86 for i := range src.Dimensions {
87 buf = pgio.AppendInt32(buf, src.Dimensions[i].Length)
88 buf = pgio.AppendInt32(buf, src.Dimensions[i].LowerBound)
89 }
90
91 return buf
92 }
93
94 type untypedTextArray struct {
95 Elements []string
96 Quoted []bool
97 Dimensions []ArrayDimension
98 }
99
100 func parseUntypedTextArray(src string) (*untypedTextArray, error) {
101 dst := &untypedTextArray{
102 Elements: []string{},
103 Quoted: []bool{},
104 Dimensions: []ArrayDimension{},
105 }
106
107 buf := bytes.NewBufferString(src)
108
109 skipWhitespace(buf)
110
111 r, _, err := buf.ReadRune()
112 if err != nil {
113 return nil, fmt.Errorf("invalid array: %w", err)
114 }
115
116 var explicitDimensions []ArrayDimension
117
118
119 if r == '[' {
120 buf.UnreadRune()
121
122 for {
123 r, _, err = buf.ReadRune()
124 if err != nil {
125 return nil, fmt.Errorf("invalid array: %w", err)
126 }
127
128 if r == '=' {
129 break
130 } else if r != '[' {
131 return nil, fmt.Errorf("invalid array, expected '[' or '=' got %v", r)
132 }
133
134 lower, err := arrayParseInteger(buf)
135 if err != nil {
136 return nil, fmt.Errorf("invalid array: %w", err)
137 }
138
139 r, _, err = buf.ReadRune()
140 if err != nil {
141 return nil, fmt.Errorf("invalid array: %w", err)
142 }
143
144 if r != ':' {
145 return nil, fmt.Errorf("invalid array, expected ':' got %v", r)
146 }
147
148 upper, err := arrayParseInteger(buf)
149 if err != nil {
150 return nil, fmt.Errorf("invalid array: %w", err)
151 }
152
153 r, _, err = buf.ReadRune()
154 if err != nil {
155 return nil, fmt.Errorf("invalid array: %w", err)
156 }
157
158 if r != ']' {
159 return nil, fmt.Errorf("invalid array, expected ']' got %v", r)
160 }
161
162 explicitDimensions = append(explicitDimensions, ArrayDimension{LowerBound: lower, Length: upper - lower + 1})
163 }
164
165 r, _, err = buf.ReadRune()
166 if err != nil {
167 return nil, fmt.Errorf("invalid array: %w", err)
168 }
169 }
170
171 if r != '{' {
172 return nil, fmt.Errorf("invalid array, expected '{' got %v", r)
173 }
174
175 implicitDimensions := []ArrayDimension{{LowerBound: 1, Length: 0}}
176
177
178 for {
179 r, _, err = buf.ReadRune()
180 if err != nil {
181 return nil, fmt.Errorf("invalid array: %w", err)
182 }
183
184 if r == '{' {
185 implicitDimensions[len(implicitDimensions)-1].Length = 1
186 implicitDimensions = append(implicitDimensions, ArrayDimension{LowerBound: 1})
187 } else {
188 buf.UnreadRune()
189 break
190 }
191 }
192 currentDim := len(implicitDimensions) - 1
193 counterDim := currentDim
194
195 for {
196 r, _, err = buf.ReadRune()
197 if err != nil {
198 return nil, fmt.Errorf("invalid array: %w", err)
199 }
200
201 switch r {
202 case '{':
203 if currentDim == counterDim {
204 implicitDimensions[currentDim].Length++
205 }
206 currentDim++
207 case ',':
208 case '}':
209 currentDim--
210 if currentDim < counterDim {
211 counterDim = currentDim
212 }
213 default:
214 buf.UnreadRune()
215 value, quoted, err := arrayParseValue(buf)
216 if err != nil {
217 return nil, fmt.Errorf("invalid array value: %w", err)
218 }
219 if currentDim == counterDim {
220 implicitDimensions[currentDim].Length++
221 }
222 dst.Quoted = append(dst.Quoted, quoted)
223 dst.Elements = append(dst.Elements, value)
224 }
225
226 if currentDim < 0 {
227 break
228 }
229 }
230
231 skipWhitespace(buf)
232
233 if buf.Len() > 0 {
234 return nil, fmt.Errorf("unexpected trailing data: %v", buf.String())
235 }
236
237 if len(dst.Elements) == 0 {
238 } else if len(explicitDimensions) > 0 {
239 dst.Dimensions = explicitDimensions
240 } else {
241 dst.Dimensions = implicitDimensions
242 }
243
244 return dst, nil
245 }
246
247 func skipWhitespace(buf *bytes.Buffer) {
248 var r rune
249 var err error
250 for r, _, _ = buf.ReadRune(); unicode.IsSpace(r); r, _, _ = buf.ReadRune() {
251 }
252
253 if err != io.EOF {
254 buf.UnreadRune()
255 }
256 }
257
258 func arrayParseValue(buf *bytes.Buffer) (string, bool, error) {
259 r, _, err := buf.ReadRune()
260 if err != nil {
261 return "", false, err
262 }
263 if r == '"' {
264 return arrayParseQuotedValue(buf)
265 }
266 buf.UnreadRune()
267
268 s := &bytes.Buffer{}
269
270 for {
271 r, _, err := buf.ReadRune()
272 if err != nil {
273 return "", false, err
274 }
275
276 switch r {
277 case ',', '}':
278 buf.UnreadRune()
279 return s.String(), false, nil
280 }
281
282 s.WriteRune(r)
283 }
284 }
285
286 func arrayParseQuotedValue(buf *bytes.Buffer) (string, bool, error) {
287 s := &bytes.Buffer{}
288
289 for {
290 r, _, err := buf.ReadRune()
291 if err != nil {
292 return "", false, err
293 }
294
295 switch r {
296 case '\\':
297 r, _, err = buf.ReadRune()
298 if err != nil {
299 return "", false, err
300 }
301 case '"':
302 r, _, err = buf.ReadRune()
303 if err != nil {
304 return "", false, err
305 }
306 buf.UnreadRune()
307 return s.String(), true, nil
308 }
309 s.WriteRune(r)
310 }
311 }
312
313 func arrayParseInteger(buf *bytes.Buffer) (int32, error) {
314 s := &bytes.Buffer{}
315
316 for {
317 r, _, err := buf.ReadRune()
318 if err != nil {
319 return 0, err
320 }
321
322 if ('0' <= r && r <= '9') || r == '-' {
323 s.WriteRune(r)
324 } else {
325 buf.UnreadRune()
326 n, err := strconv.ParseInt(s.String(), 10, 32)
327 if err != nil {
328 return 0, err
329 }
330 return int32(n), nil
331 }
332 }
333 }
334
335 func encodeTextArrayDimensions(buf []byte, dimensions []ArrayDimension) []byte {
336 var customDimensions bool
337 for _, dim := range dimensions {
338 if dim.LowerBound != 1 {
339 customDimensions = true
340 }
341 }
342
343 if !customDimensions {
344 return buf
345 }
346
347 for _, dim := range dimensions {
348 buf = append(buf, '[')
349 buf = append(buf, strconv.FormatInt(int64(dim.LowerBound), 10)...)
350 buf = append(buf, ':')
351 buf = append(buf, strconv.FormatInt(int64(dim.LowerBound+dim.Length-1), 10)...)
352 buf = append(buf, ']')
353 }
354
355 return append(buf, '=')
356 }
357
358 var quoteArrayReplacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`)
359
360 func quoteArrayElement(src string) string {
361 return `"` + quoteArrayReplacer.Replace(src) + `"`
362 }
363
364 func isSpace(ch byte) bool {
365
366
367 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'
368 }
369
370 func quoteArrayElementIfNeeded(src string) string {
371 if src == "" || (len(src) == 4 && strings.EqualFold(src, "null")) || isSpace(src[0]) || isSpace(src[len(src)-1]) || strings.ContainsAny(src, `{},"\`) {
372 return quoteArrayElement(src)
373 }
374 return src
375 }
376
377
378
379 type Array[T any] struct {
380 Elements []T
381 Dims []ArrayDimension
382 Valid bool
383 }
384
385 func (a Array[T]) Dimensions() []ArrayDimension {
386 return a.Dims
387 }
388
389 func (a Array[T]) Index(i int) any {
390 return a.Elements[i]
391 }
392
393 func (a Array[T]) IndexType() any {
394 var el T
395 return el
396 }
397
398 func (a *Array[T]) SetDimensions(dimensions []ArrayDimension) error {
399 if dimensions == nil {
400 *a = Array[T]{}
401 return nil
402 }
403
404 elementCount := cardinality(dimensions)
405 *a = Array[T]{
406 Elements: make([]T, elementCount),
407 Dims: dimensions,
408 Valid: true,
409 }
410
411 return nil
412 }
413
414 func (a Array[T]) ScanIndex(i int) any {
415 return &a.Elements[i]
416 }
417
418 func (a Array[T]) ScanIndexType() any {
419 return new(T)
420 }
421
422
423
424 type FlatArray[T any] []T
425
426 func (a FlatArray[T]) Dimensions() []ArrayDimension {
427 if a == nil {
428 return nil
429 }
430
431 return []ArrayDimension{{Length: int32(len(a)), LowerBound: 1}}
432 }
433
434 func (a FlatArray[T]) Index(i int) any {
435 return a[i]
436 }
437
438 func (a FlatArray[T]) IndexType() any {
439 var el T
440 return el
441 }
442
443 func (a *FlatArray[T]) SetDimensions(dimensions []ArrayDimension) error {
444 if dimensions == nil {
445 *a = nil
446 return nil
447 }
448
449 elementCount := cardinality(dimensions)
450 *a = make(FlatArray[T], elementCount)
451 return nil
452 }
453
454 func (a FlatArray[T]) ScanIndex(i int) any {
455 return &a[i]
456 }
457
458 func (a FlatArray[T]) ScanIndexType() any {
459 return new(T)
460 }
461
View as plain text