epgsql_oid_db.erl 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. %%% @author Sergey Prokhorov <me@seriyps.ru>
  2. %%% @doc
  3. %%% Holds Oid to 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 :: #{oid() => #type{}},
  21. by_name :: #{ {epgsql:type_name(), boolean()} => oid() }
  22. }).
  23. -type oid() :: non_neg_integer().
  24. %% Row of `typname', `oid', `typarray' from pg_type table.
  25. -type oid_entry() :: {epgsql:type_name(), Oid :: oid(), ArrayOid :: oid()}.
  26. -type oid_info() :: {Oid :: oid(), epgsql:type_name(), IsArray :: boolean()}.
  27. -opaque db() :: #oid_db{}.
  28. -opaque type_info() :: #type{}.
  29. %%
  30. %% pg_type Data preparation
  31. %%
  32. %% @doc build query to fetch OID to type_name information from PG server
  33. -spec build_query([epgsql:type_name() | binary()]) -> iolist().
  34. build_query(TypeNames) ->
  35. %% TODO: lists:join/2, ERL 19+
  36. %% XXX: we don't escape type names!
  37. ToBin = fun(B) when is_binary(B) -> B;
  38. (A) when is_atom(A) -> atom_to_binary(A, utf8)
  39. end,
  40. Types = join(",",
  41. [["'", ToBin(TypeName) | "'"]
  42. || TypeName <- TypeNames]),
  43. [<<"SELECT typname::text, oid::int4, typarray::int4 "
  44. "FROM pg_type "
  45. "WHERE typname IN (">>, Types, <<") ORDER BY typname">>].
  46. %% @doc Parse result of `squery(build_query(...))'
  47. -spec parse_rows(ordsets:ordset({binary(), binary(), binary()})) ->
  48. ordsets:ordset(oid_entry()).
  49. parse_rows(Rows) ->
  50. [{binary_to_existing_atom(TypeName, utf8),
  51. binary_to_integer(Oid),
  52. binary_to_integer(ArrayOid)}
  53. || {TypeName, Oid, ArrayOid} <- Rows].
  54. %% @doc Build list of #type{}'s by merging oid and codec lists by type name.
  55. -spec join_codecs_oids(ordsets:ordset(oid_entry()),
  56. ordsets:ordset(epgsql_codec:codec_entry())) -> [type_info()].
  57. join_codecs_oids(Oids, Codecs) ->
  58. do_join(lists:sort(Oids), lists:sort(Codecs)).
  59. do_join([{TypeName, Oid, ArrayOid} | Oids],
  60. [{TypeName, CallbackMod, CallbackState} | Codecs]) ->
  61. [#type{oid = Oid, name = TypeName, is_array = false,
  62. codec = CallbackMod, codec_state = CallbackState},
  63. #type{oid = ArrayOid, name = TypeName, is_array = true,
  64. codec = CallbackMod, codec_state = CallbackState,
  65. array_element_oid = Oid}
  66. | do_join(Oids, Codecs)];
  67. do_join([OidEntry | _Oids] = Oids, [CodecEntry | Codecs])
  68. when element(1, OidEntry) > element(1, CodecEntry) ->
  69. %% This type isn't supported by PG server. That's ok, but not vice-versa.
  70. do_join(Oids, Codecs);
  71. do_join([], _) ->
  72. %% Codecs list may be not empty. See prev clause.
  73. [].
  74. %%
  75. %% Storage API
  76. %%
  77. -spec from_list([type_info()]) -> db().
  78. from_list(Types) ->
  79. #oid_db{by_oid = maps:from_list(
  80. [{Oid, Type} || #type{oid = Oid} = Type <- Types]),
  81. by_name = maps:from_list(
  82. [{{Name, IsArray}, Oid}
  83. || #type{name = Name, is_array = IsArray, oid = Oid}
  84. <- Types])}.
  85. to_list(#oid_db{by_oid = Dict}) ->
  86. [Type || {_Oid, Type} <- maps:to_list(Dict)].
  87. %% @doc update DB adding new type definitions.
  88. %% If some of type definitions already exist, old ones will be overwritten by new ones
  89. -spec update([type_info()], db()) -> db().
  90. update(Types, #oid_db{by_oid = OldByOid, by_name = OldByName} = Store) ->
  91. #oid_db{by_oid = NewByOid, by_name = NewByName} = from_list(Types),
  92. ByOid = maps:merge(OldByOid, NewByOid),
  93. ByName = maps:merge(OldByName, NewByName),
  94. Store#oid_db{by_oid = ByOid,
  95. by_name = ByName}.
  96. %% @doc find type by OID
  97. -spec find_by_oid(oid(), db()) -> type_info() | undefined.
  98. find_by_oid(Oid, #oid_db{by_oid = Dict}) ->
  99. maps:get(Oid, Dict, undefined).
  100. %% @doc find type by type name
  101. -spec find_by_name(epgsql:type_name(), boolean(), db()) -> type_info().
  102. find_by_name(Name, IsArray, #oid_db{by_oid = ByOid} = Db) ->
  103. Oid = oid_by_name(Name, IsArray, Db),
  104. maps:get(Oid, ByOid). % or maybe find_by_oid(Oid, Store)
  105. %% @doc lookup OID by type name. May fall
  106. -spec oid_by_name(epgsql:type_name(), boolean(), db()) -> oid().
  107. oid_by_name(Name, IsArray, #oid_db{by_name = ByName}) ->
  108. maps:get({Name, IsArray}, ByName).
  109. %% @doc convert type to codec_entry()
  110. -spec type_to_codec_entry(type_info()) -> epgsql_codec:codec_entry().
  111. type_to_codec_entry(#type{name = Name, codec = Codec, codec_state = State}) ->
  112. {Name, Codec, State}.
  113. %% @doc Convert type tp oid_info()
  114. -spec type_to_oid_info(type_info()) -> oid_info().
  115. type_to_oid_info(#type{name = Name, is_array = IsArray, oid = Oid}) ->
  116. {Oid, Name, IsArray}.
  117. %% @doc For array types return its element's OID
  118. -spec type_to_element_oid(type_info()) -> oid() | undefined.
  119. type_to_element_oid(#type{array_element_oid = ElementOid, is_array = true}) ->
  120. ElementOid.
  121. %% Internal
  122. %% TODO: replace by Erlang >=19 lists:join/2
  123. join(_Sep, []) -> [];
  124. join(Sep, [H | T]) -> [H | join_prepend(Sep, T)].
  125. join_prepend(_Sep, []) -> [];
  126. join_prepend(Sep, [H | T]) -> [Sep, H | join_prepend(Sep, T)].