mirror of
https://github.com/bitmagnet-io/bitmagnet.git
synced 2026-07-27 22:51:10 -04:00
238 lines
4.5 KiB
Go
238 lines
4.5 KiB
Go
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// StringArray represents a one-dimensional array of the PostgreSQL character types.
|
|
type StringArray []string
|
|
|
|
// Scan implements the sql.Scanner interface.
|
|
func (a *StringArray) Scan(src interface{}) error {
|
|
switch src := src.(type) {
|
|
case []byte:
|
|
return a.scanBytes(src)
|
|
case string:
|
|
return a.scanBytes([]byte(src))
|
|
case nil:
|
|
*a = nil
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
|
}
|
|
|
|
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
|
dims, elems, err := parseArray(src, del)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(dims) > 1 {
|
|
return nil, fmt.Errorf(
|
|
"pq: cannot convert ARRAY%s to %s",
|
|
strings.ReplaceAll(fmt.Sprint(dims), " ", "]["),
|
|
typ,
|
|
)
|
|
}
|
|
|
|
return elems, err
|
|
}
|
|
|
|
// parseArray extracts the dimensions and elements of an array represented in
|
|
// text format. Only representations emitted by the backend are supported.
|
|
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
|
// is case-sensitive.
|
|
//
|
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
|
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
|
var depth, i int
|
|
|
|
if len(src) < 1 || src[0] != '{' {
|
|
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
|
}
|
|
|
|
Open:
|
|
for i < len(src) {
|
|
switch src[i] {
|
|
case '{':
|
|
depth++
|
|
i++
|
|
case '}':
|
|
elems = make([][]byte, 0)
|
|
goto Close
|
|
default:
|
|
break Open
|
|
}
|
|
}
|
|
|
|
dims = make([]int, i)
|
|
|
|
Element:
|
|
for i < len(src) {
|
|
switch src[i] {
|
|
case '{':
|
|
if depth == len(dims) {
|
|
break Element
|
|
}
|
|
|
|
depth++
|
|
dims[depth-1] = 0
|
|
i++
|
|
case '"':
|
|
var (
|
|
elem = []byte{}
|
|
escape bool
|
|
)
|
|
|
|
for i++; i < len(src); i++ {
|
|
if escape {
|
|
elem = append(elem, src[i])
|
|
escape = false
|
|
} else {
|
|
switch src[i] {
|
|
default:
|
|
elem = append(elem, src[i])
|
|
case '\\':
|
|
escape = true
|
|
case '"':
|
|
elems = append(elems, elem)
|
|
i++
|
|
|
|
break Element
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
for start := i; i < len(src); i++ {
|
|
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
|
elem := src[start:i]
|
|
if len(elem) == 0 {
|
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
|
}
|
|
|
|
if bytes.Equal(elem, []byte("NULL")) {
|
|
elem = nil
|
|
}
|
|
|
|
elems = append(elems, elem)
|
|
|
|
break Element
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for i < len(src) {
|
|
if bytes.HasPrefix(src[i:], del) && depth > 0 {
|
|
dims[depth-1]++
|
|
i += len(del)
|
|
|
|
goto Element
|
|
} else if src[i] != '}' || depth <= 0 {
|
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
|
}
|
|
|
|
dims[depth-1]++
|
|
depth--
|
|
i++
|
|
}
|
|
|
|
Close:
|
|
for i < len(src) {
|
|
if src[i] != '}' || depth <= 0 {
|
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
|
}
|
|
|
|
depth--
|
|
i++
|
|
}
|
|
|
|
if depth > 0 {
|
|
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
|
}
|
|
|
|
if err == nil {
|
|
for _, d := range dims {
|
|
if (len(elems) % d) != 0 {
|
|
err = fmt.Errorf(
|
|
"pq: multidimensional arrays must have elements with matching dimensions",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (a *StringArray) scanBytes(src []byte) error {
|
|
elems, err := scanLinearArray(src, []byte{','}, "StringArray")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if *a != nil && len(elems) == 0 {
|
|
*a = (*a)[:0]
|
|
} else {
|
|
b := make(StringArray, len(elems))
|
|
for i, v := range elems {
|
|
if b[i] = string(v); v == nil {
|
|
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
|
}
|
|
}
|
|
|
|
*a = b
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Value implements the driver.Valuer interface.
|
|
func (a StringArray) Value() (driver.Value, error) {
|
|
if a == nil {
|
|
//nolint:nilnil
|
|
return nil, nil
|
|
}
|
|
|
|
if n := len(a); n > 0 {
|
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
|
// and N-1 bytes of delimiters.
|
|
b := make([]byte, 1, 1+3*n)
|
|
b[0] = '{'
|
|
|
|
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
|
for i := 1; i < n; i++ {
|
|
b = append(b, ',')
|
|
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
|
}
|
|
|
|
return string(append(b, '}')), nil
|
|
}
|
|
|
|
return "{}", nil
|
|
}
|
|
|
|
func appendArrayQuotedBytes(b, v []byte) []byte {
|
|
b = append(b, '"')
|
|
|
|
for {
|
|
i := bytes.IndexAny(v, `"\`)
|
|
if i < 0 {
|
|
b = append(b, v...)
|
|
break
|
|
}
|
|
|
|
if i > 0 {
|
|
b = append(b, v[:i]...)
|
|
}
|
|
|
|
b = append(b, '\\', v[i])
|
|
v = v[i+1:]
|
|
}
|
|
|
|
return append(b, '"')
|
|
}
|