Merge branch 'pgtypeimport' into v5-dev
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# 1.9.1 (November 28, 2021)
|
||||
|
||||
* Fix: binary timestamp is assumed to be in UTC (restored behavior changed in v1.9.0)
|
||||
|
||||
# 1.9.0 (November 20, 2021)
|
||||
|
||||
* Fix binary hstore null decoding
|
||||
* Add shopspring/decimal.NullDecimal support to integration (Eli Treuherz)
|
||||
* Inet.Set supports bare IP address (Carl Dunham)
|
||||
* Add zeronull.Float8
|
||||
* Fix NULL being lost when scanning unknown OID into sql.Scanner
|
||||
* Fix BPChar.AssignTo **rune
|
||||
* Add support for fmt.Stringer and driver.Valuer in String fields encoding (Jan Dubsky)
|
||||
* Fix really big timestamp(tz)s binary format parsing (e.g. year 294276) (Jim Tsao)
|
||||
* Support `map[string]*string` as hstore (Adrian Sieger)
|
||||
* Fix parsing text array with negative bounds
|
||||
* Add infinity support for numeric (Jim Tsao)
|
||||
|
||||
# 1.8.1 (July 24, 2021)
|
||||
|
||||
* Cleaned up Go module dependency chain
|
||||
|
||||
# 1.8.0 (July 10, 2021)
|
||||
|
||||
* Maintain host bits for inet types (Cameron Daniel)
|
||||
* Support pointers of wrapping structs (Ivan Daunis)
|
||||
* Register JSONBArray at NewConnInfo() (Rueian)
|
||||
* CompositeTextScanner handles backslash escapes
|
||||
|
||||
# 1.7.0 (March 25, 2021)
|
||||
|
||||
* Fix scanning int into **sql.Scanner implementor
|
||||
* Add tsrange array type (Vasilii Novikov)
|
||||
* Fix: escaped strings when they start or end with a newline char (Stephane Martin)
|
||||
* Accept nil *time.Time in Time.Set
|
||||
* Fix numeric NaN support
|
||||
* Use Go 1.13 errors instead of xerrors
|
||||
|
||||
# 1.6.2 (December 3, 2020)
|
||||
|
||||
* Fix panic on assigning empty array to non-slice or array
|
||||
* Fix text array parsing disambiguates NULL and "NULL"
|
||||
* Fix Timestamptz.DecodeText with too short text
|
||||
|
||||
# 1.6.1 (October 31, 2020)
|
||||
|
||||
* Fix simple protocol empty array support
|
||||
|
||||
# 1.6.0 (October 24, 2020)
|
||||
|
||||
* Fix AssignTo pointer to pointer to slice and named types.
|
||||
* Fix zero length array assignment (Simo Haasanen)
|
||||
* Add float64, float32 convert to int2, int4, int8 (lqu3j)
|
||||
* Support setting infinite timestamps (Erik Agsjö)
|
||||
* Polygon improvements (duohedron)
|
||||
* Fix Inet.Set with nil (Tomas Volf)
|
||||
|
||||
# 1.5.0 (September 26, 2020)
|
||||
|
||||
* Add slice of slice mapping to multi-dimensional arrays (Simo Haasanen)
|
||||
* Fix JSONBArray
|
||||
* Fix selecting empty array
|
||||
* Text formatted values except bytea can be directly scanned to []byte
|
||||
* Add JSON marshalling for UUID (bakmataliev)
|
||||
* Improve point type conversions (bakmataliev)
|
||||
|
||||
# 1.4.2 (July 22, 2020)
|
||||
|
||||
* Fix encoding of a large composite data type (Yaz Saito)
|
||||
|
||||
# 1.4.1 (July 14, 2020)
|
||||
|
||||
* Fix ArrayType DecodeBinary empty array breaks future reads
|
||||
|
||||
# 1.4.0 (June 27, 2020)
|
||||
|
||||
* Add JSON support to ext/gofrs-uuid
|
||||
* Performance improvements in Scan path
|
||||
* Improved ext/shopspring-numeric binary decoding performance
|
||||
* Add composite type support (Maxim Ivanov and Jack Christensen)
|
||||
* Add better generic enum type support
|
||||
* Add generic array type support
|
||||
* Clarify and normalize Value semantics
|
||||
* Fix hstore with empty string values
|
||||
* Numeric supports NaN values (leighhopcroft)
|
||||
* Add slice of pointer support to array types (megaturbo)
|
||||
* Add jsonb array type (tserakhau)
|
||||
* Allow converting intervals with months and days to duration
|
||||
|
||||
# 1.3.0 (March 30, 2020)
|
||||
|
||||
* Get implemented on T instead of *T
|
||||
* Set will call Get on src if possible
|
||||
* Range types Set method supports its own type, string, and nil
|
||||
* Date.Set parses string
|
||||
* Fix correct format verb for unknown type error (Robert Welin)
|
||||
* Truncate nanoseconds in EncodeText for Timestamptz and Timestamp
|
||||
|
||||
# 1.2.0 (February 5, 2020)
|
||||
|
||||
* Add zeronull package for easier NULL <-> zero conversion
|
||||
* Add JSON marshalling for shopspring-numeric extension
|
||||
* Add JSON marshalling for Bool, Date, JSON/B, Timestamptz (Jeffrey Stiles)
|
||||
* Fix null status in UnmarshalJSON for some types (Jeffrey Stiles)
|
||||
|
||||
# 1.1.0 (January 11, 2020)
|
||||
|
||||
* Add PostgreSQL time type support
|
||||
* Add more automatic conversions of integer arrays of different types (Jean-Philippe Quéméner)
|
||||
|
||||
# 1.0.3 (November 16, 2019)
|
||||
|
||||
* Support initializing Array types from a slice of the value (Alex Gaynor)
|
||||
|
||||
# 1.0.2 (October 22, 2019)
|
||||
|
||||
* Fix scan into null into pointer to pointer implementing Decode* interface. (Jeremy Altavilla)
|
||||
|
||||
# 1.0.1 (September 19, 2019)
|
||||
|
||||
* Fix daterange OID
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013-2021 Jack Christensen
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,8 @@
|
||||
[](https://godoc.org/github.com/jackc/pgtype)
|
||||

|
||||
|
||||
# pgtype
|
||||
|
||||
pgtype implements Go types for over 70 PostgreSQL types. pgtype is the type system underlying the
|
||||
https://github.com/jackc/pgx PostgreSQL driver. These types support the binary format for enhanced performance with pgx.
|
||||
They also support the database/sql `Scan` and `Value` interfaces.
|
||||
@@ -0,0 +1,127 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ACLItem is used for PostgreSQL's aclitem data type. A sample aclitem
|
||||
// might look like this:
|
||||
//
|
||||
// postgres=arwdDxt/postgres
|
||||
//
|
||||
// Note, however, that because the user/role name part of an aclitem is
|
||||
// an identifier, it follows all the usual formatting rules for SQL
|
||||
// identifiers: if it contains spaces and other special characters,
|
||||
// it should appear in double-quotes:
|
||||
//
|
||||
// postgres=arwdDxt/"role with spaces"
|
||||
//
|
||||
type ACLItem struct {
|
||||
String string
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *ACLItem) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = ACLItem{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case string:
|
||||
*dst = ACLItem{String: value, Valid: true}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = ACLItem{}
|
||||
} else {
|
||||
*dst = ACLItem{String: *value, Valid: true}
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingStringType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to ACLItem", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst ACLItem) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.String
|
||||
}
|
||||
|
||||
func (src *ACLItem) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *string:
|
||||
*v = src.String
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot decode %#v into %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *ACLItem) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = ACLItem{}
|
||||
return nil
|
||||
}
|
||||
|
||||
*dst = ACLItem{String: string(src), Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src ACLItem) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, src.String...), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *ACLItem) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = ACLItem{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src ACLItem) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return src.String, nil
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type ACLItemArray struct {
|
||||
Elements []ACLItem
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *ACLItemArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = ACLItemArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []string:
|
||||
if value == nil {
|
||||
*dst = ACLItemArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = ACLItemArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]ACLItem, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = ACLItemArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*string:
|
||||
if value == nil {
|
||||
*dst = ACLItemArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = ACLItemArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]ACLItem, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = ACLItemArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []ACLItem:
|
||||
if value == nil {
|
||||
*dst = ACLItemArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = ACLItemArray{Valid: true}
|
||||
} else {
|
||||
*dst = ACLItemArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = ACLItemArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for ACLItemArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = ACLItemArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to ACLItemArray", src)
|
||||
}
|
||||
|
||||
*dst = ACLItemArray{
|
||||
Elements: make([]ACLItem, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]ACLItem, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to ACLItemArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *ACLItemArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to ACLItemArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in ACLItemArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst ACLItemArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *ACLItemArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]string:
|
||||
*v = make([]string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*string:
|
||||
*v = make([]*string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *ACLItemArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from ACLItemArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from ACLItemArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *ACLItemArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = ACLItemArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []ACLItem
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]ACLItem, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem ACLItem
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = ACLItemArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src ACLItemArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *ACLItemArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src ACLItemArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestACLItemArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "aclitem[]", []interface{}{
|
||||
&pgtype.ACLItemArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.ACLItemArray{},
|
||||
&pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
//{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true},
|
||||
{String: `postgres=arwdDxt/postgres`, Valid: true}, // todo: remove after fixing above case
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestACLItemArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.ACLItemArray
|
||||
}{
|
||||
{
|
||||
source: []string{"=r/postgres"},
|
||||
result: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{{String: "=r/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]string)(nil)),
|
||||
result: pgtype.ACLItemArray{},
|
||||
},
|
||||
{
|
||||
source: [][]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}},
|
||||
result: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]string{
|
||||
{{{
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres"}}},
|
||||
{{{
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres"}}}},
|
||||
result: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}},
|
||||
result: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]string{
|
||||
{{{
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres"}}},
|
||||
{{{
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres"}}}},
|
||||
result: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.ACLItemArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACLItemArrayAssignTo(t *testing.T) {
|
||||
var stringSlice []string
|
||||
type _stringSlice []string
|
||||
var namedStringSlice _stringSlice
|
||||
var stringSliceDim2 [][]string
|
||||
var stringSliceDim4 [][][][]string
|
||||
var stringArrayDim2 [2][1]string
|
||||
var stringArrayDim4 [2][1][1][3]string
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.ACLItemArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{{String: "=r/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &stringSlice,
|
||||
expected: []string{"=r/postgres"},
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{{String: "=r/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedStringSlice,
|
||||
expected: _stringSlice{"=r/postgres"},
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{},
|
||||
dst: &stringSlice,
|
||||
expected: (([]string)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{Valid: true},
|
||||
dst: &stringSlice,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &stringSliceDim2,
|
||||
expected: [][]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &stringSliceDim4,
|
||||
expected: [][][][]string{
|
||||
{{{
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres"}}},
|
||||
{{{
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres"}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim2,
|
||||
expected: [2][1]string{{"=r/postgres"}, {"postgres=arwdDxt/postgres"}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim4,
|
||||
expected: [2][1][1][3]string{
|
||||
{{{
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres"}}},
|
||||
{{{
|
||||
"postgres=arwdDxt/postgres",
|
||||
"=r/postgres",
|
||||
"postgres=arwdDxt/postgres"}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.ACLItemArray
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &stringSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &stringSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.ACLItemArray{
|
||||
Elements: []pgtype.ACLItem{
|
||||
{String: "=r/postgres", Valid: true},
|
||||
{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestACLItemTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "aclitem", []interface{}{
|
||||
&pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true},
|
||||
//&pgtype.ACLItem{String: `postgres=arwdDxt/" tricky, ' } "" \ test user "`, Valid: true},
|
||||
&pgtype.ACLItem{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestACLItemSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.ACLItem
|
||||
}{
|
||||
{source: "postgres=arwdDxt/postgres", result: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}},
|
||||
{source: (*string)(nil), result: pgtype.ACLItem{}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var d pgtype.ACLItem
|
||||
err := d.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if d != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestACLItemAssignTo(t *testing.T) {
|
||||
var s string
|
||||
var ps *string
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.ACLItem
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}, dst: &s, expected: "postgres=arwdDxt/postgres"},
|
||||
{src: pgtype.ACLItem{}, dst: &ps, expected: ((*string)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.ACLItem
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.ACLItem{String: "postgres=arwdDxt/postgres", Valid: true}, dst: &ps, expected: "postgres=arwdDxt/postgres"},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.ACLItem
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.ACLItem{}, dst: &s},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
// Information on the internals of PostgreSQL arrays can be found in
|
||||
// src/include/utils/array.h and src/backend/utils/adt/arrayfuncs.c. Of
|
||||
// particular interest is the array_send function.
|
||||
|
||||
type ArrayHeader struct {
|
||||
ContainsNull bool
|
||||
ElementOID int32
|
||||
Dimensions []ArrayDimension
|
||||
}
|
||||
|
||||
type ArrayDimension struct {
|
||||
Length int32
|
||||
LowerBound int32
|
||||
}
|
||||
|
||||
func (dst *ArrayHeader) DecodeBinary(ci *ConnInfo, src []byte) (int, error) {
|
||||
if len(src) < 12 {
|
||||
return 0, fmt.Errorf("array header too short: %d", len(src))
|
||||
}
|
||||
|
||||
rp := 0
|
||||
|
||||
numDims := int(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
|
||||
dst.ContainsNull = binary.BigEndian.Uint32(src[rp:]) == 1
|
||||
rp += 4
|
||||
|
||||
dst.ElementOID = int32(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
|
||||
if numDims > 0 {
|
||||
dst.Dimensions = make([]ArrayDimension, numDims)
|
||||
}
|
||||
if len(src) < 12+numDims*8 {
|
||||
return 0, fmt.Errorf("array header too short for %d dimensions: %d", numDims, len(src))
|
||||
}
|
||||
for i := range dst.Dimensions {
|
||||
dst.Dimensions[i].Length = int32(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
|
||||
dst.Dimensions[i].LowerBound = int32(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
}
|
||||
|
||||
return rp, nil
|
||||
}
|
||||
|
||||
func (src ArrayHeader) EncodeBinary(ci *ConnInfo, buf []byte) []byte {
|
||||
buf = pgio.AppendInt32(buf, int32(len(src.Dimensions)))
|
||||
|
||||
var containsNull int32
|
||||
if src.ContainsNull {
|
||||
containsNull = 1
|
||||
}
|
||||
buf = pgio.AppendInt32(buf, containsNull)
|
||||
|
||||
buf = pgio.AppendInt32(buf, src.ElementOID)
|
||||
|
||||
for i := range src.Dimensions {
|
||||
buf = pgio.AppendInt32(buf, src.Dimensions[i].Length)
|
||||
buf = pgio.AppendInt32(buf, src.Dimensions[i].LowerBound)
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
type UntypedTextArray struct {
|
||||
Elements []string
|
||||
Quoted []bool
|
||||
Dimensions []ArrayDimension
|
||||
}
|
||||
|
||||
func ParseUntypedTextArray(src string) (*UntypedTextArray, error) {
|
||||
dst := &UntypedTextArray{}
|
||||
|
||||
buf := bytes.NewBufferString(src)
|
||||
|
||||
skipWhitespace(buf)
|
||||
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
var explicitDimensions []ArrayDimension
|
||||
|
||||
// Array has explicit dimensions
|
||||
if r == '[' {
|
||||
buf.UnreadRune()
|
||||
|
||||
for {
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
if r == '=' {
|
||||
break
|
||||
} else if r != '[' {
|
||||
return nil, fmt.Errorf("invalid array, expected '[' or '=' got %v", r)
|
||||
}
|
||||
|
||||
lower, err := arrayParseInteger(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
if r != ':' {
|
||||
return nil, fmt.Errorf("invalid array, expected ':' got %v", r)
|
||||
}
|
||||
|
||||
upper, err := arrayParseInteger(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
if r != ']' {
|
||||
return nil, fmt.Errorf("invalid array, expected ']' got %v", r)
|
||||
}
|
||||
|
||||
explicitDimensions = append(explicitDimensions, ArrayDimension{LowerBound: lower, Length: upper - lower + 1})
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r != '{' {
|
||||
return nil, fmt.Errorf("invalid array, expected '{': %v", err)
|
||||
}
|
||||
|
||||
implicitDimensions := []ArrayDimension{{LowerBound: 1, Length: 0}}
|
||||
|
||||
// Consume all initial opening brackets. This provides number of dimensions.
|
||||
for {
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
if r == '{' {
|
||||
implicitDimensions[len(implicitDimensions)-1].Length = 1
|
||||
implicitDimensions = append(implicitDimensions, ArrayDimension{LowerBound: 1})
|
||||
} else {
|
||||
buf.UnreadRune()
|
||||
break
|
||||
}
|
||||
}
|
||||
currentDim := len(implicitDimensions) - 1
|
||||
counterDim := currentDim
|
||||
|
||||
for {
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array: %v", err)
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '{':
|
||||
if currentDim == counterDim {
|
||||
implicitDimensions[currentDim].Length++
|
||||
}
|
||||
currentDim++
|
||||
case ',':
|
||||
case '}':
|
||||
currentDim--
|
||||
if currentDim < counterDim {
|
||||
counterDim = currentDim
|
||||
}
|
||||
default:
|
||||
buf.UnreadRune()
|
||||
value, quoted, err := arrayParseValue(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid array value: %v", err)
|
||||
}
|
||||
if currentDim == counterDim {
|
||||
implicitDimensions[currentDim].Length++
|
||||
}
|
||||
dst.Quoted = append(dst.Quoted, quoted)
|
||||
dst.Elements = append(dst.Elements, value)
|
||||
}
|
||||
|
||||
if currentDim < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
skipWhitespace(buf)
|
||||
|
||||
if buf.Len() > 0 {
|
||||
return nil, fmt.Errorf("unexpected trailing data: %v", buf.String())
|
||||
}
|
||||
|
||||
if len(dst.Elements) == 0 {
|
||||
dst.Dimensions = nil
|
||||
} else if len(explicitDimensions) > 0 {
|
||||
dst.Dimensions = explicitDimensions
|
||||
} else {
|
||||
dst.Dimensions = implicitDimensions
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func skipWhitespace(buf *bytes.Buffer) {
|
||||
var r rune
|
||||
var err error
|
||||
for r, _, _ = buf.ReadRune(); unicode.IsSpace(r); r, _, _ = buf.ReadRune() {
|
||||
}
|
||||
|
||||
if err != io.EOF {
|
||||
buf.UnreadRune()
|
||||
}
|
||||
}
|
||||
|
||||
func arrayParseValue(buf *bytes.Buffer) (string, bool, error) {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if r == '"' {
|
||||
return arrayParseQuotedValue(buf)
|
||||
}
|
||||
buf.UnreadRune()
|
||||
|
||||
s := &bytes.Buffer{}
|
||||
|
||||
for {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case ',', '}':
|
||||
buf.UnreadRune()
|
||||
return s.String(), false, nil
|
||||
}
|
||||
|
||||
s.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
func arrayParseQuotedValue(buf *bytes.Buffer) (string, bool, error) {
|
||||
s := &bytes.Buffer{}
|
||||
|
||||
for {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '\\':
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
case '"':
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
buf.UnreadRune()
|
||||
return s.String(), true, nil
|
||||
}
|
||||
s.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
func arrayParseInteger(buf *bytes.Buffer) (int32, error) {
|
||||
s := &bytes.Buffer{}
|
||||
|
||||
for {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if ('0' <= r && r <= '9') || r == '-' {
|
||||
s.WriteRune(r)
|
||||
} else {
|
||||
buf.UnreadRune()
|
||||
n, err := strconv.ParseInt(s.String(), 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(n), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func EncodeTextArrayDimensions(buf []byte, dimensions []ArrayDimension) []byte {
|
||||
var customDimensions bool
|
||||
for _, dim := range dimensions {
|
||||
if dim.LowerBound != 1 {
|
||||
customDimensions = true
|
||||
}
|
||||
}
|
||||
|
||||
if !customDimensions {
|
||||
return buf
|
||||
}
|
||||
|
||||
for _, dim := range dimensions {
|
||||
buf = append(buf, '[')
|
||||
buf = append(buf, strconv.FormatInt(int64(dim.LowerBound), 10)...)
|
||||
buf = append(buf, ':')
|
||||
buf = append(buf, strconv.FormatInt(int64(dim.LowerBound+dim.Length-1), 10)...)
|
||||
buf = append(buf, ']')
|
||||
}
|
||||
|
||||
return append(buf, '=')
|
||||
}
|
||||
|
||||
var quoteArrayReplacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`)
|
||||
|
||||
func quoteArrayElement(src string) string {
|
||||
return `"` + quoteArrayReplacer.Replace(src) + `"`
|
||||
}
|
||||
|
||||
func isSpace(ch byte) bool {
|
||||
// see https://github.com/postgres/postgres/blob/REL_12_STABLE/src/backend/parser/scansup.c#L224
|
||||
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f'
|
||||
}
|
||||
|
||||
func QuoteArrayElementIfNeeded(src string) string {
|
||||
if src == "" || (len(src) == 4 && strings.ToLower(src) == "null") || isSpace(src[0]) || isSpace(src[len(src)-1]) || strings.ContainsAny(src, `{},"\`) {
|
||||
return quoteArrayElement(src)
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func findDimensionsFromValue(value reflect.Value, dimensions []ArrayDimension, elementsLength int) ([]ArrayDimension, int, bool) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
length := value.Len()
|
||||
if 0 == elementsLength {
|
||||
elementsLength = length
|
||||
} else {
|
||||
elementsLength *= length
|
||||
}
|
||||
dimensions = append(dimensions, ArrayDimension{Length: int32(length), LowerBound: 1})
|
||||
for i := 0; i < length; i++ {
|
||||
if d, l, ok := findDimensionsFromValue(value.Index(i), dimensions, elementsLength); ok {
|
||||
return d, l, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return dimensions, elementsLength, true
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseUntypedTextArray(t *testing.T) {
|
||||
tests := []struct {
|
||||
source string
|
||||
result pgtype.UntypedTextArray
|
||||
}{
|
||||
{
|
||||
source: "{}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: nil,
|
||||
Quoted: nil,
|
||||
Dimensions: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "{1}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"1"},
|
||||
Quoted: []bool{false},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "{a,b}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"a", "b"},
|
||||
Quoted: []bool{false, false},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: `{"NULL"}`,
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"NULL"},
|
||||
Quoted: []bool{true},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: `{""}`,
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{""},
|
||||
Quoted: []bool{true},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: `{"He said, \"Hello.\""}`,
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{`He said, "Hello."`},
|
||||
Quoted: []bool{true},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "{{a,b},{c,d},{e,f}}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"a", "b", "c", "d", "e", "f"},
|
||||
Quoted: []bool{false, false, false, false, false, false},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "{{{a,b},{c,d},{e,f}},{{a,b},{c,d},{e,f}}}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"a", "b", "c", "d", "e", "f", "a", "b", "c", "d", "e", "f"},
|
||||
Quoted: []bool{false, false, false, false, false, false, false, false, false, false, false, false},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 1},
|
||||
{Length: 3, LowerBound: 1},
|
||||
{Length: 2, LowerBound: 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "[4:4]={1}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"1"},
|
||||
Quoted: []bool{false},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 4}},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "[4:5][2:3]={{a,b},{c,d}}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"a", "b", "c", "d"},
|
||||
Quoted: []bool{false, false, false, false},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "[-4:-2]={1,2,3}",
|
||||
result: pgtype.UntypedTextArray{
|
||||
Elements: []string{"1", "2", "3"},
|
||||
Quoted: []bool{false, false, false},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: -4}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r, err := pgtype.ParseUntypedTextArray(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*r, tt.result) {
|
||||
t.Errorf("%d: expected %+v to be parsed to %+v, but it was %+v", i, tt.source, tt.result, *r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/jackc/pgx/issues/881
|
||||
func TestArrayAssignToEmptyToNonSlice(t *testing.T) {
|
||||
var a pgtype.Int4Array
|
||||
err := a.Set([]int32{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var iface interface{}
|
||||
err = a.AssignTo(&iface)
|
||||
require.EqualError(t, err, "cannot assign *pgtype.Int4Array to *interface {}")
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
// ArrayType represents an array type. While it implements Value, this is only in service of its type conversion duties
|
||||
// when registered as a data type in a ConnType. It should not be used directly as a Value. ArrayType is a convenience
|
||||
// type for types that do not have an concrete array type.
|
||||
type ArrayType struct {
|
||||
elements []ValueTranscoder
|
||||
dimensions []ArrayDimension
|
||||
|
||||
typeName string
|
||||
newElement func() ValueTranscoder
|
||||
|
||||
elementOID uint32
|
||||
valid bool
|
||||
}
|
||||
|
||||
func NewArrayType(typeName string, elementOID uint32, newElement func() ValueTranscoder) *ArrayType {
|
||||
return &ArrayType{typeName: typeName, elementOID: elementOID, newElement: newElement}
|
||||
}
|
||||
|
||||
func (at *ArrayType) NewTypeValue() Value {
|
||||
return &ArrayType{
|
||||
elements: at.elements,
|
||||
dimensions: at.dimensions,
|
||||
valid: at.valid,
|
||||
|
||||
typeName: at.typeName,
|
||||
elementOID: at.elementOID,
|
||||
newElement: at.newElement,
|
||||
}
|
||||
}
|
||||
|
||||
func (at *ArrayType) TypeName() string {
|
||||
return at.typeName
|
||||
}
|
||||
|
||||
func (dst *ArrayType) setNil() {
|
||||
dst.elements = nil
|
||||
dst.dimensions = nil
|
||||
dst.valid = false
|
||||
}
|
||||
|
||||
func (dst *ArrayType) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
dst.setNil()
|
||||
return nil
|
||||
}
|
||||
|
||||
sliceVal := reflect.ValueOf(src)
|
||||
if sliceVal.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("cannot set non-slice")
|
||||
}
|
||||
|
||||
if sliceVal.IsNil() {
|
||||
dst.setNil()
|
||||
return nil
|
||||
}
|
||||
|
||||
dst.elements = make([]ValueTranscoder, sliceVal.Len())
|
||||
for i := range dst.elements {
|
||||
v := dst.newElement()
|
||||
err := v.Set(sliceVal.Index(i).Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst.elements[i] = v
|
||||
}
|
||||
dst.dimensions = []ArrayDimension{{Length: int32(len(dst.elements)), LowerBound: 1}}
|
||||
dst.valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src ArrayType) Get() interface{} {
|
||||
if !src.valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
elementValues := make([]interface{}, len(src.elements))
|
||||
for i := range src.elements {
|
||||
elementValues[i] = src.elements[i].Get()
|
||||
}
|
||||
return elementValues
|
||||
}
|
||||
|
||||
func (src *ArrayType) AssignTo(dst interface{}) error {
|
||||
ptrSlice := reflect.ValueOf(dst)
|
||||
if ptrSlice.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("cannot assign to non-pointer")
|
||||
}
|
||||
|
||||
sliceVal := ptrSlice.Elem()
|
||||
sliceType := sliceVal.Type()
|
||||
|
||||
if sliceType.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("cannot assign to pointer to non-slice")
|
||||
}
|
||||
|
||||
if src.valid {
|
||||
slice := reflect.MakeSlice(sliceType, len(src.elements), len(src.elements))
|
||||
elemType := sliceType.Elem()
|
||||
|
||||
for i := range src.elements {
|
||||
ptrElem := reflect.New(elemType)
|
||||
err := src.elements[i].AssignTo(ptrElem.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slice.Index(i).Set(ptrElem.Elem())
|
||||
}
|
||||
|
||||
sliceVal.Set(slice)
|
||||
return nil
|
||||
} else {
|
||||
sliceVal.Set(reflect.Zero(sliceType))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ArrayType) BinaryFormatSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ArrayType) TextFormatSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ArrayType) PreferredFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (dst *ArrayType) DecodeResult(ci *ConnInfo, oid uint32, format int16, src []byte) error {
|
||||
if src == nil {
|
||||
dst.setNil()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case BinaryFormatCode:
|
||||
return dst.DecodeBinary(ci, src)
|
||||
case TextFormatCode:
|
||||
return dst.DecodeText(ci, src)
|
||||
}
|
||||
return fmt.Errorf("unknown format code %d", format)
|
||||
}
|
||||
|
||||
func (src ArrayType) EncodeParam(ci *ConnInfo, oid uint32, format int16, buf []byte) (newBuf []byte, err error) {
|
||||
switch format {
|
||||
case BinaryFormatCode:
|
||||
return src.EncodeBinary(ci, buf)
|
||||
case TextFormatCode:
|
||||
return src.EncodeText(ci, buf)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown format code %d", format)
|
||||
}
|
||||
|
||||
func (dst *ArrayType) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []ValueTranscoder
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]ValueTranscoder, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
elem := dst.newElement()
|
||||
var elemSrc []byte
|
||||
if s != "NULL" {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeResult(ci, dst.elementOID, TextFormatCode, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
dst.elements = elements
|
||||
dst.dimensions = uta.Dimensions
|
||||
dst.valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *ArrayType) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []ValueTranscoder
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
dst.elements = elements
|
||||
dst.dimensions = arrayHeader.Dimensions
|
||||
dst.valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements = make([]ValueTranscoder, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elem := dst.newElement()
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elem.DecodeResult(ci, dst.elementOID, BinaryFormatCode, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
|
||||
dst.elements = elements
|
||||
dst.dimensions = arrayHeader.Dimensions
|
||||
dst.valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src ArrayType) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.dimensions))
|
||||
dimElemCounts[len(src.dimensions)-1] = int(src.dimensions[len(src.dimensions)-1].Length)
|
||||
for i := len(src.dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeParam(ci, src.elementOID, TextFormatCode, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src ArrayType) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.dimensions,
|
||||
ElementOID: int32(src.elementOID),
|
||||
}
|
||||
|
||||
for i := range src.elements {
|
||||
if src.elements[i].Get() == nil {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.elements[i].EncodeParam(ci, src.elementOID, BinaryFormatCode, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *ArrayType) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src ArrayType) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestArrayTypeValue(t *testing.T) {
|
||||
arrayType := pgtype.NewArrayType("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} })
|
||||
|
||||
err := arrayType.Set(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotValue := arrayType.Get()
|
||||
require.Nil(t, gotValue)
|
||||
|
||||
slice := []string{"foo", "bar"}
|
||||
err = arrayType.AssignTo(&slice)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, slice)
|
||||
|
||||
err = arrayType.Set([]string{})
|
||||
require.NoError(t, err)
|
||||
|
||||
gotValue = arrayType.Get()
|
||||
require.Len(t, gotValue, 0)
|
||||
|
||||
err = arrayType.AssignTo(&slice)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, []string{}, slice)
|
||||
|
||||
err = arrayType.Set([]string{"baz", "quz"})
|
||||
require.NoError(t, err)
|
||||
|
||||
gotValue = arrayType.Get()
|
||||
require.Len(t, gotValue, 2)
|
||||
|
||||
err = arrayType.AssignTo(&slice)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, []string{"baz", "quz"}, slice)
|
||||
}
|
||||
|
||||
func TestArrayTypeTranscode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{
|
||||
Value: pgtype.NewArrayType("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }),
|
||||
Name: "_text",
|
||||
OID: pgtype.TextArrayOID,
|
||||
})
|
||||
|
||||
var dstStrings []string
|
||||
err := conn.QueryRow(context.Background(), "select $1::text[]", []string{"red", "green", "blue"}).Scan(&dstStrings)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, []string{"red", "green", "blue"}, dstStrings)
|
||||
}
|
||||
|
||||
func TestArrayTypeEmptyArrayDoesNotBreakArrayType(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{
|
||||
Value: pgtype.NewArrayType("_text", pgtype.TextOID, func() pgtype.ValueTranscoder { return &pgtype.Text{} }),
|
||||
Name: "_text",
|
||||
OID: pgtype.TextArrayOID,
|
||||
})
|
||||
|
||||
var dstStrings []string
|
||||
err := conn.QueryRow(context.Background(), "select '{}'::text[]").Scan(&dstStrings)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, []string{}, dstStrings)
|
||||
|
||||
err = conn.QueryRow(context.Background(), "select $1::text[]", []string{"red", "green", "blue"}).Scan(&dstStrings)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, []string{"red", "green", "blue"}, dstStrings)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
type Bit Varbit
|
||||
|
||||
func (dst *Bit) Set(src interface{}) error {
|
||||
return (*Varbit)(dst).Set(src)
|
||||
}
|
||||
|
||||
func (dst Bit) Get() interface{} {
|
||||
return (Varbit)(dst).Get()
|
||||
}
|
||||
|
||||
func (src *Bit) AssignTo(dst interface{}) error {
|
||||
return (*Varbit)(src).AssignTo(dst)
|
||||
}
|
||||
|
||||
func (dst *Bit) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return (*Varbit)(dst).DecodeBinary(ci, src)
|
||||
}
|
||||
|
||||
func (src Bit) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Varbit)(src).EncodeBinary(ci, buf)
|
||||
}
|
||||
|
||||
func (dst *Bit) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
return (*Varbit)(dst).DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (src Bit) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Varbit)(src).EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Bit) Scan(src interface{}) error {
|
||||
return (*Varbit)(dst).Scan(src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Bit) Value() (driver.Value, error) {
|
||||
return (Varbit)(src).Value()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestBitTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "bit(40)", []interface{}{
|
||||
&pgtype.Varbit{Bytes: []byte{0, 0, 0, 0, 0}, Len: 40, Valid: true},
|
||||
&pgtype.Varbit{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Valid: true},
|
||||
&pgtype.Varbit{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBitNormalize(t *testing.T) {
|
||||
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
|
||||
{
|
||||
SQL: "select B'111111111'",
|
||||
Value: &pgtype.Bit{Bytes: []byte{255, 128}, Len: 9, Valid: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Bool struct {
|
||||
Bool bool
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Bool) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Bool{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case bool:
|
||||
*dst = Bool{Bool: value, Valid: true}
|
||||
case string:
|
||||
bb, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Bool{Bool: bb, Valid: true}
|
||||
case *bool:
|
||||
if value == nil {
|
||||
*dst = Bool{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Bool{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingBoolType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Bool", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Bool) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
return dst.Bool
|
||||
}
|
||||
|
||||
func (src *Bool) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *bool:
|
||||
*v = src.Bool
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (dst *Bool) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Bool{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 1 {
|
||||
return fmt.Errorf("invalid length for bool: %v", len(src))
|
||||
}
|
||||
|
||||
*dst = Bool{Bool: src[0] == 't', Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Bool) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Bool{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 1 {
|
||||
return fmt.Errorf("invalid length for bool: %v", len(src))
|
||||
}
|
||||
|
||||
*dst = Bool{Bool: src[0] == 1, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Bool) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if src.Bool {
|
||||
buf = append(buf, 't')
|
||||
} else {
|
||||
buf = append(buf, 'f')
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Bool) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if src.Bool {
|
||||
buf = append(buf, 1)
|
||||
} else {
|
||||
buf = append(buf, 0)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Bool) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Bool{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case bool:
|
||||
*dst = Bool{Bool: src, Valid: true}
|
||||
return nil
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Bool) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return src.Bool, nil
|
||||
}
|
||||
|
||||
func (src Bool) MarshalJSON() ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
if src.Bool {
|
||||
return []byte("true"), nil
|
||||
} else {
|
||||
return []byte("false"), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (dst *Bool) UnmarshalJSON(b []byte) error {
|
||||
var v *bool
|
||||
err := json.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
*dst = Bool{}
|
||||
} else {
|
||||
*dst = Bool{Bool: *v, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type BoolArray struct {
|
||||
Elements []Bool
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *BoolArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = BoolArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []bool:
|
||||
if value == nil {
|
||||
*dst = BoolArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = BoolArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Bool, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = BoolArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*bool:
|
||||
if value == nil {
|
||||
*dst = BoolArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = BoolArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Bool, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = BoolArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Bool:
|
||||
if value == nil {
|
||||
*dst = BoolArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = BoolArray{Valid: true}
|
||||
} else {
|
||||
*dst = BoolArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = BoolArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for BoolArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = BoolArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to BoolArray", src)
|
||||
}
|
||||
|
||||
*dst = BoolArray{
|
||||
Elements: make([]Bool, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Bool, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to BoolArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *BoolArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to BoolArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in BoolArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst BoolArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *BoolArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]bool:
|
||||
*v = make([]bool, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*bool:
|
||||
*v = make([]*bool, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *BoolArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from BoolArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from BoolArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *BoolArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = BoolArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Bool
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Bool, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Bool
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = BoolArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *BoolArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = BoolArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = BoolArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Bool, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = BoolArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src BoolArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src BoolArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("bool"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "bool")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *BoolArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src BoolArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestBoolArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "bool[]", []interface{}{
|
||||
&pgtype.BoolArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.BoolArray{},
|
||||
&pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{},
|
||||
{Bool: false, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBoolArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.BoolArray
|
||||
}{
|
||||
{
|
||||
source: []bool{true},
|
||||
result: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]bool)(nil)),
|
||||
result: pgtype.BoolArray{},
|
||||
},
|
||||
{
|
||||
source: [][]bool{{true}, {false}},
|
||||
result: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]bool{{{{true, false, true}}}, {{{false, true, false}}}},
|
||||
result: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]bool{{true}, {false}},
|
||||
result: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]bool{{{{true, false, true}}}, {{{false, true, false}}}},
|
||||
result: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.BoolArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolArrayAssignTo(t *testing.T) {
|
||||
var boolSlice []bool
|
||||
type _boolSlice []bool
|
||||
var namedBoolSlice _boolSlice
|
||||
var boolSliceDim2 [][]bool
|
||||
var boolSliceDim4 [][][][]bool
|
||||
var boolArrayDim2 [2][1]bool
|
||||
var boolArrayDim4 [2][1][1][3]bool
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.BoolArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &boolSlice,
|
||||
expected: []bool{true},
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedBoolSlice,
|
||||
expected: _boolSlice{true},
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{},
|
||||
dst: &boolSlice,
|
||||
expected: (([]bool)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{Valid: true},
|
||||
dst: &boolSlice,
|
||||
expected: []bool{},
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [][]bool{{true}, {false}},
|
||||
dst: &boolSliceDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [][][][]bool{{{{true, false, true}}}, {{{false, true, false}}}},
|
||||
dst: &boolSliceDim4,
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [2][1]bool{{true}, {false}},
|
||||
dst: &boolArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true},
|
||||
{Bool: true, Valid: true},
|
||||
{Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [2][1][1][3]bool{{{{true, false, true}}}, {{{false, true, false}}}},
|
||||
dst: &boolArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.BoolArray
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &boolSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &boolArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &boolSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.BoolArray{
|
||||
Elements: []pgtype.Bool{{Bool: true, Valid: true}, {Bool: false, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &boolArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestBoolTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "bool", []interface{}{
|
||||
&pgtype.Bool{Bool: false, Valid: true},
|
||||
&pgtype.Bool{Bool: true, Valid: true},
|
||||
&pgtype.Bool{Bool: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBoolSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Bool
|
||||
}{
|
||||
{source: true, result: pgtype.Bool{Bool: true, Valid: true}},
|
||||
{source: false, result: pgtype.Bool{Bool: false, Valid: true}},
|
||||
{source: "true", result: pgtype.Bool{Bool: true, Valid: true}},
|
||||
{source: "false", result: pgtype.Bool{Bool: false, Valid: true}},
|
||||
{source: "t", result: pgtype.Bool{Bool: true, Valid: true}},
|
||||
{source: "f", result: pgtype.Bool{Bool: false, Valid: true}},
|
||||
{source: _bool(true), result: pgtype.Bool{Bool: true, Valid: true}},
|
||||
{source: _bool(false), result: pgtype.Bool{Bool: false, Valid: true}},
|
||||
{source: nil, result: pgtype.Bool{}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Bool
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolAssignTo(t *testing.T) {
|
||||
var b bool
|
||||
var _b _bool
|
||||
var pb *bool
|
||||
var _pb *_bool
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Bool
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Bool{Bool: false, Valid: true}, dst: &b, expected: false},
|
||||
{src: pgtype.Bool{Bool: true, Valid: true}, dst: &b, expected: true},
|
||||
{src: pgtype.Bool{Bool: false, Valid: true}, dst: &_b, expected: _bool(false)},
|
||||
{src: pgtype.Bool{Bool: true, Valid: true}, dst: &_b, expected: _bool(true)},
|
||||
{src: pgtype.Bool{Bool: false}, dst: &pb, expected: ((*bool)(nil))},
|
||||
{src: pgtype.Bool{Bool: false}, dst: &_pb, expected: ((*_bool)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Bool
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Bool{Bool: true, Valid: true}, dst: &pb, expected: true},
|
||||
{src: pgtype.Bool{Bool: true, Valid: true}, dst: &_pb, expected: _bool(true)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolMarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source pgtype.Bool
|
||||
result string
|
||||
}{
|
||||
{source: pgtype.Bool{}, result: "null"},
|
||||
{source: pgtype.Bool{Bool: true, Valid: true}, result: "true"},
|
||||
{source: pgtype.Bool{Bool: false, Valid: true}, result: "false"},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
r, err := tt.source.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if string(r) != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolUnmarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source string
|
||||
result pgtype.Bool
|
||||
}{
|
||||
{source: "null", result: pgtype.Bool{}},
|
||||
{source: "true", result: pgtype.Bool{Bool: true, Valid: true}},
|
||||
{source: "false", result: pgtype.Bool{Bool: false, Valid: true}},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Bool
|
||||
err := r.UnmarshalJSON([]byte(tt.source))
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Box struct {
|
||||
P [2]Vec2
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Box) Set(src interface{}) error {
|
||||
return fmt.Errorf("cannot convert %v to Box", src)
|
||||
}
|
||||
|
||||
func (dst Box) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Box) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Box) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Box{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) < 11 {
|
||||
return fmt.Errorf("invalid length for Box: %v", len(src))
|
||||
}
|
||||
|
||||
str := string(src[1:])
|
||||
|
||||
var end int
|
||||
end = strings.IndexByte(str, ',')
|
||||
|
||||
x1, err := strconv.ParseFloat(str[:end], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str = str[end+1:]
|
||||
end = strings.IndexByte(str, ')')
|
||||
|
||||
y1, err := strconv.ParseFloat(str[:end], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str = str[end+3:]
|
||||
end = strings.IndexByte(str, ',')
|
||||
|
||||
x2, err := strconv.ParseFloat(str[:end], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str = str[end+1 : len(str)-1]
|
||||
|
||||
y2, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Box{P: [2]Vec2{{x1, y1}, {x2, y2}}, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Box) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Box{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 32 {
|
||||
return fmt.Errorf("invalid length for Box: %v", len(src))
|
||||
}
|
||||
|
||||
x1 := binary.BigEndian.Uint64(src)
|
||||
y1 := binary.BigEndian.Uint64(src[8:])
|
||||
x2 := binary.BigEndian.Uint64(src[16:])
|
||||
y2 := binary.BigEndian.Uint64(src[24:])
|
||||
|
||||
*dst = Box{
|
||||
P: [2]Vec2{
|
||||
{math.Float64frombits(x1), math.Float64frombits(y1)},
|
||||
{math.Float64frombits(x2), math.Float64frombits(y2)},
|
||||
},
|
||||
Valid: true,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Box) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, fmt.Sprintf(`(%s,%s),(%s,%s)`,
|
||||
strconv.FormatFloat(src.P[0].X, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.P[0].Y, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.P[1].X, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.P[1].Y, 'f', -1, 64),
|
||||
)...)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Box) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.P[0].X))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.P[0].Y))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.P[1].X))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.P[1].Y))
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Box) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Box{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Box) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestBoxTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "box", []interface{}{
|
||||
&pgtype.Box{
|
||||
P: [2]pgtype.Vec2{{7.1, 5.2345678}, {3.14, 1.678}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Box{
|
||||
P: [2]pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Box{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestBoxNormalize(t *testing.T) {
|
||||
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
|
||||
{
|
||||
SQL: "select '3.14, 1.678, 7.1, 5.234'::box",
|
||||
Value: &pgtype.Box{
|
||||
P: [2]pgtype.Vec2{{7.1, 5.234}, {3.14, 1.678}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// BPChar is fixed-length, blank padded char type
|
||||
// character(n), char(n)
|
||||
type BPChar Text
|
||||
|
||||
// Set converts from src to dst.
|
||||
func (dst *BPChar) Set(src interface{}) error {
|
||||
return (*Text)(dst).Set(src)
|
||||
}
|
||||
|
||||
// Get returns underlying value
|
||||
func (dst BPChar) Get() interface{} {
|
||||
return (Text)(dst).Get()
|
||||
}
|
||||
|
||||
// AssignTo assigns from src to dst.
|
||||
func (src *BPChar) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *rune:
|
||||
runes := []rune(src.String)
|
||||
if len(runes) == 1 {
|
||||
*v = runes[0]
|
||||
return nil
|
||||
}
|
||||
case *string:
|
||||
*v = src.String
|
||||
return nil
|
||||
case *[]byte:
|
||||
*v = make([]byte, len(src.String))
|
||||
copy(*v, src.String)
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot decode %#v into %T", src, dst)
|
||||
}
|
||||
|
||||
func (BPChar) PreferredResultFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (dst *BPChar) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
return (*Text)(dst).DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (dst *BPChar) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return (*Text)(dst).DecodeBinary(ci, src)
|
||||
}
|
||||
|
||||
func (BPChar) PreferredParamFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (src BPChar) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Text)(src).EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
func (src BPChar) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Text)(src).EncodeBinary(ci, buf)
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *BPChar) Scan(src interface{}) error {
|
||||
return (*Text)(dst).Scan(src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src BPChar) Value() (driver.Value, error) {
|
||||
return (Text)(src).Value()
|
||||
}
|
||||
|
||||
func (src BPChar) MarshalJSON() ([]byte, error) {
|
||||
return (Text)(src).MarshalJSON()
|
||||
}
|
||||
|
||||
func (dst *BPChar) UnmarshalJSON(b []byte) error {
|
||||
return (*Text)(dst).UnmarshalJSON(b)
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type BPCharArray struct {
|
||||
Elements []BPChar
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *BPCharArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = BPCharArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []string:
|
||||
if value == nil {
|
||||
*dst = BPCharArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = BPCharArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]BPChar, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = BPCharArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*string:
|
||||
if value == nil {
|
||||
*dst = BPCharArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = BPCharArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]BPChar, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = BPCharArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []BPChar:
|
||||
if value == nil {
|
||||
*dst = BPCharArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = BPCharArray{Valid: true}
|
||||
} else {
|
||||
*dst = BPCharArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = BPCharArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for BPCharArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = BPCharArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to BPCharArray", src)
|
||||
}
|
||||
|
||||
*dst = BPCharArray{
|
||||
Elements: make([]BPChar, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]BPChar, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to BPCharArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *BPCharArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to BPCharArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in BPCharArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst BPCharArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *BPCharArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]string:
|
||||
*v = make([]string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*string:
|
||||
*v = make([]*string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *BPCharArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from BPCharArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from BPCharArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *BPCharArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = BPCharArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []BPChar
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]BPChar, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem BPChar
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = BPCharArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *BPCharArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = BPCharArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = BPCharArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]BPChar, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = BPCharArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src BPCharArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src BPCharArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("bpchar"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "bpchar")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *BPCharArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src BPCharArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestBPCharArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "char(8)[]", []interface{}{
|
||||
&pgtype.BPCharArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.BPCharArray{
|
||||
Elements: []pgtype.BPChar{
|
||||
pgtype.BPChar{String: "foo ", Valid: true},
|
||||
pgtype.BPChar{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.BPCharArray{},
|
||||
&pgtype.BPCharArray{
|
||||
Elements: []pgtype.BPChar{
|
||||
pgtype.BPChar{String: "bar ", Valid: true},
|
||||
pgtype.BPChar{String: "NuLL ", Valid: true},
|
||||
pgtype.BPChar{String: `wow"quz\`, Valid: true},
|
||||
pgtype.BPChar{String: "1 ", Valid: true},
|
||||
pgtype.BPChar{String: "1 ", Valid: true},
|
||||
pgtype.BPChar{String: "null ", Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 3, LowerBound: 1},
|
||||
{Length: 2, LowerBound: 1},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.BPCharArray{
|
||||
Elements: []pgtype.BPChar{
|
||||
pgtype.BPChar{String: " bar ", Valid: true},
|
||||
pgtype.BPChar{String: " baz ", Valid: true},
|
||||
pgtype.BPChar{String: " quz ", Valid: true},
|
||||
pgtype.BPChar{String: "foo ", Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestChar3Transcode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscodeEqFunc(t, "char(3)", []interface{}{
|
||||
&pgtype.BPChar{String: "a ", Valid: true},
|
||||
&pgtype.BPChar{String: " a ", Valid: true},
|
||||
&pgtype.BPChar{String: "嗨 ", Valid: true},
|
||||
&pgtype.BPChar{String: " ", Valid: true},
|
||||
&pgtype.BPChar{},
|
||||
}, func(aa, bb interface{}) bool {
|
||||
a := aa.(pgtype.BPChar)
|
||||
b := bb.(pgtype.BPChar)
|
||||
|
||||
return a.Valid == b.Valid && a.String == b.String
|
||||
})
|
||||
}
|
||||
|
||||
func TestBPCharAssignTo(t *testing.T) {
|
||||
var (
|
||||
str string
|
||||
run rune
|
||||
)
|
||||
simpleTests := []struct {
|
||||
src pgtype.BPChar
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.BPChar{String: "simple", Valid: true}, dst: &str, expected: "simple"},
|
||||
{src: pgtype.BPChar{String: "嗨", Valid: true}, dst: &run, expected: '嗨'},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Bytea struct {
|
||||
Bytes []byte
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Bytea) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Bytea{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case []byte:
|
||||
if value != nil {
|
||||
*dst = Bytea{Bytes: value, Valid: true}
|
||||
} else {
|
||||
*dst = Bytea{}
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingBytesType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Bytea", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Bytea) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Bytes
|
||||
}
|
||||
|
||||
func (src *Bytea) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *[]byte:
|
||||
buf := make([]byte, len(src.Bytes))
|
||||
copy(buf, src.Bytes)
|
||||
*v = buf
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeText only supports the hex format. This has been the default since
|
||||
// PostgreSQL 9.0.
|
||||
func (dst *Bytea) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Bytea{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) < 2 || src[0] != '\\' || src[1] != 'x' {
|
||||
return fmt.Errorf("invalid hex format")
|
||||
}
|
||||
|
||||
buf := make([]byte, (len(src)-2)/2)
|
||||
_, err := hex.Decode(buf, src[2:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Bytea{Bytes: buf, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Bytea) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Bytea{}
|
||||
return nil
|
||||
}
|
||||
|
||||
*dst = Bytea{Bytes: src, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Bytea) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, hex.EncodeToString(src.Bytes)...)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Bytea) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, src.Bytes...), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Bytea) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Bytea{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
buf := make([]byte, len(src))
|
||||
copy(buf, src)
|
||||
*dst = Bytea{Bytes: buf, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Bytea) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return src.Bytes, nil
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type ByteaArray struct {
|
||||
Elements []Bytea
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *ByteaArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = ByteaArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case [][]byte:
|
||||
if value == nil {
|
||||
*dst = ByteaArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = ByteaArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Bytea, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = ByteaArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Bytea:
|
||||
if value == nil {
|
||||
*dst = ByteaArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = ByteaArray{Valid: true}
|
||||
} else {
|
||||
*dst = ByteaArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = ByteaArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for ByteaArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = ByteaArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to ByteaArray", src)
|
||||
}
|
||||
|
||||
*dst = ByteaArray{
|
||||
Elements: make([]Bytea, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Bytea, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to ByteaArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *ByteaArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to ByteaArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in ByteaArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst ByteaArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *ByteaArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[][]byte:
|
||||
*v = make([][]byte, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *ByteaArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from ByteaArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from ByteaArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *ByteaArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = ByteaArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Bytea
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Bytea, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Bytea
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = ByteaArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *ByteaArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = ByteaArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = ByteaArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Bytea, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = ByteaArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src ByteaArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src ByteaArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("bytea"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "bytea")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *ByteaArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src ByteaArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestByteaArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "bytea[]", []interface{}{
|
||||
&pgtype.ByteaArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.ByteaArray{},
|
||||
&pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{}, Valid: true},
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{},
|
||||
{Bytes: []byte{1}, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{}, Valid: true},
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{1}, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestByteaArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.ByteaArray
|
||||
}{
|
||||
{
|
||||
source: [][]byte{{1, 2, 3}},
|
||||
result: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{{Bytes: []byte{1, 2, 3}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([][]byte)(nil)),
|
||||
result: pgtype.ByteaArray{},
|
||||
},
|
||||
{
|
||||
source: [][][]byte{{{1}}, {{2}}},
|
||||
result: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{{Bytes: []byte{1}, Valid: true}, {Bytes: []byte{2}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][][]byte{{{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}}}, {{{{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}}},
|
||||
result: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{4, 5, 6}, Valid: true},
|
||||
{Bytes: []byte{7, 8, 9}, Valid: true},
|
||||
{Bytes: []byte{10, 11, 12}, Valid: true},
|
||||
{Bytes: []byte{13, 14, 15}, Valid: true},
|
||||
{Bytes: []byte{16, 17, 18}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][]byte{{{1}}, {{2}}},
|
||||
result: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{{Bytes: []byte{1}, Valid: true}, {Bytes: []byte{2}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3][]byte{{{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}}}, {{{{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}}},
|
||||
result: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{4, 5, 6}, Valid: true},
|
||||
{Bytes: []byte{7, 8, 9}, Valid: true},
|
||||
{Bytes: []byte{10, 11, 12}, Valid: true},
|
||||
{Bytes: []byte{13, 14, 15}, Valid: true},
|
||||
{Bytes: []byte{16, 17, 18}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.ByteaArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteaArrayAssignTo(t *testing.T) {
|
||||
var byteByteSlice [][]byte
|
||||
var byteByteSliceDim2 [][][]byte
|
||||
var byteByteSliceDim4 [][][][][]byte
|
||||
var byteByteArraySliceDim2 [2][1][]byte
|
||||
var byteByteArraySliceDim4 [2][1][1][3][]byte
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.ByteaArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{{Bytes: []byte{1, 2, 3}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &byteByteSlice,
|
||||
expected: [][]byte{{1, 2, 3}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ByteaArray{},
|
||||
dst: &byteByteSlice,
|
||||
expected: (([][]byte)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.ByteaArray{Valid: true},
|
||||
dst: &byteByteSlice,
|
||||
expected: [][]byte{},
|
||||
},
|
||||
{
|
||||
src: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{{Bytes: []byte{1}, Valid: true}, {Bytes: []byte{2}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &byteByteSliceDim2,
|
||||
expected: [][][]byte{{{1}}, {{2}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{4, 5, 6}, Valid: true},
|
||||
{Bytes: []byte{7, 8, 9}, Valid: true},
|
||||
{Bytes: []byte{10, 11, 12}, Valid: true},
|
||||
{Bytes: []byte{13, 14, 15}, Valid: true},
|
||||
{Bytes: []byte{16, 17, 18}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &byteByteSliceDim4,
|
||||
expected: [][][][][]byte{{{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}}}, {{{{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{{Bytes: []byte{1}, Valid: true}, {Bytes: []byte{2}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &byteByteArraySliceDim2,
|
||||
expected: [2][1][]byte{{{1}}, {{2}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.ByteaArray{
|
||||
Elements: []pgtype.Bytea{
|
||||
{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
{Bytes: []byte{4, 5, 6}, Valid: true},
|
||||
{Bytes: []byte{7, 8, 9}, Valid: true},
|
||||
{Bytes: []byte{10, 11, 12}, Valid: true},
|
||||
{Bytes: []byte{13, 14, 15}, Valid: true},
|
||||
{Bytes: []byte{16, 17, 18}, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &byteByteArraySliceDim4,
|
||||
expected: [2][1][1][3][]byte{{{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}}}, {{{{10, 11, 12}, {13, 14, 15}, {16, 17, 18}}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestByteaTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "bytea", []interface{}{
|
||||
&pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true},
|
||||
&pgtype.Bytea{Bytes: []byte{}, Valid: true},
|
||||
&pgtype.Bytea{Bytes: nil},
|
||||
})
|
||||
}
|
||||
|
||||
func TestByteaSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Bytea
|
||||
}{
|
||||
{source: []byte{1, 2, 3}, result: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true}},
|
||||
{source: []byte{}, result: pgtype.Bytea{Bytes: []byte{}, Valid: true}},
|
||||
{source: []byte(nil), result: pgtype.Bytea{}},
|
||||
{source: _byteSlice{1, 2, 3}, result: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true}},
|
||||
{source: _byteSlice(nil), result: pgtype.Bytea{}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Bytea
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteaAssignTo(t *testing.T) {
|
||||
var buf []byte
|
||||
var _buf _byteSlice
|
||||
var pbuf *[]byte
|
||||
var _pbuf *_byteSlice
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Bytea
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true}, dst: &buf, expected: []byte{1, 2, 3}},
|
||||
{src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true}, dst: &_buf, expected: _byteSlice{1, 2, 3}},
|
||||
{src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true}, dst: &pbuf, expected: &[]byte{1, 2, 3}},
|
||||
{src: pgtype.Bytea{Bytes: []byte{1, 2, 3}, Valid: true}, dst: &_pbuf, expected: &_byteSlice{1, 2, 3}},
|
||||
{src: pgtype.Bytea{}, dst: &pbuf, expected: ((*[]byte)(nil))},
|
||||
{src: pgtype.Bytea{}, dst: &_pbuf, expected: ((*_byteSlice)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
// CID is PostgreSQL's Command Identifier type.
|
||||
//
|
||||
// When one does
|
||||
//
|
||||
// select cmin, cmax, * from some_table;
|
||||
//
|
||||
// it is the data type of the cmin and cmax hidden system columns.
|
||||
//
|
||||
// It is currently implemented as an unsigned four byte integer.
|
||||
// Its definition can be found in src/include/c.h as CommandId
|
||||
// in the PostgreSQL sources.
|
||||
type CID pguint32
|
||||
|
||||
// Set converts from src to dst. Note that as CID is not a general
|
||||
// number type Set does not do automatic type conversion as other number
|
||||
// types do.
|
||||
func (dst *CID) Set(src interface{}) error {
|
||||
return (*pguint32)(dst).Set(src)
|
||||
}
|
||||
|
||||
func (dst CID) Get() interface{} {
|
||||
return (pguint32)(dst).Get()
|
||||
}
|
||||
|
||||
// AssignTo assigns from src to dst. Note that as CID is not a general number
|
||||
// type AssignTo does not do automatic type conversion as other number types do.
|
||||
func (src *CID) AssignTo(dst interface{}) error {
|
||||
return (*pguint32)(src).AssignTo(dst)
|
||||
}
|
||||
|
||||
func (dst *CID) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
return (*pguint32)(dst).DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (dst *CID) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return (*pguint32)(dst).DecodeBinary(ci, src)
|
||||
}
|
||||
|
||||
func (src CID) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (pguint32)(src).EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
func (src CID) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (pguint32)(src).EncodeBinary(ci, buf)
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *CID) Scan(src interface{}) error {
|
||||
return (*pguint32)(dst).Scan(src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src CID) Value() (driver.Value, error) {
|
||||
return (pguint32)(src).Value()
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestCIDTranscode(t *testing.T) {
|
||||
pgTypeName := "cid"
|
||||
values := []interface{}{
|
||||
&pgtype.CID{Uint: 42, Valid: true},
|
||||
&pgtype.CID{},
|
||||
}
|
||||
eqFunc := func(a, b interface{}) bool {
|
||||
return reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
testutil.TestPgxSuccessfulTranscodeEqFunc(t, pgTypeName, values, eqFunc)
|
||||
testutil.TestDatabaseSQLSuccessfulTranscodeEqFunc(t, "github.com/jackc/pgx/stdlib", pgTypeName, values, eqFunc)
|
||||
}
|
||||
|
||||
func TestCIDSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.CID
|
||||
}{
|
||||
{source: uint32(1), result: pgtype.CID{Uint: 1, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.CID
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCIDAssignTo(t *testing.T) {
|
||||
var ui32 uint32
|
||||
var pui32 *uint32
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.CID
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.CID{Uint: 42, Valid: true}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.CID{}, dst: &pui32, expected: ((*uint32)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.CID
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.CID{Uint: 42, Valid: true}, dst: &pui32, expected: uint32(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.CID
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.CID{}, dst: &ui32},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package pgtype
|
||||
|
||||
type CIDR Inet
|
||||
|
||||
func (dst *CIDR) Set(src interface{}) error {
|
||||
return (*Inet)(dst).Set(src)
|
||||
}
|
||||
|
||||
func (dst CIDR) Get() interface{} {
|
||||
return (Inet)(dst).Get()
|
||||
}
|
||||
|
||||
func (src *CIDR) AssignTo(dst interface{}) error {
|
||||
return (*Inet)(src).AssignTo(dst)
|
||||
}
|
||||
|
||||
func (dst *CIDR) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
return (*Inet)(dst).DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (dst *CIDR) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return (*Inet)(dst).DecodeBinary(ci, src)
|
||||
}
|
||||
|
||||
func (src CIDR) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Inet)(src).EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
func (src CIDR) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Inet)(src).EncodeBinary(ci, buf)
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type CIDRArray struct {
|
||||
Elements []CIDR
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *CIDRArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = CIDRArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []*net.IPNet:
|
||||
if value == nil {
|
||||
*dst = CIDRArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = CIDRArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]CIDR, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = CIDRArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []net.IP:
|
||||
if value == nil {
|
||||
*dst = CIDRArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = CIDRArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]CIDR, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = CIDRArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*net.IP:
|
||||
if value == nil {
|
||||
*dst = CIDRArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = CIDRArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]CIDR, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = CIDRArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []CIDR:
|
||||
if value == nil {
|
||||
*dst = CIDRArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = CIDRArray{Valid: true}
|
||||
} else {
|
||||
*dst = CIDRArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = CIDRArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for CIDRArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = CIDRArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to CIDRArray", src)
|
||||
}
|
||||
|
||||
*dst = CIDRArray{
|
||||
Elements: make([]CIDR, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]CIDR, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to CIDRArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *CIDRArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to CIDRArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in CIDRArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst CIDRArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *CIDRArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]*net.IPNet:
|
||||
*v = make([]*net.IPNet, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]net.IP:
|
||||
*v = make([]net.IP, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*net.IP:
|
||||
*v = make([]*net.IP, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *CIDRArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from CIDRArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from CIDRArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *CIDRArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = CIDRArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []CIDR
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]CIDR, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem CIDR
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = CIDRArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *CIDRArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = CIDRArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = CIDRArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]CIDR, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = CIDRArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src CIDRArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src CIDRArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("cidr"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "cidr")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *CIDRArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src CIDRArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestCIDRArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "cidr[]", []interface{}{
|
||||
&pgtype.CIDRArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.CIDRArray{},
|
||||
&pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"), Valid: true},
|
||||
{},
|
||||
{IPNet: mustParseCIDR(t, "255.0.0.0/8"), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestCIDRArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.CIDRArray
|
||||
}{
|
||||
{
|
||||
source: []*net.IPNet{mustParseCIDR(t, "127.0.0.1/32")},
|
||||
result: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]*net.IPNet)(nil)),
|
||||
result: pgtype.CIDRArray{},
|
||||
},
|
||||
{
|
||||
source: []net.IP{mustParseCIDR(t, "127.0.0.1/32").IP},
|
||||
result: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]net.IP)(nil)),
|
||||
result: pgtype.CIDRArray{},
|
||||
},
|
||||
{
|
||||
source: [][]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
result: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
result: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
result: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
result: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.CIDRArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCIDRArrayAssignTo(t *testing.T) {
|
||||
var ipnetSlice []*net.IPNet
|
||||
var ipSlice []net.IP
|
||||
var ipSliceDim2 [][]net.IP
|
||||
var ipnetSliceDim4 [][][][]*net.IPNet
|
||||
var ipArrayDim2 [2][1]net.IP
|
||||
var ipnetArrayDim4 [2][1][1][3]*net.IPNet
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.CIDRArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipnetSlice,
|
||||
expected: []*net.IPNet{mustParseCIDR(t, "127.0.0.1/32")},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipnetSlice,
|
||||
expected: []*net.IPNet{nil},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipSlice,
|
||||
expected: []net.IP{mustParseCIDR(t, "127.0.0.1/32").IP},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipSlice,
|
||||
expected: []net.IP{nil},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{},
|
||||
dst: &ipnetSlice,
|
||||
expected: (([]*net.IPNet)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{Valid: true},
|
||||
dst: &ipnetSlice,
|
||||
expected: []*net.IPNet{},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{},
|
||||
dst: &ipSlice,
|
||||
expected: (([]net.IP)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{Valid: true},
|
||||
dst: &ipSlice,
|
||||
expected: []net.IP{},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &ipSliceDim2,
|
||||
expected: [][]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &ipnetSliceDim4,
|
||||
expected: [][][][]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &ipArrayDim2,
|
||||
expected: [2][1]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
},
|
||||
{
|
||||
src: pgtype.CIDRArray{
|
||||
Elements: []pgtype.CIDR{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &ipnetArrayDim4,
|
||||
expected: [2][1][1][3]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Circle struct {
|
||||
P Vec2
|
||||
R float64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Circle) Set(src interface{}) error {
|
||||
return fmt.Errorf("cannot convert %v to Circle", src)
|
||||
}
|
||||
|
||||
func (dst Circle) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Circle) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Circle) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Circle{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) < 9 {
|
||||
return fmt.Errorf("invalid length for Circle: %v", len(src))
|
||||
}
|
||||
|
||||
str := string(src[2:])
|
||||
end := strings.IndexByte(str, ',')
|
||||
x, err := strconv.ParseFloat(str[:end], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str = str[end+1:]
|
||||
end = strings.IndexByte(str, ')')
|
||||
|
||||
y, err := strconv.ParseFloat(str[:end], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str = str[end+2 : len(str)-1]
|
||||
|
||||
r, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Circle{P: Vec2{x, y}, R: r, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Circle) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Circle{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 24 {
|
||||
return fmt.Errorf("invalid length for Circle: %v", len(src))
|
||||
}
|
||||
|
||||
x := binary.BigEndian.Uint64(src)
|
||||
y := binary.BigEndian.Uint64(src[8:])
|
||||
r := binary.BigEndian.Uint64(src[16:])
|
||||
|
||||
*dst = Circle{
|
||||
P: Vec2{math.Float64frombits(x), math.Float64frombits(y)},
|
||||
R: math.Float64frombits(r),
|
||||
Valid: true,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Circle) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, fmt.Sprintf(`<(%s,%s),%s>`,
|
||||
strconv.FormatFloat(src.P.X, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.P.Y, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.R, 'f', -1, 64),
|
||||
)...)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Circle) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.P.X))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.P.Y))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.R))
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Circle) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Circle{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Circle) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestCircleTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "circle", []interface{}{
|
||||
&pgtype.Circle{P: pgtype.Vec2{1.234, 5.67890123}, R: 3.5, Valid: true},
|
||||
&pgtype.Circle{P: pgtype.Vec2{-1.234, -5.6789}, R: 12.9, Valid: true},
|
||||
&pgtype.Circle{},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type MyCompositeRaw struct {
|
||||
A int32
|
||||
B *string
|
||||
}
|
||||
|
||||
func (src MyCompositeRaw) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
||||
buf = pgio.AppendUint32(buf, 2)
|
||||
|
||||
buf = pgio.AppendUint32(buf, pgtype.Int4OID)
|
||||
buf = pgio.AppendInt32(buf, 4)
|
||||
buf = pgio.AppendInt32(buf, src.A)
|
||||
|
||||
buf = pgio.AppendUint32(buf, pgtype.TextOID)
|
||||
if src.B != nil {
|
||||
buf = pgio.AppendInt32(buf, int32(len(*src.B)))
|
||||
buf = append(buf, (*src.B)...)
|
||||
} else {
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (dst *MyCompositeRaw) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error {
|
||||
a := pgtype.Int4{}
|
||||
b := pgtype.Text{}
|
||||
|
||||
scanner := pgtype.NewCompositeBinaryScanner(ci, src)
|
||||
scanner.ScanDecoder(&a)
|
||||
scanner.ScanDecoder(&b)
|
||||
|
||||
if scanner.Err() != nil {
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
dst.A = a.Int
|
||||
if b.Valid {
|
||||
dst.B = &b.String
|
||||
} else {
|
||||
dst.B = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var x []byte
|
||||
|
||||
func BenchmarkBinaryEncodingManual(b *testing.B) {
|
||||
buf := make([]byte, 0, 128)
|
||||
ci := pgtype.NewConnInfo()
|
||||
v := MyCompositeRaw{4, ptrS("ABCDEFG")}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf, _ = v.EncodeBinary(ci, buf[:0])
|
||||
}
|
||||
x = buf
|
||||
}
|
||||
|
||||
func BenchmarkBinaryEncodingHelper(b *testing.B) {
|
||||
buf := make([]byte, 0, 128)
|
||||
ci := pgtype.NewConnInfo()
|
||||
v := MyType{4, ptrS("ABCDEFG")}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
buf, _ = v.EncodeBinary(ci, buf[:0])
|
||||
}
|
||||
x = buf
|
||||
}
|
||||
|
||||
func BenchmarkBinaryEncodingComposite(b *testing.B) {
|
||||
buf := make([]byte, 0, 128)
|
||||
ci := pgtype.NewConnInfo()
|
||||
f1 := 2
|
||||
f2 := ptrS("bar")
|
||||
c, err := pgtype.NewCompositeType("test", []pgtype.CompositeTypeField{
|
||||
{"a", pgtype.Int4OID},
|
||||
{"b", pgtype.TextOID},
|
||||
}, ci)
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
c.Set([]interface{}{f1, f2})
|
||||
buf, _ = c.EncodeBinary(ci, buf[:0])
|
||||
}
|
||||
x = buf
|
||||
}
|
||||
|
||||
func BenchmarkBinaryEncodingJSON(b *testing.B) {
|
||||
buf := make([]byte, 0, 128)
|
||||
ci := pgtype.NewConnInfo()
|
||||
v := MyCompositeRaw{4, ptrS("ABCDEFG")}
|
||||
j := pgtype.JSON{}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
j.Set(v)
|
||||
buf, _ = j.EncodeBinary(ci, buf[:0])
|
||||
}
|
||||
x = buf
|
||||
}
|
||||
|
||||
var dstRaw MyCompositeRaw
|
||||
|
||||
func BenchmarkBinaryDecodingManual(b *testing.B) {
|
||||
ci := pgtype.NewConnInfo()
|
||||
buf, _ := MyType{4, ptrS("ABCDEFG")}.EncodeBinary(ci, nil)
|
||||
dst := MyCompositeRaw{}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := dst.DecodeBinary(ci, buf)
|
||||
E(err)
|
||||
}
|
||||
dstRaw = dst
|
||||
}
|
||||
|
||||
var dstMyType MyType
|
||||
|
||||
func BenchmarkBinaryDecodingHelpers(b *testing.B) {
|
||||
ci := pgtype.NewConnInfo()
|
||||
buf, _ := MyType{4, ptrS("ABCDEFG")}.EncodeBinary(ci, nil)
|
||||
dst := MyType{}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := dst.DecodeBinary(ci, buf)
|
||||
E(err)
|
||||
}
|
||||
dstMyType = dst
|
||||
}
|
||||
|
||||
var gf1 int
|
||||
var gf2 *string
|
||||
|
||||
func BenchmarkBinaryDecodingCompositeScan(b *testing.B) {
|
||||
ci := pgtype.NewConnInfo()
|
||||
buf, _ := MyType{4, ptrS("ABCDEFG")}.EncodeBinary(ci, nil)
|
||||
var f1 int
|
||||
var f2 *string
|
||||
|
||||
c, err := pgtype.NewCompositeType("test", []pgtype.CompositeTypeField{
|
||||
{"a", pgtype.Int4OID},
|
||||
{"b", pgtype.TextOID},
|
||||
}, ci)
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := c.DecodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
err = c.AssignTo([]interface{}{&f1, &f2})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
gf1 = f1
|
||||
gf2 = f2
|
||||
}
|
||||
|
||||
func BenchmarkBinaryDecodingJSON(b *testing.B) {
|
||||
ci := pgtype.NewConnInfo()
|
||||
j := pgtype.JSON{}
|
||||
j.Set(MyCompositeRaw{4, ptrS("ABCDEFG")})
|
||||
buf, _ := j.EncodeBinary(ci, nil)
|
||||
|
||||
j = pgtype.JSON{}
|
||||
dst := MyCompositeRaw{}
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := j.DecodeBinary(ci, buf)
|
||||
E(err)
|
||||
err = j.AssignTo(&dst)
|
||||
E(err)
|
||||
}
|
||||
dstRaw = dst
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package pgtype
|
||||
|
||||
import "fmt"
|
||||
|
||||
// CompositeFields scans the fields of a composite type into the elements of the CompositeFields value. To scan a
|
||||
// nullable value use a *CompositeFields. It will be set to nil in case of null.
|
||||
//
|
||||
// CompositeFields implements EncodeBinary and EncodeText. However, functionality is limited due to CompositeFields not
|
||||
// knowing the PostgreSQL schema of the composite type. Prefer using a registered CompositeType.
|
||||
type CompositeFields []interface{}
|
||||
|
||||
func (cf CompositeFields) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if len(cf) == 0 {
|
||||
return fmt.Errorf("cannot decode into empty CompositeFields")
|
||||
}
|
||||
|
||||
if src == nil {
|
||||
return fmt.Errorf("cannot decode unexpected null into CompositeFields")
|
||||
}
|
||||
|
||||
scanner := NewCompositeBinaryScanner(ci, src)
|
||||
|
||||
for _, f := range cf {
|
||||
scanner.ScanValue(f)
|
||||
}
|
||||
|
||||
if scanner.Err() != nil {
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cf CompositeFields) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if len(cf) == 0 {
|
||||
return fmt.Errorf("cannot decode into empty CompositeFields")
|
||||
}
|
||||
|
||||
if src == nil {
|
||||
return fmt.Errorf("cannot decode unexpected null into CompositeFields")
|
||||
}
|
||||
|
||||
scanner := NewCompositeTextScanner(ci, src)
|
||||
|
||||
for _, f := range cf {
|
||||
scanner.ScanValue(f)
|
||||
}
|
||||
|
||||
if scanner.Err() != nil {
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeText encodes composite fields into the text format. Prefer registering a CompositeType to using
|
||||
// CompositeFields to encode directly.
|
||||
func (cf CompositeFields) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
b := NewCompositeTextBuilder(ci, buf)
|
||||
|
||||
for _, f := range cf {
|
||||
if paramEncoder, ok := f.(ParamEncoder); ok {
|
||||
b.AppendEncoder(paramEncoder)
|
||||
} else {
|
||||
b.AppendValue(f)
|
||||
}
|
||||
}
|
||||
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// EncodeBinary encodes composite fields into the binary format. Unlike CompositeType the schema of the destination is
|
||||
// unknown. Prefer registering a CompositeType to using CompositeFields to encode directly. Because the binary
|
||||
// composite format requires the OID of each field to be specified the only types that will work are those known to
|
||||
// ConnInfo.
|
||||
//
|
||||
// In particular:
|
||||
//
|
||||
// * Nil cannot be used because there is no way to determine what type it.
|
||||
// * Integer types must be exact matches. e.g. A Go int32 into a PostgreSQL bigint will fail.
|
||||
// * No dereferencing will be done. e.g. *Text must be used instead of Text.
|
||||
func (cf CompositeFields) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
b := NewCompositeBinaryBuilder(ci, buf)
|
||||
|
||||
for _, f := range cf {
|
||||
dt, ok := ci.DataTypeForValue(f)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unknown OID for %#v", f)
|
||||
}
|
||||
|
||||
if paramEncoder, ok := f.(ParamEncoder); ok {
|
||||
b.AppendEncoder(dt.OID, paramEncoder)
|
||||
} else {
|
||||
err := dt.Value.Set(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if paramEncoder, ok := dt.Value.(ParamEncoder); ok {
|
||||
b.AppendEncoder(dt.OID, paramEncoder)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Cannot encode binary format for %v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return b.Finish()
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCompositeFieldsDecode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
formats := []int16{pgx.TextFormatCode, pgx.BinaryFormatCode}
|
||||
|
||||
// Assorted values
|
||||
{
|
||||
var a int32
|
||||
var b string
|
||||
var c float64
|
||||
|
||||
for _, format := range formats {
|
||||
err := conn.QueryRow(context.Background(), "select row(1,'hi',2.1)", pgx.QueryResultFormats{format}).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c},
|
||||
)
|
||||
if !assert.NoErrorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.EqualValuesf(t, 1, a, "Format: %v", format)
|
||||
assert.EqualValuesf(t, "hi", b, "Format: %v", format)
|
||||
assert.EqualValuesf(t, 2.1, c, "Format: %v", format)
|
||||
}
|
||||
}
|
||||
|
||||
// nulls, string "null", and empty string fields
|
||||
{
|
||||
var a pgtype.Text
|
||||
var b string
|
||||
var c pgtype.Text
|
||||
var d string
|
||||
var e pgtype.Text
|
||||
|
||||
for _, format := range formats {
|
||||
err := conn.QueryRow(context.Background(), "select row(null,'null',null,'',null)", pgx.QueryResultFormats{format}).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d, &e},
|
||||
)
|
||||
if !assert.NoErrorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Nilf(t, a.Get(), "Format: %v", format)
|
||||
assert.EqualValuesf(t, "null", b, "Format: %v", format)
|
||||
assert.Nilf(t, c.Get(), "Format: %v", format)
|
||||
assert.EqualValuesf(t, "", d, "Format: %v", format)
|
||||
assert.Nilf(t, e.Get(), "Format: %v", format)
|
||||
}
|
||||
}
|
||||
|
||||
// null record
|
||||
{
|
||||
var a pgtype.Text
|
||||
var b string
|
||||
cf := pgtype.CompositeFields{&a, &b}
|
||||
|
||||
for _, format := range formats {
|
||||
// Cannot scan nil into
|
||||
err := conn.QueryRow(context.Background(), "select null::record", pgx.QueryResultFormats{format}).Scan(
|
||||
cf,
|
||||
)
|
||||
if assert.Errorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
assert.NotNilf(t, cf, "Format: %v", format)
|
||||
|
||||
// But can scan nil into *pgtype.CompositeFields
|
||||
err = conn.QueryRow(context.Background(), "select null::record", pgx.QueryResultFormats{format}).Scan(
|
||||
&cf,
|
||||
)
|
||||
if assert.Errorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
assert.Nilf(t, cf, "Format: %v", format)
|
||||
}
|
||||
}
|
||||
|
||||
// quotes and special characters
|
||||
{
|
||||
var a, b, c, d string
|
||||
|
||||
for _, format := range formats {
|
||||
err := conn.QueryRow(context.Background(), `select row('"', 'foo bar', 'foo''bar', 'baz)bar')`, pgx.QueryResultFormats{format}).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d},
|
||||
)
|
||||
if !assert.NoErrorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equalf(t, `"`, a, "Format: %v", format)
|
||||
assert.Equalf(t, `foo bar`, b, "Format: %v", format)
|
||||
assert.Equalf(t, `foo'bar`, c, "Format: %v", format)
|
||||
assert.Equalf(t, `baz)bar`, d, "Format: %v", format)
|
||||
}
|
||||
}
|
||||
|
||||
// arrays
|
||||
{
|
||||
var a []string
|
||||
var b []int64
|
||||
|
||||
for _, format := range formats {
|
||||
err := conn.QueryRow(context.Background(), `select row(array['foo', 'bar', 'baz'], array[1,2,3])`, pgx.QueryResultFormats{format}).Scan(
|
||||
pgtype.CompositeFields{&a, &b},
|
||||
)
|
||||
if !assert.NoErrorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.EqualValuesf(t, []string{"foo", "bar", "baz"}, a, "Format: %v", format)
|
||||
assert.EqualValuesf(t, []int64{1, 2, 3}, b, "Format: %v", format)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip nil fields
|
||||
{
|
||||
var a int32
|
||||
var c float64
|
||||
|
||||
for _, format := range formats {
|
||||
err := conn.QueryRow(context.Background(), "select row(1,'hi',2.1)", pgx.QueryResultFormats{format}).Scan(
|
||||
pgtype.CompositeFields{&a, nil, &c},
|
||||
)
|
||||
if !assert.NoErrorf(t, err, "Format: %v", format) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.EqualValuesf(t, 1, a, "Format: %v", format)
|
||||
assert.EqualValuesf(t, 2.1, c, "Format: %v", format)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompositeFieldsEncode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
_, err := conn.Exec(context.Background(), `drop type if exists cf_encode;
|
||||
|
||||
create type cf_encode as (
|
||||
a text,
|
||||
b int4,
|
||||
c text,
|
||||
d float8,
|
||||
e text
|
||||
);`)
|
||||
require.NoError(t, err)
|
||||
defer conn.Exec(context.Background(), "drop type cf_encode")
|
||||
|
||||
// Use simple protocol to force text or binary encoding
|
||||
simpleProtocols := []bool{true, false}
|
||||
|
||||
// Assorted values
|
||||
{
|
||||
var a string
|
||||
var b int32
|
||||
var c string
|
||||
var d float64
|
||||
var e string
|
||||
|
||||
for _, simpleProtocol := range simpleProtocols {
|
||||
err := conn.QueryRow(context.Background(), "select $1::cf_encode", pgx.QuerySimpleProtocol(simpleProtocol),
|
||||
pgtype.CompositeFields{"hi", int32(1), "ok", float64(2.1), "bye"},
|
||||
).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d, &e},
|
||||
)
|
||||
if assert.NoErrorf(t, err, "Simple Protocol: %v", simpleProtocol) {
|
||||
assert.EqualValuesf(t, "hi", a, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, 1, b, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, "ok", c, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, 2.1, d, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, "bye", e, "Simple Protocol: %v", simpleProtocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// untyped nil
|
||||
{
|
||||
var a pgtype.Text
|
||||
var b int32
|
||||
var c string
|
||||
var d pgtype.Float8
|
||||
var e pgtype.Text
|
||||
|
||||
simpleProtocol := true
|
||||
err := conn.QueryRow(context.Background(), "select $1::cf_encode", pgx.QuerySimpleProtocol(simpleProtocol),
|
||||
pgtype.CompositeFields{nil, int32(1), "null", nil, nil},
|
||||
).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d, &e},
|
||||
)
|
||||
if assert.NoErrorf(t, err, "Simple Protocol: %v", simpleProtocol) {
|
||||
assert.Nilf(t, a.Get(), "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, 1, b, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, "null", c, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Nilf(t, d.Get(), "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Nilf(t, e.Get(), "Simple Protocol: %v", simpleProtocol)
|
||||
}
|
||||
|
||||
// untyped nil cannot be represented in binary format because CompositeFields does not know the PostgreSQL schema
|
||||
// of the composite type.
|
||||
simpleProtocol = false
|
||||
err = conn.QueryRow(context.Background(), "select $1::cf_encode", pgx.QuerySimpleProtocol(simpleProtocol),
|
||||
pgtype.CompositeFields{nil, int32(1), "null", nil, nil},
|
||||
).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d, &e},
|
||||
)
|
||||
assert.Errorf(t, err, "Simple Protocol: %v", simpleProtocol)
|
||||
}
|
||||
|
||||
// nulls, string "null", and empty string fields
|
||||
{
|
||||
var a pgtype.Text
|
||||
var b int32
|
||||
var c string
|
||||
var d pgtype.Float8
|
||||
var e pgtype.Text
|
||||
|
||||
for _, simpleProtocol := range simpleProtocols {
|
||||
err := conn.QueryRow(context.Background(), "select $1::cf_encode", pgx.QuerySimpleProtocol(simpleProtocol),
|
||||
pgtype.CompositeFields{&pgtype.Text{}, int32(1), "null", &pgtype.Float8{}, &pgtype.Text{}},
|
||||
).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d, &e},
|
||||
)
|
||||
if assert.NoErrorf(t, err, "Simple Protocol: %v", simpleProtocol) {
|
||||
assert.Nilf(t, a.Get(), "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, 1, b, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, "null", c, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Nilf(t, d.Get(), "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Nilf(t, e.Get(), "Simple Protocol: %v", simpleProtocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// quotes and special characters
|
||||
{
|
||||
var a string
|
||||
var b int32
|
||||
var c string
|
||||
var d float64
|
||||
var e string
|
||||
|
||||
for _, simpleProtocol := range simpleProtocols {
|
||||
err := conn.QueryRow(
|
||||
context.Background(),
|
||||
`select $1::cf_encode`,
|
||||
pgx.QuerySimpleProtocol(simpleProtocol),
|
||||
pgtype.CompositeFields{`"`, int32(42), `foo'bar`, float64(1.2), `baz)bar`},
|
||||
).Scan(
|
||||
pgtype.CompositeFields{&a, &b, &c, &d, &e},
|
||||
)
|
||||
if assert.NoErrorf(t, err, "Simple Protocol: %v", simpleProtocol) {
|
||||
assert.Equalf(t, `"`, a, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Equalf(t, int32(42), b, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Equalf(t, `foo'bar`, c, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Equalf(t, float64(1.2), d, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.Equalf(t, `baz)bar`, e, "Simple Protocol: %v", simpleProtocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,715 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type CompositeTypeField struct {
|
||||
Name string
|
||||
OID uint32
|
||||
}
|
||||
|
||||
type CompositeType struct {
|
||||
valid bool
|
||||
|
||||
typeName string
|
||||
|
||||
fields []CompositeTypeField
|
||||
valueTranscoders []ValueTranscoder
|
||||
}
|
||||
|
||||
// NewCompositeType creates a CompositeType from fields and ci. ci is used to find the ValueTranscoders used
|
||||
// for fields. All field OIDs must be previously registered in ci.
|
||||
func NewCompositeType(typeName string, fields []CompositeTypeField, ci *ConnInfo) (*CompositeType, error) {
|
||||
valueTranscoders := make([]ValueTranscoder, len(fields))
|
||||
|
||||
for i := range fields {
|
||||
dt, ok := ci.DataTypeForOID(fields[i].OID)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no data type registered for oid: %d", fields[i].OID)
|
||||
}
|
||||
|
||||
value := NewValue(dt.Value)
|
||||
valueTranscoder, ok := value.(ValueTranscoder)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("data type for oid does not implement ValueTranscoder: %d", fields[i].OID)
|
||||
}
|
||||
|
||||
valueTranscoders[i] = valueTranscoder
|
||||
}
|
||||
|
||||
return &CompositeType{typeName: typeName, fields: fields, valueTranscoders: valueTranscoders}, nil
|
||||
}
|
||||
|
||||
// NewCompositeTypeValues creates a CompositeType from fields and values. fields and values must have the same length.
|
||||
// Prefer NewCompositeType unless overriding the transcoding of fields is required.
|
||||
func NewCompositeTypeValues(typeName string, fields []CompositeTypeField, values []ValueTranscoder) (*CompositeType, error) {
|
||||
if len(fields) != len(values) {
|
||||
return nil, errors.New("fields and valueTranscoders must have same length")
|
||||
}
|
||||
|
||||
return &CompositeType{typeName: typeName, fields: fields, valueTranscoders: values}, nil
|
||||
}
|
||||
|
||||
func (src CompositeType) Get() interface{} {
|
||||
if !src.valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
results := make(map[string]interface{}, len(src.valueTranscoders))
|
||||
for i := range src.valueTranscoders {
|
||||
results[src.fields[i].Name] = src.valueTranscoders[i].Get()
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (ct *CompositeType) NewTypeValue() Value {
|
||||
a := &CompositeType{
|
||||
typeName: ct.typeName,
|
||||
fields: ct.fields,
|
||||
valueTranscoders: make([]ValueTranscoder, len(ct.valueTranscoders)),
|
||||
}
|
||||
|
||||
for i := range ct.valueTranscoders {
|
||||
a.valueTranscoders[i] = NewValue(ct.valueTranscoders[i]).(ValueTranscoder)
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (ct *CompositeType) TypeName() string {
|
||||
return ct.typeName
|
||||
}
|
||||
|
||||
func (ct *CompositeType) Fields() []CompositeTypeField {
|
||||
return ct.fields
|
||||
}
|
||||
|
||||
func (dst *CompositeType) setNil() {
|
||||
dst.valid = false
|
||||
}
|
||||
|
||||
func (dst *CompositeType) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
dst.setNil()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case []interface{}:
|
||||
if len(value) != len(dst.valueTranscoders) {
|
||||
return fmt.Errorf("Number of fields don't match. CompositeType has %d fields", len(dst.valueTranscoders))
|
||||
}
|
||||
for i, v := range value {
|
||||
if err := dst.valueTranscoders[i].Set(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
dst.valid = true
|
||||
case *[]interface{}:
|
||||
if value == nil {
|
||||
dst.setNil()
|
||||
return nil
|
||||
}
|
||||
return dst.Set(*value)
|
||||
default:
|
||||
return fmt.Errorf("Can not convert %v to Composite", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssignTo should never be called on composite value directly
|
||||
func (src CompositeType) AssignTo(dst interface{}) error {
|
||||
if !src.valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case []interface{}:
|
||||
if len(v) != len(src.valueTranscoders) {
|
||||
return fmt.Errorf("Number of fields don't match. CompositeType has %d fields", len(src.valueTranscoders))
|
||||
}
|
||||
for i := range src.valueTranscoders {
|
||||
if v[i] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err := assignToOrSet(src.valueTranscoders[i], v[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to assign to dst[%d]: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *[]interface{}:
|
||||
return src.AssignTo(*v)
|
||||
default:
|
||||
if isPtrStruct, err := src.assignToPtrStruct(dst); isPtrStruct {
|
||||
return err
|
||||
}
|
||||
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func assignToOrSet(src Value, dst interface{}) error {
|
||||
assignToErr := src.AssignTo(dst)
|
||||
if assignToErr != nil {
|
||||
// Try to use get / set instead -- this avoids every type having to be able to AssignTo type of self.
|
||||
setSucceeded := false
|
||||
if setter, ok := dst.(Value); ok {
|
||||
err := setter.Set(src.Get())
|
||||
setSucceeded = err == nil
|
||||
}
|
||||
if !setSucceeded {
|
||||
return assignToErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src CompositeType) assignToPtrStruct(dst interface{}) (bool, error) {
|
||||
dstValue := reflect.ValueOf(dst)
|
||||
if dstValue.Kind() != reflect.Ptr {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if dstValue.IsNil() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
dstElemValue := dstValue.Elem()
|
||||
dstElemType := dstElemValue.Type()
|
||||
|
||||
if dstElemType.Kind() != reflect.Struct {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
exportedFields := make([]int, 0, dstElemType.NumField())
|
||||
for i := 0; i < dstElemType.NumField(); i++ {
|
||||
sf := dstElemType.Field(i)
|
||||
if sf.PkgPath == "" {
|
||||
exportedFields = append(exportedFields, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(exportedFields) != len(src.valueTranscoders) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for i := range exportedFields {
|
||||
err := assignToOrSet(src.valueTranscoders[i], dstElemValue.Field(exportedFields[i]).Addr().Interface())
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("unable to assign to field %s: %v", dstElemType.Field(exportedFields[i]).Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (ct *CompositeType) BinaryFormatSupported() bool {
|
||||
for _, vt := range ct.valueTranscoders {
|
||||
if !vt.BinaryFormatSupported() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ct *CompositeType) TextFormatSupported() bool {
|
||||
for _, vt := range ct.valueTranscoders {
|
||||
if !vt.TextFormatSupported() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ct *CompositeType) PreferredFormat() int16 {
|
||||
if ct.BinaryFormatSupported() {
|
||||
return BinaryFormatCode
|
||||
}
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (dst *CompositeType) DecodeResult(ci *ConnInfo, oid uint32, format int16, src []byte) error {
|
||||
if src == nil {
|
||||
dst.setNil()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case BinaryFormatCode:
|
||||
return dst.DecodeBinary(ci, src)
|
||||
case TextFormatCode:
|
||||
return dst.DecodeText(ci, src)
|
||||
}
|
||||
return fmt.Errorf("unknown format code %d", format)
|
||||
}
|
||||
|
||||
func (src CompositeType) EncodeParam(ci *ConnInfo, oid uint32, format int16, buf []byte) (newBuf []byte, err error) {
|
||||
switch format {
|
||||
case BinaryFormatCode:
|
||||
return src.EncodeBinary(ci, buf)
|
||||
case TextFormatCode:
|
||||
return src.EncodeText(ci, buf)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown format code %d", format)
|
||||
}
|
||||
|
||||
func (src CompositeType) EncodeBinary(ci *ConnInfo, buf []byte) (newBuf []byte, err error) {
|
||||
if !src.valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b := NewCompositeBinaryBuilder(ci, buf)
|
||||
for i := range src.valueTranscoders {
|
||||
b.AppendEncoder(src.fields[i].OID, src.valueTranscoders[i])
|
||||
}
|
||||
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
// DecodeBinary implements BinaryDecoder interface.
|
||||
// Opposite to Record, fields in a composite act as a "schema"
|
||||
// and decoding fails if SQL value can't be assigned due to
|
||||
// type mismatch
|
||||
func (dst *CompositeType) DecodeBinary(ci *ConnInfo, buf []byte) error {
|
||||
scanner := NewCompositeBinaryScanner(ci, buf)
|
||||
|
||||
for _, f := range dst.valueTranscoders {
|
||||
scanner.ScanDecoder(f)
|
||||
}
|
||||
|
||||
if scanner.Err() != nil {
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
dst.valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *CompositeType) DecodeText(ci *ConnInfo, buf []byte) error {
|
||||
scanner := NewCompositeTextScanner(ci, buf)
|
||||
|
||||
for _, f := range dst.valueTranscoders {
|
||||
scanner.ScanDecoder(f)
|
||||
}
|
||||
|
||||
if scanner.Err() != nil {
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
dst.valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src CompositeType) EncodeText(ci *ConnInfo, buf []byte) (newBuf []byte, err error) {
|
||||
if !src.valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b := NewCompositeTextBuilder(ci, buf)
|
||||
for _, f := range src.valueTranscoders {
|
||||
b.AppendEncoder(f)
|
||||
}
|
||||
|
||||
return b.Finish()
|
||||
}
|
||||
|
||||
type CompositeBinaryScanner struct {
|
||||
ci *ConnInfo
|
||||
rp int
|
||||
src []byte
|
||||
|
||||
fieldCount int32
|
||||
fieldBytes []byte
|
||||
fieldOID uint32
|
||||
err error
|
||||
}
|
||||
|
||||
// NewCompositeBinaryScanner a scanner over a binary encoded composite balue.
|
||||
func NewCompositeBinaryScanner(ci *ConnInfo, src []byte) *CompositeBinaryScanner {
|
||||
rp := 0
|
||||
if len(src[rp:]) < 4 {
|
||||
return &CompositeBinaryScanner{err: fmt.Errorf("Record incomplete %v", src)}
|
||||
}
|
||||
|
||||
fieldCount := int32(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
|
||||
return &CompositeBinaryScanner{
|
||||
ci: ci,
|
||||
rp: rp,
|
||||
src: src,
|
||||
fieldCount: fieldCount,
|
||||
}
|
||||
}
|
||||
|
||||
// ScanDecoder calls Next and decodes the result with d.
|
||||
func (cfs *CompositeBinaryScanner) ScanDecoder(d ResultDecoder) {
|
||||
if cfs.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfs.Next() {
|
||||
cfs.err = d.DecodeResult(cfs.ci, 0, BinaryFormatCode, cfs.fieldBytes)
|
||||
} else {
|
||||
cfs.err = errors.New("read past end of composite")
|
||||
}
|
||||
}
|
||||
|
||||
// ScanDecoder calls Next and scans the result into d.
|
||||
func (cfs *CompositeBinaryScanner) ScanValue(d interface{}) {
|
||||
if cfs.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfs.Next() {
|
||||
cfs.err = cfs.ci.Scan(cfs.OID(), BinaryFormatCode, cfs.Bytes(), d)
|
||||
} else {
|
||||
cfs.err = errors.New("read past end of composite")
|
||||
}
|
||||
}
|
||||
|
||||
// Next advances the scanner to the next field. It returns false after the last field is read or an error occurs. After
|
||||
// Next returns false, the Err method can be called to check if any errors occurred.
|
||||
func (cfs *CompositeBinaryScanner) Next() bool {
|
||||
if cfs.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if cfs.rp == len(cfs.src) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(cfs.src[cfs.rp:]) < 8 {
|
||||
cfs.err = fmt.Errorf("Record incomplete %v", cfs.src)
|
||||
return false
|
||||
}
|
||||
cfs.fieldOID = binary.BigEndian.Uint32(cfs.src[cfs.rp:])
|
||||
cfs.rp += 4
|
||||
|
||||
fieldLen := int(int32(binary.BigEndian.Uint32(cfs.src[cfs.rp:])))
|
||||
cfs.rp += 4
|
||||
|
||||
if fieldLen >= 0 {
|
||||
if len(cfs.src[cfs.rp:]) < fieldLen {
|
||||
cfs.err = fmt.Errorf("Record incomplete rp=%d src=%v", cfs.rp, cfs.src)
|
||||
return false
|
||||
}
|
||||
cfs.fieldBytes = cfs.src[cfs.rp : cfs.rp+fieldLen]
|
||||
cfs.rp += fieldLen
|
||||
} else {
|
||||
cfs.fieldBytes = nil
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (cfs *CompositeBinaryScanner) FieldCount() int {
|
||||
return int(cfs.fieldCount)
|
||||
}
|
||||
|
||||
// Bytes returns the bytes of the field most recently read by Scan().
|
||||
func (cfs *CompositeBinaryScanner) Bytes() []byte {
|
||||
return cfs.fieldBytes
|
||||
}
|
||||
|
||||
// OID returns the OID of the field most recently read by Scan().
|
||||
func (cfs *CompositeBinaryScanner) OID() uint32 {
|
||||
return cfs.fieldOID
|
||||
}
|
||||
|
||||
// Err returns any error encountered by the scanner.
|
||||
func (cfs *CompositeBinaryScanner) Err() error {
|
||||
return cfs.err
|
||||
}
|
||||
|
||||
type CompositeTextScanner struct {
|
||||
ci *ConnInfo
|
||||
rp int
|
||||
src []byte
|
||||
|
||||
fieldBytes []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// NewCompositeTextScanner a scanner over a text encoded composite value.
|
||||
func NewCompositeTextScanner(ci *ConnInfo, src []byte) *CompositeTextScanner {
|
||||
if len(src) < 2 {
|
||||
return &CompositeTextScanner{err: fmt.Errorf("Record incomplete %v", src)}
|
||||
}
|
||||
|
||||
if src[0] != '(' {
|
||||
return &CompositeTextScanner{err: fmt.Errorf("composite text format must start with '('")}
|
||||
}
|
||||
|
||||
if src[len(src)-1] != ')' {
|
||||
return &CompositeTextScanner{err: fmt.Errorf("composite text format must end with ')'")}
|
||||
}
|
||||
|
||||
return &CompositeTextScanner{
|
||||
ci: ci,
|
||||
rp: 1,
|
||||
src: src,
|
||||
}
|
||||
}
|
||||
|
||||
// ScanDecoder calls Next and decodes the result with d.
|
||||
func (cfs *CompositeTextScanner) ScanDecoder(d ResultDecoder) {
|
||||
if cfs.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfs.Next() {
|
||||
cfs.err = d.DecodeResult(cfs.ci, 0, TextFormatCode, cfs.fieldBytes)
|
||||
} else {
|
||||
cfs.err = errors.New("read past end of composite")
|
||||
}
|
||||
}
|
||||
|
||||
// ScanDecoder calls Next and scans the result into d.
|
||||
func (cfs *CompositeTextScanner) ScanValue(d interface{}) {
|
||||
if cfs.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cfs.Next() {
|
||||
cfs.err = cfs.ci.Scan(0, TextFormatCode, cfs.Bytes(), d)
|
||||
} else {
|
||||
cfs.err = errors.New("read past end of composite")
|
||||
}
|
||||
}
|
||||
|
||||
// Next advances the scanner to the next field. It returns false after the last field is read or an error occurs. After
|
||||
// Next returns false, the Err method can be called to check if any errors occurred.
|
||||
func (cfs *CompositeTextScanner) Next() bool {
|
||||
if cfs.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if cfs.rp == len(cfs.src) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch cfs.src[cfs.rp] {
|
||||
case ',', ')': // null
|
||||
cfs.rp++
|
||||
cfs.fieldBytes = nil
|
||||
return true
|
||||
case '"': // quoted value
|
||||
cfs.rp++
|
||||
cfs.fieldBytes = make([]byte, 0, 16)
|
||||
for {
|
||||
ch := cfs.src[cfs.rp]
|
||||
|
||||
if ch == '"' {
|
||||
cfs.rp++
|
||||
if cfs.src[cfs.rp] == '"' {
|
||||
cfs.fieldBytes = append(cfs.fieldBytes, '"')
|
||||
cfs.rp++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else if ch == '\\' {
|
||||
cfs.rp++
|
||||
cfs.fieldBytes = append(cfs.fieldBytes, cfs.src[cfs.rp])
|
||||
cfs.rp++
|
||||
} else {
|
||||
cfs.fieldBytes = append(cfs.fieldBytes, ch)
|
||||
cfs.rp++
|
||||
}
|
||||
}
|
||||
cfs.rp++
|
||||
return true
|
||||
default: // unquoted value
|
||||
start := cfs.rp
|
||||
for {
|
||||
ch := cfs.src[cfs.rp]
|
||||
if ch == ',' || ch == ')' {
|
||||
break
|
||||
}
|
||||
cfs.rp++
|
||||
}
|
||||
cfs.fieldBytes = cfs.src[start:cfs.rp]
|
||||
cfs.rp++
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns the bytes of the field most recently read by Scan().
|
||||
func (cfs *CompositeTextScanner) Bytes() []byte {
|
||||
return cfs.fieldBytes
|
||||
}
|
||||
|
||||
// Err returns any error encountered by the scanner.
|
||||
func (cfs *CompositeTextScanner) Err() error {
|
||||
return cfs.err
|
||||
}
|
||||
|
||||
type CompositeBinaryBuilder struct {
|
||||
ci *ConnInfo
|
||||
buf []byte
|
||||
startIdx int
|
||||
fieldCount uint32
|
||||
err error
|
||||
}
|
||||
|
||||
func NewCompositeBinaryBuilder(ci *ConnInfo, buf []byte) *CompositeBinaryBuilder {
|
||||
startIdx := len(buf)
|
||||
buf = append(buf, 0, 0, 0, 0) // allocate room for number of fields
|
||||
return &CompositeBinaryBuilder{ci: ci, buf: buf, startIdx: startIdx}
|
||||
}
|
||||
|
||||
func (b *CompositeBinaryBuilder) AppendValue(oid uint32, field interface{}) {
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dt, ok := b.ci.DataTypeForOID(oid)
|
||||
if !ok {
|
||||
b.err = fmt.Errorf("unknown data type for OID: %d", oid)
|
||||
return
|
||||
}
|
||||
|
||||
err := dt.Value.Set(field)
|
||||
if err != nil {
|
||||
b.err = err
|
||||
return
|
||||
}
|
||||
|
||||
paramEncoder, ok := dt.Value.(ParamEncoder)
|
||||
if !ok {
|
||||
b.err = fmt.Errorf("unable to encode for OID: %d", oid)
|
||||
return
|
||||
}
|
||||
|
||||
b.AppendEncoder(oid, paramEncoder)
|
||||
}
|
||||
|
||||
func (b *CompositeBinaryBuilder) AppendEncoder(oid uint32, field ParamEncoder) {
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.buf = pgio.AppendUint32(b.buf, oid)
|
||||
lengthPos := len(b.buf)
|
||||
b.buf = pgio.AppendInt32(b.buf, -1)
|
||||
fieldBuf, err := field.EncodeParam(b.ci, oid, BinaryFormatCode, b.buf)
|
||||
if err != nil {
|
||||
b.err = err
|
||||
return
|
||||
}
|
||||
if fieldBuf != nil {
|
||||
binary.BigEndian.PutUint32(fieldBuf[lengthPos:], uint32(len(fieldBuf)-len(b.buf)))
|
||||
b.buf = fieldBuf
|
||||
}
|
||||
|
||||
b.fieldCount++
|
||||
}
|
||||
|
||||
func (b *CompositeBinaryBuilder) Finish() ([]byte, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(b.buf[b.startIdx:], b.fieldCount)
|
||||
return b.buf, nil
|
||||
}
|
||||
|
||||
type CompositeTextBuilder struct {
|
||||
ci *ConnInfo
|
||||
buf []byte
|
||||
startIdx int
|
||||
fieldCount uint32
|
||||
err error
|
||||
fieldBuf [32]byte
|
||||
}
|
||||
|
||||
func NewCompositeTextBuilder(ci *ConnInfo, buf []byte) *CompositeTextBuilder {
|
||||
buf = append(buf, '(') // allocate room for number of fields
|
||||
return &CompositeTextBuilder{ci: ci, buf: buf}
|
||||
}
|
||||
|
||||
func (b *CompositeTextBuilder) AppendValue(field interface{}) {
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if field == nil {
|
||||
b.buf = append(b.buf, ',')
|
||||
return
|
||||
}
|
||||
|
||||
dt, ok := b.ci.DataTypeForValue(field)
|
||||
if !ok {
|
||||
b.err = fmt.Errorf("unknown data type for field: %v", field)
|
||||
return
|
||||
}
|
||||
|
||||
err := dt.Value.Set(field)
|
||||
if err != nil {
|
||||
b.err = err
|
||||
return
|
||||
}
|
||||
|
||||
paramEncoder, ok := dt.Value.(ParamEncoder)
|
||||
if !ok {
|
||||
b.err = fmt.Errorf("unable to encode for value: %v", field)
|
||||
return
|
||||
}
|
||||
|
||||
b.AppendEncoder(paramEncoder)
|
||||
}
|
||||
|
||||
func (b *CompositeTextBuilder) AppendEncoder(field ParamEncoder) {
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fieldBuf, err := field.EncodeParam(b.ci, 0, TextFormatCode, b.fieldBuf[0:0])
|
||||
if err != nil {
|
||||
b.err = err
|
||||
return
|
||||
}
|
||||
if fieldBuf != nil {
|
||||
b.buf = append(b.buf, quoteCompositeFieldIfNeeded(string(fieldBuf))...)
|
||||
}
|
||||
|
||||
b.buf = append(b.buf, ',')
|
||||
}
|
||||
|
||||
func (b *CompositeTextBuilder) Finish() ([]byte, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
b.buf[len(b.buf)-1] = ')'
|
||||
return b.buf, nil
|
||||
}
|
||||
|
||||
var quoteCompositeReplacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`)
|
||||
|
||||
func quoteCompositeField(src string) string {
|
||||
return `"` + quoteCompositeReplacer.Replace(src) + `"`
|
||||
}
|
||||
|
||||
func quoteCompositeFieldIfNeeded(src string) string {
|
||||
if src == "" || src[0] == ' ' || src[len(src)-1] == ' ' || strings.ContainsAny(src, `(),"\`) {
|
||||
return quoteCompositeField(src)
|
||||
}
|
||||
return src
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
pgx "github.com/jackc/pgx/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCompositeTypeSetAndGet(t *testing.T) {
|
||||
ci := pgtype.NewConnInfo()
|
||||
ct, err := pgtype.NewCompositeType("test", []pgtype.CompositeTypeField{
|
||||
{"a", pgtype.TextOID},
|
||||
{"b", pgtype.Int4OID},
|
||||
}, ci)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, nil, ct.Get())
|
||||
|
||||
nilTests := []struct {
|
||||
src interface{}
|
||||
}{
|
||||
{nil}, // nil interface
|
||||
{(*[]interface{})(nil)}, // typed nil
|
||||
}
|
||||
|
||||
for i, tt := range nilTests {
|
||||
err := ct.Set(tt.src)
|
||||
assert.NoErrorf(t, err, "%d", i)
|
||||
assert.Equal(t, nil, ct.Get())
|
||||
}
|
||||
|
||||
compatibleValuesTests := []struct {
|
||||
src []interface{}
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
src: []interface{}{"foo", int32(42)},
|
||||
expected: map[string]interface{}{"a": "foo", "b": int32(42)},
|
||||
},
|
||||
{
|
||||
src: []interface{}{nil, nil},
|
||||
expected: map[string]interface{}{"a": nil, "b": nil},
|
||||
},
|
||||
{
|
||||
src: []interface{}{&pgtype.Text{String: "hi", Valid: true}, &pgtype.Int4{Int: 7, Valid: true}},
|
||||
expected: map[string]interface{}{"a": "hi", "b": int32(7)},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range compatibleValuesTests {
|
||||
err := ct.Set(tt.src)
|
||||
assert.NoErrorf(t, err, "%d", i)
|
||||
assert.EqualValues(t, tt.expected, ct.Get())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompositeTypeAssignTo(t *testing.T) {
|
||||
ci := pgtype.NewConnInfo()
|
||||
ct, err := pgtype.NewCompositeType("test", []pgtype.CompositeTypeField{
|
||||
{"a", pgtype.TextOID},
|
||||
{"b", pgtype.Int4OID},
|
||||
}, ci)
|
||||
require.NoError(t, err)
|
||||
|
||||
{
|
||||
err := ct.Set([]interface{}{"foo", int32(42)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var a string
|
||||
var b int32
|
||||
|
||||
err = ct.AssignTo([]interface{}{&a, &b})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "foo", a)
|
||||
assert.Equal(t, int32(42), b)
|
||||
}
|
||||
|
||||
{
|
||||
err := ct.Set([]interface{}{"foo", int32(42)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var a pgtype.Text
|
||||
var b pgtype.Int4
|
||||
|
||||
err = ct.AssignTo([]interface{}{&a, &b})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, pgtype.Text{String: "foo", Valid: true}, a)
|
||||
assert.Equal(t, pgtype.Int4{Int: 42, Valid: true}, b)
|
||||
}
|
||||
|
||||
// Allow nil destination component as no-op
|
||||
{
|
||||
err := ct.Set([]interface{}{"foo", int32(42)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var b int32
|
||||
|
||||
err = ct.AssignTo([]interface{}{nil, &b})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int32(42), b)
|
||||
}
|
||||
|
||||
// *[]interface{} dest when null
|
||||
{
|
||||
err := ct.Set(nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var a pgtype.Text
|
||||
var b pgtype.Int4
|
||||
dst := []interface{}{&a, &b}
|
||||
|
||||
err = ct.AssignTo(&dst)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Nil(t, dst)
|
||||
}
|
||||
|
||||
// *[]interface{} dest when not null
|
||||
{
|
||||
err := ct.Set([]interface{}{"foo", int32(42)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var a pgtype.Text
|
||||
var b pgtype.Int4
|
||||
dst := []interface{}{&a, &b}
|
||||
|
||||
err = ct.AssignTo(&dst)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, dst)
|
||||
assert.Equal(t, pgtype.Text{String: "foo", Valid: true}, a)
|
||||
assert.Equal(t, pgtype.Int4{Int: 42, Valid: true}, b)
|
||||
}
|
||||
|
||||
// Struct fields positionally via reflection
|
||||
{
|
||||
err := ct.Set([]interface{}{"foo", int32(42)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := struct {
|
||||
A string
|
||||
B int32
|
||||
}{}
|
||||
|
||||
err = ct.AssignTo(&s)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "foo", s.A)
|
||||
assert.Equal(t, int32(42), s.B)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompositeTypeTranscode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
_, err := conn.Exec(context.Background(), `drop type if exists ct_test;
|
||||
|
||||
create type ct_test as (
|
||||
a text,
|
||||
b int4
|
||||
);`)
|
||||
require.NoError(t, err)
|
||||
defer conn.Exec(context.Background(), "drop type ct_test")
|
||||
|
||||
var oid uint32
|
||||
err = conn.QueryRow(context.Background(), `select 'ct_test'::regtype::oid`).Scan(&oid)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer conn.Exec(context.Background(), "drop type ct_test")
|
||||
|
||||
ct, err := pgtype.NewCompositeType("ct_test", []pgtype.CompositeTypeField{
|
||||
{"a", pgtype.TextOID},
|
||||
{"b", pgtype.Int4OID},
|
||||
}, conn.ConnInfo())
|
||||
require.NoError(t, err)
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: ct, Name: ct.TypeName(), OID: oid})
|
||||
|
||||
// Use simple protocol to force text or binary encoding
|
||||
simpleProtocols := []bool{true, false}
|
||||
|
||||
var a string
|
||||
var b int32
|
||||
|
||||
for _, simpleProtocol := range simpleProtocols {
|
||||
err := conn.QueryRow(context.Background(), "select $1::ct_test", pgx.QuerySimpleProtocol(simpleProtocol),
|
||||
pgtype.CompositeFields{"hi", int32(42)},
|
||||
).Scan(
|
||||
[]interface{}{&a, &b},
|
||||
)
|
||||
if assert.NoErrorf(t, err, "Simple Protocol: %v", simpleProtocol) {
|
||||
assert.EqualValuesf(t, "hi", a, "Simple Protocol: %v", simpleProtocol)
|
||||
assert.EqualValuesf(t, 42, b, "Simple Protocol: %v", simpleProtocol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/jackc/pgx/issues/874
|
||||
func TestCompositeTypeTextDecodeNested(t *testing.T) {
|
||||
newCompositeType := func(name string, fieldNames []string, vals ...pgtype.ValueTranscoder) *pgtype.CompositeType {
|
||||
fields := make([]pgtype.CompositeTypeField, len(fieldNames))
|
||||
for i, name := range fieldNames {
|
||||
fields[i] = pgtype.CompositeTypeField{Name: name}
|
||||
}
|
||||
|
||||
rowType, err := pgtype.NewCompositeTypeValues(name, fields, vals)
|
||||
require.NoError(t, err)
|
||||
return rowType
|
||||
}
|
||||
|
||||
dimensionsType := func() pgtype.ValueTranscoder {
|
||||
return newCompositeType(
|
||||
"dimensions",
|
||||
[]string{"width", "height"},
|
||||
&pgtype.Int4{},
|
||||
&pgtype.Int4{},
|
||||
)
|
||||
}
|
||||
productImageType := func() pgtype.ValueTranscoder {
|
||||
return newCompositeType(
|
||||
"product_image_type",
|
||||
[]string{"source", "dimensions"},
|
||||
&pgtype.Text{},
|
||||
dimensionsType(),
|
||||
)
|
||||
}
|
||||
productImageSetType := newCompositeType(
|
||||
"product_image_set_type",
|
||||
[]string{"name", "orig_image", "images"},
|
||||
&pgtype.Text{},
|
||||
productImageType(),
|
||||
pgtype.NewArrayType("product_image", 0, func() pgtype.ValueTranscoder {
|
||||
return productImageType()
|
||||
}),
|
||||
)
|
||||
|
||||
err := productImageSetType.DecodeText(nil, []byte(`(name,"(img1,""(11,11)"")","{""(img2,\\""(22,22)\\"")"",""(img3,\\""(33,33)\\"")""}")`))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Example_composite() {
|
||||
conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer conn.Close(context.Background())
|
||||
_, err = conn.Exec(context.Background(), `drop type if exists mytype;`)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.Exec(context.Background(), `create type mytype as (
|
||||
a int4,
|
||||
b text
|
||||
);`)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer conn.Exec(context.Background(), "drop type mytype")
|
||||
|
||||
var oid uint32
|
||||
err = conn.QueryRow(context.Background(), `select 'mytype'::regtype::oid`).Scan(&oid)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
ct, err := pgtype.NewCompositeType("mytype", []pgtype.CompositeTypeField{
|
||||
{"a", pgtype.Int4OID},
|
||||
{"b", pgtype.TextOID},
|
||||
}, conn.ConnInfo())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: ct, Name: ct.TypeName(), OID: oid})
|
||||
|
||||
var a int
|
||||
var b *string
|
||||
|
||||
err = conn.QueryRow(context.Background(), "select $1::mytype", []interface{}{2, "bar"}).Scan([]interface{}{&a, &b})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("First: a=%d b=%s\n", a, *b)
|
||||
|
||||
err = conn.QueryRow(context.Background(), "select (1, NULL)::mytype").Scan([]interface{}{&a, &b})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Second: a=%d b=%v\n", a, b)
|
||||
|
||||
scanTarget := []interface{}{&a, &b}
|
||||
err = conn.QueryRow(context.Background(), "select NULL::mytype").Scan(&scanTarget)
|
||||
E(err)
|
||||
|
||||
fmt.Printf("Third: isNull=%v\n", scanTarget == nil)
|
||||
|
||||
// Output:
|
||||
// First: a=2 b=bar
|
||||
// Second: a=1 b=<nil>
|
||||
// Third: isNull=true
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxUint = ^uint(0)
|
||||
maxInt = int(maxUint >> 1)
|
||||
minInt = -maxInt - 1
|
||||
)
|
||||
|
||||
// underlyingNumberType gets the underlying type that can be converted to Int2, Int4, Int8, Float4, or Float8
|
||||
func underlyingNumberType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
case reflect.Int:
|
||||
convVal := int(refVal.Int())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Int8:
|
||||
convVal := int8(refVal.Int())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Int16:
|
||||
convVal := int16(refVal.Int())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Int32:
|
||||
convVal := int32(refVal.Int())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Int64:
|
||||
convVal := int64(refVal.Int())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Uint:
|
||||
convVal := uint(refVal.Uint())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Uint8:
|
||||
convVal := uint8(refVal.Uint())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Uint16:
|
||||
convVal := uint16(refVal.Uint())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Uint32:
|
||||
convVal := uint32(refVal.Uint())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Uint64:
|
||||
convVal := uint64(refVal.Uint())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Float32:
|
||||
convVal := float32(refVal.Float())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Float64:
|
||||
convVal := refVal.Float()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.String:
|
||||
convVal := refVal.String()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingBoolType gets the underlying type that can be converted to Bool
|
||||
func underlyingBoolType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
case reflect.Bool:
|
||||
convVal := refVal.Bool()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingBytesType gets the underlying type that can be converted to []byte
|
||||
func underlyingBytesType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
case reflect.Slice:
|
||||
if refVal.Type().Elem().Kind() == reflect.Uint8 {
|
||||
convVal := refVal.Bytes()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingStringType gets the underlying type that can be converted to String
|
||||
func underlyingStringType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
case reflect.String:
|
||||
convVal := refVal.String()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingPtrType dereferences a pointer
|
||||
func underlyingPtrType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingTimeType gets the underlying type that can be converted to time.Time
|
||||
func underlyingTimeType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
}
|
||||
|
||||
timeType := reflect.TypeOf(time.Time{})
|
||||
if refVal.Type().ConvertibleTo(timeType) {
|
||||
return refVal.Convert(timeType).Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingUUIDType gets the underlying type that can be converted to [16]byte
|
||||
func underlyingUUIDType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return time.Time{}, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
}
|
||||
|
||||
uuidType := reflect.TypeOf([16]byte{})
|
||||
if refVal.Type().ConvertibleTo(uuidType) {
|
||||
return refVal.Convert(uuidType).Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// underlyingSliceType gets the underlying slice type
|
||||
func underlyingSliceType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
case reflect.Ptr:
|
||||
if refVal.IsNil() {
|
||||
return nil, false
|
||||
}
|
||||
convVal := refVal.Elem().Interface()
|
||||
return convVal, true
|
||||
case reflect.Slice:
|
||||
baseSliceType := reflect.SliceOf(refVal.Type().Elem())
|
||||
if refVal.Type().ConvertibleTo(baseSliceType) {
|
||||
convVal := refVal.Convert(baseSliceType)
|
||||
return convVal.Interface(), reflect.TypeOf(convVal.Interface()) != refVal.Type()
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func int64AssignTo(srcVal int64, srcValid bool, dst interface{}) error {
|
||||
if srcValid {
|
||||
switch v := dst.(type) {
|
||||
case *int:
|
||||
if srcVal < int64(minInt) {
|
||||
return fmt.Errorf("%d is less than minimum value for int", srcVal)
|
||||
} else if srcVal > int64(maxInt) {
|
||||
return fmt.Errorf("%d is greater than maximum value for int", srcVal)
|
||||
}
|
||||
*v = int(srcVal)
|
||||
case *int8:
|
||||
if srcVal < math.MinInt8 {
|
||||
return fmt.Errorf("%d is less than minimum value for int8", srcVal)
|
||||
} else if srcVal > math.MaxInt8 {
|
||||
return fmt.Errorf("%d is greater than maximum value for int8", srcVal)
|
||||
}
|
||||
*v = int8(srcVal)
|
||||
case *int16:
|
||||
if srcVal < math.MinInt16 {
|
||||
return fmt.Errorf("%d is less than minimum value for int16", srcVal)
|
||||
} else if srcVal > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for int16", srcVal)
|
||||
}
|
||||
*v = int16(srcVal)
|
||||
case *int32:
|
||||
if srcVal < math.MinInt32 {
|
||||
return fmt.Errorf("%d is less than minimum value for int32", srcVal)
|
||||
} else if srcVal > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for int32", srcVal)
|
||||
}
|
||||
*v = int32(srcVal)
|
||||
case *int64:
|
||||
if srcVal < math.MinInt64 {
|
||||
return fmt.Errorf("%d is less than minimum value for int64", srcVal)
|
||||
} else if srcVal > math.MaxInt64 {
|
||||
return fmt.Errorf("%d is greater than maximum value for int64", srcVal)
|
||||
}
|
||||
*v = int64(srcVal)
|
||||
case *uint:
|
||||
if srcVal < 0 {
|
||||
return fmt.Errorf("%d is less than zero for uint", srcVal)
|
||||
} else if uint64(srcVal) > uint64(maxUint) {
|
||||
return fmt.Errorf("%d is greater than maximum value for uint", srcVal)
|
||||
}
|
||||
*v = uint(srcVal)
|
||||
case *uint8:
|
||||
if srcVal < 0 {
|
||||
return fmt.Errorf("%d is less than zero for uint8", srcVal)
|
||||
} else if srcVal > math.MaxUint8 {
|
||||
return fmt.Errorf("%d is greater than maximum value for uint8", srcVal)
|
||||
}
|
||||
*v = uint8(srcVal)
|
||||
case *uint16:
|
||||
if srcVal < 0 {
|
||||
return fmt.Errorf("%d is less than zero for uint32", srcVal)
|
||||
} else if srcVal > math.MaxUint16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for uint16", srcVal)
|
||||
}
|
||||
*v = uint16(srcVal)
|
||||
case *uint32:
|
||||
if srcVal < 0 {
|
||||
return fmt.Errorf("%d is less than zero for uint32", srcVal)
|
||||
} else if srcVal > math.MaxUint32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for uint32", srcVal)
|
||||
}
|
||||
*v = uint32(srcVal)
|
||||
case *uint64:
|
||||
if srcVal < 0 {
|
||||
return fmt.Errorf("%d is less than zero for uint64", srcVal)
|
||||
}
|
||||
*v = uint64(srcVal)
|
||||
case sql.Scanner:
|
||||
return v.Scan(srcVal)
|
||||
default:
|
||||
if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr {
|
||||
el := v.Elem()
|
||||
switch el.Kind() {
|
||||
// if dst is a pointer to pointer, strip the pointer and try again
|
||||
case reflect.Ptr:
|
||||
if el.IsNil() {
|
||||
// allocate destination
|
||||
el.Set(reflect.New(el.Type().Elem()))
|
||||
}
|
||||
return int64AssignTo(srcVal, srcValid, el.Interface())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if el.OverflowInt(int64(srcVal)) {
|
||||
return fmt.Errorf("cannot put %d into %T", srcVal, dst)
|
||||
}
|
||||
el.SetInt(int64(srcVal))
|
||||
return nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if srcVal < 0 {
|
||||
return fmt.Errorf("%d is less than zero for %T", srcVal, dst)
|
||||
}
|
||||
if el.OverflowUint(uint64(srcVal)) {
|
||||
return fmt.Errorf("cannot put %d into %T", srcVal, dst)
|
||||
}
|
||||
el.SetUint(uint64(srcVal))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot assign %v into %T", srcVal, dst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if dst is a pointer to pointer and srcStatus is not Valid, nil it out
|
||||
if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr {
|
||||
el := v.Elem()
|
||||
if el.Kind() == reflect.Ptr {
|
||||
el.Set(reflect.Zero(el.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot assign %v %v into %T", srcVal, srcValid, dst)
|
||||
}
|
||||
|
||||
func float64AssignTo(srcVal float64, srcValid bool, dst interface{}) error {
|
||||
if srcValid {
|
||||
switch v := dst.(type) {
|
||||
case *float32:
|
||||
*v = float32(srcVal)
|
||||
case *float64:
|
||||
*v = srcVal
|
||||
default:
|
||||
if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr {
|
||||
el := v.Elem()
|
||||
switch el.Kind() {
|
||||
// if dst is a pointer to pointer, strip the pointer and try again
|
||||
case reflect.Ptr:
|
||||
if el.IsNil() {
|
||||
// allocate destination
|
||||
el.Set(reflect.New(el.Type().Elem()))
|
||||
}
|
||||
return float64AssignTo(srcVal, srcValid, el.Interface())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
i64 := int64(srcVal)
|
||||
if float64(i64) == srcVal {
|
||||
return int64AssignTo(i64, srcValid, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot assign %v into %T", srcVal, dst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if dst is a pointer to pointer and srcStatus is not Valid, nil it out
|
||||
if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr {
|
||||
el := v.Elem()
|
||||
if el.Kind() == reflect.Ptr {
|
||||
el.Set(reflect.Zero(el.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot assign %v %v into %T", srcVal, srcValid, dst)
|
||||
}
|
||||
|
||||
func NullAssignTo(dst interface{}) error {
|
||||
dstPtr := reflect.ValueOf(dst)
|
||||
|
||||
// AssignTo dst must always be a pointer
|
||||
if dstPtr.Kind() != reflect.Ptr {
|
||||
return &nullAssignmentError{dst: dst}
|
||||
}
|
||||
|
||||
dstVal := dstPtr.Elem()
|
||||
|
||||
switch dstVal.Kind() {
|
||||
case reflect.Ptr, reflect.Slice, reflect.Map:
|
||||
dstVal.Set(reflect.Zero(dstVal.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &nullAssignmentError{dst: dst}
|
||||
}
|
||||
|
||||
var kindTypes map[reflect.Kind]reflect.Type
|
||||
|
||||
func toInterface(dst reflect.Value, t reflect.Type) (interface{}, bool) {
|
||||
nextDst := dst.Convert(t)
|
||||
return nextDst.Interface(), dst.Type() != nextDst.Type()
|
||||
}
|
||||
|
||||
// GetAssignToDstType attempts to convert dst to something AssignTo can assign
|
||||
// to. If dst is a pointer to pointer it allocates a value and returns the
|
||||
// dereferences pointer. If dst is a named type such as *Foo where Foo is type
|
||||
// Foo int16, it converts dst to *int16.
|
||||
//
|
||||
// GetAssignToDstType returns the converted dst and a bool representing if any
|
||||
// change was made.
|
||||
func GetAssignToDstType(dst interface{}) (interface{}, bool) {
|
||||
dstPtr := reflect.ValueOf(dst)
|
||||
|
||||
// AssignTo dst must always be a pointer
|
||||
if dstPtr.Kind() != reflect.Ptr {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
dstVal := dstPtr.Elem()
|
||||
|
||||
// if dst is a pointer to pointer, allocate space try again with the dereferenced pointer
|
||||
if dstVal.Kind() == reflect.Ptr {
|
||||
dstVal.Set(reflect.New(dstVal.Type().Elem()))
|
||||
return dstVal.Interface(), true
|
||||
}
|
||||
|
||||
// if dst is pointer to a base type that has been renamed
|
||||
if baseValType, ok := kindTypes[dstVal.Kind()]; ok {
|
||||
return toInterface(dstPtr, reflect.PtrTo(baseValType))
|
||||
}
|
||||
|
||||
if dstVal.Kind() == reflect.Slice {
|
||||
if baseElemType, ok := kindTypes[dstVal.Type().Elem().Kind()]; ok {
|
||||
return toInterface(dstPtr, reflect.PtrTo(reflect.SliceOf(baseElemType)))
|
||||
}
|
||||
}
|
||||
|
||||
if dstVal.Kind() == reflect.Array {
|
||||
if baseElemType, ok := kindTypes[dstVal.Type().Elem().Kind()]; ok {
|
||||
return toInterface(dstPtr, reflect.PtrTo(reflect.ArrayOf(dstVal.Len(), baseElemType)))
|
||||
}
|
||||
}
|
||||
|
||||
if dstVal.Kind() == reflect.Struct {
|
||||
if dstVal.Type().NumField() == 1 && dstVal.Type().Field(0).Anonymous {
|
||||
dstPtr = dstVal.Field(0).Addr()
|
||||
nested := dstVal.Type().Field(0).Type
|
||||
if nested.Kind() == reflect.Array {
|
||||
if baseElemType, ok := kindTypes[nested.Elem().Kind()]; ok {
|
||||
return toInterface(dstPtr, reflect.PtrTo(reflect.ArrayOf(nested.Len(), baseElemType)))
|
||||
}
|
||||
}
|
||||
if _, ok := kindTypes[nested.Kind()]; ok && dstPtr.CanInterface() {
|
||||
return dstPtr.Interface(), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func init() {
|
||||
kindTypes = map[reflect.Kind]reflect.Type{
|
||||
reflect.Bool: reflect.TypeOf(false),
|
||||
reflect.Float32: reflect.TypeOf(float32(0)),
|
||||
reflect.Float64: reflect.TypeOf(float64(0)),
|
||||
reflect.Int: reflect.TypeOf(int(0)),
|
||||
reflect.Int8: reflect.TypeOf(int8(0)),
|
||||
reflect.Int16: reflect.TypeOf(int16(0)),
|
||||
reflect.Int32: reflect.TypeOf(int32(0)),
|
||||
reflect.Int64: reflect.TypeOf(int64(0)),
|
||||
reflect.Uint: reflect.TypeOf(uint(0)),
|
||||
reflect.Uint8: reflect.TypeOf(uint8(0)),
|
||||
reflect.Uint16: reflect.TypeOf(uint16(0)),
|
||||
reflect.Uint32: reflect.TypeOf(uint32(0)),
|
||||
reflect.Uint64: reflect.TypeOf(uint64(0)),
|
||||
reflect.String: reflect.TypeOf(""),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
pgx "github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
type MyType struct {
|
||||
a int32 // NULL will cause decoding error
|
||||
b *string // there can be NULL in this position in SQL
|
||||
}
|
||||
|
||||
func (dst *MyType) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
return errors.New("NULL values can't be decoded. Scan into a &*MyType to handle NULLs")
|
||||
}
|
||||
|
||||
if err := (pgtype.CompositeFields{&dst.a, &dst.b}).DecodeBinary(ci, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src MyType) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) (newBuf []byte, err error) {
|
||||
a := pgtype.Int4{src.a, true}
|
||||
var b pgtype.Text
|
||||
if src.b != nil {
|
||||
b = pgtype.Text{*src.b, true}
|
||||
} else {
|
||||
b = pgtype.Text{}
|
||||
}
|
||||
|
||||
return (pgtype.CompositeFields{&a, &b}).EncodeBinary(ci, buf)
|
||||
}
|
||||
|
||||
func ptrS(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func E(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ExampleCustomCompositeTypes demonstrates how support for custom types mappable to SQL
|
||||
// composites can be added.
|
||||
func Example_customCompositeTypes() {
|
||||
conn, err := pgx.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
|
||||
E(err)
|
||||
|
||||
defer conn.Close(context.Background())
|
||||
_, err = conn.Exec(context.Background(), `drop type if exists mytype;
|
||||
|
||||
create type mytype as (
|
||||
a int4,
|
||||
b text
|
||||
);`)
|
||||
E(err)
|
||||
defer conn.Exec(context.Background(), "drop type mytype")
|
||||
|
||||
var result *MyType
|
||||
|
||||
// Demonstrates both passing and reading back composite values
|
||||
err = conn.QueryRow(context.Background(), "select $1::mytype",
|
||||
pgx.QueryResultFormats{pgx.BinaryFormatCode}, MyType{1, ptrS("foo")}).
|
||||
Scan(&result)
|
||||
E(err)
|
||||
|
||||
fmt.Printf("First row: a=%d b=%s\n", result.a, *result.b)
|
||||
|
||||
// Because we scan into &*MyType, NULLs are handled generically by assigning nil to result
|
||||
err = conn.QueryRow(context.Background(), "select NULL::mytype", pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&result)
|
||||
E(err)
|
||||
|
||||
fmt.Printf("Second row: %v\n", result)
|
||||
|
||||
// Output:
|
||||
// First row: a=1 b=foo
|
||||
// Second row: <nil>
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func DatabaseSQLValue(ci *ConnInfo, src Value) (interface{}, error) {
|
||||
if valuer, ok := src.(driver.Valuer); ok {
|
||||
return valuer.Value()
|
||||
}
|
||||
|
||||
if textEncoder, ok := src.(TextEncoder); ok {
|
||||
buf, err := textEncoder.EncodeText(ci, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
if binaryEncoder, ok := src.(BinaryEncoder); ok {
|
||||
buf, err := binaryEncoder.EncodeBinary(ci, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("cannot convert to database/sql compatible value")
|
||||
}
|
||||
|
||||
func EncodeValueText(src TextEncoder) (interface{}, error) {
|
||||
buf, err := src.EncodeText(nil, make([]byte, 0, 32))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return string(buf), err
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Date struct {
|
||||
Time time.Time
|
||||
Valid bool
|
||||
InfinityModifier InfinityModifier
|
||||
}
|
||||
|
||||
const (
|
||||
negativeInfinityDayOffset = -2147483648
|
||||
infinityDayOffset = 2147483647
|
||||
)
|
||||
|
||||
func (dst *Date) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Date{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case time.Time:
|
||||
*dst = Date{Time: value, Valid: true}
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(value))
|
||||
case *time.Time:
|
||||
if value == nil {
|
||||
*dst = Date{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Date{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingTimeType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Date", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Date) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
if dst.InfinityModifier != None {
|
||||
return dst.InfinityModifier
|
||||
}
|
||||
return dst.Time
|
||||
}
|
||||
|
||||
func (src *Date) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *time.Time:
|
||||
if src.InfinityModifier != None {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
*v = src.Time
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (dst *Date) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Date{}
|
||||
return nil
|
||||
}
|
||||
|
||||
sbuf := string(src)
|
||||
switch sbuf {
|
||||
case "infinity":
|
||||
*dst = Date{Valid: true, InfinityModifier: Infinity}
|
||||
case "-infinity":
|
||||
*dst = Date{Valid: true, InfinityModifier: -Infinity}
|
||||
default:
|
||||
t, err := time.ParseInLocation("2006-01-02", sbuf, time.UTC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Date{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Date) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Date{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 4 {
|
||||
return fmt.Errorf("invalid length for date: %v", len(src))
|
||||
}
|
||||
|
||||
dayOffset := int32(binary.BigEndian.Uint32(src))
|
||||
|
||||
switch dayOffset {
|
||||
case infinityDayOffset:
|
||||
*dst = Date{Valid: true, InfinityModifier: Infinity}
|
||||
case negativeInfinityDayOffset:
|
||||
*dst = Date{Valid: true, InfinityModifier: -Infinity}
|
||||
default:
|
||||
t := time.Date(2000, 1, int(1+dayOffset), 0, 0, 0, 0, time.UTC)
|
||||
*dst = Date{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Date) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var s string
|
||||
|
||||
switch src.InfinityModifier {
|
||||
case None:
|
||||
s = src.Time.Format("2006-01-02")
|
||||
case Infinity:
|
||||
s = "infinity"
|
||||
case NegativeInfinity:
|
||||
s = "-infinity"
|
||||
}
|
||||
|
||||
return append(buf, s...), nil
|
||||
}
|
||||
|
||||
func (src Date) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var daysSinceDateEpoch int32
|
||||
switch src.InfinityModifier {
|
||||
case None:
|
||||
tUnix := time.Date(src.Time.Year(), src.Time.Month(), src.Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
|
||||
dateEpoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
||||
|
||||
secSinceDateEpoch := tUnix - dateEpoch
|
||||
daysSinceDateEpoch = int32(secSinceDateEpoch / 86400)
|
||||
case Infinity:
|
||||
daysSinceDateEpoch = infinityDayOffset
|
||||
case NegativeInfinity:
|
||||
daysSinceDateEpoch = negativeInfinityDayOffset
|
||||
}
|
||||
|
||||
return pgio.AppendInt32(buf, daysSinceDateEpoch), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Date) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Date{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
case time.Time:
|
||||
*dst = Date{Time: src, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Date) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if src.InfinityModifier != None {
|
||||
return src.InfinityModifier.String(), nil
|
||||
}
|
||||
return src.Time, nil
|
||||
}
|
||||
|
||||
func (src Date) MarshalJSON() ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
var s string
|
||||
|
||||
switch src.InfinityModifier {
|
||||
case None:
|
||||
s = src.Time.Format("2006-01-02")
|
||||
case Infinity:
|
||||
s = "infinity"
|
||||
case NegativeInfinity:
|
||||
s = "-infinity"
|
||||
}
|
||||
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func (dst *Date) UnmarshalJSON(b []byte) error {
|
||||
var s *string
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
*dst = Date{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch *s {
|
||||
case "infinity":
|
||||
*dst = Date{Valid: true, InfinityModifier: Infinity}
|
||||
case "-infinity":
|
||||
*dst = Date{Valid: true, InfinityModifier: -Infinity}
|
||||
default:
|
||||
t, err := time.ParseInLocation("2006-01-02", *s, time.UTC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Date{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type DateArray struct {
|
||||
Elements []Date
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *DateArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = DateArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []time.Time:
|
||||
if value == nil {
|
||||
*dst = DateArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = DateArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Date, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = DateArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*time.Time:
|
||||
if value == nil {
|
||||
*dst = DateArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = DateArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Date, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = DateArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Date:
|
||||
if value == nil {
|
||||
*dst = DateArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = DateArray{Valid: true}
|
||||
} else {
|
||||
*dst = DateArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = DateArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for DateArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = DateArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to DateArray", src)
|
||||
}
|
||||
|
||||
*dst = DateArray{
|
||||
Elements: make([]Date, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Date, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to DateArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *DateArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to DateArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in DateArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst DateArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *DateArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]time.Time:
|
||||
*v = make([]time.Time, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*time.Time:
|
||||
*v = make([]*time.Time, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *DateArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from DateArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from DateArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *DateArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = DateArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Date
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Date, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Date
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = DateArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *DateArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = DateArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = DateArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Date, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = DateArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src DateArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src DateArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("date"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "date")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *DateArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src DateArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestDateArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "date[]", []interface{}{
|
||||
&pgtype.DateArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.DateArray{},
|
||||
&pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2017, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{},
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2015, 2, 2, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2015, 2, 4, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestDateArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.DateArray
|
||||
}{
|
||||
{
|
||||
source: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||
result: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]time.Time)(nil)),
|
||||
result: pgtype.DateArray{},
|
||||
},
|
||||
{
|
||||
source: [][]time.Time{
|
||||
{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||
{time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC)}},
|
||||
result: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]time.Time{
|
||||
{{{
|
||||
time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC)}}},
|
||||
{{{
|
||||
time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC)}}}},
|
||||
result: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]time.Time{
|
||||
{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||
{time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC)}},
|
||||
result: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]time.Time{
|
||||
{{{
|
||||
time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC)}}},
|
||||
{{{
|
||||
time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC)}}}},
|
||||
result: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.DateArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateArrayAssignTo(t *testing.T) {
|
||||
var timeSlice []time.Time
|
||||
var timeSliceDim2 [][]time.Time
|
||||
var timeSliceDim4 [][][][]time.Time
|
||||
var timeArrayDim2 [2][1]time.Time
|
||||
var timeArrayDim4 [2][1][1][3]time.Time
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.DateArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &timeSlice,
|
||||
expected: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{},
|
||||
dst: &timeSlice,
|
||||
expected: (([]time.Time)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{Valid: true},
|
||||
dst: &timeSlice,
|
||||
expected: []time.Time{},
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &timeSliceDim2,
|
||||
expected: [][]time.Time{
|
||||
{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||
{time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC)}},
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &timeSliceDim4,
|
||||
expected: [][][][]time.Time{
|
||||
{{{
|
||||
time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC)}}},
|
||||
{{{
|
||||
time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC)}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &timeArrayDim2,
|
||||
expected: [2][1]time.Time{
|
||||
{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||
{time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC)}},
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &timeArrayDim4,
|
||||
expected: [2][1][1][3]time.Time{
|
||||
{{{
|
||||
time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2017, 5, 6, 0, 0, 0, 0, time.UTC)}}},
|
||||
{{{
|
||||
time.Date(2018, 7, 8, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2019, 9, 10, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2020, 11, 12, 0, 0, 0, 0, time.UTC)}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.DateArray
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &timeSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &timeArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &timeSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.DateArray{
|
||||
Elements: []pgtype.Date{
|
||||
{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
{Time: time.Date(2016, 3, 4, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &timeArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestDateTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscodeEqFunc(t, "date", []interface{}{
|
||||
&pgtype.Date{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
&pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
&pgtype.Date{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
&pgtype.Date{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
&pgtype.Date{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
&pgtype.Date{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
&pgtype.Date{},
|
||||
&pgtype.Date{Valid: true, InfinityModifier: pgtype.Infinity},
|
||||
&pgtype.Date{Valid: true, InfinityModifier: -pgtype.Infinity},
|
||||
}, func(a, b interface{}) bool {
|
||||
at := a.(pgtype.Date)
|
||||
bt := b.(pgtype.Date)
|
||||
|
||||
return at.Time.Equal(bt.Time) && at.Valid == bt.Valid && at.InfinityModifier == bt.InfinityModifier
|
||||
})
|
||||
}
|
||||
|
||||
func TestDateSet(t *testing.T) {
|
||||
type _time time.Time
|
||||
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Date
|
||||
}{
|
||||
{source: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)), result: pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: "1999-12-31", result: pgtype.Date{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var d pgtype.Date
|
||||
err := d.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if d != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateAssignTo(t *testing.T) {
|
||||
var tim time.Time
|
||||
var ptim *time.Time
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Date
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), Valid: true}, dst: &tim, expected: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local)},
|
||||
{src: pgtype.Date{Time: time.Time{}}, dst: &ptim, expected: ((*time.Time)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Date
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), Valid: true}, dst: &ptim, expected: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Date
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), InfinityModifier: pgtype.Infinity, Valid: true}, dst: &tim},
|
||||
{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), InfinityModifier: pgtype.NegativeInfinity, Valid: true}, dst: &tim},
|
||||
{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local)}, dst: &tim},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateMarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source pgtype.Date
|
||||
result string
|
||||
}{
|
||||
{source: pgtype.Date{}, result: "null"},
|
||||
{source: pgtype.Date{Time: time.Date(2012, 3, 29, 0, 0, 0, 0, time.UTC), Valid: true}, result: "\"2012-03-29\""},
|
||||
{source: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 0, time.FixedZone("", -6*60*60)), Valid: true}, result: "\"2012-03-29\""},
|
||||
{source: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 555*1000*1000, time.FixedZone("", -6*60*60)), Valid: true}, result: "\"2012-03-29\""},
|
||||
{source: pgtype.Date{InfinityModifier: pgtype.Infinity, Valid: true}, result: "\"infinity\""},
|
||||
{source: pgtype.Date{InfinityModifier: pgtype.NegativeInfinity, Valid: true}, result: "\"-infinity\""},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
r, err := tt.source.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if string(r) != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateUnmarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source string
|
||||
result pgtype.Date
|
||||
}{
|
||||
{source: "null", result: pgtype.Date{}},
|
||||
{source: "\"2012-03-29\"", result: pgtype.Date{Time: time.Date(2012, 3, 29, 0, 0, 0, 0, time.UTC), Valid: true}},
|
||||
{source: "\"2012-03-29\"", result: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 0, time.FixedZone("", -6*60*60)), Valid: true}},
|
||||
{source: "\"2012-03-29\"", result: pgtype.Date{Time: time.Date(2012, 3, 29, 10, 5, 45, 555*1000*1000, time.FixedZone("", -6*60*60)), Valid: true}},
|
||||
{source: "\"infinity\"", result: pgtype.Date{InfinityModifier: pgtype.Infinity, Valid: true}},
|
||||
{source: "\"-infinity\"", result: pgtype.Date{InfinityModifier: pgtype.NegativeInfinity, Valid: true}},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Date
|
||||
err := r.UnmarshalJSON([]byte(tt.source))
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r.Time.Year() != tt.result.Time.Year() || r.Time.Month() != tt.result.Time.Month() || r.Time.Day() != tt.result.Time.Day() || r.Valid != tt.result.Valid || r.InfinityModifier != tt.result.InfinityModifier {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Daterange struct {
|
||||
Lower Date
|
||||
Upper Date
|
||||
LowerType BoundType
|
||||
UpperType BoundType
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Daterange) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Daterange{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case Daterange:
|
||||
*dst = value
|
||||
case *Daterange:
|
||||
*dst = *value
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(value))
|
||||
default:
|
||||
return fmt.Errorf("cannot convert %v to Daterange", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Daterange) Get() interface{} {
|
||||
if !src.Valid {
|
||||
return nil
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func (src *Daterange) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Daterange) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Daterange{}
|
||||
return nil
|
||||
}
|
||||
|
||||
utr, err := ParseUntypedTextRange(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Daterange{Valid: true}
|
||||
|
||||
dst.LowerType = utr.LowerType
|
||||
dst.UpperType = utr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeText(ci, []byte(utr.Lower)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeText(ci, []byte(utr.Upper)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Daterange) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Daterange{}
|
||||
return nil
|
||||
}
|
||||
|
||||
ubr, err := ParseUntypedBinaryRange(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Daterange{Valid: true}
|
||||
|
||||
dst.LowerType = ubr.LowerType
|
||||
dst.UpperType = ubr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeBinary(ci, ubr.Lower); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeBinary(ci, ubr.Upper); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Daterange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch src.LowerType {
|
||||
case Exclusive, Unbounded:
|
||||
buf = append(buf, '(')
|
||||
case Inclusive:
|
||||
buf = append(buf, '[')
|
||||
case Empty:
|
||||
return append(buf, "empty"...), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown lower bound type %v", src.LowerType)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
buf, err = src.Lower.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if buf == nil {
|
||||
return nil, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, ',')
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
buf, err = src.Upper.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if buf == nil {
|
||||
return nil, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Exclusive, Unbounded:
|
||||
buf = append(buf, ')')
|
||||
case Inclusive:
|
||||
buf = append(buf, ']')
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown upper bound type %v", src.UpperType)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Daterange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var rangeType byte
|
||||
switch src.LowerType {
|
||||
case Inclusive:
|
||||
rangeType |= lowerInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= lowerUnboundedMask
|
||||
case Exclusive:
|
||||
case Empty:
|
||||
return append(buf, emptyMask), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown LowerType: %v", src.LowerType)
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Inclusive:
|
||||
rangeType |= upperInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= upperUnboundedMask
|
||||
case Exclusive:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown UpperType: %v", src.UpperType)
|
||||
}
|
||||
|
||||
buf = append(buf, rangeType)
|
||||
|
||||
var err error
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
buf, err = src.Lower.EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
buf, err = src.Upper.EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Daterange) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Daterange{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Daterange) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestDaterangeTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscodeEqFunc(t, "daterange", []interface{}{
|
||||
&pgtype.Daterange{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true},
|
||||
&pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1800, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Daterange{},
|
||||
}, func(aa, bb interface{}) bool {
|
||||
a := aa.(pgtype.Daterange)
|
||||
b := bb.(pgtype.Daterange)
|
||||
|
||||
return a.Valid == b.Valid &&
|
||||
a.Lower.Time.Equal(b.Lower.Time) &&
|
||||
a.Lower.Valid == b.Lower.Valid &&
|
||||
a.Lower.InfinityModifier == b.Lower.InfinityModifier &&
|
||||
a.Upper.Time.Equal(b.Upper.Time) &&
|
||||
a.Upper.Valid == b.Upper.Valid &&
|
||||
a.Upper.InfinityModifier == b.Upper.InfinityModifier
|
||||
})
|
||||
}
|
||||
|
||||
func TestDaterangeNormalize(t *testing.T) {
|
||||
testutil.TestSuccessfulNormalizeEqFunc(t, []testutil.NormalizeTest{
|
||||
{
|
||||
SQL: "select daterange('2010-01-01', '2010-01-11', '(]')",
|
||||
Value: pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(2010, 1, 2, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2010, 1, 12, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
}, func(aa, bb interface{}) bool {
|
||||
a := aa.(pgtype.Daterange)
|
||||
b := bb.(pgtype.Daterange)
|
||||
|
||||
return a.Valid == b.Valid &&
|
||||
a.Lower.Time.Equal(b.Lower.Time) &&
|
||||
a.Lower.Valid == b.Lower.Valid &&
|
||||
a.Lower.InfinityModifier == b.Lower.InfinityModifier &&
|
||||
a.Upper.Time.Equal(b.Upper.Time) &&
|
||||
a.Upper.Valid == b.Upper.Valid &&
|
||||
a.Upper.InfinityModifier == b.Upper.InfinityModifier
|
||||
})
|
||||
}
|
||||
|
||||
func TestDaterangeSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Daterange
|
||||
}{
|
||||
{
|
||||
source: nil,
|
||||
result: pgtype.Daterange{},
|
||||
},
|
||||
{
|
||||
source: &pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
result: pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
source: pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
result: pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "[1990-12-31,2028-01-01)",
|
||||
result: pgtype.Daterange{
|
||||
Lower: pgtype.Date{Time: time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
Upper: pgtype.Date{Time: time.Date(2028, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
|
||||
LowerType: pgtype.Inclusive,
|
||||
UpperType: pgtype.Exclusive,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Daterange
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type EnumArray struct {
|
||||
Elements []GenericText
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *EnumArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = EnumArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []string:
|
||||
if value == nil {
|
||||
*dst = EnumArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = EnumArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]GenericText, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = EnumArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*string:
|
||||
if value == nil {
|
||||
*dst = EnumArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = EnumArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]GenericText, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = EnumArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []GenericText:
|
||||
if value == nil {
|
||||
*dst = EnumArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = EnumArray{Valid: true}
|
||||
} else {
|
||||
*dst = EnumArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = EnumArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for EnumArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = EnumArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to EnumArray", src)
|
||||
}
|
||||
|
||||
*dst = EnumArray{
|
||||
Elements: make([]GenericText, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]GenericText, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to EnumArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *EnumArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to EnumArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in EnumArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst EnumArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *EnumArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]string:
|
||||
*v = make([]string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*string:
|
||||
*v = make([]*string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *EnumArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from EnumArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from EnumArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *EnumArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = EnumArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []GenericText
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]GenericText, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem GenericText
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = EnumArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src EnumArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *EnumArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src EnumArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestEnumArrayTranscode(t *testing.T) {
|
||||
setupConn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, setupConn)
|
||||
|
||||
if _, err := setupConn.Exec(context.Background(), "drop type if exists color"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := setupConn.Exec(context.Background(), "create type color as enum ('red', 'green', 'blue')"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testutil.TestSuccessfulTranscode(t, "color[]", []interface{}{
|
||||
&pgtype.EnumArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{
|
||||
{String: "red", Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.EnumArray{},
|
||||
&pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{
|
||||
{String: "red", Valid: true},
|
||||
{String: "green", Valid: true},
|
||||
{String: "blue", Valid: true},
|
||||
{String: "red", Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnumArrayArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.EnumArray
|
||||
}{
|
||||
{
|
||||
source: []string{"foo"},
|
||||
result: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]string)(nil)),
|
||||
result: pgtype.EnumArray{},
|
||||
},
|
||||
{
|
||||
source: [][]string{{"foo"}, {"bar"}},
|
||||
result: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]string{{{{"foo", "bar", "baz"}}}, {{{"wibble", "wobble", "wubble"}}}},
|
||||
result: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{
|
||||
{String: "foo", Valid: true},
|
||||
{String: "bar", Valid: true},
|
||||
{String: "baz", Valid: true},
|
||||
{String: "wibble", Valid: true},
|
||||
{String: "wobble", Valid: true},
|
||||
{String: "wubble", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]string{{"foo"}, {"bar"}},
|
||||
result: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]string{{{{"foo", "bar", "baz"}}}, {{{"wibble", "wobble", "wubble"}}}},
|
||||
result: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{
|
||||
{String: "foo", Valid: true},
|
||||
{String: "bar", Valid: true},
|
||||
{String: "baz", Valid: true},
|
||||
{String: "wibble", Valid: true},
|
||||
{String: "wobble", Valid: true},
|
||||
{String: "wubble", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.EnumArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumArrayArrayAssignTo(t *testing.T) {
|
||||
var stringSlice []string
|
||||
type _stringSlice []string
|
||||
var namedStringSlice _stringSlice
|
||||
var stringSliceDim2 [][]string
|
||||
var stringSliceDim4 [][][][]string
|
||||
var stringArrayDim2 [2][1]string
|
||||
var stringArrayDim4 [2][1][1][3]string
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.EnumArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &stringSlice,
|
||||
expected: []string{"foo"},
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedStringSlice,
|
||||
expected: _stringSlice{"bar"},
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{},
|
||||
dst: &stringSlice,
|
||||
expected: (([]string)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{Valid: true},
|
||||
dst: &stringSlice,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &stringSliceDim2,
|
||||
expected: [][]string{{"foo"}, {"bar"}},
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{
|
||||
{String: "foo", Valid: true},
|
||||
{String: "bar", Valid: true},
|
||||
{String: "baz", Valid: true},
|
||||
{String: "wibble", Valid: true},
|
||||
{String: "wobble", Valid: true},
|
||||
{String: "wubble", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &stringSliceDim4,
|
||||
expected: [][][][]string{{{{"foo", "bar", "baz"}}}, {{{"wibble", "wobble", "wubble"}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim2,
|
||||
expected: [2][1]string{{"foo"}, {"bar"}},
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{
|
||||
{String: "foo", Valid: true},
|
||||
{String: "bar", Valid: true},
|
||||
{String: "baz", Valid: true},
|
||||
{String: "wibble", Valid: true},
|
||||
{String: "wobble", Valid: true},
|
||||
{String: "wubble", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim4,
|
||||
expected: [2][1][1][3]string{{{{"foo", "bar", "baz"}}}, {{{"wibble", "wobble", "wubble"}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.EnumArray
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &stringSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &stringSlice,
|
||||
},
|
||||
{
|
||||
src: pgtype.EnumArray{
|
||||
Elements: []pgtype.GenericText{{String: "foo", Valid: true}, {String: "bar", Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &stringArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package pgtype
|
||||
|
||||
import "fmt"
|
||||
|
||||
// EnumType represents a enum type. While it implements Value, this is only in service of its type conversion duties
|
||||
// when registered as a data type in a ConnType. It should not be used directly as a Value.
|
||||
type EnumType struct {
|
||||
value string
|
||||
valid bool
|
||||
|
||||
typeName string // PostgreSQL type name
|
||||
members []string // enum members
|
||||
membersMap map[string]string // map to quickly lookup member and reuse string instead of allocating
|
||||
}
|
||||
|
||||
// NewEnumType initializes a new EnumType. It retains a read-only reference to members. members must not be changed.
|
||||
func NewEnumType(typeName string, members []string) *EnumType {
|
||||
et := &EnumType{typeName: typeName, members: members}
|
||||
et.membersMap = make(map[string]string, len(members))
|
||||
for _, m := range members {
|
||||
et.membersMap[m] = m
|
||||
}
|
||||
return et
|
||||
}
|
||||
|
||||
func (et *EnumType) NewTypeValue() Value {
|
||||
return &EnumType{
|
||||
value: et.value,
|
||||
valid: et.valid,
|
||||
|
||||
typeName: et.typeName,
|
||||
members: et.members,
|
||||
membersMap: et.membersMap,
|
||||
}
|
||||
}
|
||||
|
||||
func (et *EnumType) TypeName() string {
|
||||
return et.typeName
|
||||
}
|
||||
|
||||
func (et *EnumType) Members() []string {
|
||||
return et.members
|
||||
}
|
||||
|
||||
// Set assigns src to dst. Set purposely does not check that src is a member. This allows continued error free
|
||||
// operation in the event the PostgreSQL enum type is modified during a connection.
|
||||
func (dst *EnumType) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
dst.valid = false
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case string:
|
||||
dst.value = value
|
||||
dst.valid = true
|
||||
case *string:
|
||||
if value == nil {
|
||||
dst.valid = false
|
||||
} else {
|
||||
dst.value = *value
|
||||
dst.valid = true
|
||||
}
|
||||
case []byte:
|
||||
if value == nil {
|
||||
dst.valid = false
|
||||
} else {
|
||||
dst.value = string(value)
|
||||
dst.valid = true
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingStringType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to enum %s", value, dst.typeName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst EnumType) Get() interface{} {
|
||||
if !dst.valid {
|
||||
return nil
|
||||
}
|
||||
return dst.value
|
||||
}
|
||||
|
||||
func (src *EnumType) AssignTo(dst interface{}) error {
|
||||
if !src.valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *string:
|
||||
*v = src.value
|
||||
return nil
|
||||
case *[]byte:
|
||||
*v = make([]byte, len(src.value))
|
||||
copy(*v, src.value)
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (EnumType) PreferredResultFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (dst *EnumType) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
dst.valid = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup the string in membersMap to avoid an allocation.
|
||||
if s, found := dst.membersMap[string(src)]; found {
|
||||
dst.value = s
|
||||
} else {
|
||||
// If an enum type is modified after the initial connection it is possible to receive an unexpected value.
|
||||
// Gracefully handle this situation. Purposely NOT modifying members and membersMap to allow for sharing members
|
||||
// and membersMap between connections.
|
||||
dst.value = string(src)
|
||||
}
|
||||
dst.valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *EnumType) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return dst.DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (EnumType) PreferredParamFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (src EnumType) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, src.value...), nil
|
||||
}
|
||||
|
||||
func (src EnumType) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return src.EncodeText(ci, buf)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupEnum(t *testing.T, conn *pgx.Conn) *pgtype.EnumType {
|
||||
_, err := conn.Exec(context.Background(), "drop type if exists pgtype_enum_color;")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = conn.Exec(context.Background(), "create type pgtype_enum_color as enum ('blue', 'green', 'purple');")
|
||||
require.NoError(t, err)
|
||||
|
||||
var oid uint32
|
||||
err = conn.QueryRow(context.Background(), "select oid from pg_type where typname=$1;", "pgtype_enum_color").Scan(&oid)
|
||||
require.NoError(t, err)
|
||||
|
||||
et := pgtype.NewEnumType("pgtype_enum_color", []string{"blue", "green", "purple"})
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: et, Name: "pgtype_enum_color", OID: oid})
|
||||
|
||||
return et
|
||||
}
|
||||
|
||||
func cleanupEnum(t *testing.T, conn *pgx.Conn) {
|
||||
_, err := conn.Exec(context.Background(), "drop type if exists pgtype_enum_color;")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnumTypeTranscode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
setupEnum(t, conn)
|
||||
defer cleanupEnum(t, conn)
|
||||
|
||||
var dst string
|
||||
err := conn.QueryRow(context.Background(), "select $1::pgtype_enum_color", "blue").Scan(&dst)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, "blue", dst)
|
||||
}
|
||||
|
||||
func TestEnumTypeSet(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
enumType := setupEnum(t, conn)
|
||||
defer cleanupEnum(t, conn)
|
||||
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result interface{}
|
||||
}{
|
||||
{source: "blue", result: "blue"},
|
||||
{source: _string("green"), result: "green"},
|
||||
{source: (*string)(nil), result: nil},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
err := enumType.Set(tt.source)
|
||||
assert.NoErrorf(t, err, "%d", i)
|
||||
assert.Equalf(t, tt.result, enumType.Get(), "%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumTypeAssignTo(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
enumType := setupEnum(t, conn)
|
||||
defer cleanupEnum(t, conn)
|
||||
|
||||
{
|
||||
var s string
|
||||
|
||||
err := enumType.Set("blue")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = enumType.AssignTo(&s)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "blue", s)
|
||||
}
|
||||
|
||||
{
|
||||
var ps *string
|
||||
|
||||
err := enumType.Set("blue")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = enumType.AssignTo(&ps)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, "blue", *ps)
|
||||
}
|
||||
|
||||
{
|
||||
var ps *string
|
||||
|
||||
err := enumType.Set(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = enumType.AssignTo(&ps)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, (*string)(nil), ps)
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
bytesTests := []struct {
|
||||
src interface{}
|
||||
dst *[]byte
|
||||
expected []byte
|
||||
}{
|
||||
{src: "blue", dst: &buf, expected: []byte("blue")},
|
||||
{src: nil, dst: &buf, expected: nil},
|
||||
}
|
||||
|
||||
for i, tt := range bytesTests {
|
||||
err := enumType.Set(tt.src)
|
||||
require.NoError(t, err, "%d", i)
|
||||
|
||||
err = enumType.AssignTo(tt.dst)
|
||||
require.NoError(t, err, "%d", i)
|
||||
|
||||
if bytes.Compare(*tt.dst, tt.expected) != 0 {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var s string
|
||||
|
||||
err := enumType.Set(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = enumType.AssignTo(&s)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Float4 struct {
|
||||
Float float32
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Float4) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Float4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case float32:
|
||||
*dst = Float4{Float: value, Valid: true}
|
||||
case float64:
|
||||
*dst = Float4{Float: float32(value), Valid: true}
|
||||
case int8:
|
||||
*dst = Float4{Float: float32(value), Valid: true}
|
||||
case uint8:
|
||||
*dst = Float4{Float: float32(value), Valid: true}
|
||||
case int16:
|
||||
*dst = Float4{Float: float32(value), Valid: true}
|
||||
case uint16:
|
||||
*dst = Float4{Float: float32(value), Valid: true}
|
||||
case int32:
|
||||
f32 := float32(value)
|
||||
if int32(f32) == value {
|
||||
*dst = Float4{Float: f32, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case uint32:
|
||||
f32 := float32(value)
|
||||
if uint32(f32) == value {
|
||||
*dst = Float4{Float: f32, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case int64:
|
||||
f32 := float32(value)
|
||||
if int64(f32) == value {
|
||||
*dst = Float4{Float: f32, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case uint64:
|
||||
f32 := float32(value)
|
||||
if uint64(f32) == value {
|
||||
*dst = Float4{Float: f32, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case int:
|
||||
f32 := float32(value)
|
||||
if int(f32) == value {
|
||||
*dst = Float4{Float: f32, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case uint:
|
||||
f32 := float32(value)
|
||||
if uint(f32) == value {
|
||||
*dst = Float4{Float: f32, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case string:
|
||||
num, err := strconv.ParseFloat(value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Float4{Float: float32(num), Valid: true}
|
||||
case *float64:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float32:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int8:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint8:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int16:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint16:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int32:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint32:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int64:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint64:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Float4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float8", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Float4) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Float
|
||||
}
|
||||
|
||||
func (src *Float4) AssignTo(dst interface{}) error {
|
||||
return float64AssignTo(float64(src.Float), src.Valid, dst)
|
||||
}
|
||||
|
||||
func (dst *Float4) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(string(src), 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Float4{Float: float32(n), Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float4) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 4 {
|
||||
return fmt.Errorf("invalid length for float4: %v", len(src))
|
||||
}
|
||||
|
||||
n := int32(binary.BigEndian.Uint32(src))
|
||||
|
||||
*dst = Float4{Float: math.Float32frombits(uint32(n)), Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Float4) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, strconv.FormatFloat(float64(src.Float), 'f', -1, 32)...)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Float4) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendUint32(buf, math.Float32bits(src.Float))
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Float4) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Float4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case float64:
|
||||
*dst = Float4{Float: float32(src), Valid: true}
|
||||
return nil
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Float4) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return float64(src.Float), nil
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Float4Array struct {
|
||||
Elements []Float4
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Float4Array) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Float4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []float32:
|
||||
if value == nil {
|
||||
*dst = Float4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Float4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Float4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*float32:
|
||||
if value == nil {
|
||||
*dst = Float4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Float4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Float4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Float4:
|
||||
if value == nil {
|
||||
*dst = Float4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float4Array{Valid: true}
|
||||
} else {
|
||||
*dst = Float4Array{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = Float4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for Float4Array", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = Float4Array{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float4Array", src)
|
||||
}
|
||||
|
||||
*dst = Float4Array{
|
||||
Elements: make([]Float4, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Float4, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to Float4Array, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float4Array) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to Float4Array")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in Float4Array", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst Float4Array) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Float4Array) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]float32:
|
||||
*v = make([]float32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*float32:
|
||||
*v = make([]*float32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float4Array) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Float4Array")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Float4Array")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *Float4Array) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Float4
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Float4, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Float4
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float4Array{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float4Array) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Float4Array{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Float4, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float4Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Float4Array) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Float4Array) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("float4"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "float4")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Float4Array) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Float4Array) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestFloat4ArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "float4[]", []interface{}{
|
||||
&pgtype.Float4Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Float4Array{},
|
||||
&pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{},
|
||||
{Float: 6, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat4ArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float4Array
|
||||
}{
|
||||
{
|
||||
source: []float32{1},
|
||||
result: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]float32)(nil)),
|
||||
result: pgtype.Float4Array{},
|
||||
},
|
||||
{
|
||||
source: [][]float32{{1}, {2}},
|
||||
result: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]float32{{1}, {2}},
|
||||
result: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float4Array
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat4ArrayAssignTo(t *testing.T) {
|
||||
var float32Slice []float32
|
||||
var namedFloat32Slice _float32Slice
|
||||
var float32SliceDim2 [][]float32
|
||||
var float32SliceDim4 [][][][]float32
|
||||
var float32ArrayDim2 [2][1]float32
|
||||
var float32ArrayDim4 [2][1][1][3]float32
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float4Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1.23, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &float32Slice,
|
||||
expected: []float32{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1.23, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedFloat32Slice,
|
||||
expected: _float32Slice{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{},
|
||||
dst: &float32Slice,
|
||||
expected: (([]float32)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{Valid: true},
|
||||
dst: &float32Slice,
|
||||
expected: []float32{},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [][]float32{{1}, {2}},
|
||||
dst: &float32SliceDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [][][][]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &float32SliceDim4,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [2][1]float32{{1}, {2}},
|
||||
dst: &float32ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [2][1][1][3]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &float32ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float4Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &float32Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &float32ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &float32Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &float32ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestFloat4Transcode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "float4", []interface{}{
|
||||
&pgtype.Float4{Float: -1, Valid: true},
|
||||
&pgtype.Float4{Float: 0, Valid: true},
|
||||
&pgtype.Float4{Float: 0.00001, Valid: true},
|
||||
&pgtype.Float4{Float: 1, Valid: true},
|
||||
&pgtype.Float4{Float: 9999.99, Valid: true},
|
||||
&pgtype.Float4{Float: 0},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat4Set(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float4
|
||||
}{
|
||||
{source: float32(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: float64(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: int8(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: int16(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: int32(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: int64(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: int8(-1), result: pgtype.Float4{Float: -1, Valid: true}},
|
||||
{source: int16(-1), result: pgtype.Float4{Float: -1, Valid: true}},
|
||||
{source: int32(-1), result: pgtype.Float4{Float: -1, Valid: true}},
|
||||
{source: int64(-1), result: pgtype.Float4{Float: -1, Valid: true}},
|
||||
{source: uint8(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: uint16(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: uint32(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: uint64(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: "1", result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
{source: _int8(1), result: pgtype.Float4{Float: 1, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float4
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat4AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
var f32 float32
|
||||
var f64 float64
|
||||
var pf32 *float32
|
||||
var pf64 *float64
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float4
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &f32, expected: float32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &f64, expected: float64(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Float4{Float: 0}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Float4{Float: 0}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Float4
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &pf32, expected: float32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Valid: true}, dst: &pf64, expected: float64(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float4
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Float4{Float: 150, Valid: true}, dst: &i8},
|
||||
{src: pgtype.Float4{Float: 40000, Valid: true}, dst: &i16},
|
||||
{src: pgtype.Float4{Float: -1, Valid: true}, dst: &ui8},
|
||||
{src: pgtype.Float4{Float: -1, Valid: true}, dst: &ui16},
|
||||
{src: pgtype.Float4{Float: -1, Valid: true}, dst: &ui32},
|
||||
{src: pgtype.Float4{Float: -1, Valid: true}, dst: &ui64},
|
||||
{src: pgtype.Float4{Float: -1, Valid: true}, dst: &ui},
|
||||
{src: pgtype.Float4{Float: 0}, dst: &i32},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Float8 struct {
|
||||
Float float64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Float8) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Float8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case float32:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case float64:
|
||||
*dst = Float8{Float: value, Valid: true}
|
||||
case int8:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case uint8:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case int16:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case uint16:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case int32:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case uint32:
|
||||
*dst = Float8{Float: float64(value), Valid: true}
|
||||
case int64:
|
||||
f64 := float64(value)
|
||||
if int64(f64) == value {
|
||||
*dst = Float8{Float: f64, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case uint64:
|
||||
f64 := float64(value)
|
||||
if uint64(f64) == value {
|
||||
*dst = Float8{Float: f64, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case int:
|
||||
f64 := float64(value)
|
||||
if int(f64) == value {
|
||||
*dst = Float8{Float: f64, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case uint:
|
||||
f64 := float64(value)
|
||||
if uint(f64) == value {
|
||||
*dst = Float8{Float: f64, Valid: true}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case string:
|
||||
num, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Float8{Float: float64(num), Valid: true}
|
||||
case *float64:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float32:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int8:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint8:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int16:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint16:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int32:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint32:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int64:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint64:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Float8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float8", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Float8) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Float
|
||||
}
|
||||
|
||||
func (src *Float8) AssignTo(dst interface{}) error {
|
||||
return float64AssignTo(src.Float, src.Valid, dst)
|
||||
}
|
||||
|
||||
func (dst *Float8) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(string(src), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Float8{Float: n, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float8) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 8 {
|
||||
return fmt.Errorf("invalid length for float4: %v", len(src))
|
||||
}
|
||||
|
||||
n := int64(binary.BigEndian.Uint64(src))
|
||||
|
||||
*dst = Float8{Float: math.Float64frombits(uint64(n)), Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Float8) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, strconv.FormatFloat(float64(src.Float), 'f', -1, 64)...)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Float8) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.Float))
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Float8) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Float8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case float64:
|
||||
*dst = Float8{Float: src, Valid: true}
|
||||
return nil
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Float8) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return src.Float, nil
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Float8Array struct {
|
||||
Elements []Float8
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Float8Array) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Float8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []float64:
|
||||
if value == nil {
|
||||
*dst = Float8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Float8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Float8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*float64:
|
||||
if value == nil {
|
||||
*dst = Float8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Float8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Float8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Float8:
|
||||
if value == nil {
|
||||
*dst = Float8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float8Array{Valid: true}
|
||||
} else {
|
||||
*dst = Float8Array{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = Float8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for Float8Array", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = Float8Array{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float8Array", src)
|
||||
}
|
||||
|
||||
*dst = Float8Array{
|
||||
Elements: make([]Float8, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Float8, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to Float8Array, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float8Array) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to Float8Array")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in Float8Array", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst Float8Array) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Float8Array) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]float64:
|
||||
*v = make([]float64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*float64:
|
||||
*v = make([]*float64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float8Array) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Float8Array")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Float8Array")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *Float8Array) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Float8
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Float8, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Float8
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float8Array{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float8Array) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Float8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Float8Array{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Float8, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float8Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Float8Array) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Float8Array) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("float8"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "float8")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Float8Array) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Float8Array) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestFloat8ArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "float8[]", []interface{}{
|
||||
&pgtype.Float8Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
{Float: 1, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Float8Array{},
|
||||
&pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{},
|
||||
{Float: 6, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat8ArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float8Array
|
||||
}{
|
||||
{
|
||||
source: []float64{1},
|
||||
result: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]float64)(nil)),
|
||||
result: pgtype.Float8Array{},
|
||||
},
|
||||
{
|
||||
source: [][]float64{{1}, {2}},
|
||||
result: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]float64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float8Array
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat8ArrayAssignTo(t *testing.T) {
|
||||
var float64Slice []float64
|
||||
var namedFloat64Slice _float64Slice
|
||||
var float64SliceDim2 [][]float64
|
||||
var float64SliceDim4 [][][][]float64
|
||||
var float64ArrayDim2 [2][1]float64
|
||||
var float64ArrayDim4 [2][1][1][3]float64
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float8Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1.23, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &float64Slice,
|
||||
expected: []float64{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1.23, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedFloat64Slice,
|
||||
expected: _float64Slice{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{},
|
||||
dst: &float64Slice,
|
||||
expected: (([]float64)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{Valid: true},
|
||||
dst: &float64Slice,
|
||||
expected: []float64{},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [][]float64{{1}, {2}},
|
||||
dst: &float64SliceDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [][][][]float64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &float64SliceDim4,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [2][1]float64{{1}, {2}},
|
||||
dst: &float64ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
{Float: 1, Valid: true},
|
||||
{Float: 2, Valid: true},
|
||||
{Float: 3, Valid: true},
|
||||
{Float: 4, Valid: true},
|
||||
{Float: 5, Valid: true},
|
||||
{Float: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [2][1][1][3]float64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &float64ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float8Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &float64Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &float64ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &float64Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Valid: true}, {Float: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &float64ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestFloat8Transcode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "float8", []interface{}{
|
||||
&pgtype.Float8{Float: -1, Valid: true},
|
||||
&pgtype.Float8{Float: 0, Valid: true},
|
||||
&pgtype.Float8{Float: 0.00001, Valid: true},
|
||||
&pgtype.Float8{Float: 1, Valid: true},
|
||||
&pgtype.Float8{Float: 9999.99, Valid: true},
|
||||
&pgtype.Float8{Float: 0},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat8Set(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float8
|
||||
}{
|
||||
{source: float32(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: float64(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: int8(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: int16(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: int32(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: int64(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: int8(-1), result: pgtype.Float8{Float: -1, Valid: true}},
|
||||
{source: int16(-1), result: pgtype.Float8{Float: -1, Valid: true}},
|
||||
{source: int32(-1), result: pgtype.Float8{Float: -1, Valid: true}},
|
||||
{source: int64(-1), result: pgtype.Float8{Float: -1, Valid: true}},
|
||||
{source: uint8(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: uint16(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: uint32(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: uint64(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: "1", result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
{source: _int8(1), result: pgtype.Float8{Float: 1, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float8
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat8AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
var f32 float32
|
||||
var f64 float64
|
||||
var pf32 *float32
|
||||
var pf64 *float64
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float8
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &f32, expected: float32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &f64, expected: float64(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Float8{Float: 0}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Float8{Float: 0}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Float8
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &pf32, expected: float32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Valid: true}, dst: &pf64, expected: float64(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float8
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Float8{Float: 150, Valid: true}, dst: &i8},
|
||||
{src: pgtype.Float8{Float: 40000, Valid: true}, dst: &i16},
|
||||
{src: pgtype.Float8{Float: -1, Valid: true}, dst: &ui8},
|
||||
{src: pgtype.Float8{Float: -1, Valid: true}, dst: &ui16},
|
||||
{src: pgtype.Float8{Float: -1, Valid: true}, dst: &ui32},
|
||||
{src: pgtype.Float8{Float: -1, Valid: true}, dst: &ui64},
|
||||
{src: pgtype.Float8{Float: -1, Valid: true}, dst: &ui},
|
||||
{src: pgtype.Float8{Float: 0}, dst: &i32},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
// GenericBinary is a placeholder for binary format values that no other type exists
|
||||
// to handle.
|
||||
type GenericBinary Bytea
|
||||
|
||||
func (dst *GenericBinary) Set(src interface{}) error {
|
||||
return (*Bytea)(dst).Set(src)
|
||||
}
|
||||
|
||||
func (dst GenericBinary) Get() interface{} {
|
||||
return (Bytea)(dst).Get()
|
||||
}
|
||||
|
||||
func (src *GenericBinary) AssignTo(dst interface{}) error {
|
||||
return (*Bytea)(src).AssignTo(dst)
|
||||
}
|
||||
|
||||
func (dst *GenericBinary) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return (*Bytea)(dst).DecodeBinary(ci, src)
|
||||
}
|
||||
|
||||
func (src GenericBinary) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Bytea)(src).EncodeBinary(ci, buf)
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *GenericBinary) Scan(src interface{}) error {
|
||||
return (*Bytea)(dst).Scan(src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src GenericBinary) Value() (driver.Value, error) {
|
||||
return (Bytea)(src).Value()
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
// GenericText is a placeholder for text format values that no other type exists
|
||||
// to handle.
|
||||
type GenericText Text
|
||||
|
||||
func (dst *GenericText) Set(src interface{}) error {
|
||||
return (*Text)(dst).Set(src)
|
||||
}
|
||||
|
||||
func (dst GenericText) Get() interface{} {
|
||||
return (Text)(dst).Get()
|
||||
}
|
||||
|
||||
func (src *GenericText) AssignTo(dst interface{}) error {
|
||||
return (*Text)(src).AssignTo(dst)
|
||||
}
|
||||
|
||||
func (dst *GenericText) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
return (*Text)(dst).DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (src GenericText) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (Text)(src).EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *GenericText) Scan(src interface{}) error {
|
||||
return (*Text)(dst).Scan(src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src GenericText) Value() (driver.Value, error) {
|
||||
return (Text)(src).Value()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module github.com/jackc/pgtype
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/jackc/pgconn v1.10.1
|
||||
github.com/jackc/pgio v1.0.0
|
||||
github.com/jackc/pgx/v4 v4.14.2-0.20211129172902-cf0de913ee8f
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
|
||||
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
|
||||
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.14.2-0.20211129172902-cf0de913ee8f h1:Y3Es3mIYatTvP4CXPXfmJtHWe8eq4E8owY6Fq61hEik=
|
||||
github.com/jackc/pgx/v4 v4.14.2-0.20211129172902-cf0de913ee8f/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
@@ -0,0 +1,446 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
// Hstore represents an hstore column that can be null or have null values
|
||||
// associated with its keys.
|
||||
type Hstore struct {
|
||||
Map map[string]Text
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Hstore) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Hstore{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case map[string]string:
|
||||
m := make(map[string]Text, len(value))
|
||||
for k, v := range value {
|
||||
m[k] = Text{String: v, Valid: true}
|
||||
}
|
||||
*dst = Hstore{Map: m, Valid: true}
|
||||
case map[string]*string:
|
||||
m := make(map[string]Text, len(value))
|
||||
for k, v := range value {
|
||||
if v == nil {
|
||||
m[k] = Text{}
|
||||
} else {
|
||||
m[k] = Text{String: *v, Valid: true}
|
||||
}
|
||||
}
|
||||
*dst = Hstore{Map: m, Valid: true}
|
||||
default:
|
||||
return fmt.Errorf("cannot convert %v to Hstore", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Hstore) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Map
|
||||
}
|
||||
|
||||
func (src *Hstore) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *map[string]string:
|
||||
*v = make(map[string]string, len(src.Map))
|
||||
for k, val := range src.Map {
|
||||
if !val.Valid {
|
||||
return fmt.Errorf("cannot decode %#v into %T", src, dst)
|
||||
}
|
||||
(*v)[k] = val.String
|
||||
}
|
||||
return nil
|
||||
case *map[string]*string:
|
||||
*v = make(map[string]*string, len(src.Map))
|
||||
for k, val := range src.Map {
|
||||
if val.Valid {
|
||||
(*v)[k] = &val.String
|
||||
} else {
|
||||
(*v)[k] = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (dst *Hstore) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Hstore{}
|
||||
return nil
|
||||
}
|
||||
|
||||
keys, values, err := parseHstore(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := make(map[string]Text, len(keys))
|
||||
for i := range keys {
|
||||
m[keys[i]] = values[i]
|
||||
}
|
||||
|
||||
*dst = Hstore{Map: m, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Hstore) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Hstore{}
|
||||
return nil
|
||||
}
|
||||
|
||||
rp := 0
|
||||
|
||||
if len(src[rp:]) < 4 {
|
||||
return fmt.Errorf("hstore incomplete %v", src)
|
||||
}
|
||||
pairCount := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
|
||||
m := make(map[string]Text, pairCount)
|
||||
|
||||
for i := 0; i < pairCount; i++ {
|
||||
if len(src[rp:]) < 4 {
|
||||
return fmt.Errorf("hstore incomplete %v", src)
|
||||
}
|
||||
keyLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
|
||||
if len(src[rp:]) < keyLen {
|
||||
return fmt.Errorf("hstore incomplete %v", src)
|
||||
}
|
||||
key := string(src[rp : rp+keyLen])
|
||||
rp += keyLen
|
||||
|
||||
if len(src[rp:]) < 4 {
|
||||
return fmt.Errorf("hstore incomplete %v", src)
|
||||
}
|
||||
valueLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
|
||||
var valueBuf []byte
|
||||
if valueLen >= 0 {
|
||||
valueBuf = src[rp : rp+valueLen]
|
||||
rp += valueLen
|
||||
}
|
||||
|
||||
var value Text
|
||||
err := value.DecodeBinary(ci, valueBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[key] = value
|
||||
}
|
||||
|
||||
*dst = Hstore{Map: m, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Hstore) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
firstPair := true
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for k, v := range src.Map {
|
||||
if firstPair {
|
||||
firstPair = false
|
||||
} else {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
buf = append(buf, quoteHstoreElementIfNeeded(k)...)
|
||||
buf = append(buf, "=>"...)
|
||||
|
||||
elemBuf, err := v.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
} else {
|
||||
buf = append(buf, quoteHstoreElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Hstore) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendInt32(buf, int32(len(src.Map)))
|
||||
|
||||
var err error
|
||||
for k, v := range src.Map {
|
||||
buf = pgio.AppendInt32(buf, int32(len(k)))
|
||||
buf = append(buf, k...)
|
||||
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := v.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, err
|
||||
}
|
||||
|
||||
var quoteHstoreReplacer = strings.NewReplacer(`\`, `\\`, `"`, `\"`)
|
||||
|
||||
func quoteHstoreElement(src string) string {
|
||||
return `"` + quoteArrayReplacer.Replace(src) + `"`
|
||||
}
|
||||
|
||||
func quoteHstoreElementIfNeeded(src string) string {
|
||||
if src == "" || (len(src) == 4 && strings.ToLower(src) == "null") || strings.ContainsAny(src, ` {},"\=>`) {
|
||||
return quoteArrayElement(src)
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
const (
|
||||
hsPre = iota
|
||||
hsKey
|
||||
hsSep
|
||||
hsVal
|
||||
hsNul
|
||||
hsNext
|
||||
)
|
||||
|
||||
type hstoreParser struct {
|
||||
str string
|
||||
pos int
|
||||
}
|
||||
|
||||
func newHSP(in string) *hstoreParser {
|
||||
return &hstoreParser{
|
||||
pos: 0,
|
||||
str: in,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hstoreParser) Consume() (r rune, end bool) {
|
||||
if p.pos >= len(p.str) {
|
||||
end = true
|
||||
return
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(p.str[p.pos:])
|
||||
p.pos += w
|
||||
return
|
||||
}
|
||||
|
||||
func (p *hstoreParser) Peek() (r rune, end bool) {
|
||||
if p.pos >= len(p.str) {
|
||||
end = true
|
||||
return
|
||||
}
|
||||
r, _ = utf8.DecodeRuneInString(p.str[p.pos:])
|
||||
return
|
||||
}
|
||||
|
||||
// parseHstore parses the string representation of an hstore column (the same
|
||||
// you would get from an ordinary SELECT) into two slices of keys and values. it
|
||||
// is used internally in the default parsing of hstores.
|
||||
func parseHstore(s string) (k []string, v []Text, err error) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
keys := []string{}
|
||||
values := []Text{}
|
||||
p := newHSP(s)
|
||||
|
||||
r, end := p.Consume()
|
||||
state := hsPre
|
||||
|
||||
for !end {
|
||||
switch state {
|
||||
case hsPre:
|
||||
if r == '"' {
|
||||
state = hsKey
|
||||
} else {
|
||||
err = errors.New("String does not begin with \"")
|
||||
}
|
||||
case hsKey:
|
||||
switch r {
|
||||
case '"': //End of the key
|
||||
keys = append(keys, buf.String())
|
||||
buf = bytes.Buffer{}
|
||||
state = hsSep
|
||||
case '\\': //Potential escaped character
|
||||
n, end := p.Consume()
|
||||
switch {
|
||||
case end:
|
||||
err = errors.New("Found EOS in key, expecting character or \"")
|
||||
case n == '"', n == '\\':
|
||||
buf.WriteRune(n)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
buf.WriteRune(n)
|
||||
}
|
||||
default: //Any other character
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
case hsSep:
|
||||
if r == '=' {
|
||||
r, end = p.Consume()
|
||||
switch {
|
||||
case end:
|
||||
err = errors.New("Found EOS after '=', expecting '>'")
|
||||
case r == '>':
|
||||
r, end = p.Consume()
|
||||
switch {
|
||||
case end:
|
||||
err = errors.New("Found EOS after '=>', expecting '\"' or 'NULL'")
|
||||
case r == '"':
|
||||
state = hsVal
|
||||
case r == 'N':
|
||||
state = hsNul
|
||||
default:
|
||||
err = fmt.Errorf("Invalid character '%c' after '=>', expecting '\"' or 'NULL'", r)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("Invalid character after '=', expecting '>'")
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Invalid character '%c' after value, expecting '='", r)
|
||||
}
|
||||
case hsVal:
|
||||
switch r {
|
||||
case '"': //End of the value
|
||||
values = append(values, Text{String: buf.String(), Valid: true})
|
||||
buf = bytes.Buffer{}
|
||||
state = hsNext
|
||||
case '\\': //Potential escaped character
|
||||
n, end := p.Consume()
|
||||
switch {
|
||||
case end:
|
||||
err = errors.New("Found EOS in key, expecting character or \"")
|
||||
case n == '"', n == '\\':
|
||||
buf.WriteRune(n)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
buf.WriteRune(n)
|
||||
}
|
||||
default: //Any other character
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
case hsNul:
|
||||
nulBuf := make([]rune, 3)
|
||||
nulBuf[0] = r
|
||||
for i := 1; i < 3; i++ {
|
||||
r, end = p.Consume()
|
||||
if end {
|
||||
err = errors.New("Found EOS in NULL value")
|
||||
return
|
||||
}
|
||||
nulBuf[i] = r
|
||||
}
|
||||
if nulBuf[0] == 'U' && nulBuf[1] == 'L' && nulBuf[2] == 'L' {
|
||||
values = append(values, Text{})
|
||||
state = hsNext
|
||||
} else {
|
||||
err = fmt.Errorf("Invalid NULL value: 'N%s'", string(nulBuf))
|
||||
}
|
||||
case hsNext:
|
||||
if r == ',' {
|
||||
r, end = p.Consume()
|
||||
switch {
|
||||
case end:
|
||||
err = errors.New("Found EOS after ',', expcting space")
|
||||
case (unicode.IsSpace(r)):
|
||||
r, end = p.Consume()
|
||||
state = hsKey
|
||||
default:
|
||||
err = fmt.Errorf("Invalid character '%c' after ', ', expecting \"", r)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Invalid character '%c' after value, expecting ','", r)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r, end = p.Consume()
|
||||
}
|
||||
if state != hsNext {
|
||||
err = errors.New("Improperly formatted hstore")
|
||||
return
|
||||
}
|
||||
k = keys
|
||||
v = values
|
||||
return
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Hstore) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Hstore{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Hstore) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type HstoreArray struct {
|
||||
Elements []Hstore
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *HstoreArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = HstoreArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []map[string]string:
|
||||
if value == nil {
|
||||
*dst = HstoreArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = HstoreArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Hstore, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = HstoreArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Hstore:
|
||||
if value == nil {
|
||||
*dst = HstoreArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = HstoreArray{Valid: true}
|
||||
} else {
|
||||
*dst = HstoreArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = HstoreArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for HstoreArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = HstoreArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to HstoreArray", src)
|
||||
}
|
||||
|
||||
*dst = HstoreArray{
|
||||
Elements: make([]Hstore, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Hstore, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to HstoreArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *HstoreArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to HstoreArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in HstoreArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst HstoreArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *HstoreArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]map[string]string:
|
||||
*v = make([]map[string]string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *HstoreArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from HstoreArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from HstoreArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *HstoreArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = HstoreArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Hstore
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Hstore, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Hstore
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = HstoreArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *HstoreArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = HstoreArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = HstoreArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Hstore, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = HstoreArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src HstoreArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src HstoreArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("hstore"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "hstore")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *HstoreArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src HstoreArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
func TestHstoreArrayTranscode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
|
||||
var hstoreOID uint32
|
||||
err := conn.QueryRow(context.Background(), "select t.oid from pg_type t where t.typname='hstore';").Scan(&hstoreOID)
|
||||
if err != nil {
|
||||
t.Fatalf("did not find hstore OID, %v", err)
|
||||
}
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.Hstore{}, Name: "hstore", OID: hstoreOID})
|
||||
|
||||
var hstoreArrayOID uint32
|
||||
err = conn.QueryRow(context.Background(), "select t.oid from pg_type t where t.typname='_hstore';").Scan(&hstoreArrayOID)
|
||||
if err != nil {
|
||||
t.Fatalf("did not find _hstore OID, %v", err)
|
||||
}
|
||||
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.HstoreArray{}, Name: "_hstore", OID: hstoreArrayOID})
|
||||
|
||||
text := func(s string) pgtype.Text {
|
||||
return pgtype.Text{String: s, Valid: true}
|
||||
}
|
||||
|
||||
values := []pgtype.Hstore{
|
||||
{Map: map[string]pgtype.Text{}, Valid: true},
|
||||
{Map: map[string]pgtype.Text{"foo": text("bar")}, Valid: true},
|
||||
{Map: map[string]pgtype.Text{"foo": text("bar"), "baz": text("quz")}, Valid: true},
|
||||
{Map: map[string]pgtype.Text{"NULL": text("bar")}, Valid: true},
|
||||
{Map: map[string]pgtype.Text{"foo": text("NULL")}, Valid: true},
|
||||
{},
|
||||
}
|
||||
|
||||
specialStrings := []string{
|
||||
`"`,
|
||||
`'`,
|
||||
`\`,
|
||||
`\\`,
|
||||
`=>`,
|
||||
` `,
|
||||
`\ / / \\ => " ' " '`,
|
||||
}
|
||||
for _, s := range specialStrings {
|
||||
// Special key values
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{s + "foo": text("bar")}, Valid: true}) // at beginning
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s + "bar": text("bar")}, Valid: true}) // in middle
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s: text("bar")}, Valid: true}) // at end
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{s: text("bar")}, Valid: true}) // is key
|
||||
|
||||
// Special value values
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s + "bar")}, Valid: true}) // at beginning
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s + "bar")}, Valid: true}) // in middle
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s)}, Valid: true}) // at end
|
||||
values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s)}, Valid: true}) // is key
|
||||
}
|
||||
|
||||
src := &pgtype.HstoreArray{
|
||||
Elements: values,
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: int32(len(values)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
_, err = conn.Prepare(context.Background(), "test", "select $1::hstore[]")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
formats := []struct {
|
||||
name string
|
||||
formatCode int16
|
||||
}{
|
||||
{name: "TextFormat", formatCode: pgx.TextFormatCode},
|
||||
{name: "BinaryFormat", formatCode: pgx.BinaryFormatCode},
|
||||
}
|
||||
|
||||
for _, fc := range formats {
|
||||
queryResultFormats := pgx.QueryResultFormats{fc.formatCode}
|
||||
vEncoder := testutil.ForceEncoder(src, fc.formatCode)
|
||||
if vEncoder == nil {
|
||||
t.Logf("%#v does not implement %v", src, fc.name)
|
||||
continue
|
||||
}
|
||||
|
||||
var result pgtype.HstoreArray
|
||||
err := conn.QueryRow(context.Background(), "test", queryResultFormats, vEncoder).Scan(&result)
|
||||
if err != nil {
|
||||
t.Errorf("%v: %v", fc.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result.Valid != src.Valid {
|
||||
t.Errorf("%v: expected Valid %v, got %v", fc.formatCode, src.Valid, result.Valid)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(result.Elements) != len(src.Elements) {
|
||||
t.Errorf("%v: expected %v elements, got %v", fc.formatCode, len(src.Elements), len(result.Elements))
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range result.Elements {
|
||||
a := src.Elements[i]
|
||||
b := result.Elements[i]
|
||||
|
||||
if a.Valid != b.Valid {
|
||||
t.Errorf("%v element idx %d: expected Valid %v, got %v", fc.formatCode, i, a.Valid, b.Valid)
|
||||
}
|
||||
|
||||
if len(a.Map) != len(b.Map) {
|
||||
t.Errorf("%v element idx %d: expected %v pairs, got %v", fc.formatCode, i, len(a.Map), len(b.Map))
|
||||
}
|
||||
|
||||
for k := range a.Map {
|
||||
if a.Map[k] != b.Map[k] {
|
||||
t.Errorf("%v element idx %d: expected key %v to be %v, got %v", fc.formatCode, i, k, a.Map[k], b.Map[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHstoreArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
src interface{}
|
||||
result pgtype.HstoreArray
|
||||
}{
|
||||
{
|
||||
src: []map[string]string{{"foo": "bar"}},
|
||||
result: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
src: [][]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
||||
result: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
src: [][][][]map[string]string{
|
||||
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
||||
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
||||
result: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"bar": {String: "baz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
src: [2][1]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
||||
result: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
src: [2][1][1][3]map[string]string{
|
||||
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
||||
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
||||
result: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"bar": {String: "baz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var dst pgtype.HstoreArray
|
||||
err := dst.Set(tt.src)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(dst, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHstoreArrayAssignTo(t *testing.T) {
|
||||
var hstoreSlice []map[string]string
|
||||
var hstoreSliceDim2 [][]map[string]string
|
||||
var hstoreSliceDim4 [][][][]map[string]string
|
||||
var hstoreArrayDim2 [2][1]map[string]string
|
||||
var hstoreArrayDim4 [2][1][1][3]map[string]string
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.HstoreArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &hstoreSlice,
|
||||
expected: []map[string]string{{"foo": "bar"}}},
|
||||
{
|
||||
src: pgtype.HstoreArray{}, dst: &hstoreSlice, expected: (([]map[string]string)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.HstoreArray{Valid: true}, dst: &hstoreSlice, expected: []map[string]string{},
|
||||
},
|
||||
{
|
||||
src: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &hstoreSliceDim2,
|
||||
expected: [][]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"bar": {String: "baz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &hstoreSliceDim4,
|
||||
expected: [][][][]map[string]string{
|
||||
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
||||
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &hstoreArrayDim2,
|
||||
expected: [2][1]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.HstoreArray{
|
||||
Elements: []pgtype.Hstore{
|
||||
{
|
||||
Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"baz": {String: "quz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"bar": {String: "baz", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wibble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wubble": {String: "wabble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
{
|
||||
Map: map[string]pgtype.Text{"wabble": {String: "wobble", Valid: true}},
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &hstoreArrayDim4,
|
||||
expected: [2][1][1][3]map[string]string{
|
||||
{{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
|
||||
{{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestHstoreTranscode(t *testing.T) {
|
||||
text := func(s string) pgtype.Text {
|
||||
return pgtype.Text{String: s, Valid: true}
|
||||
}
|
||||
|
||||
values := []interface{}{
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{}, Valid: true},
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(""), "bar": text(""), "baz": text("123")}, Valid: true},
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("bar")}, Valid: true},
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("bar"), "baz": text("quz")}, Valid: true},
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"NULL": text("bar")}, Valid: true},
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("NULL")}, Valid: true},
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"": text("bar")}, Valid: true},
|
||||
&pgtype.Hstore{
|
||||
Map: map[string]pgtype.Text{"a": text("a"), "b": {}, "c": text("c"), "d": {}, "e": text("e")},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Hstore{},
|
||||
}
|
||||
|
||||
specialStrings := []string{
|
||||
`"`,
|
||||
`'`,
|
||||
`\`,
|
||||
`\\`,
|
||||
`=>`,
|
||||
` `,
|
||||
`\ / / \\ => " ' " '`,
|
||||
}
|
||||
for _, s := range specialStrings {
|
||||
// Special key values
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{s + "foo": text("bar")}, Valid: true}) // at beginning
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s + "bar": text("bar")}, Valid: true}) // in middle
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s: text("bar")}, Valid: true}) // at end
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{s: text("bar")}, Valid: true}) // is key
|
||||
|
||||
// Special value values
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s + "bar")}, Valid: true}) // at beginning
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s + "bar")}, Valid: true}) // in middle
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s)}, Valid: true}) // at end
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s)}, Valid: true}) // is key
|
||||
}
|
||||
|
||||
testutil.TestSuccessfulTranscodeEqFunc(t, "hstore", values, func(ai, bi interface{}) bool {
|
||||
a := ai.(pgtype.Hstore)
|
||||
b := bi.(pgtype.Hstore)
|
||||
|
||||
if len(a.Map) != len(b.Map) || a.Valid != b.Valid {
|
||||
return false
|
||||
}
|
||||
|
||||
for k := range a.Map {
|
||||
if a.Map[k] != b.Map[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func TestHstoreTranscodeNullable(t *testing.T) {
|
||||
text := func(s string, valid bool) pgtype.Text {
|
||||
return pgtype.Text{String: s, Valid: valid}
|
||||
}
|
||||
|
||||
values := []interface{}{
|
||||
&pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("", false)}, Valid: true},
|
||||
}
|
||||
|
||||
specialStrings := []string{
|
||||
`"`,
|
||||
`'`,
|
||||
`\`,
|
||||
`\\`,
|
||||
`=>`,
|
||||
` `,
|
||||
`\ / / \\ => " ' " '`,
|
||||
}
|
||||
for _, s := range specialStrings {
|
||||
// Special key values
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{s + "foo": text("", false)}, Valid: true}) // at beginning
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s + "bar": text("", false)}, Valid: true}) // in middle
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s: text("", false)}, Valid: true}) // at end
|
||||
values = append(values, &pgtype.Hstore{Map: map[string]pgtype.Text{s: text("", false)}, Valid: true}) // is key
|
||||
}
|
||||
|
||||
testutil.TestSuccessfulTranscodeEqFunc(t, "hstore", values, func(ai, bi interface{}) bool {
|
||||
a := ai.(pgtype.Hstore)
|
||||
b := bi.(pgtype.Hstore)
|
||||
|
||||
if len(a.Map) != len(b.Map) || a.Valid != b.Valid {
|
||||
return false
|
||||
}
|
||||
|
||||
for k := range a.Map {
|
||||
if a.Map[k] != b.Map[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func TestHstoreSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
src map[string]string
|
||||
result pgtype.Hstore
|
||||
}{
|
||||
{src: map[string]string{"foo": "bar"}, result: pgtype.Hstore{Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}}, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var dst pgtype.Hstore
|
||||
err := dst.Set(tt.src)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(dst, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHstoreSetNullable(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
src map[string]*string
|
||||
result pgtype.Hstore
|
||||
}{
|
||||
{src: map[string]*string{"foo": nil}, result: pgtype.Hstore{Map: map[string]pgtype.Text{"foo": {}}, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var dst pgtype.Hstore
|
||||
err := dst.Set(tt.src)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(dst, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHstoreAssignTo(t *testing.T) {
|
||||
var m map[string]string
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Hstore
|
||||
dst *map[string]string
|
||||
expected map[string]string
|
||||
}{
|
||||
{src: pgtype.Hstore{Map: map[string]pgtype.Text{"foo": {String: "bar", Valid: true}}, Valid: true}, dst: &m, expected: map[string]string{"foo": "bar"}},
|
||||
{src: pgtype.Hstore{}, dst: &m, expected: ((map[string]string)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*tt.dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHstoreAssignToNullable(t *testing.T) {
|
||||
var m map[string]*string
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Hstore
|
||||
dst *map[string]*string
|
||||
expected map[string]*string
|
||||
}{
|
||||
{src: pgtype.Hstore{Map: map[string]pgtype.Text{"foo": {}}, Valid: true}, dst: &m, expected: map[string]*string{"foo": nil}},
|
||||
{src: pgtype.Hstore{}, dst: &m, expected: ((map[string]*string)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*tt.dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Network address family is dependent on server socket.h value for AF_INET.
|
||||
// In practice, all platforms appear to have the same value. See
|
||||
// src/include/utils/inet.h for more information.
|
||||
const (
|
||||
defaultAFInet = 2
|
||||
defaultAFInet6 = 3
|
||||
)
|
||||
|
||||
// Inet represents both inet and cidr PostgreSQL types.
|
||||
type Inet struct {
|
||||
IPNet *net.IPNet
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Inet) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Inet{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case net.IPNet:
|
||||
*dst = Inet{IPNet: &value, Valid: true}
|
||||
case net.IP:
|
||||
if len(value) == 0 {
|
||||
*dst = Inet{}
|
||||
} else {
|
||||
bitCount := len(value) * 8
|
||||
mask := net.CIDRMask(bitCount, bitCount)
|
||||
*dst = Inet{IPNet: &net.IPNet{Mask: mask, IP: value}, Valid: true}
|
||||
}
|
||||
case string:
|
||||
ip, ipnet, err := net.ParseCIDR(value)
|
||||
if err != nil {
|
||||
ip = net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("unable to parse inet address: %s", value)
|
||||
}
|
||||
ipnet = &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)}
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
ipnet.Mask = net.CIDRMask(32, 32)
|
||||
}
|
||||
}
|
||||
ipnet.IP = ip
|
||||
*dst = Inet{IPNet: ipnet, Valid: true}
|
||||
case *net.IPNet:
|
||||
if value == nil {
|
||||
*dst = Inet{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *net.IP:
|
||||
if value == nil {
|
||||
*dst = Inet{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Inet{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingPtrType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Inet", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Inet) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.IPNet
|
||||
}
|
||||
|
||||
func (src *Inet) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *net.IPNet:
|
||||
*v = net.IPNet{
|
||||
IP: make(net.IP, len(src.IPNet.IP)),
|
||||
Mask: make(net.IPMask, len(src.IPNet.Mask)),
|
||||
}
|
||||
copy(v.IP, src.IPNet.IP)
|
||||
copy(v.Mask, src.IPNet.Mask)
|
||||
return nil
|
||||
case *net.IP:
|
||||
if oneCount, bitCount := src.IPNet.Mask.Size(); oneCount != bitCount {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
*v = make(net.IP, len(src.IPNet.IP))
|
||||
copy(*v, src.IPNet.IP)
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (dst *Inet) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Inet{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ipnet *net.IPNet
|
||||
var err error
|
||||
|
||||
if ip := net.ParseIP(string(src)); ip != nil {
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
bitCount := len(ip) * 8
|
||||
mask := net.CIDRMask(bitCount, bitCount)
|
||||
ipnet = &net.IPNet{Mask: mask, IP: ip}
|
||||
} else {
|
||||
ip, ipnet, err = net.ParseCIDR(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
ones, _ := ipnet.Mask.Size()
|
||||
*ipnet = net.IPNet{IP: ip, Mask: net.CIDRMask(ones, len(ip)*8)}
|
||||
}
|
||||
|
||||
*dst = Inet{IPNet: ipnet, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Inet) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Inet{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 8 && len(src) != 20 {
|
||||
return fmt.Errorf("Received an invalid size for a inet: %d", len(src))
|
||||
}
|
||||
|
||||
// ignore family
|
||||
bits := src[1]
|
||||
// ignore is_cidr
|
||||
addressLength := src[3]
|
||||
|
||||
var ipnet net.IPNet
|
||||
ipnet.IP = make(net.IP, int(addressLength))
|
||||
copy(ipnet.IP, src[4:])
|
||||
if ipv4 := ipnet.IP.To4(); ipv4 != nil {
|
||||
ipnet.IP = ipv4
|
||||
}
|
||||
ipnet.Mask = net.CIDRMask(int(bits), len(ipnet.IP)*8)
|
||||
|
||||
*dst = Inet{IPNet: &ipnet, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Inet) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, src.IPNet.String()...), nil
|
||||
}
|
||||
|
||||
// EncodeBinary encodes src into w.
|
||||
func (src Inet) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var family byte
|
||||
switch len(src.IPNet.IP) {
|
||||
case net.IPv4len:
|
||||
family = defaultAFInet
|
||||
case net.IPv6len:
|
||||
family = defaultAFInet6
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected IP length: %v", len(src.IPNet.IP))
|
||||
}
|
||||
|
||||
buf = append(buf, family)
|
||||
|
||||
ones, _ := src.IPNet.Mask.Size()
|
||||
buf = append(buf, byte(ones))
|
||||
|
||||
// is_cidr is ignored on server
|
||||
buf = append(buf, 0)
|
||||
|
||||
buf = append(buf, byte(len(src.IPNet.IP)))
|
||||
|
||||
return append(buf, src.IPNet.IP...), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Inet) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Inet{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Inet) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type InetArray struct {
|
||||
Elements []Inet
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *InetArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = InetArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []*net.IPNet:
|
||||
if value == nil {
|
||||
*dst = InetArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = InetArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Inet, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = InetArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []net.IP:
|
||||
if value == nil {
|
||||
*dst = InetArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = InetArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Inet, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = InetArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*net.IP:
|
||||
if value == nil {
|
||||
*dst = InetArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = InetArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]Inet, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = InetArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Inet:
|
||||
if value == nil {
|
||||
*dst = InetArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = InetArray{Valid: true}
|
||||
} else {
|
||||
*dst = InetArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = InetArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for InetArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = InetArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to InetArray", src)
|
||||
}
|
||||
|
||||
*dst = InetArray{
|
||||
Elements: make([]Inet, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Inet, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to InetArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *InetArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to InetArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in InetArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst InetArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *InetArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]*net.IPNet:
|
||||
*v = make([]*net.IPNet, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]net.IP:
|
||||
*v = make([]net.IP, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*net.IP:
|
||||
*v = make([]*net.IP, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *InetArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from InetArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from InetArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *InetArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = InetArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Inet
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Inet, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Inet
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = InetArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *InetArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = InetArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = InetArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Inet, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = InetArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src InetArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src InetArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("inet"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "inet")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *InetArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src InetArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInetArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "inet[]", []interface{}{
|
||||
&pgtype.InetArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.InetArray{},
|
||||
&pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"), Valid: true},
|
||||
{},
|
||||
{IPNet: mustParseCIDR(t, "255.0.0.0/8"), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInetArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.InetArray
|
||||
}{
|
||||
{
|
||||
source: []*net.IPNet{mustParseCIDR(t, "127.0.0.1/32")},
|
||||
result: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]*net.IPNet)(nil)),
|
||||
result: pgtype.InetArray{},
|
||||
},
|
||||
{
|
||||
source: []net.IP{mustParseCIDR(t, "127.0.0.1/32").IP},
|
||||
result: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]net.IP)(nil)),
|
||||
result: pgtype.InetArray{},
|
||||
},
|
||||
{
|
||||
source: [][]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
result: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
result: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
result: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
result: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.InetArray
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInetArrayAssignTo(t *testing.T) {
|
||||
var ipnetSlice []*net.IPNet
|
||||
var ipSlice []net.IP
|
||||
var ipSliceDim2 [][]net.IP
|
||||
var ipnetSliceDim4 [][][][]*net.IPNet
|
||||
var ipArrayDim2 [2][1]net.IP
|
||||
var ipnetArrayDim4 [2][1][1][3]*net.IPNet
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.InetArray
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipnetSlice,
|
||||
expected: []*net.IPNet{mustParseCIDR(t, "127.0.0.1/32")},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipnetSlice,
|
||||
expected: []*net.IPNet{nil},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipSlice,
|
||||
expected: []net.IP{mustParseCIDR(t, "127.0.0.1/32").IP},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &ipSlice,
|
||||
expected: []net.IP{nil},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{},
|
||||
dst: &ipnetSlice,
|
||||
expected: (([]*net.IPNet)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{Valid: true},
|
||||
dst: &ipnetSlice,
|
||||
expected: []*net.IPNet{},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{},
|
||||
dst: &ipSlice,
|
||||
expected: (([]net.IP)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{Valid: true},
|
||||
dst: &ipSlice,
|
||||
expected: []net.IP{},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &ipSliceDim2,
|
||||
expected: [][]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &ipnetSliceDim4,
|
||||
expected: [][][][]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/32"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &ipArrayDim2,
|
||||
expected: [2][1]net.IP{{mustParseCIDR(t, "127.0.0.1/32").IP}, {mustParseCIDR(t, "10.0.0.1/32").IP}},
|
||||
},
|
||||
{
|
||||
src: pgtype.InetArray{
|
||||
Elements: []pgtype.Inet{
|
||||
{IPNet: mustParseCIDR(t, "127.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "10.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "172.16.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "192.168.0.1/16"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "224.0.0.1/24"), Valid: true},
|
||||
{IPNet: mustParseCIDR(t, "169.168.0.1/16"), Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
dst: &ipnetArrayDim4,
|
||||
expected: [2][1][1][3]*net.IPNet{
|
||||
{{{
|
||||
mustParseCIDR(t, "127.0.0.1/24"),
|
||||
mustParseCIDR(t, "10.0.0.1/24"),
|
||||
mustParseCIDR(t, "172.16.0.1/16")}}},
|
||||
{{{
|
||||
mustParseCIDR(t, "192.168.0.1/16"),
|
||||
mustParseCIDR(t, "224.0.0.1/24"),
|
||||
mustParseCIDR(t, "169.168.0.1/16")}}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInetTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "inet", []interface{}{
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "0.0.0.0/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "127.0.0.1/8"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "12.34.56.65/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "192.168.1.16/24"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "255.0.0.0/8"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "255.255.255.255/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "10.0.0.1"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "2607:f8b0:4009:80b::200e"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "::1/64"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "::/0"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "::1/128"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseInet(t, "2607:f8b0:4009:80b::200e/64"), Valid: true},
|
||||
&pgtype.Inet{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestCidrTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "cidr", []interface{}{
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "0.0.0.0/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "12.34.56.0/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "192.168.1.0/24"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "255.0.0.0/8"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "255.255.255.255/32"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "::/128"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "::/0"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "::1/128"), Valid: true},
|
||||
&pgtype.Inet{IPNet: mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"), Valid: true},
|
||||
&pgtype.Inet{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInetSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Inet
|
||||
}{
|
||||
{source: mustParseCIDR(t, "127.0.0.1/32"), result: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
{source: mustParseCIDR(t, "127.0.0.1/32").IP, result: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
{source: "127.0.0.1/32", result: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}},
|
||||
{source: "1.2.3.4/24", result: pgtype.Inet{IPNet: &net.IPNet{IP: net.ParseIP("1.2.3.4"), Mask: net.CIDRMask(24, 32)}, Valid: true}},
|
||||
{source: "10.0.0.1", result: pgtype.Inet{IPNet: mustParseInet(t, "10.0.0.1"), Valid: true}},
|
||||
{source: "2607:f8b0:4009:80b::200e", result: pgtype.Inet{IPNet: mustParseInet(t, "2607:f8b0:4009:80b::200e"), Valid: true}},
|
||||
{source: net.ParseIP(""), result: pgtype.Inet{}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Inet
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equalf(t, tt.result.Valid, r.Valid, "%d: Status", i)
|
||||
if tt.result.Valid {
|
||||
assert.Equalf(t, tt.result.IPNet.Mask, r.IPNet.Mask, "%d: IP", i)
|
||||
assert.Truef(t, tt.result.IPNet.IP.Equal(r.IPNet.IP), "%d: Mask", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInetAssignTo(t *testing.T) {
|
||||
var ipnet net.IPNet
|
||||
var pipnet *net.IPNet
|
||||
var ip net.IP
|
||||
var pip *net.IP
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Inet
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}, dst: &ipnet, expected: *mustParseCIDR(t, "127.0.0.1/32")},
|
||||
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}, dst: &ip, expected: mustParseCIDR(t, "127.0.0.1/32").IP},
|
||||
{src: pgtype.Inet{}, dst: &pipnet, expected: ((*net.IPNet)(nil))},
|
||||
{src: pgtype.Inet{}, dst: &pip, expected: ((*net.IP)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %#v, but result was %#v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Inet
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}, dst: &pipnet, expected: *mustParseCIDR(t, "127.0.0.1/32")},
|
||||
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Valid: true}, dst: &pip, expected: mustParseCIDR(t, "127.0.0.1/32").IP},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Inet
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "192.168.0.0/16"), Valid: true}, dst: &ip},
|
||||
{src: pgtype.Inet{}, dst: &ipnet},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int2 struct {
|
||||
Int int16
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int2) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int2{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case int8:
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case uint8:
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case int16:
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case uint16:
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case int32:
|
||||
if value < math.MinInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case uint32:
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case int64:
|
||||
if value < math.MinInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case uint64:
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case int:
|
||||
if value < math.MinInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case uint:
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case string:
|
||||
num, err := strconv.ParseInt(value, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Int2{Int: int16(num), Valid: true}
|
||||
case float32:
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%f is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case float64:
|
||||
if value > math.MaxInt16 {
|
||||
return fmt.Errorf("%f is greater than maximum value for Int2", value)
|
||||
}
|
||||
*dst = Int2{Int: int16(value), Valid: true}
|
||||
case *int8:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint8:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int16:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint16:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int32:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint32:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int64:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint64:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float32:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float64:
|
||||
if value == nil {
|
||||
*dst = Int2{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int2", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Int2) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Int
|
||||
}
|
||||
|
||||
func (src *Int2) AssignTo(dst interface{}) error {
|
||||
return int64AssignTo(int64(src.Int), src.Valid, dst)
|
||||
}
|
||||
|
||||
func (dst *Int2) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int2{}
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(string(src), 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int2{Int: int16(n), Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int2) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int2{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 2 {
|
||||
return fmt.Errorf("invalid length for int2: %v", len(src))
|
||||
}
|
||||
|
||||
n := int16(binary.BigEndian.Uint16(src))
|
||||
*dst = Int2{Int: n, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int2) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, strconv.FormatInt(int64(src.Int), 10)...), nil
|
||||
}
|
||||
|
||||
func (src Int2) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pgio.AppendInt16(buf, src.Int), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int2) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int2{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case int64:
|
||||
if src < math.MinInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", src)
|
||||
}
|
||||
if src > math.MaxInt16 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int2", src)
|
||||
}
|
||||
*dst = Int2{Int: int16(src), Valid: true}
|
||||
return nil
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int2) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return int64(src.Int), nil
|
||||
}
|
||||
|
||||
func (src Int2) MarshalJSON() ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return []byte(strconv.FormatInt(int64(src.Int), 10)), nil
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int2Array struct {
|
||||
Elements []Int2
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int2Array) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Int2Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []int16:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int16:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint16:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint16:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int32:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int32:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint32:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint32:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int64:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int64:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint64:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint64:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int2, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int2Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Int2:
|
||||
if value == nil {
|
||||
*dst = Int2Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
} else {
|
||||
*dst = Int2Array{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = Int2Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for Int2Array", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = Int2Array{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int2Array", src)
|
||||
}
|
||||
|
||||
*dst = Int2Array{
|
||||
Elements: make([]Int2, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Int2, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to Int2Array, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int2Array) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to Int2Array")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in Int2Array", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst Int2Array) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Int2Array) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]int16:
|
||||
*v = make([]int16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int16:
|
||||
*v = make([]*int16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint16:
|
||||
*v = make([]uint16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint16:
|
||||
*v = make([]*uint16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int32:
|
||||
*v = make([]int32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int32:
|
||||
*v = make([]*int32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint32:
|
||||
*v = make([]uint32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint32:
|
||||
*v = make([]*uint32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int64:
|
||||
*v = make([]int64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int64:
|
||||
*v = make([]*int64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint64:
|
||||
*v = make([]uint64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint64:
|
||||
*v = make([]*uint64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int:
|
||||
*v = make([]int, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int:
|
||||
*v = make([]*int, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint:
|
||||
*v = make([]uint, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint:
|
||||
*v = make([]*uint, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Int2Array) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Int2Array")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Int2Array")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *Int2Array) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int2Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Int2
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Int2, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Int2
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Int2Array{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int2Array) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int2Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Int2Array{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Int2, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Int2Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int2Array) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Int2Array) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("int2"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "int2")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int2Array) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int2Array) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt2ArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int2[]", []interface{}{
|
||||
&pgtype.Int2Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int2Array{},
|
||||
&pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{},
|
||||
{Int: 6, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt2ArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Int2Array
|
||||
}{
|
||||
{
|
||||
source: []int64{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int32{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int16{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint64{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint32{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint16{1},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]int16)(nil)),
|
||||
result: pgtype.Int2Array{},
|
||||
},
|
||||
{
|
||||
source: [][]int16{{1}, {2}},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]int16{{1}, {2}},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int2Array
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt2ArrayAssignTo(t *testing.T) {
|
||||
var int16Slice []int16
|
||||
var uint16Slice []uint16
|
||||
var namedInt16Slice _int16Slice
|
||||
var int16SliceDim2 [][]int16
|
||||
var int16SliceDim4 [][][][]int16
|
||||
var int16ArrayDim2 [2][1]int16
|
||||
var int16ArrayDim4 [2][1][1][3]int16
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Int2Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &int16Slice,
|
||||
expected: []int16{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &uint16Slice,
|
||||
expected: []uint16{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedInt16Slice,
|
||||
expected: _int16Slice{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{},
|
||||
dst: &int16Slice,
|
||||
expected: (([]int16)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{Valid: true},
|
||||
dst: &int16Slice,
|
||||
expected: []int16{},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [][]int16{{1}, {2}},
|
||||
dst: &int16SliceDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [][][][]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &int16SliceDim4,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [2][1]int16{{1}, {2}},
|
||||
dst: &int16ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [2][1][1][3]int16{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &int16ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Int2Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &int16Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: -1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &uint16Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &int16ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &int16Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int2Array{
|
||||
Elements: []pgtype.Int2{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &int16ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt2Transcode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int2", []interface{}{
|
||||
&pgtype.Int2{Int: math.MinInt16, Valid: true},
|
||||
&pgtype.Int2{Int: -1, Valid: true},
|
||||
&pgtype.Int2{Int: 0, Valid: true},
|
||||
&pgtype.Int2{Int: 1, Valid: true},
|
||||
&pgtype.Int2{Int: math.MaxInt16, Valid: true},
|
||||
&pgtype.Int2{Int: 0},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt2Set(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Int2
|
||||
}{
|
||||
{source: int8(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: int16(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: int32(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: int64(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: int8(-1), result: pgtype.Int2{Int: -1, Valid: true}},
|
||||
{source: int16(-1), result: pgtype.Int2{Int: -1, Valid: true}},
|
||||
{source: int32(-1), result: pgtype.Int2{Int: -1, Valid: true}},
|
||||
{source: int64(-1), result: pgtype.Int2{Int: -1, Valid: true}},
|
||||
{source: uint8(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: uint16(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: uint32(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: uint64(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: float32(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: float64(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: "1", result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
{source: _int8(1), result: pgtype.Int2{Int: 1, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int2
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt2AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Int2
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &i8, expected: int8(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Int2{Int: 0}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Int2{Int: 0}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Int2
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &pi8, expected: int8(42)},
|
||||
{src: pgtype.Int2{Int: 42, Valid: true}, dst: &_pi8, expected: _int8(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Int2
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Int2{Int: 150, Valid: true}, dst: &i8},
|
||||
{src: pgtype.Int2{Int: -1, Valid: true}, dst: &ui8},
|
||||
{src: pgtype.Int2{Int: -1, Valid: true}, dst: &ui16},
|
||||
{src: pgtype.Int2{Int: -1, Valid: true}, dst: &ui32},
|
||||
{src: pgtype.Int2{Int: -1, Valid: true}, dst: &ui64},
|
||||
{src: pgtype.Int2{Int: -1, Valid: true}, dst: &ui},
|
||||
{src: pgtype.Int2{Int: 0}, dst: &i16},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int4 struct {
|
||||
Int int32
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int4) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case int8:
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case uint8:
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case int16:
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case uint16:
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case int32:
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case uint32:
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case int64:
|
||||
if value < math.MinInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case uint64:
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case int:
|
||||
if value < math.MinInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case uint:
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case string:
|
||||
num, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Int4{Int: int32(num), Valid: true}
|
||||
case float32:
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%f is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case float64:
|
||||
if value > math.MaxInt32 {
|
||||
return fmt.Errorf("%f is greater than maximum value for Int4", value)
|
||||
}
|
||||
*dst = Int4{Int: int32(value), Valid: true}
|
||||
case *int8:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint8:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int16:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint16:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int32:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint32:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int64:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint64:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float32:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float64:
|
||||
if value == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int4", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Int4) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Int
|
||||
}
|
||||
|
||||
func (src *Int4) AssignTo(dst interface{}) error {
|
||||
return int64AssignTo(int64(src.Int), src.Valid, dst)
|
||||
}
|
||||
|
||||
func (dst *Int4) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(string(src), 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int4{Int: int32(n), Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int4) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 4 {
|
||||
return fmt.Errorf("invalid length for int4: %v", len(src))
|
||||
}
|
||||
|
||||
n := int32(binary.BigEndian.Uint32(src))
|
||||
*dst = Int4{Int: n, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int4) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, strconv.FormatInt(int64(src.Int), 10)...), nil
|
||||
}
|
||||
|
||||
func (src Int4) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pgio.AppendInt32(buf, src.Int), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int4) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int4{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case int64:
|
||||
if src < math.MinInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", src)
|
||||
}
|
||||
if src > math.MaxInt32 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int4", src)
|
||||
}
|
||||
*dst = Int4{Int: int32(src), Valid: true}
|
||||
return nil
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int4) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return int64(src.Int), nil
|
||||
}
|
||||
|
||||
func (src Int4) MarshalJSON() ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return []byte(strconv.FormatInt(int64(src.Int), 10)), nil
|
||||
}
|
||||
|
||||
func (dst *Int4) UnmarshalJSON(b []byte) error {
|
||||
var n *int32
|
||||
err := json.Unmarshal(b, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == nil {
|
||||
*dst = Int4{}
|
||||
} else {
|
||||
*dst = Int4{Int: *n, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int4Array struct {
|
||||
Elements []Int4
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int4Array) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Int4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []int16:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int16:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint16:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint16:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int32:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int32:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint32:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint32:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int64:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int64:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint64:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint64:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Int4:
|
||||
if value == nil {
|
||||
*dst = Int4Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
} else {
|
||||
*dst = Int4Array{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = Int4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for Int4Array", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = Int4Array{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int4Array", src)
|
||||
}
|
||||
|
||||
*dst = Int4Array{
|
||||
Elements: make([]Int4, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Int4, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to Int4Array, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int4Array) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to Int4Array")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in Int4Array", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst Int4Array) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Int4Array) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]int16:
|
||||
*v = make([]int16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int16:
|
||||
*v = make([]*int16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint16:
|
||||
*v = make([]uint16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint16:
|
||||
*v = make([]*uint16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int32:
|
||||
*v = make([]int32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int32:
|
||||
*v = make([]*int32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint32:
|
||||
*v = make([]uint32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint32:
|
||||
*v = make([]*uint32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int64:
|
||||
*v = make([]int64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int64:
|
||||
*v = make([]*int64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint64:
|
||||
*v = make([]uint64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint64:
|
||||
*v = make([]*uint64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int:
|
||||
*v = make([]int, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int:
|
||||
*v = make([]*int, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint:
|
||||
*v = make([]uint, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint:
|
||||
*v = make([]*uint, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Int4Array) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Int4Array")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Int4Array")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *Int4Array) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Int4
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Int4, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Int4
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Int4Array{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int4Array) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Int4Array{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Int4, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Int4Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int4Array) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Int4Array) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("int4"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "int4")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int4Array) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int4Array) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt4ArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int4[]", []interface{}{
|
||||
&pgtype.Int4Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int4Array{},
|
||||
&pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{},
|
||||
{Int: 6, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt4ArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Int4Array
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
source: []int64{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int32{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int16{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int{1, math.MaxInt32 + 1, 2},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
source: []uint64{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint32{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint16{1},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]int32)(nil)),
|
||||
result: pgtype.Int4Array{},
|
||||
},
|
||||
{
|
||||
source: [][]int32{{1}, {2}},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]int32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]int32{{1}, {2}},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]int32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int4Array
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
if tt.expectedError {
|
||||
continue
|
||||
}
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if tt.expectedError {
|
||||
t.Errorf("%d: an error was expected, %v", i, tt)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt4ArrayAssignTo(t *testing.T) {
|
||||
var int32Slice []int32
|
||||
var uint32Slice []uint32
|
||||
var namedInt32Slice _int32Slice
|
||||
var int32SliceDim2 [][]int32
|
||||
var int32SliceDim4 [][][][]int32
|
||||
var int32ArrayDim2 [2][1]int32
|
||||
var int32ArrayDim4 [2][1][1][3]int32
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Int4Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &int32Slice,
|
||||
expected: []int32{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &uint32Slice,
|
||||
expected: []uint32{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedInt32Slice,
|
||||
expected: _int32Slice{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{},
|
||||
dst: &int32Slice,
|
||||
expected: (([]int32)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{Valid: true},
|
||||
dst: &int32Slice,
|
||||
expected: []int32{},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [][]int32{{1}, {2}},
|
||||
dst: &int32SliceDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [][][][]int32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &int32SliceDim4,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [2][1]int32{{1}, {2}},
|
||||
dst: &int32ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [2][1][1][3]int32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &int32ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Int4Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &int32Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: -1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &uint32Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &int32ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &int32Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int4Array{
|
||||
Elements: []pgtype.Int4{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &int32ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt4Transcode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int4", []interface{}{
|
||||
&pgtype.Int4{Int: math.MinInt32, Valid: true},
|
||||
&pgtype.Int4{Int: -1, Valid: true},
|
||||
&pgtype.Int4{Int: 0, Valid: true},
|
||||
&pgtype.Int4{Int: 1, Valid: true},
|
||||
&pgtype.Int4{Int: math.MaxInt32, Valid: true},
|
||||
&pgtype.Int4{Int: 0},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt4Set(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Int4
|
||||
}{
|
||||
{source: int8(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: int16(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: int32(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: int64(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: int8(-1), result: pgtype.Int4{Int: -1, Valid: true}},
|
||||
{source: int16(-1), result: pgtype.Int4{Int: -1, Valid: true}},
|
||||
{source: int32(-1), result: pgtype.Int4{Int: -1, Valid: true}},
|
||||
{source: int64(-1), result: pgtype.Int4{Int: -1, Valid: true}},
|
||||
{source: uint8(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: uint16(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: uint32(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: uint64(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: float32(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: float64(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: "1", result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
{source: _int8(1), result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int4
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt4AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Int4
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &i8, expected: int8(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Int4{Int: 0}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Int4{Int: 0}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Int4
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &pi8, expected: int8(42)},
|
||||
{src: pgtype.Int4{Int: 42, Valid: true}, dst: &_pi8, expected: _int8(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Int4
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Int4{Int: 150, Valid: true}, dst: &i8},
|
||||
{src: pgtype.Int4{Int: 40000, Valid: true}, dst: &i16},
|
||||
{src: pgtype.Int4{Int: -1, Valid: true}, dst: &ui8},
|
||||
{src: pgtype.Int4{Int: -1, Valid: true}, dst: &ui16},
|
||||
{src: pgtype.Int4{Int: -1, Valid: true}, dst: &ui32},
|
||||
{src: pgtype.Int4{Int: -1, Valid: true}, dst: &ui64},
|
||||
{src: pgtype.Int4{Int: -1, Valid: true}, dst: &ui},
|
||||
{src: pgtype.Int4{Int: 0}, dst: &i32},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt4MarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source pgtype.Int4
|
||||
result string
|
||||
}{
|
||||
{source: pgtype.Int4{Int: 0}, result: "null"},
|
||||
{source: pgtype.Int4{Int: 1, Valid: true}, result: "1"},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
r, err := tt.source.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if string(r) != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt4UnmarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source string
|
||||
result pgtype.Int4
|
||||
}{
|
||||
{source: "null", result: pgtype.Int4{Int: 0}},
|
||||
{source: "1", result: pgtype.Int4{Int: 1, Valid: true}},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int4
|
||||
err := r.UnmarshalJSON([]byte(tt.source))
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int4range struct {
|
||||
Lower Int4
|
||||
Upper Int4
|
||||
LowerType BoundType
|
||||
UpperType BoundType
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int4range) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Int4range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case Int4range:
|
||||
*dst = value
|
||||
case *Int4range:
|
||||
*dst = *value
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(value))
|
||||
default:
|
||||
return fmt.Errorf("cannot convert %v to Int4range", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int4range) Get() interface{} {
|
||||
if !src.Valid {
|
||||
return nil
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func (src *Int4range) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Int4range) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
utr, err := ParseUntypedTextRange(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int4range{Valid: true}
|
||||
|
||||
dst.LowerType = utr.LowerType
|
||||
dst.UpperType = utr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeText(ci, []byte(utr.Lower)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeText(ci, []byte(utr.Upper)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int4range) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
ubr, err := ParseUntypedBinaryRange(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int4range{Valid: true}
|
||||
|
||||
dst.LowerType = ubr.LowerType
|
||||
dst.UpperType = ubr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeBinary(ci, ubr.Lower); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeBinary(ci, ubr.Upper); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int4range) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch src.LowerType {
|
||||
case Exclusive, Unbounded:
|
||||
buf = append(buf, '(')
|
||||
case Inclusive:
|
||||
buf = append(buf, '[')
|
||||
case Empty:
|
||||
return append(buf, "empty"...), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown lower bound type %v", src.LowerType)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
buf, err = src.Lower.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if buf == nil {
|
||||
return nil, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, ',')
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
buf, err = src.Upper.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if buf == nil {
|
||||
return nil, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Exclusive, Unbounded:
|
||||
buf = append(buf, ')')
|
||||
case Inclusive:
|
||||
buf = append(buf, ']')
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown upper bound type %v", src.UpperType)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Int4range) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var rangeType byte
|
||||
switch src.LowerType {
|
||||
case Inclusive:
|
||||
rangeType |= lowerInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= lowerUnboundedMask
|
||||
case Exclusive:
|
||||
case Empty:
|
||||
return append(buf, emptyMask), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown LowerType: %v", src.LowerType)
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Inclusive:
|
||||
rangeType |= upperInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= upperUnboundedMask
|
||||
case Exclusive:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown UpperType: %v", src.UpperType)
|
||||
}
|
||||
|
||||
buf = append(buf, rangeType)
|
||||
|
||||
var err error
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
buf, err = src.Lower.EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
buf, err = src.Upper.EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int4range) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int4range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int4range) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt4rangeTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int4range", []interface{}{
|
||||
&pgtype.Int4range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true},
|
||||
&pgtype.Int4range{Lower: pgtype.Int4{Int: 1, Valid: true}, Upper: pgtype.Int4{Int: 10, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Valid: true},
|
||||
&pgtype.Int4range{Lower: pgtype.Int4{Int: -42, Valid: true}, Upper: pgtype.Int4{Int: -5, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Valid: true},
|
||||
&pgtype.Int4range{Lower: pgtype.Int4{Int: 1, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Unbounded, Valid: true},
|
||||
&pgtype.Int4range{Upper: pgtype.Int4{Int: 1, Valid: true}, LowerType: pgtype.Unbounded, UpperType: pgtype.Exclusive, Valid: true},
|
||||
&pgtype.Int4range{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt4rangeNormalize(t *testing.T) {
|
||||
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
|
||||
{
|
||||
SQL: "select int4range(1, 10, '(]')",
|
||||
Value: pgtype.Int4range{Lower: pgtype.Int4{Int: 2, Valid: true}, Upper: pgtype.Int4{Int: 11, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Valid: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int8 struct {
|
||||
Int int64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int8) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case int8:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case uint8:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case int16:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case uint16:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case int32:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case uint32:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case int64:
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case uint64:
|
||||
if value > math.MaxInt64 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int8", value)
|
||||
}
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case int:
|
||||
if int64(value) < math.MinInt64 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int8", value)
|
||||
}
|
||||
if int64(value) > math.MaxInt64 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int8", value)
|
||||
}
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case uint:
|
||||
if uint64(value) > math.MaxInt64 {
|
||||
return fmt.Errorf("%d is greater than maximum value for Int8", value)
|
||||
}
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case string:
|
||||
num, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Int8{Int: num, Valid: true}
|
||||
case float32:
|
||||
if value > math.MaxInt64 {
|
||||
return fmt.Errorf("%f is greater than maximum value for Int8", value)
|
||||
}
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case float64:
|
||||
if value > math.MaxInt64 {
|
||||
return fmt.Errorf("%f is greater than maximum value for Int8", value)
|
||||
}
|
||||
*dst = Int8{Int: int64(value), Valid: true}
|
||||
case *int8:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint8:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int16:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint16:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int32:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint32:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int64:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint64:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *int:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *uint:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float32:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
case *float64:
|
||||
if value == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
return dst.Set(*value)
|
||||
}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int8", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Int8) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst.Int
|
||||
}
|
||||
|
||||
func (src *Int8) AssignTo(dst interface{}) error {
|
||||
return int64AssignTo(int64(src.Int), src.Valid, dst)
|
||||
}
|
||||
|
||||
func (dst *Int8) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(string(src), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int8{Int: n, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int8) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 8 {
|
||||
return fmt.Errorf("invalid length for int8: %v", len(src))
|
||||
}
|
||||
|
||||
n := int64(binary.BigEndian.Uint64(src))
|
||||
|
||||
*dst = Int8{Int: n, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int8) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, strconv.FormatInt(src.Int, 10)...), nil
|
||||
}
|
||||
|
||||
func (src Int8) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return pgio.AppendInt64(buf, src.Int), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int8) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int8{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case int64:
|
||||
*dst = Int8{Int: src, Valid: true}
|
||||
return nil
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int8) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return int64(src.Int), nil
|
||||
}
|
||||
|
||||
func (src Int8) MarshalJSON() ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return []byte(strconv.FormatInt(src.Int, 10)), nil
|
||||
}
|
||||
|
||||
func (dst *Int8) UnmarshalJSON(b []byte) error {
|
||||
var n *int64
|
||||
err := json.Unmarshal(b, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == nil {
|
||||
*dst = Int8{}
|
||||
} else {
|
||||
*dst = Int8{Int: *n, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int8Array struct {
|
||||
Elements []Int8
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int8Array) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Int8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []int16:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int16:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint16:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint16:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int32:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int32:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint32:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint32:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int64:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int64:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint64:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint64:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []int:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*int:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []uint:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []*uint:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
elements := make([]Int8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Int8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []Int8:
|
||||
if value == nil {
|
||||
*dst = Int8Array{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
} else {
|
||||
*dst = Int8Array{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = Int8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for Int8Array", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = Int8Array{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int8Array", src)
|
||||
}
|
||||
|
||||
*dst = Int8Array{
|
||||
Elements: make([]Int8, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]Int8, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to Int8Array, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int8Array) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to Int8Array")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in Int8Array", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst Int8Array) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Int8Array) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]int16:
|
||||
*v = make([]int16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int16:
|
||||
*v = make([]*int16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint16:
|
||||
*v = make([]uint16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint16:
|
||||
*v = make([]*uint16, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int32:
|
||||
*v = make([]int32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int32:
|
||||
*v = make([]*int32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint32:
|
||||
*v = make([]uint32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint32:
|
||||
*v = make([]*uint32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int64:
|
||||
*v = make([]int64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int64:
|
||||
*v = make([]*int64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint64:
|
||||
*v = make([]uint64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint64:
|
||||
*v = make([]*uint64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]int:
|
||||
*v = make([]int, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*int:
|
||||
*v = make([]*int, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]uint:
|
||||
*v = make([]uint, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[]*uint:
|
||||
*v = make([]*uint, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Int8Array) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Int8Array")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from Int8Array")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *Int8Array) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []Int8
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Int8, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Int8
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Int8Array{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int8Array) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int8Array{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Int8Array{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Int8, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Int8Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int8Array) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Int8Array) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("int8"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "int8")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int8Array) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int8Array) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt8ArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int8[]", []interface{}{
|
||||
&pgtype.Int8Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int8Array{},
|
||||
&pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{},
|
||||
{Int: 6, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt8ArraySet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Int8Array
|
||||
}{
|
||||
{
|
||||
source: []int64{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int32{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int16{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []int{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint64{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint32{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint16{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: []uint{1},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: (([]int64)(nil)),
|
||||
result: pgtype.Int8Array{},
|
||||
},
|
||||
{
|
||||
source: [][]int64{{1}, {2}},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [][][][]int64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1]int64{{1}, {2}},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
},
|
||||
{
|
||||
source: [2][1][1][3]int64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
result: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int8Array
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt8ArrayAssignTo(t *testing.T) {
|
||||
var int64Slice []int64
|
||||
var uint64Slice []uint64
|
||||
var namedInt64Slice _int64Slice
|
||||
var int64SliceDim2 [][]int64
|
||||
var int64SliceDim4 [][][][]int64
|
||||
var int64ArrayDim2 [2][1]int64
|
||||
var int64ArrayDim4 [2][1][1][3]int64
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Int8Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &int64Slice,
|
||||
expected: []int64{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &uint64Slice,
|
||||
expected: []uint64{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &namedInt64Slice,
|
||||
expected: _int64Slice{1},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{},
|
||||
dst: &int64Slice,
|
||||
expected: (([]int64)(nil)),
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{Valid: true},
|
||||
dst: &int64Slice,
|
||||
expected: []int64{},
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [][]int64{{1}, {2}},
|
||||
dst: &int64SliceDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [][][][]int64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &int64SliceDim4,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
expected: [2][1]int64{{1}, {2}},
|
||||
dst: &int64ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{
|
||||
{Int: 1, Valid: true},
|
||||
{Int: 2, Valid: true},
|
||||
{Int: 3, Valid: true},
|
||||
{Int: 4, Valid: true},
|
||||
{Int: 5, Valid: true},
|
||||
{Int: 6, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{LowerBound: 1, Length: 2},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 1},
|
||||
{LowerBound: 1, Length: 3}},
|
||||
Valid: true},
|
||||
expected: [2][1][1][3]int64{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
|
||||
dst: &int64ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Int8Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &int64Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: -1, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
dst: &uint64Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &int64ArrayDim2,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
|
||||
Valid: true},
|
||||
dst: &int64Slice,
|
||||
},
|
||||
{
|
||||
src: pgtype.Int8Array{
|
||||
Elements: []pgtype.Int8{{Int: 1, Valid: true}, {Int: 2, Valid: true}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
|
||||
Valid: true},
|
||||
dst: &int64ArrayDim4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt8Transcode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "int8", []interface{}{
|
||||
&pgtype.Int8{Int: math.MinInt64, Valid: true},
|
||||
&pgtype.Int8{Int: -1, Valid: true},
|
||||
&pgtype.Int8{Int: 0, Valid: true},
|
||||
&pgtype.Int8{Int: 1, Valid: true},
|
||||
&pgtype.Int8{Int: math.MaxInt64, Valid: true},
|
||||
&pgtype.Int8{Int: 0},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt8Set(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Int8
|
||||
}{
|
||||
{source: int8(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: int16(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: int32(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: int64(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: int8(-1), result: pgtype.Int8{Int: -1, Valid: true}},
|
||||
{source: int16(-1), result: pgtype.Int8{Int: -1, Valid: true}},
|
||||
{source: int32(-1), result: pgtype.Int8{Int: -1, Valid: true}},
|
||||
{source: int64(-1), result: pgtype.Int8{Int: -1, Valid: true}},
|
||||
{source: uint8(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: uint16(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: uint32(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: uint64(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: float32(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: float64(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: "1", result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
{source: _int8(1), result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int8
|
||||
err := r.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt8AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Int8
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &i8, expected: int8(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Int8{Int: 0}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Int8{Int: 0}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Int8
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &pi8, expected: int8(42)},
|
||||
{src: pgtype.Int8{Int: 42, Valid: true}, dst: &_pi8, expected: _int8(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Int8
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Int8{Int: 150, Valid: true}, dst: &i8},
|
||||
{src: pgtype.Int8{Int: 40000, Valid: true}, dst: &i16},
|
||||
{src: pgtype.Int8{Int: 5000000000, Valid: true}, dst: &i32},
|
||||
{src: pgtype.Int8{Int: -1, Valid: true}, dst: &ui8},
|
||||
{src: pgtype.Int8{Int: -1, Valid: true}, dst: &ui16},
|
||||
{src: pgtype.Int8{Int: -1, Valid: true}, dst: &ui32},
|
||||
{src: pgtype.Int8{Int: -1, Valid: true}, dst: &ui64},
|
||||
{src: pgtype.Int8{Int: -1, Valid: true}, dst: &ui},
|
||||
{src: pgtype.Int8{Int: 0}, dst: &i64},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt8MarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source pgtype.Int8
|
||||
result string
|
||||
}{
|
||||
{source: pgtype.Int8{Int: 0}, result: "null"},
|
||||
{source: pgtype.Int8{Int: 1, Valid: true}, result: "1"},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
r, err := tt.source.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if string(r) != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt8UnmarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source string
|
||||
result pgtype.Int8
|
||||
}{
|
||||
{source: "null", result: pgtype.Int8{Int: 0}},
|
||||
{source: "1", result: pgtype.Int8{Int: 1, Valid: true}},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Int8
|
||||
err := r.UnmarshalJSON([]byte(tt.source))
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Int8range struct {
|
||||
Lower Int8
|
||||
Upper Int8
|
||||
LowerType BoundType
|
||||
UpperType BoundType
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Int8range) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = Int8range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case Int8range:
|
||||
*dst = value
|
||||
case *Int8range:
|
||||
*dst = *value
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(value))
|
||||
default:
|
||||
return fmt.Errorf("cannot convert %v to Int8range", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int8range) Get() interface{} {
|
||||
if !src.Valid {
|
||||
return nil
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func (src *Int8range) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Int8range) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int8range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
utr, err := ParseUntypedTextRange(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int8range{Valid: true}
|
||||
|
||||
dst.LowerType = utr.LowerType
|
||||
dst.UpperType = utr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeText(ci, []byte(utr.Lower)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeText(ci, []byte(utr.Upper)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int8range) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int8range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
ubr, err := ParseUntypedBinaryRange(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int8range{Valid: true}
|
||||
|
||||
dst.LowerType = ubr.LowerType
|
||||
dst.UpperType = ubr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeBinary(ci, ubr.Lower); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeBinary(ci, ubr.Upper); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int8range) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch src.LowerType {
|
||||
case Exclusive, Unbounded:
|
||||
buf = append(buf, '(')
|
||||
case Inclusive:
|
||||
buf = append(buf, '[')
|
||||
case Empty:
|
||||
return append(buf, "empty"...), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown lower bound type %v", src.LowerType)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
buf, err = src.Lower.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if buf == nil {
|
||||
return nil, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, ',')
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
buf, err = src.Upper.EncodeText(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if buf == nil {
|
||||
return nil, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Exclusive, Unbounded:
|
||||
buf = append(buf, ')')
|
||||
case Inclusive:
|
||||
buf = append(buf, ']')
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown upper bound type %v", src.UpperType)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Int8range) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var rangeType byte
|
||||
switch src.LowerType {
|
||||
case Inclusive:
|
||||
rangeType |= lowerInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= lowerUnboundedMask
|
||||
case Exclusive:
|
||||
case Empty:
|
||||
return append(buf, emptyMask), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown LowerType: %v", src.LowerType)
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Inclusive:
|
||||
rangeType |= upperInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= upperUnboundedMask
|
||||
case Exclusive:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown UpperType: %v", src.UpperType)
|
||||
}
|
||||
|
||||
buf = append(buf, rangeType)
|
||||
|
||||
var err error
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
buf, err = src.Lower.EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
buf, err = src.Upper.EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int8range) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int8range{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int8range) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestInt8rangeTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "Int8range", []interface{}{
|
||||
&pgtype.Int8range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Valid: true},
|
||||
&pgtype.Int8range{Lower: pgtype.Int8{Int: 1, Valid: true}, Upper: pgtype.Int8{Int: 10, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Valid: true},
|
||||
&pgtype.Int8range{Lower: pgtype.Int8{Int: -42, Valid: true}, Upper: pgtype.Int8{Int: -5, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Valid: true},
|
||||
&pgtype.Int8range{Lower: pgtype.Int8{Int: 1, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Unbounded, Valid: true},
|
||||
&pgtype.Int8range{Upper: pgtype.Int8{Int: 1, Valid: true}, LowerType: pgtype.Unbounded, UpperType: pgtype.Exclusive, Valid: true},
|
||||
&pgtype.Int8range{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt8rangeNormalize(t *testing.T) {
|
||||
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
|
||||
{
|
||||
SQL: "select Int8range(1, 10, '(]')",
|
||||
Value: pgtype.Int8range{Lower: pgtype.Int8{Int: 2, Valid: true}, Upper: pgtype.Int8{Int: 11, Valid: true}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Valid: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
<%
|
||||
[
|
||||
["int4", ["int16", "int32", "int64", "uint64", "pgtype.Int4"], [[1, 1], [1, 10], [10, 1], [100, 10]]],
|
||||
["numeric", ["int64", "float64", "pgtype.Numeric"], [[1, 1], [1, 10], [10, 1], [100, 10]]],
|
||||
].each do |pg_type, go_types, rows_columns|
|
||||
%>
|
||||
<% go_types.each do |go_type| %>
|
||||
<% rows_columns.each do |rows, columns| %>
|
||||
<% [["Text", "pgx.TextFormatCode"], ["Binary", "pgx.BinaryFormatCode"]].each do |formatName, formatCode| %>
|
||||
func BenchmarkQuery<%= formatName %>FormatDecode_PG_<%= pg_type %>_to_Go_<%= go_type.gsub(/\W/, "_") %>_<%= rows %>_rows_<%= columns %>_columns(b *testing.B) {
|
||||
conn := testutil.MustConnectPgx(b)
|
||||
defer testutil.MustCloseContext(b, conn)
|
||||
|
||||
b.ResetTimer()
|
||||
var v [<%= columns %>]<%= go_type %>
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := conn.QueryFunc(
|
||||
context.Background(),
|
||||
`select <% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>n::<%= pg_type %> + <%= col_idx%><% end %> from generate_series(1, <%= rows %>) n`,
|
||||
[]interface{}{pgx.QueryResultFormats{<%= formatCode %>}},
|
||||
[]interface{}{<% columns.times do |col_idx| %><% if col_idx != 0 %>, <% end %>&v[<%= col_idx%>]<% end %>},
|
||||
func(pgx.QueryFuncRow) error { return nil },
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
erb integration_benchmark_test.go.erb > integration_benchmark_test.go
|
||||
goimports -w integration_benchmark_test.go
|
||||
@@ -0,0 +1,244 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
const (
|
||||
microsecondsPerSecond = 1000000
|
||||
microsecondsPerMinute = 60 * microsecondsPerSecond
|
||||
microsecondsPerHour = 60 * microsecondsPerMinute
|
||||
microsecondsPerDay = 24 * microsecondsPerHour
|
||||
microsecondsPerMonth = 30 * microsecondsPerDay
|
||||
)
|
||||
|
||||
type Interval struct {
|
||||
Microseconds int64
|
||||
Days int32
|
||||
Months int32
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Interval) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Interval{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case time.Duration:
|
||||
*dst = Interval{Microseconds: int64(value) / 1000, Valid: true}
|
||||
default:
|
||||
if originalSrc, ok := underlyingPtrType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Interval", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst Interval) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Interval) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
switch v := dst.(type) {
|
||||
case *time.Duration:
|
||||
us := int64(src.Months)*microsecondsPerMonth + int64(src.Days)*microsecondsPerDay + src.Microseconds
|
||||
*v = time.Duration(us) * time.Microsecond
|
||||
return nil
|
||||
default:
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
return fmt.Errorf("unable to assign to %T", dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (dst *Interval) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Interval{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var microseconds int64
|
||||
var days int32
|
||||
var months int32
|
||||
|
||||
parts := strings.Split(string(src), " ")
|
||||
|
||||
for i := 0; i < len(parts)-1; i += 2 {
|
||||
scalar, err := strconv.ParseInt(parts[i], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad interval format")
|
||||
}
|
||||
|
||||
switch parts[i+1] {
|
||||
case "year", "years":
|
||||
months += int32(scalar * 12)
|
||||
case "mon", "mons":
|
||||
months += int32(scalar)
|
||||
case "day", "days":
|
||||
days = int32(scalar)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts)%2 == 1 {
|
||||
timeParts := strings.SplitN(parts[len(parts)-1], ":", 3)
|
||||
if len(timeParts) != 3 {
|
||||
return fmt.Errorf("bad interval format")
|
||||
}
|
||||
|
||||
var negative bool
|
||||
if timeParts[0][0] == '-' {
|
||||
negative = true
|
||||
timeParts[0] = timeParts[0][1:]
|
||||
}
|
||||
|
||||
hours, err := strconv.ParseInt(timeParts[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad interval hour format: %s", timeParts[0])
|
||||
}
|
||||
|
||||
minutes, err := strconv.ParseInt(timeParts[1], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad interval minute format: %s", timeParts[1])
|
||||
}
|
||||
|
||||
secondParts := strings.SplitN(timeParts[2], ".", 2)
|
||||
|
||||
seconds, err := strconv.ParseInt(secondParts[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad interval second format: %s", secondParts[0])
|
||||
}
|
||||
|
||||
var uSeconds int64
|
||||
if len(secondParts) == 2 {
|
||||
uSeconds, err = strconv.ParseInt(secondParts[1], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad interval decimal format: %s", secondParts[1])
|
||||
}
|
||||
|
||||
for i := 0; i < 6-len(secondParts[1]); i++ {
|
||||
uSeconds *= 10
|
||||
}
|
||||
}
|
||||
|
||||
microseconds = hours * microsecondsPerHour
|
||||
microseconds += minutes * microsecondsPerMinute
|
||||
microseconds += seconds * microsecondsPerSecond
|
||||
microseconds += uSeconds
|
||||
|
||||
if negative {
|
||||
microseconds = -microseconds
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Interval{Months: months, Days: days, Microseconds: microseconds, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Interval) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Interval{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 16 {
|
||||
return fmt.Errorf("Received an invalid size for a interval: %d", len(src))
|
||||
}
|
||||
|
||||
microseconds := int64(binary.BigEndian.Uint64(src))
|
||||
days := int32(binary.BigEndian.Uint32(src[8:]))
|
||||
months := int32(binary.BigEndian.Uint32(src[12:]))
|
||||
|
||||
*dst = Interval{Microseconds: microseconds, Days: days, Months: months, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Interval) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if src.Months != 0 {
|
||||
buf = append(buf, strconv.FormatInt(int64(src.Months), 10)...)
|
||||
buf = append(buf, " mon "...)
|
||||
}
|
||||
|
||||
if src.Days != 0 {
|
||||
buf = append(buf, strconv.FormatInt(int64(src.Days), 10)...)
|
||||
buf = append(buf, " day "...)
|
||||
}
|
||||
|
||||
absMicroseconds := src.Microseconds
|
||||
if absMicroseconds < 0 {
|
||||
absMicroseconds = -absMicroseconds
|
||||
buf = append(buf, '-')
|
||||
}
|
||||
|
||||
hours := absMicroseconds / microsecondsPerHour
|
||||
minutes := (absMicroseconds % microsecondsPerHour) / microsecondsPerMinute
|
||||
seconds := (absMicroseconds % microsecondsPerMinute) / microsecondsPerSecond
|
||||
microseconds := absMicroseconds % microsecondsPerSecond
|
||||
|
||||
timeStr := fmt.Sprintf("%02d:%02d:%02d.%06d", hours, minutes, seconds, microseconds)
|
||||
return append(buf, timeStr...), nil
|
||||
}
|
||||
|
||||
// EncodeBinary encodes src into w.
|
||||
func (src Interval) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendInt64(buf, src.Microseconds)
|
||||
buf = pgio.AppendInt32(buf, src.Days)
|
||||
return pgio.AppendInt32(buf, src.Months), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Interval) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Interval{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Interval) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIntervalTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "interval", []interface{}{
|
||||
&pgtype.Interval{Microseconds: 1, Valid: true},
|
||||
&pgtype.Interval{Microseconds: 1000000, Valid: true},
|
||||
&pgtype.Interval{Microseconds: 1000001, Valid: true},
|
||||
&pgtype.Interval{Microseconds: 123202800000000, Valid: true},
|
||||
&pgtype.Interval{Days: 1, Valid: true},
|
||||
&pgtype.Interval{Months: 1, Valid: true},
|
||||
&pgtype.Interval{Months: 12, Valid: true},
|
||||
&pgtype.Interval{Months: 13, Days: 15, Microseconds: 1000001, Valid: true},
|
||||
&pgtype.Interval{Microseconds: -1, Valid: true},
|
||||
&pgtype.Interval{Microseconds: -1000000, Valid: true},
|
||||
&pgtype.Interval{Microseconds: -1000001, Valid: true},
|
||||
&pgtype.Interval{Microseconds: -123202800000000, Valid: true},
|
||||
&pgtype.Interval{Days: -1, Valid: true},
|
||||
&pgtype.Interval{Months: -1, Valid: true},
|
||||
&pgtype.Interval{Months: -12, Valid: true},
|
||||
&pgtype.Interval{Months: -13, Days: -15, Microseconds: -1000001, Valid: true},
|
||||
&pgtype.Interval{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntervalNormalize(t *testing.T) {
|
||||
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
|
||||
{
|
||||
SQL: "select '1 second'::interval",
|
||||
Value: &pgtype.Interval{Microseconds: 1000000, Valid: true},
|
||||
},
|
||||
{
|
||||
SQL: "select '1.000001 second'::interval",
|
||||
Value: &pgtype.Interval{Microseconds: 1000001, Valid: true},
|
||||
},
|
||||
{
|
||||
SQL: "select '34223 hours'::interval",
|
||||
Value: &pgtype.Interval{Microseconds: 123202800000000, Valid: true},
|
||||
},
|
||||
{
|
||||
SQL: "select '1 day'::interval",
|
||||
Value: &pgtype.Interval{Days: 1, Valid: true},
|
||||
},
|
||||
{
|
||||
SQL: "select '1 month'::interval",
|
||||
Value: &pgtype.Interval{Months: 1, Valid: true},
|
||||
},
|
||||
{
|
||||
SQL: "select '1 year'::interval",
|
||||
Value: &pgtype.Interval{Months: 12, Valid: true},
|
||||
},
|
||||
{
|
||||
SQL: "select '-13 mon'::interval",
|
||||
Value: &pgtype.Interval{Months: -13, Valid: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntervalLossyConversionToDuration(t *testing.T) {
|
||||
interval := &pgtype.Interval{Months: 1, Days: 1, Valid: true}
|
||||
var d time.Duration
|
||||
err := interval.AssignTo(&d)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, int64(2678400000000000), d.Nanoseconds())
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type JSON struct {
|
||||
Bytes []byte
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *JSON) Set(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = JSON{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
switch value := src.(type) {
|
||||
case string:
|
||||
*dst = JSON{Bytes: []byte(value), Valid: true}
|
||||
case *string:
|
||||
if value == nil {
|
||||
*dst = JSON{}
|
||||
} else {
|
||||
*dst = JSON{Bytes: []byte(*value), Valid: true}
|
||||
}
|
||||
case []byte:
|
||||
if value == nil {
|
||||
*dst = JSON{}
|
||||
} else {
|
||||
*dst = JSON{Bytes: value, Valid: true}
|
||||
}
|
||||
// Encode* methods are defined on *JSON. If JSON is passed directly then the
|
||||
// struct itself would be encoded instead of Bytes. This is clearly a footgun
|
||||
// so detect and return an error. See https://github.com/jackc/pgx/issues/350.
|
||||
case JSON:
|
||||
return errors.New("use pointer to pgtype.JSON instead of value")
|
||||
// Same as above but for JSONB (because they share implementation)
|
||||
case JSONB:
|
||||
return errors.New("use pointer to pgtype.JSONB instead of value")
|
||||
|
||||
default:
|
||||
buf, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = JSON{Bytes: buf, Valid: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst JSON) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
var i interface{}
|
||||
err := json.Unmarshal(dst.Bytes, &i)
|
||||
if err != nil {
|
||||
return dst
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (src *JSON) AssignTo(dst interface{}) error {
|
||||
switch v := dst.(type) {
|
||||
case *string:
|
||||
if src.Valid {
|
||||
*v = string(src.Bytes)
|
||||
} else {
|
||||
return fmt.Errorf("cannot assign non-valid to %T", dst)
|
||||
}
|
||||
case **string:
|
||||
if src.Valid {
|
||||
s := string(src.Bytes)
|
||||
*v = &s
|
||||
return nil
|
||||
} else {
|
||||
*v = nil
|
||||
return nil
|
||||
}
|
||||
case *[]byte:
|
||||
if !src.Valid {
|
||||
*v = nil
|
||||
} else {
|
||||
buf := make([]byte, len(src.Bytes))
|
||||
copy(buf, src.Bytes)
|
||||
*v = buf
|
||||
}
|
||||
default:
|
||||
data := src.Bytes
|
||||
if data == nil || !src.Valid {
|
||||
data = []byte("null")
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, dst)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (JSON) PreferredResultFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (dst *JSON) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = JSON{}
|
||||
return nil
|
||||
}
|
||||
|
||||
*dst = JSON{Bytes: src, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *JSON) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
return dst.DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (JSON) PreferredParamFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (src JSON) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return append(buf, src.Bytes...), nil
|
||||
}
|
||||
|
||||
func (src JSON) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return src.EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *JSON) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = JSON{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src JSON) Value() (driver.Value, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return src.Bytes, nil
|
||||
}
|
||||
|
||||
func (src JSON) MarshalJSON() ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return src.Bytes, nil
|
||||
}
|
||||
|
||||
func (dst *JSON) UnmarshalJSON(b []byte) error {
|
||||
if b == nil || string(b) == "null" {
|
||||
*dst = JSON{}
|
||||
} else {
|
||||
*dst = JSON{Bytes: b, Valid: true}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestJSONTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "json", []interface{}{
|
||||
&pgtype.JSON{Bytes: []byte("{}"), Valid: true},
|
||||
&pgtype.JSON{Bytes: []byte("null"), Valid: true},
|
||||
&pgtype.JSON{Bytes: []byte("42"), Valid: true},
|
||||
&pgtype.JSON{Bytes: []byte(`"hello"`), Valid: true},
|
||||
&pgtype.JSON{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestJSONSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.JSON
|
||||
}{
|
||||
{source: "{}", result: pgtype.JSON{Bytes: []byte("{}"), Valid: true}},
|
||||
{source: []byte("{}"), result: pgtype.JSON{Bytes: []byte("{}"), Valid: true}},
|
||||
{source: ([]byte)(nil), result: pgtype.JSON{}},
|
||||
{source: (*string)(nil), result: pgtype.JSON{}},
|
||||
{source: []int{1, 2, 3}, result: pgtype.JSON{Bytes: []byte("[1,2,3]"), Valid: true}},
|
||||
{source: map[string]interface{}{"foo": "bar"}, result: pgtype.JSON{Bytes: []byte(`{"foo":"bar"}`), Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var d pgtype.JSON
|
||||
err := d.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(d, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONAssignTo(t *testing.T) {
|
||||
var s string
|
||||
var ps *string
|
||||
var b []byte
|
||||
|
||||
rawStringTests := []struct {
|
||||
src pgtype.JSON
|
||||
dst *string
|
||||
expected string
|
||||
}{
|
||||
{src: pgtype.JSON{Bytes: []byte("{}"), Valid: true}, dst: &s, expected: "{}"},
|
||||
}
|
||||
|
||||
for i, tt := range rawStringTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if *tt.dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
rawBytesTests := []struct {
|
||||
src pgtype.JSON
|
||||
dst *[]byte
|
||||
expected []byte
|
||||
}{
|
||||
{src: pgtype.JSON{Bytes: []byte("{}"), Valid: true}, dst: &b, expected: []byte("{}")},
|
||||
{src: pgtype.JSON{}, dst: &b, expected: (([]byte)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range rawBytesTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if bytes.Compare(tt.expected, *tt.dst) != 0 {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
var mapDst map[string]interface{}
|
||||
type structDst struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
var strDst structDst
|
||||
|
||||
unmarshalTests := []struct {
|
||||
src pgtype.JSON
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.JSON{Bytes: []byte(`{"foo":"bar"}`), Valid: true}, dst: &mapDst, expected: map[string]interface{}{"foo": "bar"}},
|
||||
{src: pgtype.JSON{Bytes: []byte(`{"name":"John","age":42}`), Valid: true}, dst: &strDst, expected: structDst{Name: "John", Age: 42}},
|
||||
}
|
||||
for i, tt := range unmarshalTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.JSON
|
||||
dst **string
|
||||
expected *string
|
||||
}{
|
||||
{src: pgtype.JSON{}, dst: &ps, expected: ((*string)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if *tt.dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONMarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source pgtype.JSON
|
||||
result string
|
||||
}{
|
||||
{source: pgtype.JSON{}, result: "null"},
|
||||
{source: pgtype.JSON{Bytes: []byte("{\"a\": 1}"), Valid: true}, result: "{\"a\": 1}"},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
r, err := tt.source.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if string(r) != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, string(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONUnmarshalJSON(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source string
|
||||
result pgtype.JSON
|
||||
}{
|
||||
{source: "null", result: pgtype.JSON{}},
|
||||
{source: "{\"a\": 1}", result: pgtype.JSON{Bytes: []byte("{\"a\": 1}"), Valid: true}},
|
||||
}
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.JSON
|
||||
err := r.UnmarshalJSON([]byte(tt.source))
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if string(r.Bytes) != string(tt.result.Bytes) || r.Valid != tt.result.Valid {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type JSONB JSON
|
||||
|
||||
func (dst *JSONB) Set(src interface{}) error {
|
||||
return (*JSON)(dst).Set(src)
|
||||
}
|
||||
|
||||
func (dst JSONB) Get() interface{} {
|
||||
return (JSON)(dst).Get()
|
||||
}
|
||||
|
||||
func (src *JSONB) AssignTo(dst interface{}) error {
|
||||
return (*JSON)(src).AssignTo(dst)
|
||||
}
|
||||
|
||||
func (JSONB) PreferredResultFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (dst *JSONB) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
return (*JSON)(dst).DecodeText(ci, src)
|
||||
}
|
||||
|
||||
func (dst *JSONB) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = JSONB{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) == 0 {
|
||||
return fmt.Errorf("jsonb too short")
|
||||
}
|
||||
|
||||
if src[0] != 1 {
|
||||
return fmt.Errorf("unknown jsonb version number %d", src[0])
|
||||
}
|
||||
|
||||
*dst = JSONB{Bytes: src[1:], Valid: true}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (JSONB) PreferredParamFormat() int16 {
|
||||
return TextFormatCode
|
||||
}
|
||||
|
||||
func (src JSONB) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
return (JSON)(src).EncodeText(ci, buf)
|
||||
}
|
||||
|
||||
func (src JSONB) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, 1)
|
||||
return append(buf, src.Bytes...), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *JSONB) Scan(src interface{}) error {
|
||||
return (*JSON)(dst).Scan(src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src JSONB) Value() (driver.Value, error) {
|
||||
return (JSON)(src).Value()
|
||||
}
|
||||
|
||||
func (src JSONB) MarshalJSON() ([]byte, error) {
|
||||
return (JSON)(src).MarshalJSON()
|
||||
}
|
||||
|
||||
func (dst *JSONB) UnmarshalJSON(b []byte) error {
|
||||
return (*JSON)(dst).UnmarshalJSON(b)
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Code generated by erb. DO NOT EDIT.
|
||||
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type JSONBArray struct {
|
||||
Elements []JSONB
|
||||
Dimensions []ArrayDimension
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *JSONBArray) Set(src interface{}) error {
|
||||
// untyped nil and typed nil interfaces are different
|
||||
if src == nil {
|
||||
*dst = JSONBArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, ok := src.(interface{ Get() interface{} }); ok {
|
||||
value2 := value.Get()
|
||||
if value2 != value {
|
||||
return dst.Set(value2)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to match to select common types:
|
||||
switch value := src.(type) {
|
||||
|
||||
case []string:
|
||||
if value == nil {
|
||||
*dst = JSONBArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = JSONBArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]JSONB, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = JSONBArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case [][]byte:
|
||||
if value == nil {
|
||||
*dst = JSONBArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = JSONBArray{Valid: true}
|
||||
} else {
|
||||
elements := make([]JSONB, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].Set(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = JSONBArray{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
case []JSONB:
|
||||
if value == nil {
|
||||
*dst = JSONBArray{}
|
||||
} else if len(value) == 0 {
|
||||
*dst = JSONBArray{Valid: true}
|
||||
} else {
|
||||
*dst = JSONBArray{
|
||||
Elements: value,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
reflectedValue := reflect.ValueOf(src)
|
||||
if !reflectedValue.IsValid() || reflectedValue.IsZero() {
|
||||
*dst = JSONBArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find dimensions of %v for JSONBArray", src)
|
||||
}
|
||||
if elementsLength == 0 {
|
||||
*dst = JSONBArray{Valid: true}
|
||||
return nil
|
||||
}
|
||||
if len(dimensions) == 0 {
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.Set(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to JSONBArray", src)
|
||||
}
|
||||
|
||||
*dst = JSONBArray{
|
||||
Elements: make([]JSONB, elementsLength),
|
||||
Dimensions: dimensions,
|
||||
Valid: true,
|
||||
}
|
||||
elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
// Maybe the target was one dimension too far, try again:
|
||||
if len(dst.Dimensions) > 1 {
|
||||
dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
|
||||
elementsLength = 0
|
||||
for _, dim := range dst.Dimensions {
|
||||
if elementsLength == 0 {
|
||||
elementsLength = int(dim.Length)
|
||||
} else {
|
||||
elementsLength *= int(dim.Length)
|
||||
}
|
||||
}
|
||||
dst.Elements = make([]JSONB, elementsLength)
|
||||
elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if elementCount != len(dst.Elements) {
|
||||
return fmt.Errorf("cannot convert %v to JSONBArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *JSONBArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(dst.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
valueLen := value.Len()
|
||||
if int32(valueLen) != dst.Dimensions[dimension].Length {
|
||||
return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
|
||||
}
|
||||
for i := 0; i < valueLen; i++ {
|
||||
var err error
|
||||
index, err = dst.setRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if !value.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot convert all values to JSONBArray")
|
||||
}
|
||||
if err := dst.Elements[index].Set(value.Interface()); err != nil {
|
||||
return 0, fmt.Errorf("%v in JSONBArray", err)
|
||||
}
|
||||
index++
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst JSONBArray) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *JSONBArray) AssignTo(dst interface{}) error {
|
||||
if !src.Valid {
|
||||
return NullAssignTo(dst)
|
||||
}
|
||||
|
||||
if len(src.Dimensions) <= 1 {
|
||||
// Attempt to match to select common types:
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]string:
|
||||
*v = make([]string, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *[][]byte:
|
||||
*v = make([][]byte, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to something AssignTo can use directly.
|
||||
if nextDst, retry := GetAssignToDstType(dst); retry {
|
||||
return src.AssignTo(nextDst)
|
||||
}
|
||||
|
||||
// Fallback to reflection if an optimised match was not found.
|
||||
// The reflection is necessary for arrays and multidimensional slices,
|
||||
// but it comes with a 20-50% performance penalty for large arrays/slices
|
||||
value := reflect.ValueOf(dst)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
default:
|
||||
return fmt.Errorf("cannot assign %T to %T", src, dst)
|
||||
}
|
||||
|
||||
if len(src.Elements) == 0 {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value.Set(reflect.MakeSlice(value.Type(), 0, 0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
elementCount, err := src.assignToRecursive(value, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elementCount != len(src.Elements) {
|
||||
return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *JSONBArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
if len(src.Dimensions) == dimension {
|
||||
break
|
||||
}
|
||||
|
||||
length := int(src.Dimensions[dimension].Length)
|
||||
if reflect.Array == kind {
|
||||
typ := value.Type()
|
||||
if typ.Len() != length {
|
||||
return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
|
||||
}
|
||||
value.Set(reflect.New(typ).Elem())
|
||||
} else {
|
||||
value.Set(reflect.MakeSlice(value.Type(), length, length))
|
||||
}
|
||||
|
||||
var err error
|
||||
for i := 0; i < length; i++ {
|
||||
index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
if len(src.Dimensions) != dimension {
|
||||
return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
|
||||
}
|
||||
if !value.CanAddr() {
|
||||
return 0, fmt.Errorf("cannot assign all values from JSONBArray")
|
||||
}
|
||||
addr := value.Addr()
|
||||
if !addr.CanInterface() {
|
||||
return 0, fmt.Errorf("cannot assign all values from JSONBArray")
|
||||
}
|
||||
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index++
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (dst *JSONBArray) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = JSONBArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var elements []JSONB
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]JSONB, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem JSONB
|
||||
var elemSrc []byte
|
||||
if s != "NULL" || uta.Quoted[i] {
|
||||
elemSrc = []byte(s)
|
||||
}
|
||||
err = elem.DecodeText(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = JSONBArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *JSONBArray) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = JSONBArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
rp, err := arrayHeader.DecodeBinary(ci, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = JSONBArray{Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]JSONB, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||
rp += 4
|
||||
var elemSrc []byte
|
||||
if elemLen >= 0 {
|
||||
elemSrc = src[rp : rp+elemLen]
|
||||
rp += elemLen
|
||||
}
|
||||
err = elements[i].DecodeBinary(ci, elemSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = JSONBArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src JSONBArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
return append(buf, '{', '}'), nil
|
||||
}
|
||||
|
||||
buf = EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
inElemBuf := make([]byte, 0, 32)
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
buf = append(buf, '{')
|
||||
}
|
||||
}
|
||||
|
||||
elemBuf, err := elem.EncodeText(ci, inElemBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf == nil {
|
||||
buf = append(buf, `NULL`...)
|
||||
} else {
|
||||
buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
buf = append(buf, '}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src JSONBArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
arrayHeader := ArrayHeader{
|
||||
Dimensions: src.Dimensions,
|
||||
}
|
||||
|
||||
if dt, ok := ci.DataTypeForName("jsonb"); ok {
|
||||
arrayHeader.ElementOID = int32(dt.OID)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to find oid for type name %v", "jsonb")
|
||||
}
|
||||
|
||||
for i := range src.Elements {
|
||||
if !src.Elements[i].Valid {
|
||||
arrayHeader.ContainsNull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf = arrayHeader.EncodeBinary(ci, buf)
|
||||
|
||||
for i := range src.Elements {
|
||||
sp := len(buf)
|
||||
buf = pgio.AppendInt32(buf, -1)
|
||||
|
||||
elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if elemBuf != nil {
|
||||
buf = elemBuf
|
||||
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||
}
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *JSONBArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
return dst.DecodeText(nil, nil)
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src JSONBArray) Value() (driver.Value, error) {
|
||||
buf, err := src.EncodeText(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestJSONBArrayTranscode(t *testing.T) {
|
||||
testutil.TestSuccessfulTranscode(t, "jsonb[]", []interface{}{
|
||||
&pgtype.JSONBArray{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.JSONBArray{
|
||||
Elements: []pgtype.JSONB{
|
||||
{Bytes: []byte(`"foo"`), Valid: true},
|
||||
{},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
&pgtype.JSONBArray{},
|
||||
&pgtype.JSONBArray{
|
||||
Elements: []pgtype.JSONB{
|
||||
{Bytes: []byte(`"foo"`), Valid: true},
|
||||
{Bytes: []byte("null"), Valid: true},
|
||||
{Bytes: []byte("42"), Valid: true},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}},
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package pgtype_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgtype/testutil"
|
||||
)
|
||||
|
||||
func TestJSONBTranscode(t *testing.T) {
|
||||
conn := testutil.MustConnectPgx(t)
|
||||
defer testutil.MustCloseContext(t, conn)
|
||||
if _, ok := conn.ConnInfo().DataTypeForName("jsonb"); !ok {
|
||||
t.Skip("Skipping due to no jsonb type")
|
||||
}
|
||||
|
||||
testutil.TestSuccessfulTranscode(t, "jsonb", []interface{}{
|
||||
&pgtype.JSONB{Bytes: []byte("{}"), Valid: true},
|
||||
&pgtype.JSONB{Bytes: []byte("null"), Valid: true},
|
||||
&pgtype.JSONB{Bytes: []byte("42"), Valid: true},
|
||||
&pgtype.JSONB{Bytes: []byte(`"hello"`), Valid: true},
|
||||
&pgtype.JSONB{},
|
||||
})
|
||||
}
|
||||
|
||||
func TestJSONBSet(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.JSONB
|
||||
}{
|
||||
{source: "{}", result: pgtype.JSONB{Bytes: []byte("{}"), Valid: true}},
|
||||
{source: []byte("{}"), result: pgtype.JSONB{Bytes: []byte("{}"), Valid: true}},
|
||||
{source: ([]byte)(nil), result: pgtype.JSONB{}},
|
||||
{source: (*string)(nil), result: pgtype.JSONB{}},
|
||||
{source: []int{1, 2, 3}, result: pgtype.JSONB{Bytes: []byte("[1,2,3]"), Valid: true}},
|
||||
{source: map[string]interface{}{"foo": "bar"}, result: pgtype.JSONB{Bytes: []byte(`{"foo":"bar"}`), Valid: true}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var d pgtype.JSONB
|
||||
err := d.Set(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(d, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONBAssignTo(t *testing.T) {
|
||||
var s string
|
||||
var ps *string
|
||||
var b []byte
|
||||
|
||||
rawStringTests := []struct {
|
||||
src pgtype.JSONB
|
||||
dst *string
|
||||
expected string
|
||||
}{
|
||||
{src: pgtype.JSONB{Bytes: []byte("{}"), Valid: true}, dst: &s, expected: "{}"},
|
||||
}
|
||||
|
||||
for i, tt := range rawStringTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if *tt.dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
rawBytesTests := []struct {
|
||||
src pgtype.JSONB
|
||||
dst *[]byte
|
||||
expected []byte
|
||||
}{
|
||||
{src: pgtype.JSONB{Bytes: []byte("{}"), Valid: true}, dst: &b, expected: []byte("{}")},
|
||||
{src: pgtype.JSONB{}, dst: &b, expected: (([]byte)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range rawBytesTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if bytes.Compare(tt.expected, *tt.dst) != 0 {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
var mapDst map[string]interface{}
|
||||
type structDst struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
var strDst structDst
|
||||
|
||||
unmarshalTests := []struct {
|
||||
src pgtype.JSONB
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.JSONB{Bytes: []byte(`{"foo":"bar"}`), Valid: true}, dst: &mapDst, expected: map[string]interface{}{"foo": "bar"}},
|
||||
{src: pgtype.JSONB{Bytes: []byte(`{"name":"John","age":42}`), Valid: true}, dst: &strDst, expected: structDst{Name: "John", Age: 42}},
|
||||
}
|
||||
for i, tt := range unmarshalTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.JSONB
|
||||
dst **string
|
||||
expected *string
|
||||
}{
|
||||
{src: pgtype.JSONB{}, dst: &ps, expected: ((*string)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if *tt.dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, *tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package pgtype
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgio"
|
||||
)
|
||||
|
||||
type Line struct {
|
||||
A, B, C float64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (dst *Line) Set(src interface{}) error {
|
||||
return fmt.Errorf("cannot convert %v to Line", src)
|
||||
}
|
||||
|
||||
func (dst Line) Get() interface{} {
|
||||
if !dst.Valid {
|
||||
return nil
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (src *Line) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Line) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Line{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) < 7 {
|
||||
return fmt.Errorf("invalid length for Line: %v", len(src))
|
||||
}
|
||||
|
||||
parts := strings.SplitN(string(src[1:len(src)-1]), ",", 3)
|
||||
if len(parts) < 3 {
|
||||
return fmt.Errorf("invalid format for line")
|
||||
}
|
||||
|
||||
a, err := strconv.ParseFloat(parts[0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := strconv.ParseFloat(parts[2], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Line{A: a, B: b, C: c, Valid: true}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Line) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Line{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(src) != 24 {
|
||||
return fmt.Errorf("invalid length for Line: %v", len(src))
|
||||
}
|
||||
|
||||
a := binary.BigEndian.Uint64(src)
|
||||
b := binary.BigEndian.Uint64(src[8:])
|
||||
c := binary.BigEndian.Uint64(src[16:])
|
||||
|
||||
*dst = Line{
|
||||
A: math.Float64frombits(a),
|
||||
B: math.Float64frombits(b),
|
||||
C: math.Float64frombits(c),
|
||||
Valid: true,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Line) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = append(buf, fmt.Sprintf(`{%s,%s,%s}`,
|
||||
strconv.FormatFloat(src.A, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.B, 'f', -1, 64),
|
||||
strconv.FormatFloat(src.C, 'f', -1, 64),
|
||||
)...)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (src Line) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
|
||||
if !src.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.A))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.B))
|
||||
buf = pgio.AppendUint64(buf, math.Float64bits(src.C))
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Line) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Line{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
srcCopy := make([]byte, len(src))
|
||||
copy(srcCopy, src)
|
||||
return dst.DecodeText(nil, srcCopy)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Line) Value() (driver.Value, error) {
|
||||
return EncodeValueText(src)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user