diff --git a/values.go b/values.go index 31f754ef..f2e43473 100644 --- a/values.go +++ b/values.go @@ -19,6 +19,7 @@ const ( BoolOid = 16 ByteaOid = 17 CharOid = 18 + NameOid = 19 Int8Oid = 20 Int2Oid = 21 Int4Oid = 23 @@ -102,6 +103,7 @@ func init() { "int2": BinaryFormatCode, "int4": BinaryFormatCode, "int8": BinaryFormatCode, + "name": BinaryFormatCode, "oid": BinaryFormatCode, "record": BinaryFormatCode, "text": BinaryFormatCode, @@ -261,6 +263,57 @@ func (n NullString) Encode(w *WriteBuf, oid Oid) error { return encodeString(w, oid, n.String) } +// Name is a type used for PostgreSQL's special 63-byte +// name data type, used for identifiers like table names. +// The pg_class.relname column is a good example of where the +// name data type is used. +// +// Note that the underlying Go data type of pgx.Name is string, +// so there is no way to enforce the 63-byte length. Inputting +// a longer name into PostgreSQL will result in silent truncation +// to 63 bytes. +// +// Also, if you have custom-compiled PostgreSQL and set +// NAMEDATALEN to a different value, obviously that number of +// bytes applies, rather than the default 63. +type Name string + +// NullName represents a pgx.Name that may be null. NullName 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 NullName struct { + Name Name + Valid bool // Valid is true if Name is not NULL +} + +func (n *NullName) Scan(vr *ValueReader) error { + if vr.Type().DataType != NameOid { + return SerializationError(fmt.Sprintf("NullName.Scan cannot decode OID %d", vr.Type().DataType)) + } + + if vr.Len() == -1 { + n.Name, n.Valid = "", false + return nil + } + + n.Valid = true + n.Name = Name(decodeText(vr)) + return vr.Err() +} + +func (n NullName) FormatCode() int16 { return TextFormatCode } + +func (n NullName) Encode(w *WriteBuf, oid Oid) error { + if !n.Valid { + w.WriteInt32(-1) + return nil + } + + return encodeString(w, oid, string(n.Name)) +} + // 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", in double-quotes, @@ -911,6 +964,10 @@ func Encode(wbuf *WriteBuf, oid Oid, arg interface{}) error { return encodeUInt(wbuf, oid, arg) case Char: return encodeChar(wbuf, oid, arg) + case Name: + // The name data type goes over the wire using the same format as string, + // so just cast to string and use encodeString + return encodeString(wbuf, oid, string(arg)) case int8: return encodeInt8(wbuf, oid, arg) case uint8: @@ -1089,6 +1146,9 @@ func Decode(vr *ValueReader, d interface{}) error { *v = uint64(n) case *Char: *v = decodeChar(vr) + case *Name: + // name goes over the wire just like text + *v = Name(decodeText(vr)) case *Oid: *v = decodeOid(vr) case *Xid: diff --git a/values_test.go b/values_test.go index 9c5dbfb2..fc5a58b3 100644 --- a/values_test.go +++ b/values_test.go @@ -562,6 +562,7 @@ func TestNullX(t *testing.T) { i16 pgx.NullInt16 i32 pgx.NullInt32 c pgx.NullChar + n pgx.NullName oid pgx.NullOid xid pgx.NullXid cid pgx.NullCid @@ -596,6 +597,8 @@ func TestNullX(t *testing.T) { {"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::name", []interface{}{pgx.NullString{String: "foo", Valid: true}}, []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "foo", Valid: true}}}, + {"select $1::name", []interface{}{pgx.NullString{String: "foo", Valid: false}}, []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "", Valid: false}}}, {"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}}},