diff --git a/conn.go b/conn.go index 78f323b1..023b9d97 100644 --- a/conn.go +++ b/conn.go @@ -287,6 +287,7 @@ func (c *Conn) connect(config ConnConfig, network, address string, tlsConfig *tl Int4OID: &pgtype.Int4{}, Int8ArrayOID: &pgtype.Int8Array{}, Int8OID: &pgtype.Int8{}, + NameOID: &pgtype.Name{}, OIDOID: &pgtype.OID{}, TextArrayOID: &pgtype.TextArray{}, TextOID: &pgtype.Text{}, diff --git a/pgtype/name.go b/pgtype/name.go new file mode 100644 index 00000000..3ff81f12 --- /dev/null +++ b/pgtype/name.go @@ -0,0 +1,44 @@ +package pgtype + +import ( + "io" +) + +// 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 Text + +func (dst *Name) ConvertFrom(src interface{}) error { + return (*Text)(dst).ConvertFrom(src) +} + +func (src *Name) AssignTo(dst interface{}) error { + return (*Text)(src).AssignTo(dst) +} + +func (dst *Name) DecodeText(r io.Reader) error { + return (*Text)(dst).DecodeText(r) +} + +func (dst *Name) DecodeBinary(r io.Reader) error { + return (*Text)(dst).DecodeBinary(r) +} + +func (src Name) EncodeText(w io.Writer) error { + return (Text)(src).EncodeText(w) +} + +func (src Name) EncodeBinary(w io.Writer) error { + return (Text)(src).EncodeBinary(w) +} diff --git a/pgtype/name_test.go b/pgtype/name_test.go new file mode 100644 index 00000000..c5f7de17 --- /dev/null +++ b/pgtype/name_test.go @@ -0,0 +1,97 @@ +package pgtype_test + +import ( + "reflect" + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestNameTranscode(t *testing.T) { + testSuccessfulTranscode(t, "name", []interface{}{ + pgtype.Name{String: "", Status: pgtype.Present}, + pgtype.Name{String: "foo", Status: pgtype.Present}, + pgtype.Name{Status: pgtype.Null}, + }) +} + +func TestNameConvertFrom(t *testing.T) { + successfulTests := []struct { + source interface{} + result pgtype.Name + }{ + {source: "foo", result: pgtype.Name{String: "foo", Status: pgtype.Present}}, + {source: _string("bar"), result: pgtype.Name{String: "bar", Status: pgtype.Present}}, + {source: (*string)(nil), result: pgtype.Name{Status: pgtype.Null}}, + } + + for i, tt := range successfulTests { + var d pgtype.Name + err := d.ConvertFrom(tt.source) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if d != tt.result { + t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, d) + } + } +} + +func TestNameAssignTo(t *testing.T) { + var s string + var ps *string + + simpleTests := []struct { + src pgtype.Name + dst interface{} + expected interface{} + }{ + {src: pgtype.Name{String: "foo", Status: pgtype.Present}, dst: &s, expected: "foo"}, + {src: pgtype.Name{Status: pgtype.Null}, dst: &ps, expected: ((*string)(nil))}, + } + + for i, tt := range simpleTests { + err := tt.src.AssignTo(tt.dst) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { + t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) + } + } + + pointerAllocTests := []struct { + src pgtype.Name + dst interface{} + expected interface{} + }{ + {src: pgtype.Name{String: "foo", Status: pgtype.Present}, dst: &ps, expected: "foo"}, + } + + for i, tt := range pointerAllocTests { + err := tt.src.AssignTo(tt.dst) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { + t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) + } + } + + errorTests := []struct { + src pgtype.Name + dst interface{} + }{ + {src: pgtype.Name{Status: pgtype.Null}, dst: &s}, + } + + for i, tt := range errorTests { + err := tt.src.AssignTo(tt.dst) + if err == nil { + t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) + } + } +} diff --git a/values.go b/values.go index 59d6f3c4..8e7ef4ac 100644 --- a/values.go +++ b/values.go @@ -371,57 +371,6 @@ func (n NullAclItem) Encode(w *WriteBuf, oid OID) error { return encodeString(w, oid, string(n.AclItem)) } -// 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, @@ -1002,10 +951,6 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error { // The aclitem 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 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)) default: if strippedArg, ok := stripNamedType(&refVal); ok { return Encode(wbuf, oid, strippedArg) @@ -1078,9 +1023,6 @@ func Decode(vr *ValueReader, d interface{}) error { case *AclItem: // aclitem goes over the wire just like text *v = AclItem(decodeText(vr)) - case *Name: - // name goes over the wire just like text - *v = Name(decodeText(vr)) case *Tid: *v = decodeTid(vr) case *string: diff --git a/values_test.go b/values_test.go index 65811959..0e51effe 100644 --- a/values_test.go +++ b/values_test.go @@ -570,7 +570,6 @@ func TestNullX(t *testing.T) { i32 pgx.NullInt32 c pgx.NullChar a pgx.NullAclItem - n pgx.NullName tid pgx.NullTid i64 pgx.NullInt64 f32 pgx.NullFloat32 @@ -596,8 +595,6 @@ 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.NullName{Name: "foo", Valid: true}}, []interface{}{&actual.n}, allTypes{n: pgx.NullName{Name: "foo", Valid: true}}}, - {"select $1::name", []interface{}{pgx.NullName{Name: "foo", Valid: false}}, []interface{}{&actual.n}, allTypes{n: pgx.NullName{Name: "", Valid: false}}}, {"select $1::aclitem", []interface{}{pgx.NullAclItem{AclItem: "postgres=arwdDxt/postgres", Valid: true}}, []interface{}{&actual.a}, allTypes{a: pgx.NullAclItem{AclItem: "postgres=arwdDxt/postgres", Valid: true}}}, {"select $1::aclitem", []interface{}{pgx.NullAclItem{AclItem: "postgres=arwdDxt/postgres", Valid: false}}, []interface{}{&actual.a}, allTypes{a: pgx.NullAclItem{AclItem: "", Valid: false}}}, // A tricky (and valid) aclitem can still be used, especially with Go's useful backticks