Enhance support for custom types

* Add per connection oid to name map
* Global default type format is now based on names not oids
* Add better docs for custom types
pull/37/head
Jack Christensen 2014-09-13 11:33:34 -05:00
parent 68034af721
commit 8f81acfb5f
6 changed files with 104 additions and 36 deletions

View File

@ -139,9 +139,7 @@ configure the TLS connection.
pgx includes support for the common data types like integers, floats, strings,
dates, and times that have direct mappings between Go and SQL. Support can be
added for additional types like point, hstore, numeric, etc. that do not have
direct mappings in Go by the types implementing Scanner and Encoder. See
example_custom_type_test.go for an example of a custom type for the PostgreSQL
point type.
direct mappings in Go. See the documentation for more information.
### Null Mapping

45
conn.go
View File

@ -1,9 +1,3 @@
// Package pgx is a PostgreSQL database driver.
//
// pgx provides lower level access to PostgreSQL than the standard database/sql
// It remains as similar to the database/sql interface as possible while
// providing better speed and access to PostgreSQL specific features. Import
// github.com/jack/pgx/stdlib to use pgx as a database/sql compatible driver.
package pgx
import (
@ -46,6 +40,7 @@ type Conn struct {
Pid int32 // backend pid
SecretKey int32 // key to use to send a cancel query message to the server
RuntimeParams map[string]string // parameters that have been reported by the server
PgTypes map[Oid]PgType // oids to PgTypes
config ConnConfig // config used when establishing this connection
TxStatus byte
preparedStatements map[string]*PreparedStatement
@ -68,6 +63,11 @@ type Notification struct {
Payload string
}
type PgType struct {
Name string // name of type e.g. int4, text, date
DefaultFormat int16 // default format (text or binary) this type will be requested in
}
type CommandTag string
// RowsAffected returns the number of rows affected. If the CommandTag was not
@ -189,6 +189,12 @@ func Connect(config ConnConfig) (c *Conn, err error) {
c.rxReadyForQuery(r)
c.logger = &connLogger{logger: c.logger, pid: c.Pid}
c.logger.Info("Connection established")
err = c.loadPgTypes()
if err != nil {
return nil, err
}
return c, nil
default:
if err = c.processContextFreeMsg(t, r); err != nil {
@ -198,6 +204,29 @@ func Connect(config ConnConfig) (c *Conn, err error) {
}
}
func (c *Conn) loadPgTypes() error {
rows, err := c.Query("select t.oid, t.typname from pg_type t where t.typtype='b'")
if err != nil {
return err
}
c.PgTypes = make(map[Oid]PgType, 128)
for rows.Next() {
var oid Oid
var t PgType
rows.Scan(&oid, &t.Name)
// The zero value is text format so we ignore any types without a default type format
t.DefaultFormat, _ = DefaultTypeFormats[t.Name]
c.PgTypes[oid] = t
}
return rows.Err()
}
// Close closes a connection. It is safe to call Close on a already closed
// connection.
func (c *Conn) Close() (err error) {
@ -291,7 +320,9 @@ func (c *Conn) Prepare(name, sql string) (ps *PreparedStatement, err error) {
case rowDescription:
ps.FieldDescriptions = c.rxRowDescription(r)
for i := range ps.FieldDescriptions {
ps.FieldDescriptions[i].FormatCode, _ = DefaultOidFormats[ps.FieldDescriptions[i].DataType]
t, _ := c.PgTypes[ps.FieldDescriptions[i].DataType]
ps.FieldDescriptions[i].DataTypeName = t.Name
ps.FieldDescriptions[i].FormatCode = t.DefaultFormat
}
case noData:
case readyForQuery:

33
doc.go Normal file
View File

@ -0,0 +1,33 @@
// Package pgx is a PostgreSQL database driver.
/*
pgx provides lower level access to PostgreSQL than the standard database/sql
It remains as similar to the database/sql interface as possible while
providing better speed and access to PostgreSQL specific features. Import
github.com/jack/pgx/stdlib to use pgx as a database/sql compatible driver.
Custom Type Support
pgx includes support for the common data types like integers, floats, strings,
dates, and times that have direct mappings between Go and SQL. Support can be
added for additional types like point, hstore, numeric, etc. that do not have
direct mappings in Go by the types implementing Scanner and Encoder.
Custom types can support text or binary formats. Binary format can provide a
large performance increase. The natural place for deciding the format for a
value would be in Scanner as it is responsible for decoding the returned data.
However, that is impossible as the query has already been sent by the time the
Scanner is invoked. The solution to this is the global DefaultTypeFormats. If a
custom type prefers binary format it should register it there.
pgx.DefaultTypeFormats["point"] = pgx.BinaryFormatCode
Note that the type is referred to by name, not by OID. This is because custom
PostgreSQL types like hstore will have different OIDs on different servers. When
pgx establishes a connection it queries the pg_type table for all types. It then
matches the names in DefaultTypeFormats with the returned OIDs and stores it in
Conn.PgTypes.
See example_custom_type_test.go for an example of a custom type for the
PostgreSQL point type.
*/
package pgx

View File

@ -18,11 +18,9 @@ type NullPoint struct {
Valid bool // Valid is true if not NULL
}
const pointOid = 600
func (p *NullPoint) Scan(vr *pgx.ValueReader) error {
if vr.Type().DataType != pointOid {
return pgx.SerializationError(fmt.Sprintf("NullPoint.Scan cannot decode OID %d", vr.Type().DataType))
if vr.Type().DataTypeName != "point" {
return pgx.SerializationError(fmt.Sprintf("NullPoint.Scan cannot decode %s (OID %d)", vr.Type().DataTypeName, vr.Type().DataType))
}
if vr.Len() == -1 {

View File

@ -56,6 +56,7 @@ type FieldDescription struct {
AttributeNumber int16
DataType Oid
DataTypeSize int16
DataTypeName string
Modifier int32
FormatCode int16
}

View File

@ -39,27 +39,32 @@ const (
BinaryFormatCode = 1
)
var DefaultOidFormats map[Oid]int16
// 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
// executed by the time Scan is called so it has no chance to set the format.
// So for types that should be returned in binary th
var DefaultTypeFormats map[string]int16
func init() {
DefaultOidFormats = make(map[Oid]int16)
DefaultOidFormats[BoolOid] = BinaryFormatCode
DefaultOidFormats[ByteaOid] = BinaryFormatCode
DefaultOidFormats[Int2Oid] = BinaryFormatCode
DefaultOidFormats[Int4Oid] = BinaryFormatCode
DefaultOidFormats[Int8Oid] = BinaryFormatCode
DefaultOidFormats[Float4Oid] = BinaryFormatCode
DefaultOidFormats[Float8Oid] = BinaryFormatCode
DefaultOidFormats[DateOid] = BinaryFormatCode
DefaultOidFormats[TimestampTzOid] = BinaryFormatCode
DefaultOidFormats[Int2ArrayOid] = BinaryFormatCode
DefaultOidFormats[Int4ArrayOid] = BinaryFormatCode
DefaultOidFormats[Int8ArrayOid] = BinaryFormatCode
DefaultOidFormats[Float4ArrayOid] = BinaryFormatCode
DefaultOidFormats[Float8ArrayOid] = BinaryFormatCode
DefaultOidFormats[TextArrayOid] = BinaryFormatCode
DefaultOidFormats[VarcharArrayOid] = BinaryFormatCode
DefaultOidFormats[OidOid] = BinaryFormatCode
DefaultTypeFormats = make(map[string]int16)
DefaultTypeFormats["_float4"] = BinaryFormatCode
DefaultTypeFormats["_float8"] = BinaryFormatCode
DefaultTypeFormats["_int2"] = BinaryFormatCode
DefaultTypeFormats["_int4"] = BinaryFormatCode
DefaultTypeFormats["_int8"] = BinaryFormatCode
DefaultTypeFormats["_text"] = BinaryFormatCode
DefaultTypeFormats["_varchar"] = BinaryFormatCode
DefaultTypeFormats["bool"] = BinaryFormatCode
DefaultTypeFormats["bytea"] = BinaryFormatCode
DefaultTypeFormats["date"] = BinaryFormatCode
DefaultTypeFormats["float4"] = BinaryFormatCode
DefaultTypeFormats["float8"] = BinaryFormatCode
DefaultTypeFormats["int2"] = BinaryFormatCode
DefaultTypeFormats["int4"] = BinaryFormatCode
DefaultTypeFormats["int8"] = BinaryFormatCode
DefaultTypeFormats["oid"] = BinaryFormatCode
DefaultTypeFormats["timestamptz"] = BinaryFormatCode
}
type SerializationError string
@ -70,9 +75,11 @@ func (e SerializationError) Error() string {
// Scanner is an interface used to decode values from the PostgreSQL server.
type Scanner interface {
// Scan MUST check r.Type().DataType and r.Type().FormatCode before decoding.
// It should not assume that it was called on a data type or format that it
// understands.
// Scan MUST check r.Type().DataType (to check by OID) or
// r.Type().DataTypeName (to check by name) to ensure that it is scanning an
// expected column type. It also MUST check r.Type().FormatCode before
// decoding. It should not assume that it was called on a data type or format
// that it understands.
Scan(r *ValueReader) error
}