1060 lines
24 KiB
Go
1060 lines
24 KiB
Go
package sqltypes
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// marshalYAMLSlice returns the value used by yaml.Marshaler implementations
|
|
// for the nullable array types below: nil when invalid, the slice otherwise.
|
|
func marshalYAMLSlice[T any](valid bool, vals []T) (any, error) {
|
|
if !valid {
|
|
return nil, nil
|
|
}
|
|
return vals, nil
|
|
}
|
|
|
|
// unmarshalYAMLSlice returns the value used by yaml.Unmarshaler
|
|
// implementations for the nullable array types below.
|
|
func unmarshalYAMLSlice[T any](value *yaml.Node) (vals []T, valid bool, err error) {
|
|
if value == nil || value.Tag == "!!null" {
|
|
return nil, false, nil
|
|
}
|
|
if err := value.Decode(&vals); err != nil {
|
|
return nil, false, err
|
|
}
|
|
return vals, true, nil
|
|
}
|
|
|
|
// xmlArrayItemName is the element name used for each item when a nullable
|
|
// array type is encoded as XML, since XML has no native list type.
|
|
var xmlArrayItemName = xml.Name{Local: "item"}
|
|
|
|
// marshalXMLSlice writes a nullable array type as XML: an empty element when
|
|
// invalid, otherwise the start tag followed by one <item> child per element.
|
|
func marshalXMLSlice[T any](e *xml.Encoder, start xml.StartElement, valid bool, vals []T) error {
|
|
if !valid {
|
|
return e.EncodeElement("", start)
|
|
}
|
|
if err := e.EncodeToken(start); err != nil {
|
|
return err
|
|
}
|
|
for _, v := range vals {
|
|
if err := e.EncodeElement(v, xml.StartElement{Name: xmlArrayItemName}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return e.EncodeToken(start.End())
|
|
}
|
|
|
|
// unmarshalXMLSlice reads back the XML written by marshalXMLSlice.
|
|
//
|
|
// XML has no native null representation: an element with no <item> children
|
|
// unmarshals to a valid, empty slice rather than an invalid one.
|
|
func unmarshalXMLSlice[T any](d *xml.Decoder, start xml.StartElement) ([]T, error) {
|
|
var vals []T
|
|
for {
|
|
tok, err := d.Token()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch t := tok.(type) {
|
|
case xml.StartElement:
|
|
var v T
|
|
if err := d.DecodeElement(&v, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
vals = append(vals, v)
|
|
case xml.EndElement:
|
|
if t.Name == start.Name {
|
|
return vals, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// parsePostgresArrayElements parses a PostgreSQL array literal (e.g. `{a,"b,c",d}`)
|
|
// into a slice of raw string elements. Each element retains its unquoted/unescaped value.
|
|
func parsePostgresArrayElements(s string) ([]string, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" || strings.EqualFold(s, "null") || strings.EqualFold(s, "NULL") {
|
|
return nil, nil
|
|
}
|
|
if !strings.HasPrefix(s, "{") || !strings.HasSuffix(s, "}") {
|
|
return nil, fmt.Errorf("not a valid PostgreSQL array literal: %q", s)
|
|
}
|
|
inner := s[1 : len(s)-1]
|
|
if inner == "" {
|
|
return []string{}, nil
|
|
}
|
|
|
|
var result []string
|
|
var cur strings.Builder
|
|
inQuotes := false
|
|
i := 0
|
|
for i < len(inner) {
|
|
c := inner[i]
|
|
switch {
|
|
case c == '"' && !inQuotes:
|
|
inQuotes = true
|
|
case c == '"' && inQuotes:
|
|
if i+1 < len(inner) && inner[i+1] == '"' {
|
|
cur.WriteByte('"')
|
|
i++
|
|
} else {
|
|
inQuotes = false
|
|
}
|
|
case c == '\\' && inQuotes:
|
|
if i+1 < len(inner) {
|
|
cur.WriteByte(inner[i+1])
|
|
i++
|
|
}
|
|
case c == ',' && !inQuotes:
|
|
result = append(result, cur.String())
|
|
cur.Reset()
|
|
default:
|
|
cur.WriteByte(c)
|
|
}
|
|
i++
|
|
}
|
|
result = append(result, cur.String())
|
|
return result, nil
|
|
}
|
|
|
|
// formatPostgresStringArray formats a []string back into a PostgreSQL array literal.
|
|
func formatPostgresStringArray(vals []string) string {
|
|
if vals == nil {
|
|
return "NULL"
|
|
}
|
|
parts := make([]string, len(vals))
|
|
for i, v := range vals {
|
|
// Quote if value contains comma, double-quote, backslash, braces, whitespace, or is empty.
|
|
needsQuote := v == "" || strings.ContainsAny(v, `,"\\{}`+"\t\n\r ")
|
|
if needsQuote {
|
|
v = strings.ReplaceAll(v, `\`, `\\`)
|
|
v = strings.ReplaceAll(v, `"`, `""`)
|
|
parts[i] = `"` + v + `"`
|
|
} else {
|
|
parts[i] = v
|
|
}
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}"
|
|
}
|
|
|
|
// ── SqlStringArray ───────────────────────────────────────────────────────────
|
|
|
|
// SqlStringArray is a nullable PostgreSQL text[] / varchar[] array.
|
|
type SqlStringArray struct {
|
|
Val []string
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlStringArray) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlStringArray: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = elems
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlStringArray) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
return formatPostgresStringArray(a.Val), nil
|
|
}
|
|
|
|
func (a SqlStringArray) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlStringArray) UnmarshalJSON(b []byte) error {
|
|
s := strings.TrimSpace(string(b))
|
|
if s == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []string
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlStringArray) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlStringArray) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[string](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlStringArray) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlStringArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[string](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlStringArray(v []string) SqlStringArray {
|
|
return SqlStringArray{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlInt16Array ────────────────────────────────────────────────────────────
|
|
|
|
type SqlInt16Array struct {
|
|
Val []int16
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlInt16Array) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlInt16Array: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]int16, len(elems))
|
|
for i, e := range elems {
|
|
n, err := strconv.ParseInt(strings.TrimSpace(e), 10, 16)
|
|
if err != nil {
|
|
return fmt.Errorf("SqlInt16Array: element %d %q: %w", i, e, err)
|
|
}
|
|
a.Val[i] = int16(n)
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt16Array) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
parts[i] = strconv.FormatInt(int64(v), 10)
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlInt16Array) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlInt16Array) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []int16
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt16Array) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlInt16Array) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[int16](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt16Array) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlInt16Array) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[int16](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlInt16Array(v []int16) SqlInt16Array {
|
|
return SqlInt16Array{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlInt32Array ────────────────────────────────────────────────────────────
|
|
|
|
type SqlInt32Array struct {
|
|
Val []int32
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlInt32Array) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlInt32Array: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]int32, len(elems))
|
|
for i, e := range elems {
|
|
n, err := strconv.ParseInt(strings.TrimSpace(e), 10, 32)
|
|
if err != nil {
|
|
return fmt.Errorf("SqlInt32Array: element %d %q: %w", i, e, err)
|
|
}
|
|
a.Val[i] = int32(n)
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt32Array) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
parts[i] = strconv.FormatInt(int64(v), 10)
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlInt32Array) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlInt32Array) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []int32
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt32Array) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlInt32Array) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[int32](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt32Array) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlInt32Array) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[int32](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlInt32Array(v []int32) SqlInt32Array {
|
|
return SqlInt32Array{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlInt64Array ────────────────────────────────────────────────────────────
|
|
|
|
type SqlInt64Array struct {
|
|
Val []int64
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlInt64Array) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlInt64Array: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]int64, len(elems))
|
|
for i, e := range elems {
|
|
n, err := strconv.ParseInt(strings.TrimSpace(e), 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("SqlInt64Array: element %d %q: %w", i, e, err)
|
|
}
|
|
a.Val[i] = n
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt64Array) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
parts[i] = strconv.FormatInt(v, 10)
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlInt64Array) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlInt64Array) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []int64
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt64Array) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlInt64Array) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[int64](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlInt64Array) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlInt64Array) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[int64](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlInt64Array(v []int64) SqlInt64Array {
|
|
return SqlInt64Array{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlFloat32Array ──────────────────────────────────────────────────────────
|
|
|
|
type SqlFloat32Array struct {
|
|
Val []float32
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlFloat32Array) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlFloat32Array: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]float32, len(elems))
|
|
for i, e := range elems {
|
|
f, err := strconv.ParseFloat(strings.TrimSpace(e), 32)
|
|
if err != nil {
|
|
return fmt.Errorf("SqlFloat32Array: element %d %q: %w", i, e, err)
|
|
}
|
|
a.Val[i] = float32(f)
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlFloat32Array) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
parts[i] = strconv.FormatFloat(float64(v), 'f', -1, 32)
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlFloat32Array) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlFloat32Array) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []float32
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlFloat32Array) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlFloat32Array) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[float32](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlFloat32Array) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlFloat32Array) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[float32](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlFloat32Array(v []float32) SqlFloat32Array {
|
|
return SqlFloat32Array{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlFloat64Array ──────────────────────────────────────────────────────────
|
|
|
|
type SqlFloat64Array struct {
|
|
Val []float64
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlFloat64Array) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlFloat64Array: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]float64, len(elems))
|
|
for i, e := range elems {
|
|
f, err := strconv.ParseFloat(strings.TrimSpace(e), 64)
|
|
if err != nil {
|
|
return fmt.Errorf("SqlFloat64Array: element %d %q: %w", i, e, err)
|
|
}
|
|
a.Val[i] = f
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlFloat64Array) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
parts[i] = strconv.FormatFloat(v, 'f', -1, 64)
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlFloat64Array) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlFloat64Array) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []float64
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlFloat64Array) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlFloat64Array) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[float64](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlFloat64Array) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlFloat64Array) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[float64](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlFloat64Array(v []float64) SqlFloat64Array {
|
|
return SqlFloat64Array{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlBoolArray ─────────────────────────────────────────────────────────────
|
|
|
|
type SqlBoolArray struct {
|
|
Val []bool
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlBoolArray) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlBoolArray: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]bool, len(elems))
|
|
for i, e := range elems {
|
|
e = strings.ToLower(strings.TrimSpace(e))
|
|
a.Val[i] = e == "t" || e == "true" || e == "1" || e == "yes"
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlBoolArray) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
if v {
|
|
parts[i] = "t"
|
|
} else {
|
|
parts[i] = "f"
|
|
}
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlBoolArray) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlBoolArray) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []bool
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlBoolArray) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlBoolArray) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[bool](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlBoolArray) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlBoolArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[bool](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlBoolArray(v []bool) SqlBoolArray {
|
|
return SqlBoolArray{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlUUIDArray ─────────────────────────────────────────────────────────────
|
|
|
|
type SqlUUIDArray struct {
|
|
Val []uuid.UUID
|
|
Valid bool
|
|
}
|
|
|
|
func (a *SqlUUIDArray) Scan(value any) error {
|
|
if value == nil {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch v := value.(type) {
|
|
case string:
|
|
s = v
|
|
case []byte:
|
|
s = string(v)
|
|
default:
|
|
return fmt.Errorf("SqlUUIDArray: cannot scan type %T", value)
|
|
}
|
|
elems, err := parsePostgresArrayElements(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val = make([]uuid.UUID, len(elems))
|
|
for i, e := range elems {
|
|
u, err := uuid.Parse(strings.TrimSpace(e))
|
|
if err != nil {
|
|
return fmt.Errorf("SqlUUIDArray: element %d %q: %w", i, e, err)
|
|
}
|
|
a.Val[i] = u
|
|
}
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlUUIDArray) Value() (driver.Value, error) {
|
|
if !a.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(a.Val))
|
|
for i, v := range a.Val {
|
|
parts[i] = v.String()
|
|
}
|
|
return "{" + strings.Join(parts, ",") + "}", nil
|
|
}
|
|
|
|
func (a SqlUUIDArray) MarshalJSON() ([]byte, error) {
|
|
if !a.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(a.Val)
|
|
}
|
|
|
|
func (a *SqlUUIDArray) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
a.Valid = false
|
|
a.Val = nil
|
|
return nil
|
|
}
|
|
var vals []uuid.UUID
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
a.Val = vals
|
|
a.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (a SqlUUIDArray) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlUUIDArray) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[uuid.UUID](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (a SqlUUIDArray) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, a.Valid, a.Val)
|
|
}
|
|
|
|
func (a *SqlUUIDArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[uuid.UUID](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.Val, a.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlUUIDArray(v []uuid.UUID) SqlUUIDArray {
|
|
return SqlUUIDArray{Val: v, Valid: true}
|
|
}
|
|
|
|
// ── SqlVector ────────────────────────────────────────────────────────────────
|
|
|
|
// SqlVector is a nullable pgvector `vector` type backed by []float32.
|
|
// Wire format: `[1.0,2.0,3.0]` (square brackets, comma-separated floats).
|
|
type SqlVector struct {
|
|
Val []float32
|
|
Valid bool
|
|
}
|
|
|
|
func (v *SqlVector) Scan(value any) error {
|
|
if value == nil {
|
|
v.Valid = false
|
|
v.Val = nil
|
|
return nil
|
|
}
|
|
var s string
|
|
switch val := value.(type) {
|
|
case string:
|
|
s = val
|
|
case []byte:
|
|
s = string(val)
|
|
default:
|
|
return fmt.Errorf("SqlVector: cannot scan type %T", value)
|
|
}
|
|
s = strings.TrimSpace(s)
|
|
if !strings.HasPrefix(s, "[") || !strings.HasSuffix(s, "]") {
|
|
return fmt.Errorf("SqlVector: not a valid vector literal: %q", s)
|
|
}
|
|
inner := s[1 : len(s)-1]
|
|
if inner == "" {
|
|
v.Val = []float32{}
|
|
v.Valid = true
|
|
return nil
|
|
}
|
|
parts := strings.Split(inner, ",")
|
|
v.Val = make([]float32, len(parts))
|
|
for i, p := range parts {
|
|
f, err := strconv.ParseFloat(strings.TrimSpace(p), 32)
|
|
if err != nil {
|
|
return fmt.Errorf("SqlVector: element %d %q: %w", i, p, err)
|
|
}
|
|
v.Val[i] = float32(f)
|
|
}
|
|
v.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (v SqlVector) Value() (driver.Value, error) {
|
|
if !v.Valid {
|
|
return nil, nil
|
|
}
|
|
parts := make([]string, len(v.Val))
|
|
for i, f := range v.Val {
|
|
parts[i] = strconv.FormatFloat(float64(f), 'f', -1, 32)
|
|
}
|
|
return "[" + strings.Join(parts, ",") + "]", nil
|
|
}
|
|
|
|
func (v SqlVector) MarshalJSON() ([]byte, error) {
|
|
if !v.Valid {
|
|
return []byte("null"), nil
|
|
}
|
|
return json.Marshal(v.Val)
|
|
}
|
|
|
|
func (v *SqlVector) UnmarshalJSON(b []byte) error {
|
|
if strings.TrimSpace(string(b)) == "null" {
|
|
v.Valid = false
|
|
v.Val = nil
|
|
return nil
|
|
}
|
|
var vals []float32
|
|
if err := json.Unmarshal(b, &vals); err != nil {
|
|
return err
|
|
}
|
|
v.Val = vals
|
|
v.Valid = true
|
|
return nil
|
|
}
|
|
|
|
func (v SqlVector) MarshalYAML() (any, error) {
|
|
return marshalYAMLSlice(v.Valid, v.Val)
|
|
}
|
|
|
|
func (v *SqlVector) UnmarshalYAML(value *yaml.Node) error {
|
|
vals, valid, err := unmarshalYAMLSlice[float32](value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.Val, v.Valid = vals, valid
|
|
return nil
|
|
}
|
|
|
|
func (v SqlVector) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
return marshalXMLSlice(e, start, v.Valid, v.Val)
|
|
}
|
|
|
|
func (v *SqlVector) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
vals, err := unmarshalXMLSlice[float32](d, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.Val, v.Valid = vals, true
|
|
return nil
|
|
}
|
|
|
|
func NewSqlVector(val []float32) SqlVector {
|
|
return SqlVector{Val: val, Valid: true}
|
|
}
|