From d14de1d1fca7afc2ac5d522aa8a171abccc8c744 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Tue, 4 Apr 2017 08:40:41 -0500 Subject: [PATCH] Add path --- pgtype/path.go | 207 ++++++++++++++++++++++++++++++++++++++++++++ pgtype/path_test.go | 28 ++++++ pgtype/pgtype.go | 1 + v3.md | 2 - 4 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 pgtype/path.go create mode 100644 pgtype/path_test.go diff --git a/pgtype/path.go b/pgtype/path.go new file mode 100644 index 00000000..fb4193d9 --- /dev/null +++ b/pgtype/path.go @@ -0,0 +1,207 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + "io" + "math" + "strconv" + "strings" + + "github.com/jackc/pgx/pgio" +) + +type Path struct { + P []Vec2 + Closed bool + Status Status +} + +func (dst *Path) Set(src interface{}) error { + return fmt.Errorf("cannot convert %v to Path", src) +} + +func (dst *Path) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Path) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *Path) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Path{Status: Null} + return nil + } + + if len(src) < 7 { + return fmt.Errorf("invalid length for Path: %v", len(src)) + } + + closed := src[0] == '(' + points := make([]Vec2, 0) + + str := string(src[2:]) + + for { + 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 + } + + points = append(points, Vec2{x, y}) + + if end+3 < len(str) { + str = str[end+3:] + } else { + break + } + } + + *dst = Path{P: points, Closed: closed, Status: Present} + return nil +} + +func (dst *Path) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Path{Status: Null} + return nil + } + + if len(src) < 5 { + return fmt.Errorf("invalid length for Path: %v", len(src)) + } + + closed := src[0] == 1 + pointCount := int(binary.BigEndian.Uint32(src[1:])) + + rp := 5 + + if 5+pointCount*16 != len(src) { + return fmt.Errorf("invalid length for Path with %d points: %v", pointCount, len(src)) + } + + points := make([]Vec2, pointCount) + for i := 0; i < len(points); i++ { + x := binary.BigEndian.Uint64(src[rp:]) + rp += 8 + y := binary.BigEndian.Uint64(src[rp:]) + rp += 8 + points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)} + } + + *dst = Path{ + P: points, + Closed: closed, + Status: Present, + } + return nil +} + +func (src *Path) EncodeText(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + var startByte, endByte byte + if src.Closed { + startByte = '(' + endByte = ')' + } else { + startByte = '[' + endByte = ']' + } + if err := pgio.WriteByte(w, startByte); err != nil { + return false, err + } + + for i, p := range src.P { + if i > 0 { + if err := pgio.WriteByte(w, ','); err != nil { + return false, err + } + } + if _, err := io.WriteString(w, fmt.Sprintf(`(%f,%f)`, p.X, p.Y)); err != nil { + return false, err + } + } + + err := pgio.WriteByte(w, endByte) + return false, err +} + +func (src *Path) EncodeBinary(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + var closeByte byte + if src.Closed { + closeByte = 1 + } + if err := pgio.WriteByte(w, closeByte); err != nil { + return false, err + } + + if _, err := pgio.WriteInt32(w, int32(len(src.P))); err != nil { + return false, err + } + + for _, p := range src.P { + if _, err := pgio.WriteUint64(w, math.Float64bits(p.X)); err != nil { + return false, err + } + + if _, err := pgio.WriteUint64(w, math.Float64bits(p.Y)); err != nil { + return false, err + } + } + + return false, nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *Path) Scan(src interface{}) error { + if src == nil { + *dst = Path{Status: Null} + return nil + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + return dst.DecodeText(nil, src) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src *Path) Value() (driver.Value, error) { + return encodeValueText(src) +} diff --git a/pgtype/path_test.go b/pgtype/path_test.go new file mode 100644 index 00000000..4e5f7f62 --- /dev/null +++ b/pgtype/path_test.go @@ -0,0 +1,28 @@ +package pgtype_test + +import ( + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestPathTranscode(t *testing.T) { + testSuccessfulTranscode(t, "path", []interface{}{ + &pgtype.Path{ + P: []pgtype.Vec2{{3.14, 1.678}, {7.1, 5.234}}, + Closed: false, + Status: pgtype.Present, + }, + &pgtype.Path{ + P: []pgtype.Vec2{{3.14, 1.678}, {7.1, 5.234}, {23.1, 9.34}}, + Closed: true, + Status: pgtype.Present, + }, + &pgtype.Path{ + P: []pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, + Closed: true, + Status: pgtype.Present, + }, + &pgtype.Path{Status: pgtype.Null}, + }) +} diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 6d1f49af..18d21e20 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -248,6 +248,7 @@ func init() { "numeric": &Numeric{}, "numrange": &Numrange{}, "oid": &OidValue{}, + "path": &Path{}, "point": &Point{}, "record": &Record{}, "text": &Text{}, diff --git a/v3.md b/v3.md index 26c86f12..412d759d 100644 --- a/v3.md +++ b/v3.md @@ -68,8 +68,6 @@ something like: select array[1,2,3], array[4,5,6,7] pgtype TODO: -path -path polygon circle macaddr