epgsql_binary.erl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. %%% Copyright (C) 2008 - Will Glozer. All rights reserved.
  2. %% XXX: maybe merge this module into epgsql_codec?
  3. -module(epgsql_binary).
  4. -export([new_codec/2,
  5. update_codec/2,
  6. type_to_oid/2,
  7. typeinfo_to_name_array/2,
  8. typeinfo_to_oid_info/2,
  9. oid_to_name/2,
  10. oid_to_info/2,
  11. oid_to_decoder/3,
  12. decode/2, encode/3, supports/2]).
  13. %% Composite type decoders
  14. -export([decode_record/3, decode_array/3]).
  15. -export_type([codec/0, decoder/0]).
  16. -include("protocol.hrl").
  17. -record(codec,
  18. {opts = [] :: list(), % not used yet
  19. oid_db :: epgsql_oid_db:db()}).
  20. -opaque codec() :: #codec{}.
  21. -opaque decoder() :: {fun((binary(), epgsql:type_name(), epgsql_codec:codec_state()) -> any()),
  22. epgsql:type_name(),
  23. epgsql_codec:codec_state()}.
  24. -type type() :: epgsql:type_name() | {array, epgsql:type_name()}.
  25. -type maybe_unknown_type() :: type() | {unknown_oid, epgsql_oid_db:oid()}.
  26. -define(RECORD_OID, 2249).
  27. -define(RECORD_ARRAY_OID, 2287).
  28. %% Codec is used to convert data (result rows and query parameters) between Erlang and postgresql formats
  29. %% It uses mappings between OID, type names and `epgsql_codec_*' modules (epgsql_oid_db)
  30. -spec new_codec(epgsql_sock:pg_sock(), list()) -> codec().
  31. new_codec(PgSock, Opts) ->
  32. Codecs = default_codecs(),
  33. Oids = default_oids(),
  34. new_codec(PgSock, Codecs, Oids, Opts).
  35. new_codec(PgSock, Codecs, Oids, Opts) ->
  36. CodecEntries = epgsql_codec:init_mods(Codecs, PgSock),
  37. Types = epgsql_oid_db:join_codecs_oids(Oids, CodecEntries),
  38. #codec{oid_db = epgsql_oid_db:from_list(Types), opts = Opts}.
  39. -spec update_codec([epgsql_oid_db:type_info()], codec()) -> codec().
  40. update_codec(TypeInfos, #codec{oid_db = Db} = Codec) ->
  41. Codec#codec{oid_db = epgsql_oid_db:update(TypeInfos, Db)}.
  42. -spec oid_to_name(epgsql_oid_db:oid(), codec()) -> maybe_unknown_type().
  43. oid_to_name(Oid, Codec) ->
  44. case oid_to_info(Oid, Codec) of
  45. undefined ->
  46. {unknown_oid, Oid};
  47. Type ->
  48. case epgsql_oid_db:type_to_oid_info(Type) of
  49. {_, Name, true} -> {array, Name};
  50. {_, Name, false} -> Name
  51. end
  52. end.
  53. -spec type_to_oid(type(), codec()) -> epgsql_oid_db:oid().
  54. type_to_oid({array, Name}, Codec) ->
  55. type_to_oid(Name, true, Codec);
  56. type_to_oid(Name, Codec) ->
  57. type_to_oid(Name, false, Codec).
  58. -spec type_to_oid(epgsql:type_name(), boolean(), codec()) -> epgsql_oid_db:oid().
  59. type_to_oid(TypeName, IsArray, #codec{oid_db = Db}) ->
  60. epgsql_oid_db:oid_by_name(TypeName, IsArray, Db).
  61. -spec type_to_type_info(type(), codec()) -> epgsql_oid_db:type_info() | undefined.
  62. type_to_type_info({array, Name}, Codec) ->
  63. type_to_info(Name, true, Codec);
  64. type_to_type_info(Name, Codec) ->
  65. type_to_info(Name, false, Codec).
  66. -spec oid_to_info(epgsql_oid_db:oid(), codec()) -> epgsql_oid_db:type_info() | undefined.
  67. oid_to_info(Oid, #codec{oid_db = Db}) ->
  68. epgsql_oid_db:find_by_oid(Oid, Db).
  69. -spec type_to_info(epgsql:type_name(), boolean(), codec()) -> epgsql_oid_db:type_info().
  70. type_to_info(TypeName, IsArray, #codec{oid_db = Db}) ->
  71. epgsql_oid_db:find_by_name(TypeName, IsArray, Db).
  72. -spec typeinfo_to_name_array(Unknown | epgsql_oid_db:type_info(), _) -> Unknown | type() when
  73. Unknown :: {unknown_oid, epgsql_oid_db:oid()}.
  74. typeinfo_to_name_array({unknown_oid, _} = Unknown, _) -> Unknown;
  75. typeinfo_to_name_array(TypeInfo, _) ->
  76. case epgsql_oid_db:type_to_oid_info(TypeInfo) of
  77. {_, Name, false} -> Name;
  78. {_, Name, true} -> {array, Name}
  79. end.
  80. -spec typeinfo_to_oid_info(Unknown | epgsql_oid_db:type_info(), _) ->
  81. Unknown | epgsql_oid_db:oid_info() when
  82. Unknown :: {unknown_oid, epgsql_oid_db:oid()}.
  83. typeinfo_to_oid_info({unknown_oid, _} = Unknown, _) -> Unknown;
  84. typeinfo_to_oid_info(TypeInfo, _) ->
  85. epgsql_oid_db:type_to_oid_info(TypeInfo).
  86. %%
  87. %% Decode
  88. %%
  89. %% @doc decode single cell
  90. -spec decode(binary(), decoder()) -> any().
  91. decode(Bin, {Fun, TypeName, State}) ->
  92. Fun(Bin, TypeName, State).
  93. %% @doc generate decoder to decode PG binary of datatype specified as OID
  94. -spec oid_to_decoder(epgsql_oid_db:oid(), binary | text, codec()) -> decoder().
  95. oid_to_decoder(?RECORD_OID, binary, Codec) ->
  96. {fun ?MODULE:decode_record/3, record, Codec};
  97. oid_to_decoder(?RECORD_ARRAY_OID, binary, Codec) ->
  98. %% See `make_array_decoder/3'
  99. {fun ?MODULE:decode_array/3, [], oid_to_decoder(?RECORD_OID, binary, Codec)};
  100. oid_to_decoder(Oid, Format, #codec{oid_db = Db}) ->
  101. case epgsql_oid_db:find_by_oid(Oid, Db) of
  102. undefined when Format == binary ->
  103. {fun epgsql_codec_noop:decode/3, undefined, []};
  104. undefined when Format == text ->
  105. {fun epgsql_codec_noop:decode_text/3, undefined, []};
  106. Type ->
  107. make_decoder(Type, Format)
  108. end.
  109. -spec make_decoder(epgsql_oid_db:type_info(), binary | text) -> decoder().
  110. make_decoder(Type, Format) ->
  111. {Name, Mod, State} = epgsql_oid_db:type_to_codec_entry(Type),
  112. {_Oid, Name, IsArray} = epgsql_oid_db:type_to_oid_info(Type),
  113. make_decoder(Name, Mod, State, Format, IsArray).
  114. make_decoder(_Name, _Mod, _State, text, true) ->
  115. %% Don't try to decode text arrays
  116. {fun epgsql_codec_noop:decode_text/3, undefined, []};
  117. make_decoder(Name, Mod, State, text, false) ->
  118. %% decode_text/3 is optional callback. If it's not defined, do NOOP.
  119. case erlang:function_exported(Mod, decode_text, 3) of
  120. true ->
  121. {fun Mod:decode_text/3, Name, State};
  122. false ->
  123. {fun epgsql_codec_noop:decode_text/3, undefined, []}
  124. end;
  125. make_decoder(Name, Mod, State, binary, true) ->
  126. make_array_decoder(Name, Mod, State);
  127. make_decoder(Name, Mod, State, binary, false) ->
  128. {fun Mod:decode/3, Name, State}.
  129. %% Array decoding
  130. %%% $PG$/src/backend/utils/adt/arrayfuncs.c
  131. make_array_decoder(Name, Mod, State) ->
  132. {fun ?MODULE:decode_array/3, [], {fun Mod:decode/3, Name, State}}.
  133. decode_array(<<NDims:?int32, _HasNull:?int32, _Oid:?int32, Rest/binary>>, _, ElemDecoder) ->
  134. %% 4b: n_dimensions;
  135. %% 4b: flags;
  136. %% 4b: Oid // should be the same as in column spec;
  137. %% (4b: n_elements;
  138. %% 4b: lower_bound) * n_dimensions
  139. %% (dynamic-size data)
  140. %% Lower bound - eg, zero-bound or 1-bound or N-bound array. We ignore it, see
  141. %% https://www.postgresql.org/docs/current/static/arrays.html#arrays-io
  142. {Dims, Data} = erlang:split_binary(Rest, NDims * 2 * 4),
  143. Lengths = [Len || <<Len:?int32, _LBound:?int32>> <= Dims],
  144. {Array, <<>>} = decode_array1(Data, Lengths, ElemDecoder),
  145. Array.
  146. decode_array1(Data, [], _) ->
  147. %% zero-dimensional array
  148. {[], Data};
  149. decode_array1(Data, [Len], ElemDecoder) ->
  150. %% 1-dimensional array
  151. decode_elements(Data, [], Len, ElemDecoder);
  152. decode_array1(Data, [Len | T], ElemDecoder) ->
  153. %% multidimensional array
  154. F = fun(_N, Rest) -> decode_array1(Rest, T, ElemDecoder) end,
  155. lists:mapfoldl(F, Data, lists:seq(1, Len)).
  156. decode_elements(Rest, Acc, 0, _ElDec) ->
  157. {lists:reverse(Acc), Rest};
  158. decode_elements(<<-1:?int32, Rest/binary>>, Acc, N, ElDec) ->
  159. decode_elements(Rest, [null | Acc], N - 1, ElDec);
  160. decode_elements(<<Len:?int32, Value:Len/binary, Rest/binary>>, Acc, N, ElemDecoder) ->
  161. Value2 = decode(Value, ElemDecoder),
  162. decode_elements(Rest, [Value2 | Acc], N - 1, ElemDecoder).
  163. %% Record decoding
  164. %% $PG$/src/backend/utils/adt/rowtypes.c
  165. decode_record(<<Size:?int32, Bin/binary>>, record, Codec) ->
  166. list_to_tuple(decode_record1(Bin, Size, Codec)).
  167. decode_record1(<<>>, 0, _Codec) -> [];
  168. decode_record1(<<_Type:?int32, -1:?int32, Rest/binary>>, Size, Codec) ->
  169. [null | decode_record1(Rest, Size - 1, Codec)];
  170. decode_record1(<<Oid:?int32, Len:?int32, ValueBin:Len/binary, Rest/binary>>, Size, #codec{oid_db = Db} = Codec) ->
  171. Value =
  172. case epgsql_oid_db:find_by_oid(Oid, Db) of
  173. undefined -> ValueBin;
  174. Type ->
  175. {Name, Mod, State} = epgsql_oid_db:type_to_codec_entry(Type),
  176. Mod:decode(ValueBin, Name, State)
  177. end,
  178. [Value | decode_record1(Rest, Size - 1, Codec)].
  179. %%
  180. %% Encode
  181. %%
  182. %% Convert erlang value to PG binary of type, specified by type name
  183. -spec encode(epgsql:type_name() | {array, epgsql:type_name()}, any(), codec()) -> iolist().
  184. encode(TypeName, Value, Codec) ->
  185. Type = type_to_type_info(TypeName, Codec),
  186. encode_with_type(Type, Value).
  187. encode_with_type(Type, Value) ->
  188. {Name, Mod, State} = epgsql_oid_db:type_to_codec_entry(Type),
  189. case epgsql_oid_db:type_to_oid_info(Type) of
  190. {_ArrayOid, _, true} ->
  191. %FIXME: check if this OID is the same as was returned by 'Describe'
  192. ElementOid = epgsql_oid_db:type_to_element_oid(Type),
  193. encode_array(Value, ElementOid, {Mod, Name, State});
  194. {_Oid, _, false} ->
  195. encode_value(Value, {Mod, Name, State})
  196. end.
  197. encode_value(Value, {Mod, Name, State}) ->
  198. Payload = Mod:encode(Value, Name, State),
  199. [<<(iolist_size(Payload)):?int32>> | Payload].
  200. %% Number of dimensions determined at encode-time by introspection of data, so,
  201. %% we can't encode array of lists (eg. strings).
  202. encode_array(Array, Oid, ValueEncoder) ->
  203. {Data, {NDims, Lengths}} = encode_array(Array, 0, [], ValueEncoder),
  204. Lens = [<<N:?int32, 1:?int32>> || N <- lists:reverse(Lengths)],
  205. Hdr = <<NDims:?int32, 0:?int32, Oid:?int32>>,
  206. Payload = [Hdr, Lens, Data],
  207. [<<(iolist_size(Payload)):?int32>> | Payload].
  208. encode_array([], NDims, Lengths, _Codec) ->
  209. {[], {NDims, Lengths}};
  210. encode_array([H | _] = Array, NDims, Lengths, ValueEncoder) when not is_list(H) ->
  211. F = fun(E, Len) -> {encode_value(E, ValueEncoder), Len + 1} end,
  212. {Data, Len} = lists:mapfoldl(F, 0, Array),
  213. {Data, {NDims + 1, [Len | Lengths]}};
  214. encode_array(Array, NDims, Lengths, Codec) ->
  215. Lengths2 = [length(Array) | Lengths],
  216. F = fun(A2, {_NDims, _Lengths}) -> encode_array(A2, NDims, Lengths2, Codec) end,
  217. {Data, {NDims2, Lengths3}} = lists:mapfoldl(F, {NDims, Lengths2}, Array),
  218. {Data, {NDims2 + 1, Lengths3}}.
  219. %% Supports
  220. supports(RecOid, _) when RecOid == ?RECORD_OID; RecOid == ?RECORD_ARRAY_OID ->
  221. true;
  222. supports(Oid, #codec{oid_db = Db}) ->
  223. epgsql_oid_db:find_by_oid(Oid, Db) =/= undefined.
  224. %% Default codec set
  225. %% XXX: maybe move to application env?
  226. -spec default_codecs() -> [{epgsql_codec:codec_mod(), any()}].
  227. default_codecs() ->
  228. [{epgsql_codec_boolean,[]},
  229. {epgsql_codec_bpchar,[]},
  230. {epgsql_codec_datetime,[]},
  231. {epgsql_codec_float,[]},
  232. {epgsql_codec_geometric, []},
  233. %% {epgsql_codec_hstore, []},
  234. {epgsql_codec_integer,[]},
  235. {epgsql_codec_intrange,[]},
  236. {epgsql_codec_json,[]},
  237. {epgsql_codec_net,[]},
  238. %% {epgsql_codec_postgis,[]},
  239. {epgsql_codec_text,[]},
  240. {epgsql_codec_uuid,[]}].
  241. -spec default_oids() -> [epgsql_oid_db:oid_entry()].
  242. default_oids() ->
  243. [{bool, 16, 1000},
  244. {bpchar, 1042, 1014},
  245. {bytea, 17, 1001},
  246. {char, 18, 1002},
  247. {cidr, 650, 651},
  248. {date, 1082, 1182},
  249. {float4, 700, 1021},
  250. {float8, 701, 1022},
  251. %% {geometry, 17063, 17071},
  252. %% {hstore, 16935, 16940},
  253. {inet, 869, 1041},
  254. {int2, 21, 1005},
  255. {int4, 23, 1007},
  256. {int4range, 3904, 3905},
  257. {int8, 20, 1016},
  258. {int8range, 3926, 3927},
  259. {interval, 1186, 1187},
  260. {json, 114, 199},
  261. {jsonb, 3802, 3807},
  262. {macaddr, 829, 1040},
  263. {macaddr8, 774, 775},
  264. {point, 600, 1017},
  265. {text, 25, 1009},
  266. {time, 1083, 1183},
  267. {timestamp, 1114, 1115},
  268. {timestamptz, 1184, 1185},
  269. {timetz, 1266, 1270},
  270. {uuid, 2950, 2951},
  271. {varchar, 1043, 1015}].