zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

types.ex (10579B)


      1 defmodule Postgrex.Types do
      2   @moduledoc """
      3   Encodes and decodes between PostgreSQL protocol and Elixir values.
      4   """
      5 
      6   alias Postgrex.TypeInfo
      7   import Postgrex.BinaryUtils
      8 
      9   @typedoc """
     10   PostgreSQL internal identifier that maps to a type. See
     11   <https://www.postgresql.org/docs/9.4/static/datatype-oid.html>.
     12   """
     13   @type oid :: pos_integer
     14 
     15   @typedoc """
     16   State used by the encoder/decoder functions
     17   """
     18   @opaque state :: {module, :ets.tid()}
     19 
     20   @typedoc """
     21   Term used to describe type information
     22   """
     23   @opaque type :: module | {module, [oid], [type]} | {module, nil, state}
     24 
     25   ### BOOTSTRAP TYPES AND EXTENSIONS ###
     26 
     27   @doc false
     28   @spec new(module) :: state
     29   def new(module) do
     30     {module, :ets.new(__MODULE__, [:protected, {:read_concurrency, true}])}
     31   end
     32 
     33   @doc false
     34   @spec owner(state) :: {:ok, pid} | :error
     35   def owner({_, table}) do
     36     case :ets.info(table, :owner) do
     37       owner when is_pid(owner) ->
     38         {:ok, owner}
     39 
     40       :undefined ->
     41         :error
     42     end
     43   end
     44 
     45   @doc false
     46   @spec bootstrap_query({pos_integer, non_neg_integer, non_neg_integer}, state) :: binary | nil
     47   def bootstrap_query(version, {_, table}) do
     48     case :ets.info(table, :size) do
     49       0 ->
     50         # avoid loading information about table-types
     51         # since there might be a lot them and most likely
     52         # they won't be used; subsequent bootstrap will
     53         # fetch them along with any other "new" types
     54         filter_oids = """
     55         WHERE (t.typrelid = 0)
     56         AND (t.typelem = 0 OR NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type s WHERE s.typrelid != 0 AND s.oid = t.typelem))
     57         """
     58 
     59         build_bootstrap_query(version, filter_oids)
     60 
     61       _ ->
     62         nil
     63     end
     64   end
     65 
     66   defp build_bootstrap_query(version, filter_oids) do
     67     {typelem, join_domain} =
     68       if version >= {9, 0, 0} do
     69         {"coalesce(d.typelem, t.typelem)", "LEFT JOIN pg_type AS d ON t.typbasetype = d.oid"}
     70       else
     71         {"t.typelem", ""}
     72       end
     73 
     74     {rngsubtype, join_range} =
     75       if version >= {9, 2, 0} do
     76         {"coalesce(r.rngsubtype, 0)",
     77          "LEFT JOIN pg_range AS r ON r.rngtypid = t.oid OR (t.typbasetype <> 0 AND r.rngtypid = t.typbasetype)"}
     78       else
     79         {"0", ""}
     80       end
     81 
     82     """
     83     SELECT t.oid, t.typname, t.typsend, t.typreceive, t.typoutput, t.typinput,
     84            #{typelem}, #{rngsubtype}, ARRAY (
     85       SELECT a.atttypid
     86       FROM pg_attribute AS a
     87       WHERE a.attrelid = t.typrelid AND a.attnum > 0 AND NOT a.attisdropped
     88       ORDER BY a.attnum
     89     )
     90     FROM pg_type AS t
     91     #{join_domain}
     92     #{join_range}
     93     #{filter_oids}
     94     """
     95   end
     96 
     97   @doc false
     98   @spec reload_query({pos_integer, non_neg_integer, non_neg_integer}, [oid, ...], state) ::
     99           binary | nil
    100   def reload_query(version, oids, {_, table}) do
    101     case Enum.reject(oids, &:ets.member(table, &1)) do
    102       [] ->
    103         nil
    104 
    105       oids ->
    106         build_bootstrap_query(version, "WHERE t.oid IN (#{Enum.join(oids, ", ")})")
    107     end
    108   end
    109 
    110   @doc false
    111   @spec build_type_info(binary) :: TypeInfo.t()
    112   def build_type_info(row) do
    113     [oid, type, send, receive, output, input, array_oid, base_oid, comp_oids] = row_decode(row)
    114     oid = String.to_integer(oid)
    115     array_oid = String.to_integer(array_oid)
    116     base_oid = String.to_integer(base_oid)
    117     comp_oids = parse_oids(comp_oids)
    118 
    119     %TypeInfo{
    120       oid: oid,
    121       type: :binary.copy(type),
    122       send: :binary.copy(send),
    123       receive: :binary.copy(receive),
    124       output: :binary.copy(output),
    125       input: :binary.copy(input),
    126       array_elem: array_oid,
    127       base_type: base_oid,
    128       comp_elems: comp_oids
    129     }
    130   end
    131 
    132   @doc false
    133   @spec associate_type_infos([TypeInfo.t()], state) :: :ok
    134   def associate_type_infos(type_infos, {module, table}) do
    135     _ =
    136       for %TypeInfo{oid: oid} = type_info <- type_infos do
    137         true = :ets.insert_new(table, {oid, type_info, nil})
    138       end
    139 
    140     _ =
    141       for %TypeInfo{oid: oid} = type_info <- type_infos do
    142         info = find(type_info, :any, module, table)
    143         true = :ets.update_element(table, oid, {3, info})
    144       end
    145 
    146     :ok
    147   end
    148 
    149   defp find(type_info, formats, module, table) do
    150     case apply(module, :find, [type_info, formats]) do
    151       {:super_binary, extension, nil} ->
    152         {:binary, {extension, nil, {module, table}}}
    153 
    154       {:super_binary, extension, sub_oids} when formats == :any ->
    155         super_find(sub_oids, extension, module, table) ||
    156           find(type_info, :text, module, table)
    157 
    158       {:super_binary, extension, sub_oids} ->
    159         super_find(sub_oids, extension, module, table)
    160 
    161       nil ->
    162         nil
    163 
    164       info ->
    165         info
    166     end
    167   end
    168 
    169   defp super_find(sub_oids, extension, module, table) do
    170     case sub_find(sub_oids, module, table, []) do
    171       {:ok, sub_types} ->
    172         {:binary, {extension, sub_oids, sub_types}}
    173 
    174       :error ->
    175         nil
    176     end
    177   end
    178 
    179   defp sub_find([oid | oids], module, table, acc) do
    180     case :ets.lookup(table, oid) do
    181       [{_, _, {:binary, types}}] ->
    182         sub_find(oids, module, table, [types | acc])
    183 
    184       [{_, type_info, _}] ->
    185         case find(type_info, :binary, module, table) do
    186           {:binary, types} ->
    187             sub_find(oids, module, table, [types | acc])
    188 
    189           nil ->
    190             :error
    191         end
    192 
    193       [] ->
    194         :error
    195     end
    196   end
    197 
    198   defp sub_find([], _, _, acc) do
    199     {:ok, Enum.reverse(acc)}
    200   end
    201 
    202   defp row_decode(<<>>), do: []
    203 
    204   defp row_decode(<<-1::int32(), rest::binary>>) do
    205     [nil | row_decode(rest)]
    206   end
    207 
    208   defp row_decode(<<len::uint32(), value::binary(len), rest::binary>>) do
    209     [value | row_decode(rest)]
    210   end
    211 
    212   defp parse_oids(nil) do
    213     []
    214   end
    215 
    216   defp parse_oids("{}") do
    217     []
    218   end
    219 
    220   defp parse_oids("{" <> rest) do
    221     parse_oids(rest, [])
    222   end
    223 
    224   defp parse_oids(bin, acc) do
    225     case Integer.parse(bin) do
    226       {int, "," <> rest} -> parse_oids(rest, [int | acc])
    227       {int, "}"} -> Enum.reverse([int | acc])
    228     end
    229   end
    230 
    231   ### TYPE ENCODING / DECODING ###
    232 
    233   @doc """
    234   Defines a type module with custom extensions and options.
    235 
    236   `Postgrex.Types.define/3` must be called on its own file, outside of
    237   any module and function, as it only needs to be defined once during
    238   compilation.
    239 
    240   Type modules are given to Postgrex on `start_link` via the `:types`
    241   option and are used to control how Postgrex encodes and decodes data
    242   coming from Postgrex.
    243 
    244   For example, to define a new type module with a custom extension
    245   called `MyExtension` while also changing `Postgrex`'s default
    246   behaviour regarding binary decoding, you may create a new file
    247   called "lib/my_app/postgrex_types.ex" with the following:
    248 
    249       Postgrex.Types.define(MyApp.PostgrexTypes, [MyExtension], [decode_binary: :reference])
    250 
    251   The line above will define a new module, called `MyApp.PostgrexTypes`
    252   which can be passed as `:types` when starting Postgrex. The type module
    253   works by rewriting and inlining the extensions' encode and decode
    254   expressions in an optimal fashion for postgrex to encode parameters and
    255   decode multiple rows at a time.
    256 
    257   ## Extensions
    258 
    259   Extensions is a list of `Postgrex.Extension` modules or a 2-tuple
    260   containing the module and a keyword list. The keyword, defaulting
    261   to `[]`, will be passed to the modules `init/1` callback.
    262 
    263   Extensions at the front of the list will take priority over later
    264   extensions when the `matching/1` callback returns have conflicting
    265   matches. If an extension is not provided for a type then Postgrex
    266   will fallback to default encoding/decoding methods where possible.
    267   All extensions that ship as part of Postgrex are included out of the
    268   box.
    269 
    270   See `Postgrex.Extension` for more information on extensions.
    271 
    272   ## Options
    273 
    274     * `:null` - The atom to use as a stand in for postgres' `NULL` in
    275       encoding and decoding. The module attribute `@null` is registered
    276       with the value so that extension can access the value if desired
    277       (default: `nil`);
    278 
    279     * `:decode_binary` - Either `:copy` to copy binary values when decoding
    280       with default extensions that return binaries or `:reference` to use a
    281       reference counted binary of the binary received from the socket.
    282       Referencing a potentially larger binary can be more efficient if the binary
    283       value is going to be garbaged collected soon because a copy is avoided.
    284       However the larger binary can not be garbage collected until all references
    285       are garbage collected (default: `:copy`);
    286 
    287     * `:json` - The JSON module to encode and decode JSON binaries, calls
    288       `module.encode_to_iodata!/1` to encode and `module.decode!/1` to decode.
    289       If `nil` then no default JSON handling
    290       (default: `Application.get_env(:postgrex, :json_library, Jason)`);
    291 
    292     * `:bin_opt_info` - Either `true` to enable binary optimisation information,
    293       or `false` to disable, for more information see `Kernel.SpecialForms.<<>>/1`
    294       in Elixir (default: `false`);
    295 
    296     * `:debug_defaults` - Generate debug information when building default
    297       extensions so they point to the proper source. Enabling such option
    298       will increase the time to compile the type module (default: `false`);
    299 
    300     * `:moduledoc` - The moduledoc to be used for the generated module.
    301 
    302   """
    303   def define(module, extensions, opts \\ []) do
    304     Postgrex.TypeModule.define(module, extensions, opts)
    305   end
    306 
    307   @doc false
    308   @spec encode_params([term], [type], state) :: iodata | :error
    309   def encode_params(params, types, {mod, _}) do
    310     apply(mod, :encode_params, [params, types])
    311   end
    312 
    313   @doc false
    314   @spec decode_rows(binary, [type], [row], state) ::
    315           {:more, iodata, [row], non_neg_integer} | {:ok, [row], binary}
    316         when row: var
    317   def decode_rows(binary, types, rows, {mod, _}) do
    318     apply(mod, :decode_rows, [binary, types, rows])
    319   end
    320 
    321   @doc false
    322   @spec decode_simple(binary, state) :: [String.t()]
    323   def decode_simple(binary, {mod, _}) do
    324     apply(mod, :decode_simple, [binary])
    325   end
    326 
    327   @doc false
    328   @spec fetch(oid, state) ::
    329           {:ok, {:binary | :text, type}} | {:error, TypeInfo.t() | nil, module}
    330   def fetch(oid, {mod, table}) do
    331     try do
    332       :ets.lookup_element(table, oid, 3)
    333     rescue
    334       ArgumentError ->
    335         {:error, nil, mod}
    336     else
    337       {_, _} = info ->
    338         {:ok, info}
    339 
    340       nil ->
    341         fetch_type_info(oid, mod, table)
    342     end
    343   end
    344 
    345   defp fetch_type_info(oid, mod, table) do
    346     try do
    347       :ets.lookup_element(table, oid, 2)
    348     rescue
    349       ArgumentError ->
    350         {:error, nil, mod}
    351     else
    352       type_info ->
    353         {:error, type_info, mod}
    354     end
    355   end
    356 end