diff --git a/README.md b/README.md index 5b82b507..2f8f74e6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/conn.go b/conn.go index 0ca6e7ce..53f84c75 100644 --- a/conn.go +++ b/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 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: diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..a63485d4 --- /dev/null +++ b/doc.go @@ -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 diff --git a/example_custom_type_test.go b/example_custom_type_test.go index 75cc4d8c..c8d8e220 100644 --- a/example_custom_type_test.go +++ b/example_custom_type_test.go @@ -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 { diff --git a/messages.go b/messages.go index fc8de65f..81abace8 100644 --- a/messages.go +++ b/messages.go @@ -56,6 +56,7 @@ type FieldDescription struct { AttributeNumber int16 DataType Oid DataTypeSize int16 + DataTypeName string Modifier int32 FormatCode int16 } diff --git a/values.go b/values.go index 058f1bad..f928b456 100644 --- a/values.go +++ b/values.go @@ -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 }