1%%% @doc
2%%% Behaviour for postgresql datatype codecs.
3%%% XXX: this module and callbacks "know nothing" about OIDs.
4%%% XXX: state of codec shouldn't leave epgsql_sock process. If you need to
5%%% return "pointer" to data type/codec, it's better to return OID or type name.
6%%% @end
7%%% Created : 12 Oct 2017 by Sergey Prokhorov <me@seriyps.ru>
8
9-module(epgsql_codec).
10-export([init_mods/2, encode/4, decode/4, decode_text/4]).
11
12-export_type([codec_state/0, codec_mod/0, codec_entry/0]).
13
14%%
15%% Behaviour
16%%
17-type codec_state() :: any().
18-type codec_mod() :: module().
19
20-optional_callbacks([decode_text/3]).
21
22%% Called on connection start-up
23-callback init(any(), epgsql_sock:pg_sock()) -> codec_state().
24
25%% List of supported type names
26-callback names() -> [epgsql:type_name()].
27
28%% Encode Erlang representation to PG binary
29%% Called for each parameter, binary protocol (equery)
30-callback encode(Cell :: any(), epgsql:type_name(), codec_state()) -> iodata().
31
32%% Decode PG binary to erlang representation
33%% Called for each cell in each row, binary protocol (equery)
34-callback decode(Cell :: binary(), epgsql:type_name(), codec_state()) -> any().
35
36%% Decode PG string representation (text protocol) to erlang term.
37%% Called for each cell in each row, text protocol (squery)
38-callback decode_text(Cell :: binary(), epgsql:type_name(), codec_state()) ->
39    any().
40
41%% ==========
42-type codec_entry() :: {epgsql:type_name(),
43                        Mod :: codec_mod(),
44                        CallbackState :: any()}.
45
46-spec init_mods([{codec_mod(), any()}], epgsql_sock:pg_sock()) ->
47                       ordsets:ordset(codec_entry()).
48init_mods(Codecs, PgSock) ->
49    ModState = [{Mod, Mod:init(Opts, PgSock)} || {Mod, Opts} <- Codecs],
50    build_mapping(ModState, sets:new(), []).
51
52build_mapping([{Mod, _State} = MS | ModStates], Set, Acc) ->
53    Names = Mod:names(),
54    {Set1, Acc1} = add_names(Names, MS, Set, Acc),
55    build_mapping(ModStates, Set1, Acc1);
56build_mapping([], _, Acc) ->
57    ordsets:from_list(Acc).
58
59add_names([Name | Names], {Mod, State} = MS, Set, Acc) ->
60    case sets:is_element(Name, Set) of
61        true ->
62            add_names(Names, MS, Set, Acc);
63        false ->
64            Set1 = sets:add_element(Name, Set),
65            Acc1 = [{Name, Mod, State} | Acc],
66            add_names(Names, MS, Set1, Acc1)
67    end;
68add_names([], _, Set, Acc) ->
69    {Set, Acc}.
70
71-spec encode(codec_mod(), any(), epgsql:type_name(), codec_state()) -> iodata().
72encode(Mod, Cell, TypeName, CodecState) ->
73    Mod:encode(Cell, TypeName, CodecState).
74
75-spec decode(codec_mod(), binary(), epgsql:type_name(), codec_state()) -> any().
76decode(Mod, Cell, TypeName, CodecState) ->
77    Mod:decode(Cell, TypeName, CodecState).
78
79-spec decode_text(codec_mod(), binary(), epgsql:type_name(), codec_state()) -> any().
80decode_text(Mod, Cell, TypeName, CodecState) ->
81    Mod:decode(Cell, TypeName, CodecState).
82