# 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](https://www.postgresql.org/docs/current/static/xtypes.html) or created by [CREATE TYPE](https://www.postgresql.org/docs/current/static/sql-createtype.html)) ## This can be done by following steps ### Implement epgsql_codec behaviour callback module See [epgsql_codec](src/epgsql_codec.erl) This module should have following functions exported: ```erlang 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. ```erlang 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. ```erlang 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](https://github.com/postgres/postgres/tree/master/src/backend/utils/adt). *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. ```erlang 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. ```erlang 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: ```erlang 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](src/epgsql_sock.erl) for API functions. * Note that any error in callback functions will cause crash of epgsql connection process!