From c25e3dd82605c0d12a44019bf5fcb444f71fa0b7 Mon Sep 17 00:00:00 2001 From: Manni Wood Date: Thu, 29 Sep 2016 00:25:19 -0400 Subject: [PATCH 1/3] Adds Name/NullName types --- values.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ values_test.go | 28 ++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/values.go b/values.go index 545f1031..ac472c51 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 @@ -65,6 +66,12 @@ const maxUint = ^uint(0) const maxInt = int(maxUint >> 1) const minInt = -maxInt - 1 +// NameDataLen is the same as PostgreSQL's NAMEDATALEN, defined in +// src/include/pg_config_manual.h. It is how many bytes long identifiers +// are allowed to be, including the trailing '\0' at the end of C strings. +// (Identifieres are table names, column names, function names, etc.) +const NameDataLen = 64 + // DefaultTypeFormats maps type names to their default requested format (text // or binary). In theory the Scanner interface should be the one to determine // the format of the returned values. However, the query has already been @@ -91,6 +98,7 @@ func init() { "bool": BinaryFormatCode, "bytea": BinaryFormatCode, "char": BinaryFormatCode, + "name": BinaryFormatCode, "cidr": BinaryFormatCode, "date": BinaryFormatCode, "float4": BinaryFormatCode, @@ -259,6 +267,55 @@ func (n NullString) Encode(w *WriteBuf, oid Oid) error { return encodeString(w, oid, n.String) } +// The pgx.Name type is for PostgreSQL's special 63-byte +// name data type, used for identifiers like table names. +type Name string + +// LengthOK is a convenience method that returns false if a name is longer +// than PostgreSQL will allow. PostgreSQL identifiers are allowed +// to be 63 bytes long (NAMEDATALEN in the PostgreSQL source code +// is defined as 64 bytes long, but the 64th char is the '\0' C +// string terminator.) +func (n Name) LengthOK() bool { + return len(string(n)) < NameDataLen +} + +// 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 Char 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, @@ -906,6 +963,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: @@ -1084,6 +1145,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 7de4e0c2..0deb3982 100644 --- a/values_test.go +++ b/values_test.go @@ -551,6 +551,28 @@ func TestInetCidrTranscodeWithJustIP(t *testing.T) { } } +func TestNameLengthOK(t *testing.T) { + tests := []struct { + input pgx.Name + expected bool + }{ + {"", true}, + {"1234", true}, + {"123456789012345678901234567890123456789012345678901234567890123", true}, + {"1234567890123456789012345678901234567890123456789012345678901234", false}, + } + + var actual bool + + for i, tt := range tests { + actual = tt.input.LengthOK() + + if actual != tt.expected { + t.Errorf("%d. Expected %v, got %v (name -> %v)", i, tt.expected, actual, tt.input) + } + } +} + func TestNullX(t *testing.T) { t.Parallel() @@ -562,6 +584,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 +619,11 @@ 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}}}, + // bytes past NameDataLen-1 (63 bytes) get silently truncated by PostgreSQL + {"select $1::name", []interface{}{pgx.NullString{String: "1234567890123456789012345678901234567890123456789012345678901234", Valid: true}}, + []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "123456789012345678901234567890123456789012345678901234567890123", 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}}}, From ca96431b5e3a3431212e405c37e40cd9bc3989cc Mon Sep 17 00:00:00 2001 From: Manni Wood Date: Thu, 29 Sep 2016 00:36:56 -0400 Subject: [PATCH 2/3] Fixes a documentation typo --- values.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/values.go b/values.go index ac472c51..d0940421 100644 --- a/values.go +++ b/values.go @@ -287,7 +287,7 @@ func (n Name) LengthOK() bool { // If Valid is false then the value is NULL. type NullName struct { Name Name - Valid bool // Valid is true if Char is not NULL + Valid bool // Valid is true if Name is not NULL } func (n *NullName) Scan(vr *ValueReader) error { From c8575984d842fe3f99205f166e3429dbe37b8a24 Mon Sep 17 00:00:00 2001 From: Manni Wood Date: Sat, 1 Oct 2016 13:46:48 -0400 Subject: [PATCH 3/3] Removes name length convenience method --- values.go | 28 ++++++++++++---------------- values_test.go | 25 ------------------------- 2 files changed, 12 insertions(+), 41 deletions(-) diff --git a/values.go b/values.go index d0940421..45f3e9b0 100644 --- a/values.go +++ b/values.go @@ -66,12 +66,6 @@ const maxUint = ^uint(0) const maxInt = int(maxUint >> 1) const minInt = -maxInt - 1 -// NameDataLen is the same as PostgreSQL's NAMEDATALEN, defined in -// src/include/pg_config_manual.h. It is how many bytes long identifiers -// are allowed to be, including the trailing '\0' at the end of C strings. -// (Identifieres are table names, column names, function names, etc.) -const NameDataLen = 64 - // DefaultTypeFormats maps type names to their default requested format (text // or binary). In theory the Scanner interface should be the one to determine // the format of the returned values. However, the query has already been @@ -267,19 +261,21 @@ func (n NullString) Encode(w *WriteBuf, oid Oid) error { return encodeString(w, oid, n.String) } -// The pgx.Name type is for PostgreSQL's special 63-byte +// 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 -// LengthOK is a convenience method that returns false if a name is longer -// than PostgreSQL will allow. PostgreSQL identifiers are allowed -// to be 63 bytes long (NAMEDATALEN in the PostgreSQL source code -// is defined as 64 bytes long, but the 64th char is the '\0' C -// string terminator.) -func (n Name) LengthOK() bool { - return len(string(n)) < NameDataLen -} - // 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. diff --git a/values_test.go b/values_test.go index 0deb3982..6a92ee30 100644 --- a/values_test.go +++ b/values_test.go @@ -551,28 +551,6 @@ func TestInetCidrTranscodeWithJustIP(t *testing.T) { } } -func TestNameLengthOK(t *testing.T) { - tests := []struct { - input pgx.Name - expected bool - }{ - {"", true}, - {"1234", true}, - {"123456789012345678901234567890123456789012345678901234567890123", true}, - {"1234567890123456789012345678901234567890123456789012345678901234", false}, - } - - var actual bool - - for i, tt := range tests { - actual = tt.input.LengthOK() - - if actual != tt.expected { - t.Errorf("%d. Expected %v, got %v (name -> %v)", i, tt.expected, actual, tt.input) - } - } -} - func TestNullX(t *testing.T) { t.Parallel() @@ -621,9 +599,6 @@ func TestNullX(t *testing.T) { {"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}}}, - // bytes past NameDataLen-1 (63 bytes) get silently truncated by PostgreSQL - {"select $1::name", []interface{}{pgx.NullString{String: "1234567890123456789012345678901234567890123456789012345678901234", Valid: true}}, - []interface{}{&actual.s}, allTypes{s: pgx.NullString{String: "123456789012345678901234567890123456789012345678901234567890123", 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}}},