diff --git a/pgtype/box.go b/pgtype/box.go new file mode 100644 index 00000000..eaaddbff --- /dev/null +++ b/pgtype/box.go @@ -0,0 +1,168 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + "io" + "math" + "strconv" + "strings" + + "github.com/jackc/pgx/pgio" +) + +type Box struct { + Corners [2]Vec2 + Status Status +} + +func (dst *Box) Set(src interface{}) error { + return fmt.Errorf("cannot convert %v to Box", src) +} + +func (dst *Box) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +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{Status: Null} + 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{Corners: [2]Vec2{{x1, y1}, {x2, y2}}, Status: Present} + return nil +} + +func (dst *Box) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Box{Status: Null} + 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{ + Corners: [2]Vec2{ + {math.Float64frombits(x1), math.Float64frombits(y1)}, + {math.Float64frombits(x2), math.Float64frombits(y2)}, + }, + Status: Present, + } + return nil +} + +func (src *Box) EncodeText(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + _, err := io.WriteString(w, fmt.Sprintf(`(%f,%f),(%f,%f)`, + src.Corners[0].X, src.Corners[0].Y, src.Corners[1].X, src.Corners[1].Y)) + return false, err +} + +func (src *Box) EncodeBinary(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + if _, err := pgio.WriteUint64(w, math.Float64bits(src.Corners[0].X)); err != nil { + return false, err + } + + if _, err := pgio.WriteUint64(w, math.Float64bits(src.Corners[0].Y)); err != nil { + return false, err + } + + if _, err := pgio.WriteUint64(w, math.Float64bits(src.Corners[1].X)); err != nil { + return false, err + } + + _, err := pgio.WriteUint64(w, math.Float64bits(src.Corners[1].Y)) + return false, err +} + +// Scan implements the database/sql Scanner interface. +func (dst *Box) Scan(src interface{}) error { + if src == nil { + *dst = Box{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 *Box) Value() (driver.Value, error) { + return encodeValueText(src) +} diff --git a/pgtype/box_test.go b/pgtype/box_test.go new file mode 100644 index 00000000..21446dc3 --- /dev/null +++ b/pgtype/box_test.go @@ -0,0 +1,33 @@ +package pgtype_test + +import ( + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestBoxTranscode(t *testing.T) { + testSuccessfulTranscode(t, "box", []interface{}{ + &pgtype.Box{ + Corners: [2]pgtype.Vec2{{7.1, 5.234}, {3.14, 1.678}}, + Status: pgtype.Present, + }, + &pgtype.Box{ + Corners: [2]pgtype.Vec2{{7.1, 1.678}, {-13.14, -5.234}}, + Status: pgtype.Present, + }, + &pgtype.Box{Status: pgtype.Null}, + }) +} + +func TestBoxNormalize(t *testing.T) { + testSuccessfulNormalize(t, []normalizeTest{ + { + sql: "select '3.14, 1.678, 7.1, 5.234'::box", + value: &pgtype.Box{ + Corners: [2]pgtype.Vec2{{7.1, 5.234}, {3.14, 1.678}}, + Status: pgtype.Present, + }, + }, + }) +} diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 911ab70e..b29bc90c 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -223,6 +223,7 @@ func init() { "_varchar": &VarcharArray{}, "aclitem": &Aclitem{}, "bool": &Bool{}, + "box": &Box{}, "bytea": &Bytea{}, "char": &QChar{}, "cid": &Cid{}, diff --git a/pgtype/point.go b/pgtype/point.go index 1b40bc44..94f753e3 100644 --- a/pgtype/point.go +++ b/pgtype/point.go @@ -12,9 +12,13 @@ import ( "github.com/jackc/pgx/pgio" ) +type Vec2 struct { + X float64 + Y float64 +} + type Point struct { - X float64 - Y float64 + Vec2 Status Status } @@ -62,7 +66,7 @@ func (dst *Point) DecodeText(ci *ConnInfo, src []byte) error { return err } - *dst = Point{X: x, Y: y, Status: Present} + *dst = Point{Vec2: Vec2{x, y}, Status: Present} return nil } @@ -80,8 +84,7 @@ func (dst *Point) DecodeBinary(ci *ConnInfo, src []byte) error { y := binary.BigEndian.Uint64(src[8:]) *dst = Point{ - X: math.Float64frombits(x), - Y: math.Float64frombits(y), + Vec2: Vec2{math.Float64frombits(x), math.Float64frombits(y)}, Status: Present, } return nil diff --git a/pgtype/point_test.go b/pgtype/point_test.go index 4ddb8009..723dfa60 100644 --- a/pgtype/point_test.go +++ b/pgtype/point_test.go @@ -8,8 +8,8 @@ import ( func TestPointTranscode(t *testing.T) { testSuccessfulTranscode(t, "point", []interface{}{ - &pgtype.Point{X: 1.234, Y: 5.6789, Status: pgtype.Present}, - &pgtype.Point{X: -1.234, Y: -5.6789, Status: pgtype.Present}, + &pgtype.Point{Vec2: pgtype.Vec2{1.234, 5.6789}, Status: pgtype.Present}, + &pgtype.Point{Vec2: pgtype.Vec2{-1.234, -5.6789}, Status: pgtype.Present}, &pgtype.Point{Status: pgtype.Null}, }) } diff --git a/v3.md b/v3.md index 70a378ad..1930508f 100644 --- a/v3.md +++ b/v3.md @@ -70,7 +70,6 @@ select array[1,2,3], array[4,5,6,7] pgtype TODO: line lseg -box path path polygon