From c25e3dd82605c0d12a44019bf5fcb444f71fa0b7 Mon Sep 17 00:00:00 2001 From: Manni Wood Date: Thu, 29 Sep 2016 00:25:19 -0400 Subject: [PATCH] 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}}},