-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathstring_array.go
More file actions
133 lines (115 loc) · 2.54 KB
/
string_array.go
File metadata and controls
133 lines (115 loc) · 2.54 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
package pq_types
import (
"bytes"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"sort"
"strings"
"unicode"
)
// StringArray is a slice of string values, compatible with PostgreSQL's varchar[].
type StringArray []string
func (a StringArray) Len() int { return len(a) }
func (a StringArray) Less(i, j int) bool { return a[i] < a[j] }
func (a StringArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// Value implements database/sql/driver Valuer interface.
func (a StringArray) Value() (driver.Value, error) {
if a == nil {
return nil, nil
}
res := make([]string, len(a))
for i, e := range a {
r := e
r = strings.Replace(r, `\`, `\\`, -1)
r = strings.Replace(r, `"`, `\"`, -1)
res[i] = `"` + r + `"`
}
return []byte("{" + strings.Join(res, ",") + "}"), nil
}
// Scan implements database/sql Scanner interface.
func (a *StringArray) Scan(value interface{}) error {
if value == nil {
*a = nil
return nil
}
var b []byte
switch v := value.(type) {
case []byte:
b = v
case string:
b = []byte(v)
default:
return fmt.Errorf("StringArray.Scan: expected []byte or string, got %T (%q)", value, value)
}
if len(b) < 2 || b[0] != '{' || b[len(b)-1] != '}' {
return fmt.Errorf("StringArray.Scan: unexpected data %q", b)
}
// reuse underlying array if present
if *a == nil {
*a = make(StringArray, 0)
}
*a = (*a)[:0]
if len(b) == 2 { // '{}'
return nil
}
reader := bytes.NewReader(b[1 : len(b)-1]) // skip '{' and '}'
// helper function to read next rune and check if it valid
readRune := func() (rune, error) {
r, _, err := reader.ReadRune()
if err != nil {
return 0, err
}
if r == unicode.ReplacementChar {
return 0, fmt.Errorf("StringArray.Scan: invalid rune")
}
return r, nil
}
var q bool
var e []rune
for {
// read next rune and check if we are done
r, err := readRune()
if err == io.EOF {
break
}
if err != nil {
return err
}
switch r {
case '"':
// enter or leave quotes
q = !q
continue
case ',':
// end of element unless in we are in quotes
if !q {
*a = append(*a, string(e))
e = e[:0]
continue
}
case '\\':
// skip to next rune, it should be present
n, err := readRune()
if err != nil {
return err
}
r = n
}
e = append(e, r)
}
// we should not be in quotes at this point
if q {
panic("StringArray.Scan bug")
}
// add last element
*a = append(*a, string(e))
return nil
}
// check interfaces
var (
_ sort.Interface = StringArray{}
_ driver.Valuer = StringArray{}
_ sql.Scanner = &StringArray{}
)