5e997e82f4
Squashed commit of the following: commit c19454582b335ce5bdda6320f7e4e8c76cfeaf44 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Mar 3 15:24:47 2017 -0600 Add AssignTo to pgtype.Timestamptz Also handle infinity for pgtype.Date commit 7329933610b38f4bc15731b1f7c55c520b49e300 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Mar 3 15:12:18 2017 -0600 Implement AssignTo for most pgtypes commit cc3d1e4af896d34ec98c3bf2e982d0367451f21c Author: Jack Christensen <jack@jackchristensen.com> Date: Thu Mar 2 21:19:07 2017 -0600 Use pgtype.Int2Array in pgx commit 36da5cc2178d1a31a56dc6e6f128843bd80dea0b Author: Jack Christensen <jack@jackchristensen.com> Date: Tue Feb 28 21:45:33 2017 -0600 Add text array transcoding commit 1b0f18d99f38b69f8c2db26388815e67b2b03d59 Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 27 19:28:55 2017 -0600 Add ParseUntypedTextArray commit 0f50ce3e833fc38495d333228daf04f5142be676 Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 27 18:54:20 2017 -0600 wip commit d934f273627d79997035c282416db922f2fbe87a Author: Jack Christensen <jack@jackchristensen.com> Date: Sun Feb 26 17:14:32 2017 -0600 WIP - beginning text format array parsing commit 7276ad33ce7fa9c250745a3ed909998f3dae4a32 Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 22:50:11 2017 -0600 Beginning binary arrays commit 917faa5a3175d376222423c10aca297a20f96448 Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 19:36:35 2017 -0600 Fix incomplete tests commit de8c140cfb98b7b047d53c5718ccbf12eaf813a1 Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 19:32:22 2017 -0600 Add timestamptz null and infinity commit 7d9f954de4e071a1eccac762248079b90dbeb53f Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 18:19:38 2017 -0600 Add infinity to pgtype.Date commit 7bf783ae20ba05571c2fb9f661183233c95eab41 Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 17:19:55 2017 -0600 Add Status to pgtype.Date commit 984500455c9b9a4b6221758540d248e6410d93a4 Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 16:54:01 2017 -0600 Add status to Int4 and Int8 commit 6fe76fcfc2de31552790db3b093480a9d5b2a742 Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 16:40:27 2017 -0600 Extract testSuccessfulTranscode commit 001647c1da03f796014cf21f41c9a7fd2cfadfde Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 16:15:51 2017 -0600 Add Status to pgtype.Int2 commit 720451f06d13d9c9fa2a0482e010f24bf4627c2a Author: Jack Christensen <jack@jackchristensen.com> Date: Sat Feb 25 15:56:44 2017 -0600 Add status to pgtype.Bool commit 325f700b6edff215a692b10bc5b94cdfe1100769 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 17:28:15 2017 -0600 Add date to conversion system commit 4a9343e45d3897f59eab98a0009d2ddbe07e02d7 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 16:28:35 2017 -0600 Add bool to oid based encoding commit d984fcafab1476cf84852485b6711f4b2069eb6d Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 16:15:38 2017 -0600 Add pgtype interfaces commit 0f93bfc2de4023b069b966c0988bf7f0469d1809 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 14:48:34 2017 -0600 Begin introduction of Convert commit e5707023cac7c07342b8c910e480d09a1caaf6ee Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 14:10:56 2017 -0600 Move bool to pgtype commit bb764d2129efe7fb21e841dbb35e6d0dc7586d37 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 13:45:05 2017 -0600 Add Int2 test commit 08c49437f455a32f7c3f0a524cd21a895d440301 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 13:44:09 2017 -0600 Add Int4 test commit 16722952222fd15c53c8fa84974645504a6d0dc0 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 24 08:56:59 2017 -0600 Add int8 tests commit 83a5447cd2c46b58d0880023cc4e9af0c84988a2 Author: Jack Christensen <jack@jackchristensen.com> Date: Wed Feb 22 18:08:05 2017 -0600 wip commit 0ca0ee72068a72b016729b01fccef22474595285 Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 18:56:52 2017 -0600 wip commit d2c2baf4ea2cd0793d68c7094c425217df952bec Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 18:46:10 2017 -0600 wip commit f78371da0098356527b193fd496a338da5fe414b Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 17:43:39 2017 -0600 wip commit 3366699bea62ec0110db05f3cb2998d58ac9ce5d Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 14:07:47 2017 -0600 wip commit 66b79e940870fd0133ebb10ac1547e1d4d7d0b51 Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 13:35:37 2017 -0600 Extract pgio commit 8b07d97d1305ed98fd76db6e306a289c0af92d56 Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 13:20:00 2017 -0600 wip commit 62f1adb3427f4317b708da075dce50c4d4daff7b Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 12:08:46 2017 -0600 wip commit a712d2546933a5a8433c65eef0ff2ee135077c87 Author: Jack Christensen <jack@jackchristensen.com> Date: Mon Feb 20 09:30:52 2017 -0600 wip commit 4faf97cc588126dda160fc360680719572a23105 Author: Jack Christensen <jack@jackchristensen.com> Date: Fri Feb 17 22:20:18 2017 -0600 wip
376 lines
6.8 KiB
Go
376 lines
6.8 KiB
Go
package pgtype
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"unicode"
|
|
|
|
"github.com/jackc/pgx/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 (ah *ArrayHeader) DecodeBinary(r io.Reader) error {
|
|
numDims, err := pgio.ReadInt32(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if numDims > 0 {
|
|
ah.Dimensions = make([]ArrayDimension, numDims)
|
|
}
|
|
|
|
containsNull, err := pgio.ReadInt32(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ah.ContainsNull = containsNull == 1
|
|
|
|
ah.ElementOID, err = pgio.ReadInt32(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range ah.Dimensions {
|
|
ah.Dimensions[i].Length, err = pgio.ReadInt32(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ah.Dimensions[i].LowerBound, err = pgio.ReadInt32(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ah *ArrayHeader) EncodeBinary(w io.Writer) error {
|
|
_, err := pgio.WriteInt32(w, int32(len(ah.Dimensions)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var containsNull int32
|
|
if ah.ContainsNull {
|
|
containsNull = 1
|
|
}
|
|
_, err = pgio.WriteInt32(w, containsNull)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = pgio.WriteInt32(w, ah.ElementOID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range ah.Dimensions {
|
|
_, err = pgio.WriteInt32(w, ah.Dimensions[i].Length)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = pgio.WriteInt32(w, ah.Dimensions[i].LowerBound)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type UntypedTextArray struct {
|
|
Elements []string
|
|
Dimensions []ArrayDimension
|
|
}
|
|
|
|
func ParseUntypedTextArray(src string) (*UntypedTextArray, error) {
|
|
uta := &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, err := arrayParseValue(buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid array value: %v", err)
|
|
}
|
|
if currentDim == counterDim {
|
|
implicitDimensions[currentDim].Length++
|
|
}
|
|
uta.Elements = append(uta.Elements, value)
|
|
}
|
|
|
|
if currentDim < 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
skipWhitespace(buf)
|
|
|
|
if buf.Len() > 0 {
|
|
return nil, fmt.Errorf("unexpected trailing data: %v", buf.String())
|
|
}
|
|
|
|
if len(uta.Elements) == 0 {
|
|
uta.Dimensions = nil
|
|
} else if len(explicitDimensions) > 0 {
|
|
uta.Dimensions = explicitDimensions
|
|
} else {
|
|
uta.Dimensions = implicitDimensions
|
|
}
|
|
|
|
return uta, 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, error) {
|
|
r, _, err := buf.ReadRune()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if r == '"' {
|
|
return arrayParseQuotedValue(buf)
|
|
}
|
|
buf.UnreadRune()
|
|
|
|
s := &bytes.Buffer{}
|
|
|
|
for {
|
|
r, _, err := buf.ReadRune()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
switch r {
|
|
case ',', '}':
|
|
buf.UnreadRune()
|
|
return s.String(), nil
|
|
}
|
|
|
|
s.WriteRune(r)
|
|
}
|
|
}
|
|
|
|
func arrayParseQuotedValue(buf *bytes.Buffer) (string, error) {
|
|
s := &bytes.Buffer{}
|
|
|
|
for {
|
|
r, _, err := buf.ReadRune()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
switch r {
|
|
case '\\':
|
|
r, _, err = buf.ReadRune()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
case '"':
|
|
r, _, err = buf.ReadRune()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
buf.UnreadRune()
|
|
return s.String(), 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' {
|
|
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(w io.Writer, dimensions []ArrayDimension) error {
|
|
var customDimensions bool
|
|
for _, dim := range dimensions {
|
|
if dim.LowerBound != 1 {
|
|
customDimensions = true
|
|
}
|
|
}
|
|
|
|
if !customDimensions {
|
|
return nil
|
|
}
|
|
|
|
for _, dim := range dimensions {
|
|
err := pgio.WriteByte(w, '[')
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.WriteString(w, strconv.FormatInt(int64(dim.LowerBound), 10))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = pgio.WriteByte(w, ':')
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.WriteString(w, strconv.FormatInt(int64(dim.LowerBound+dim.Length-1), 10))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = pgio.WriteByte(w, ']')
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return pgio.WriteByte(w, '=')
|
|
}
|