mirror of https://github.com/jackc/pgx.git
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 typespull/37/head
parent
68034af721
commit
8f81acfb5f
|
@ -139,9 +139,7 @@ configure the TLS connection.
|
||||||
pgx includes support for the common data types like integers, floats, strings,
|
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
|
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
|
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
|
direct mappings in Go. See the documentation for more information.
|
||||||
example_custom_type_test.go for an example of a custom type for the PostgreSQL
|
|
||||||
point type.
|
|
||||||
|
|
||||||
### Null Mapping
|
### Null Mapping
|
||||||
|
|
||||||
|
|
45
conn.go
45
conn.go
|
@ -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
|
package pgx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -46,6 +40,7 @@ type Conn struct {
|
||||||
Pid int32 // backend pid
|
Pid int32 // backend pid
|
||||||
SecretKey int32 // key to use to send a cancel query message to the server
|
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
|
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
|
config ConnConfig // config used when establishing this connection
|
||||||
TxStatus byte
|
TxStatus byte
|
||||||
preparedStatements map[string]*PreparedStatement
|
preparedStatements map[string]*PreparedStatement
|
||||||
|
@ -68,6 +63,11 @@ type Notification struct {
|
||||||
Payload string
|
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
|
type CommandTag string
|
||||||
|
|
||||||
// RowsAffected returns the number of rows affected. If the CommandTag was not
|
// 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.rxReadyForQuery(r)
|
||||||
c.logger = &connLogger{logger: c.logger, pid: c.Pid}
|
c.logger = &connLogger{logger: c.logger, pid: c.Pid}
|
||||||
c.logger.Info("Connection established")
|
c.logger.Info("Connection established")
|
||||||
|
|
||||||
|
err = c.loadPgTypes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
default:
|
default:
|
||||||
if err = c.processContextFreeMsg(t, r); err != nil {
|
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
|
// Close closes a connection. It is safe to call Close on a already closed
|
||||||
// connection.
|
// connection.
|
||||||
func (c *Conn) Close() (err error) {
|
func (c *Conn) Close() (err error) {
|
||||||
|
@ -291,7 +320,9 @@ func (c *Conn) Prepare(name, sql string) (ps *PreparedStatement, err error) {
|
||||||
case rowDescription:
|
case rowDescription:
|
||||||
ps.FieldDescriptions = c.rxRowDescription(r)
|
ps.FieldDescriptions = c.rxRowDescription(r)
|
||||||
for i := range ps.FieldDescriptions {
|
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 noData:
|
||||||
case readyForQuery:
|
case readyForQuery:
|
||||||
|
|
|
@ -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
|
|
@ -18,11 +18,9 @@ type NullPoint struct {
|
||||||
Valid bool // Valid is true if not NULL
|
Valid bool // Valid is true if not NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
const pointOid = 600
|
|
||||||
|
|
||||||
func (p *NullPoint) Scan(vr *pgx.ValueReader) error {
|
func (p *NullPoint) Scan(vr *pgx.ValueReader) error {
|
||||||
if vr.Type().DataType != pointOid {
|
if vr.Type().DataTypeName != "point" {
|
||||||
return pgx.SerializationError(fmt.Sprintf("NullPoint.Scan cannot decode OID %d", vr.Type().DataType))
|
return pgx.SerializationError(fmt.Sprintf("NullPoint.Scan cannot decode %s (OID %d)", vr.Type().DataTypeName, vr.Type().DataType))
|
||||||
}
|
}
|
||||||
|
|
||||||
if vr.Len() == -1 {
|
if vr.Len() == -1 {
|
||||||
|
|
|
@ -56,6 +56,7 @@ type FieldDescription struct {
|
||||||
AttributeNumber int16
|
AttributeNumber int16
|
||||||
DataType Oid
|
DataType Oid
|
||||||
DataTypeSize int16
|
DataTypeSize int16
|
||||||
|
DataTypeName string
|
||||||
Modifier int32
|
Modifier int32
|
||||||
FormatCode int16
|
FormatCode int16
|
||||||
}
|
}
|
||||||
|
|
51
values.go
51
values.go
|
@ -39,27 +39,32 @@ const (
|
||||||
BinaryFormatCode = 1
|
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() {
|
func init() {
|
||||||
DefaultOidFormats = make(map[Oid]int16)
|
DefaultTypeFormats = make(map[string]int16)
|
||||||
DefaultOidFormats[BoolOid] = BinaryFormatCode
|
DefaultTypeFormats["_float4"] = BinaryFormatCode
|
||||||
DefaultOidFormats[ByteaOid] = BinaryFormatCode
|
DefaultTypeFormats["_float8"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Int2Oid] = BinaryFormatCode
|
DefaultTypeFormats["_int2"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Int4Oid] = BinaryFormatCode
|
DefaultTypeFormats["_int4"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Int8Oid] = BinaryFormatCode
|
DefaultTypeFormats["_int8"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Float4Oid] = BinaryFormatCode
|
DefaultTypeFormats["_text"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Float8Oid] = BinaryFormatCode
|
DefaultTypeFormats["_varchar"] = BinaryFormatCode
|
||||||
DefaultOidFormats[DateOid] = BinaryFormatCode
|
DefaultTypeFormats["bool"] = BinaryFormatCode
|
||||||
DefaultOidFormats[TimestampTzOid] = BinaryFormatCode
|
DefaultTypeFormats["bytea"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Int2ArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["date"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Int4ArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["float4"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Int8ArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["float8"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Float4ArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["int2"] = BinaryFormatCode
|
||||||
DefaultOidFormats[Float8ArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["int4"] = BinaryFormatCode
|
||||||
DefaultOidFormats[TextArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["int8"] = BinaryFormatCode
|
||||||
DefaultOidFormats[VarcharArrayOid] = BinaryFormatCode
|
DefaultTypeFormats["oid"] = BinaryFormatCode
|
||||||
DefaultOidFormats[OidOid] = BinaryFormatCode
|
DefaultTypeFormats["timestamptz"] = BinaryFormatCode
|
||||||
}
|
}
|
||||||
|
|
||||||
type SerializationError string
|
type SerializationError string
|
||||||
|
@ -70,9 +75,11 @@ func (e SerializationError) Error() string {
|
||||||
|
|
||||||
// Scanner is an interface used to decode values from the PostgreSQL server.
|
// Scanner is an interface used to decode values from the PostgreSQL server.
|
||||||
type Scanner interface {
|
type Scanner interface {
|
||||||
// Scan MUST check r.Type().DataType and r.Type().FormatCode before decoding.
|
// Scan MUST check r.Type().DataType (to check by OID) or
|
||||||
// It should not assume that it was called on a data type or format that it
|
// r.Type().DataTypeName (to check by name) to ensure that it is scanning an
|
||||||
// understands.
|
// 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
|
Scan(r *ValueReader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue