From 818dcbf2b664379261d49ce1569844fc680988d8 Mon Sep 17 00:00:00 2001 From: Manni Wood Date: Sat, 17 Sep 2016 23:11:59 -0400 Subject: [PATCH] Adds "char" type --- values.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++- values_test.go | 4 +++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/values.go b/values.go index ae7b7cf7..ffc1ceab 100644 --- a/values.go +++ b/values.go @@ -18,6 +18,7 @@ import ( const ( BoolOid = 16 ByteaOid = 17 + CharOid = 18 Int8Oid = 20 Int2Oid = 21 Int4Oid = 23 @@ -88,6 +89,7 @@ func init() { "_varchar": BinaryFormatCode, "bool": BinaryFormatCode, "bytea": BinaryFormatCode, + "char": BinaryFormatCode, "cidr": BinaryFormatCode, "date": BinaryFormatCode, "float4": BinaryFormatCode, @@ -256,7 +258,53 @@ func (n NullString) Encode(w *WriteBuf, oid Oid) error { return encodeString(w, oid, n.String) } -// NullInt16 represents an smallint that may be null. NullInt16 implements the +// The pgx.Char type is for PostgreSQL's special 8-bit-only +// "char" type more akin to the C language's char type, or Go's byte type. +// (Note that the name in PostgreSQL itself is "char" and not char.) +// It gets used a lot +// in PostgreSQL's system tables to hold a single ASCII character value. +type Char byte + +// NullChar represents a pgx.Char that may be null. NullChar implements the +// Scanner and Encoder interfaces so it may be used both as an argument to +// Query[Row] and a destination for Scan for prepared and unprepared queries. +// +// If Valid is false then the value is NULL. +type NullChar struct { + Char Char + Valid bool // Valid is true if Char is not NULL +} + +func (n *NullChar) Scan(vr *ValueReader) error { + if vr.Type().DataType != CharOid { + return SerializationError(fmt.Sprintf("NullChar.Scan cannot decode OID %d", vr.Type().DataType)) + } + + if vr.Len() == -1 { + n.Char, n.Valid = 0, false + return nil + } + n.Valid = true + n.Char = decodeChar(vr) + return vr.Err() +} + +func (n NullChar) FormatCode() int16 { return BinaryFormatCode } + +func (n NullChar) Encode(w *WriteBuf, oid Oid) error { + if oid != CharOid { + return SerializationError(fmt.Sprintf("NullChar.Encode cannot encode into OID %d", oid)) + } + + if !n.Valid { + w.WriteInt32(-1) + return nil + } + + return encodeChar(w, oid, n.Char) +} + +// NullInt16 represents a smallint that may be null. NullInt16 implements the // Scanner and Encoder interfaces so it may be used both as an argument to // Query[Row] and a destination for Scan for prepared and unprepared queries. // @@ -810,6 +858,8 @@ func Encode(wbuf *WriteBuf, oid Oid, arg interface{}) error { return encodeInt(wbuf, oid, arg) case uint: return encodeUInt(wbuf, oid, arg) + case Char: + return encodeChar(wbuf, oid, arg) case int8: return encodeInt8(wbuf, oid, arg) case uint8: @@ -986,6 +1036,8 @@ func Decode(vr *ValueReader, d interface{}) error { return fmt.Errorf("%d is less than zero for uint64", n) } *v = uint64(n) + case *Char: + *v = decodeChar(vr) case *Oid: *v = decodeOid(vr) case *Xid: @@ -1185,6 +1237,30 @@ func decodeInt8(vr *ValueReader) int64 { return vr.ReadInt64() } +func decodeChar(vr *ValueReader) Char { + if vr.Len() == -1 { + vr.Fatal(ProtocolError("Cannot decode null into char")) + return Char(0) + } + + if vr.Type().DataType != CharOid { + vr.Fatal(ProtocolError(fmt.Sprintf("Cannot decode oid %v into char", vr.Type().DataType))) + return Char(0) + } + + if vr.Type().FormatCode != BinaryFormatCode { + vr.Fatal(ProtocolError(fmt.Sprintf("Unknown field description format code: %v", vr.Type().FormatCode))) + return Char(0) + } + + if vr.Len() != 1 { + vr.Fatal(ProtocolError(fmt.Sprintf("Received an invalid size for a char: %d", vr.Len()))) + return Char(0) + } + + return Char(vr.ReadByte()) +} + func decodeInt2(vr *ValueReader) int16 { if vr.Len() == -1 { vr.Fatal(ProtocolError("Cannot decode null into int16")) @@ -1270,6 +1346,12 @@ func encodeUInt(w *WriteBuf, oid Oid, value uint) error { return nil } +func encodeChar(w *WriteBuf, oid Oid, value Char) error { + w.WriteInt32(1) + w.WriteByte(byte(value)) + return nil +} + func encodeInt8(w *WriteBuf, oid Oid, value int8) error { switch oid { case Int2Oid: diff --git a/values_test.go b/values_test.go index cea70b9c..10ef5de4 100644 --- a/values_test.go +++ b/values_test.go @@ -594,6 +594,7 @@ func TestNullX(t *testing.T) { s pgx.NullString i16 pgx.NullInt16 i32 pgx.NullInt32 + c pgx.NullChar xid pgx.NullXid cid pgx.NullCid tid pgx.NullTid @@ -621,6 +622,9 @@ func TestNullX(t *testing.T) { {"select $1::xid", []interface{}{pgx.NullXid{Xid: 1, Valid: true}}, []interface{}{&actual.xid}, allTypes{xid: pgx.NullXid{Xid: 1, Valid: true}}}, {"select $1::xid", []interface{}{pgx.NullXid{Xid: 1, Valid: false}}, []interface{}{&actual.xid}, allTypes{xid: pgx.NullXid{Xid: 0, Valid: false}}}, {"select $1::xid", []interface{}{pgx.NullXid{Xid: 4294967295, Valid: true}}, []interface{}{&actual.xid}, allTypes{xid: pgx.NullXid{Xid: 4294967295, Valid: true}}}, + {"select $1::\"char\"", []interface{}{pgx.NullChar{Char: 1, Valid: true}}, []interface{}{&actual.c}, allTypes{c: pgx.NullChar{Char: 1, Valid: true}}}, + {"select $1::\"char\"", []interface{}{pgx.NullChar{Char: 1, Valid: false}}, []interface{}{&actual.c}, allTypes{c: pgx.NullChar{Char: 0, Valid: false}}}, + {"select $1::\"char\"", []interface{}{pgx.NullChar{Char: 255, Valid: true}}, []interface{}{&actual.c}, allTypes{c: pgx.NullChar{Char: 255, Valid: true}}}, {"select $1::cid", []interface{}{pgx.NullCid{Cid: 1, Valid: true}}, []interface{}{&actual.cid}, allTypes{cid: pgx.NullCid{Cid: 1, Valid: true}}}, {"select $1::cid", []interface{}{pgx.NullCid{Cid: 1, Valid: false}}, []interface{}{&actual.cid}, allTypes{cid: pgx.NullCid{Cid: 0, Valid: false}}}, {"select $1::cid", []interface{}{pgx.NullCid{Cid: 4294967295, Valid: true}}, []interface{}{&actual.cid}, allTypes{cid: pgx.NullCid{Cid: 4294967295, Valid: true}}},