Skip to content

Latest commit

 

History

History
94 lines (73 loc) · 4.13 KB

pluggable_types.md

File metadata and controls

94 lines (73 loc) · 4.13 KB

Pluggable types

It's possible to make a custom datatype encoder/decoder as well as to change encoding/decoding of existing supported datatype. You can't have specific decoding rules for specific column or for specific query. Codec update affects any occurrence of this datatype for this connection.

Possible usecases

  • Decode JSON inside epgsql
  • Change datetime representation
  • Add support for standard datatype that isn't supported by epgsql yet
  • Add support for contrib datatypes
  • Add codecs for your own custom datatypes (eg implemented on C level or created by CREATE TYPE)

This can be done by following steps

Implement epgsql_codec behaviour callback module

See epgsql_codec

This module should have following functions exported:

init(any(), epgsql_sock:pg_sock()) -> codec_state().

Will be called only once on connection setup or when update_type_cache/2 is called. Should initialize codec's internal state (if needed). This state will be passed as 1st argument to other callbacks later.

names() -> [epgsql:type_name()].

Will be called immediately after init. It should return list of postgresql type names this codec is able to handle. Names should be the same as in column typname of pg_type table.

encode(Data :: any(), epgsql:type_name(), codec_state()) -> iodata().

Will be called when parameter of matching type is passed to equery or bind etc. 2nd argument is the name of matching type (useful when names/0 returns more than one name). It should convert data to iolist / binary in a postgresql binary format representation. Postgresql binary format usually not documented, so you most likely end up checking postgresql source code. TIP: it's usually more efficient to encode data as iolist, because in that case it will be written directly to socket without any extra copying. So, you don't need to call iolist_to_binary/1 on your data before returning it from this function.

decode(Data :: binary(), epgsql:type_name(), codec_state()) -> any()

If equery or execute returns a dataset that has columns of matching type, this function will be called for each "cell" of this type. It should parse postgresql binary format and return appropriate erlang representation.

decode_text(Data :: binary(), epgsql:type_name(), codec_state()) -> any().

Optional callback. Will be called (if defined) in the same situation as decode/3, but for squery command results and data will be in postgresql text, not binary representation. By default epgsql will just return it as is.

It would be nice to also define and export in_data(), out_data() and data() typespecs.

Example: if your codec's names/0 returns [my_type, my_other_type] and following command was executed:

epgsql:equery(C, "SELECT $1::my_type, $1::my_type", [my_value])

Then encode(my_value, my_type, codec_state()) will be called (only once). And, since we are doing select of a 2 values of type my_type, callback decode(binary(), my_type, codec_state()) will be called 2 times.

Load this codec into epgsql

It can be done by calling epgsql:update_type_cache(Connection, [{CallbackModuleName, InitOptions}]) or by providing {codecs, [{CallbackModuleName, InitOptions}]} connect option.

You may define new datatypes as well as redefine already supported ones.

Tips

  • When you just want to slightly change default decoding/encoding, it may be easier to emulate inheritance by calling default codec's functions and then modifying what they return
  • Again, try to return iolists from encode/3 when ever possible
  • You may pass options to init/2. It's the 2nd element of the tuple {ModName, Opts}.
  • You may use some context information from connection (it's internal record passed as 2nd argument to init/2). See epgsql_sock.erl for API functions.
  • Note that any error in callback functions will cause crash of epgsql connection process!