epgsql_oid_db.erl 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. %%% @author Sergey Prokhorov <me@seriyps.ru>
  2. %%% @doc
  3. %%% Holds Oid <-> Type mappings (forward and reverse).
  4. %%% See https://www.postgresql.org/docs/current/static/catalog-pg-type.html
  5. %%% @end
  6. -module(epgsql_oid_db).
  7. -export([build_query/1, parse_rows/1, join_codecs_oids/2]).
  8. -export([from_list/1, to_list/1, update/2,
  9. find_by_oid/2, find_by_name/3, oid_by_name/3,
  10. type_to_codec_entry/1, type_to_oid_info/1, type_to_element_oid/1]).
  11. -export_type([oid/0, oid_info/0, oid_entry/0, type_info/0, db/0]).
  12. -record(type,
  13. {oid :: oid(),
  14. name :: epgsql:type_name(),
  15. is_array :: boolean(),
  16. array_element_oid :: oid() | undefined,
  17. codec :: module(),
  18. codec_state :: any()}).
  19. -record(oid_db,
  20. {by_oid :: kv(oid(), #type{}),
  21. by_name :: kv(epgsql:type_name(), oid())}).
  22. -type oid() :: non_neg_integer().
  23. %% Row of `typname', `oid', `typarray' from pg_type table.
  24. -type oid_entry() :: {epgsql:type_name(), Oid :: oid(), ArrayOid :: oid()}.
  25. -type oid_info() :: {Oid :: oid(), epgsql:type_name(), IsArray :: boolean()}.
  26. -opaque db() :: #oid_db{}.
  27. -opaque type_info() :: #type{}.
  28. %%
  29. %% pg_type Data preparation
  30. %%
  31. %% @doc build query to fetch OID<->type_name information from PG server
  32. -spec build_query([epgsql:type_name() | binary()]) -> iolist().
  33. build_query(TypeNames) ->
  34. %% TODO: lists:join/2, ERL 19+
  35. %% XXX: we don't escape type names!
  36. ToBin = fun(B) when is_binary(B) -> B;
  37. (A) when is_atom(A) -> atom_to_binary(A, utf8)
  38. end,
  39. Types = join(",",
  40. [["'", ToBin(TypeName) | "'"]
  41. || TypeName <- TypeNames]),
  42. [<<"SELECT typname::text, oid::int4, typarray::int4 "
  43. "FROM pg_type "
  44. "WHERE typname IN (">>, Types, <<") ORDER BY typname">>].
  45. %% @doc Parse result of `squery(build_query(...))'
  46. -spec parse_rows(ordsets:ordset({binary(), binary(), binary()})) ->
  47. ordsets:ordset(oid_entry()).
  48. parse_rows(Rows) ->
  49. [{binary_to_existing_atom(TypeName, utf8),
  50. binary_to_integer(Oid),
  51. binary_to_integer(ArrayOid)}
  52. || {TypeName, Oid, ArrayOid} <- Rows].
  53. %% @doc Build list of #type{}'s by merging oid and codec lists by type name.
  54. -spec join_codecs_oids(ordsets:ordset(oid_entry()),
  55. ordsets:ordset(epgsql_codec:codec_entry())) -> [type_info()].
  56. join_codecs_oids(Oids, Codecs) ->
  57. do_join(lists:sort(Oids), lists:sort(Codecs)).
  58. do_join([{TypeName, Oid, ArrayOid} | Oids],
  59. [{TypeName, CallbackMod, CallbackState} | Codecs]) ->
  60. [#type{oid = Oid, name = TypeName, is_array = false,
  61. codec = CallbackMod, codec_state = CallbackState},
  62. #type{oid = ArrayOid, name = TypeName, is_array = true,
  63. codec = CallbackMod, codec_state = CallbackState,
  64. array_element_oid = Oid}
  65. | do_join(Oids, Codecs)];
  66. do_join([OidEntry | _Oids] = Oids, [CodecEntry | Codecs])
  67. when element(1, OidEntry) > element(1, CodecEntry) ->
  68. %% This type isn't supported by PG server. That's ok, but not vice-versa.
  69. do_join(Oids, Codecs);
  70. do_join([], _) ->
  71. %% Codecs list may be not empty. See prev clause.
  72. [].
  73. %%
  74. %% Storage API
  75. %%
  76. -spec from_list([type_info()]) -> db().
  77. from_list(Types) ->
  78. #oid_db{by_oid = kv_from_list(
  79. [{Oid, Type} || #type{oid = Oid} = Type <- Types]),
  80. by_name = kv_from_list(
  81. [{{Name, IsArray}, Oid}
  82. || #type{name = Name, is_array = IsArray, oid = Oid}
  83. <- Types])}.
  84. to_list(#oid_db{by_oid = Dict}) ->
  85. [Type || {_Oid, Type} <- kv_to_list(Dict)].
  86. %% @doc update DB adding new type definitions.
  87. %% If some of type definitions already exist, old ones will be overwritten by new ones
  88. -spec update([type_info()], db()) -> db().
  89. update(Types, #oid_db{by_oid = OldByOid, by_name = OldByName} = Store) ->
  90. #oid_db{by_oid = NewByOid, by_name = NewByName} = from_list(Types),
  91. ByOid = kv_merge(OldByOid, NewByOid),
  92. ByName = kv_merge(OldByName, NewByName),
  93. Store#oid_db{by_oid = ByOid,
  94. by_name = ByName}.
  95. %% @doc find type by OID
  96. -spec find_by_oid(oid(), db()) -> type_info() | undefined.
  97. find_by_oid(Oid, #oid_db{by_oid = Dict}) ->
  98. kv_get(Oid, Dict, undefined).
  99. %% @doc find type by type name
  100. -spec find_by_name(epgsql:type_name(), boolean(), db()) -> type_info().
  101. find_by_name(Name, IsArray, #oid_db{by_oid = ByOid} = Db) ->
  102. Oid = oid_by_name(Name, IsArray, Db),
  103. kv_get(Oid, ByOid). % or maybe find_by_oid(Oid, Store)
  104. %% @doc lookup OID by type name. May fall
  105. -spec oid_by_name(epgsql:type_name(), boolean(), db()) -> oid().
  106. oid_by_name(Name, IsArray, #oid_db{by_name = ByName}) ->
  107. kv_get({Name, IsArray}, ByName).
  108. %% @doc convert type to codec_entry()
  109. -spec type_to_codec_entry(type_info()) -> epgsql_codec:codec_entry().
  110. type_to_codec_entry(#type{name = Name, codec = Codec, codec_state = State}) ->
  111. {Name, Codec, State}.
  112. %% @doc Convert type tp oid_info()
  113. -spec type_to_oid_info(type_info()) -> oid_info().
  114. type_to_oid_info(#type{name = Name, is_array = IsArray, oid = Oid}) ->
  115. {Oid, Name, IsArray}.
  116. %% @doc For array types return its element's OID
  117. -spec type_to_element_oid(type_info()) -> oid() | undefined.
  118. type_to_element_oid(#type{array_element_oid = ElementOid, is_array = true}) ->
  119. ElementOid.
  120. %% Internal
  121. %% TODO: replace by Erlang >=19 lists:join/2
  122. join(_Sep, []) -> [];
  123. join(Sep, [H | T]) -> [H | join_prepend(Sep, T)].
  124. join_prepend(_Sep, []) -> [];
  125. join_prepend(Sep, [H | T]) -> [Sep, H | join_prepend(Sep, T)].
  126. %% K-V storage
  127. %% In Erlang 17 map access time is O(n), so, it's faster to use dicts.
  128. %% In Erlang >=18 maps are the most eficient choice
  129. -ifdef(FAST_MAPS).
  130. -type kv(K, V) :: #{K => V}.
  131. kv_from_list(L) ->
  132. maps:from_list(L).
  133. kv_to_list(Map) ->
  134. maps:to_list(Map).
  135. kv_get(Key, Map) ->
  136. maps:get(Key, Map).
  137. kv_get(Key, Map, Default) ->
  138. maps:get(Key, Map, Default).
  139. kv_merge(Map1, Map2) ->
  140. maps:merge(Map1, Map2).
  141. -else.
  142. -type kv(K, V) :: dict:dict(K, V).
  143. kv_from_list(L) ->
  144. dict:from_list(L).
  145. kv_to_list(Dict) ->
  146. dict:to_list(Dict).
  147. kv_get(Key, Dict) ->
  148. dict:fetch(Key, Dict).
  149. kv_get(Key, Dict, Default) ->
  150. case dict:find(Key, Dict) of
  151. {ok, Value} -> Value;
  152. error -> Default
  153. end.
  154. kv_merge(Dict1, Dict2) ->
  155. dict:merge(fun(_, _, V2) -> V2 end, Dict1, Dict2).
  156. -endif.