From dc71bedebfa0a5c8709811d88748c4c9e805a847 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Tue, 4 Apr 2017 20:30:04 -0500 Subject: [PATCH] Add pgtype.Polygon --- pgtype/pgtype.go | 1 + pgtype/polygon.go | 186 +++++++++++++++++++++++++++++++++++++++++ pgtype/polygon_test.go | 21 +++++ v3.md | 1 - 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 pgtype/polygon.go create mode 100644 pgtype/polygon_test.go diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 5c8adb6e..cb0cec2c 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -250,6 +250,7 @@ func init() { "oid": &OidValue{}, "path": &Path{}, "point": &Point{}, + "polygon": &Polygon{}, "record": &Record{}, "text": &Text{}, "tid": &Tid{}, diff --git a/pgtype/polygon.go b/pgtype/polygon.go new file mode 100644 index 00000000..1e2df011 --- /dev/null +++ b/pgtype/polygon.go @@ -0,0 +1,186 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + "io" + "math" + "strconv" + "strings" + + "github.com/jackc/pgx/pgio" +) + +type Polygon struct { + P []Vec2 + Status Status +} + +func (dst *Polygon) Set(src interface{}) error { + return fmt.Errorf("cannot convert %v to Polygon", src) +} + +func (dst *Polygon) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Polygon) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *Polygon) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Polygon{Status: Null} + return nil + } + + if len(src) < 7 { + return fmt.Errorf("invalid length for Polygon: %v", len(src)) + } + + 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 = Polygon{P: points, Status: Present} + return nil +} + +func (dst *Polygon) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Polygon{Status: Null} + return nil + } + + if len(src) < 5 { + return fmt.Errorf("invalid length for Polygon: %v", len(src)) + } + + pointCount := int(binary.BigEndian.Uint32(src)) + rp := 4 + + if 4+pointCount*16 != len(src) { + return fmt.Errorf("invalid length for Polygon 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 = Polygon{ + P: points, + Status: Present, + } + return nil +} + +func (src *Polygon) EncodeText(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + if err := pgio.WriteByte(w, '('); 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, ')') + return false, err +} + +func (src *Polygon) EncodeBinary(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + 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 *Polygon) Scan(src interface{}) error { + if src == nil { + *dst = Polygon{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 *Polygon) Value() (driver.Value, error) { + return encodeValueText(src) +} diff --git a/pgtype/polygon_test.go b/pgtype/polygon_test.go new file mode 100644 index 00000000..3a7e1431 --- /dev/null +++ b/pgtype/polygon_test.go @@ -0,0 +1,21 @@ +package pgtype_test + +import ( + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestPolygonTranscode(t *testing.T) { + testSuccessfulTranscode(t, "polygon", []interface{}{ + &pgtype.Polygon{ + P: []pgtype.Vec2{{3.14, 1.678}, {7.1, 5.234}, {5.0, 3.234}}, + Status: pgtype.Present, + }, + &pgtype.Polygon{ + P: []pgtype.Vec2{{3.14, -1.678}, {7.1, -5.234}, {23.1, 9.34}}, + Status: pgtype.Present, + }, + &pgtype.Polygon{Status: pgtype.Null}, + }) +} diff --git a/v3.md b/v3.md index 412d759d..a879e384 100644 --- a/v3.md +++ b/v3.md @@ -68,7 +68,6 @@ something like: select array[1,2,3], array[4,5,6,7] pgtype TODO: -polygon circle macaddr varbit