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