Packages
postgrex
0.5.2
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.0
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.13
0.15.12
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.13.0-rc.0
0.12.2
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
PostgreSQL driver for Elixir
Current section
Files
Jump to
Current section
Files
lib/postgrex/types.ex
defmodule Postgrex.Types do
@moduledoc false
alias Postgrex.TypeInfo
import Postgrex.BinaryUtils
require Decimal
@types [ "bool", "bpchar", "text", "varchar", "bytea", "int2", "int4", "int8",
"float4", "float8", "numeric", "date", "time", "timetz", "timestamp",
"timestamptz", "interval" ]
@gd_epoch :calendar.date_to_gregorian_days({2000, 1, 1})
@gs_epoch :calendar.datetime_to_gregorian_seconds({{2000, 1, 1}, {0, 0, 0}})
@days_in_month 30
@secs_in_day 24 * 60 * 60
@numeric_base 10_000
def build_types(rows) do
Enum.reduce(rows, HashDict.new, fn row, acc ->
[oid, type, send, array_oid, comp_oids] = row
oid = String.to_integer(oid)
send_size = byte_size(send)
array_oid = String.to_integer(array_oid)
comp_oids = parse_oids(comp_oids)
send =
cond do
String.ends_with?(send, "_send") ->
:binary.part(send, 0, send_size - 5)
String.ends_with?(send, "send") ->
:binary.part(send, 0, send_size - 4)
true ->
nil
end
info = %TypeInfo{oid: oid, sender: send, type: type, array_elem: array_oid,
comp_elems: comp_oids}
Dict.put(acc, oid, info)
end)
end
defp parse_oids("{}") do
[]
end
defp parse_oids("{" <> rest) do
parse_oids(rest, [])
end
defp parse_oids(bin, acc) do
case Integer.parse(bin) do
{int, "," <> rest} -> parse_oids(rest, [int|acc])
{int, "}"} -> Enum.reverse([int|acc])
end
end
def bootstrap_query do
"""
SELECT t.oid, t.typname, t.typsend, t.typelem, ARRAY (
SELECT a.atttypid
FROM pg_attribute AS a
WHERE a.attrelid = t.typrelid AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
)
FROM pg_type AS t
"""
end
def format(types, oid, formatter) do
case Dict.fetch(types, oid) do
{:ok, %TypeInfo{sender: sender, type: type, array_elem: array_oid, comp_elems: comp_oids} = info} ->
cond do
formatter && (format = formatter.(info)) ->
format
sender == "array" and format(types, array_oid, formatter) == :binary ->
:binary
sender == "record" and type != "record" and
Enum.all?(comp_oids, &(format(types, &1, formatter) == :binary)) ->
:binary
binary_type?(sender) ->
:binary
true ->
:text
end
:error ->
:text
end
end
def encode_value(%TypeInfo{} = info, {types, encoder, formatter}, default, value) do
bin = if encoder, do: encoder.(info, default, value)
result = case format(types, info.oid, formatter) do
:binary ->
if bin = bin || default.(value), do: {:binary, bin}
:text when not nil?(bin) ->
{:text, bin}
:text when is_binary(value) ->
{:text, value}
_ ->
nil
end
if nil?(result) do
throw {:postgrex_encode, "unable to encode value `#{inspect value}` as type #{info.type}"}
end
result
end
def decode_value(info, format, decoder, default, bin) do
decoded = if decoder, do: decoder.(info, format, default, bin)
cond do
decoded -> decoded
true -> default.(bin)
end
end
def decode_binary(%TypeInfo{sender: "bool"}, _, << 1 :: int8 >>),
do: true
def decode_binary(%TypeInfo{sender: "bool"}, _, << 0 :: int8 >>),
do: false
def decode_binary(%TypeInfo{sender: "bpchar"}, _, bin),
do: bin
def decode_binary(%TypeInfo{sender: "text"}, _, bin),
do: bin
def decode_binary(%TypeInfo{sender: "varchar"}, _, bin),
do: bin
def decode_binary(%TypeInfo{sender: "bytea"}, _, bin),
do: bin
def decode_binary(%TypeInfo{sender: "int2"}, _, << n :: int16 >>),
do: n
def decode_binary(%TypeInfo{sender: "int4"}, _, << n :: int32 >>),
do: n
def decode_binary(%TypeInfo{sender: "int8"}, _, << n :: int64 >>),
do: n
def decode_binary(%TypeInfo{sender: "float4"}, _, << 127, 192, 0, 0 >>),
do: :NaN
def decode_binary(%TypeInfo{sender: "float4"}, _, << 127, 128, 0, 0 >>),
do: :inf
def decode_binary(%TypeInfo{sender: "float4"}, _, << 255, 128, 0, 0 >>),
do: :"-inf"
def decode_binary(%TypeInfo{sender: "float4"}, _, << n :: float32 >>),
do: n
def decode_binary(%TypeInfo{sender: "float8"}, _, << 127, 248, 0, 0, 0, 0, 0, 0 >>),
do: :NaN
def decode_binary(%TypeInfo{sender: "float8"}, _, << 127, 240, 0, 0, 0, 0, 0, 0 >>),
do: :inf
def decode_binary(%TypeInfo{sender: "float8"}, _, << 255, 240, 0, 0, 0, 0, 0, 0 >>),
do: :"-inf"
def decode_binary(%TypeInfo{sender: "float8"}, _, << n :: float64 >>),
do: n
def decode_binary(%TypeInfo{sender: "numeric"}, _, << ndigits :: int16, weight :: int16, sign :: uint16, scale :: int16, tail :: binary >>),
do: decode_numeric(ndigits, weight, sign, scale, tail)
def decode_binary(%TypeInfo{sender: "date"}, _, << n :: int32 >>),
do: decode_date(n)
def decode_binary(%TypeInfo{sender: "time"}, _, << n :: int64 >>),
do: decode_time(n)
def decode_binary(%TypeInfo{sender: "timetz"}, _, << n :: int64, _tz :: int32 >>),
do: decode_time(n)
def decode_binary(%TypeInfo{sender: "timestamp"}, _, << n :: int64 >>),
do: decode_timestamp(n)
def decode_binary(%TypeInfo{sender: "timestamptz"}, _, << n :: int64 >>),
do: decode_timestamp(n)
def decode_binary(%TypeInfo{sender: "interval"}, _, << s :: int64, d :: int32, m :: int32 >>),
do: decode_interval(s, d, m)
def decode_binary(%TypeInfo{sender: "array"}, extra, bin),
do: decode_array(bin, extra)
def decode_binary(%TypeInfo{sender: "record"}, extra, bin),
do: decode_record(bin, extra)
def decode_binary(%TypeInfo{}, _, _),
do: nil
def decode_text(%TypeInfo{type: "void"}, _, ""),
do: :void
def decode_text(%TypeInfo{}, _, text),
do: text
def encode(%TypeInfo{sender: "bool"}, _, true),
do: << 1 >>
def encode(%TypeInfo{sender: "bool"}, _, false),
do: << 0 >>
def encode(%TypeInfo{sender: "bpchar"}, _, bin) when is_binary(bin),
do: bin
def encode(%TypeInfo{sender: "text"}, _, bin) when is_binary(bin),
do: bin
def encode(%TypeInfo{sender: "varchar"}, _, bin) when is_binary(bin),
do: bin
def encode(%TypeInfo{sender: "bytea"}, _, bin) when is_binary(bin),
do: bin
def encode(%TypeInfo{sender: "int2"}, _, n) when is_integer(n),
do: << n :: int16 >>
def encode(%TypeInfo{sender: "int4"}, _, n) when is_integer(n),
do: << n :: int32 >>
def encode(%TypeInfo{sender: "int8"}, _, n) when is_integer(n),
do: << n :: int64 >>
def encode(%TypeInfo{sender: "float4"}, _, :NaN),
do: << 127, 192, 0, 0 >>
def encode(%TypeInfo{sender: "float4"}, _, :inf),
do: << 127, 128, 0, 0 >>
def encode(%TypeInfo{sender: "float4"}, _, :"-inf"),
do: << 255, 128, 0, 0 >>
def encode(%TypeInfo{sender: "float4"}, _, n) when is_number(n),
do: << n :: float32 >>
def encode(%TypeInfo{sender: "float8"}, _, :NaN),
do: << 127, 248, 0, 0, 0, 0, 0, 0 >>
def encode(%TypeInfo{sender: "float8"}, _, :inf),
do: << 127, 240, 0, 0, 0, 0, 0, 0 >>
def encode(%TypeInfo{sender: "float8"}, _, :"-inf"),
do: << 255, 240, 0, 0, 0, 0, 0, 0 >>
def encode(%TypeInfo{sender: "float8"}, _, n) when is_number(n),
do: << n :: float64 >>
def encode(%TypeInfo{sender: "numeric"}, _, n),
do: encode_numeric(n)
def encode(%TypeInfo{sender: "date"}, _, date),
do: encode_date(date)
def encode(%TypeInfo{sender: "time"}, _, time),
do: encode_time(time)
def encode(%TypeInfo{sender: "timestamp"}, _, timestamp),
do: encode_timestamp(timestamp)
def encode(%TypeInfo{sender: "timestamptz"}, _, timestamp),
do: encode_timestamp(timestamp)
def encode(%TypeInfo{sender: "interval"}, _, interval),
do: encode_interval(interval)
def encode(%TypeInfo{sender: "array", oid: oid}, extra, list) when is_list(list),
do: encode_array(list, oid, extra)
def encode(%TypeInfo{sender: "record", oid: oid}, extra, tuple) when is_tuple(tuple),
do: encode_record(tuple, oid, extra)
def encode(%TypeInfo{}, _, _),
do: nil
Enum.each(@types, fn type ->
defp binary_type?(unquote(type)), do: true
end)
defp binary_type?(_), do: false
### decode helpers ###
defp decode_numeric(0, _weight, 0xC000, _scale, "") do
Decimal.new(1, :qNaN, 0)
end
defp decode_numeric(_num_digits, weight, sign, scale, bin) do
{value, weight} = decode_numeric_int(bin, weight, 0)
case sign do
0x0000 -> sign = 1
0x4000 -> sign = -1
end
{coef, exp} = scale(value, (weight+1)*4, -scale)
Decimal.new(sign, coef, exp)
end
defp scale(coef, exp, scale) when scale == exp,
do: {coef, exp}
defp scale(coef, exp, scale) when scale > exp,
do: scale(div(coef, 10), exp+1, scale)
defp scale(coef, exp, scale) when scale < exp,
do: scale(coef * 10, exp-1, scale)
defp decode_numeric_int("", weight, acc), do: {acc, weight}
defp decode_numeric_int(<< digit :: int16, tail :: binary >>, weight, acc) do
acc = (acc * @numeric_base) + digit
decode_numeric_int(tail, weight - 1, acc)
end
defp decode_date(days) do
:calendar.gregorian_days_to_date(days + @gd_epoch)
end
defp decode_time(microsecs) do
secs = div(microsecs, 1_000_000)
:calendar.seconds_to_time(secs)
end
defp decode_timestamp(microsecs) do
secs = div(microsecs, 1_000_000)
:calendar.gregorian_seconds_to_datetime(secs + @gs_epoch)
end
defp decode_interval(microsecs, days, months) do
{months, days, div(microsecs, 1_000_000)}
end
defp decode_array(<< ndims :: int32, _has_null :: int32, oid :: int32, rest :: binary >>,
{types, _} = extra) do
{dims, rest} = :erlang.split_binary(rest, ndims * 2 * 4)
lengths = for << len :: int32, _lbound :: int32 <- dims >>, do: len
info = Dict.fetch!(types, oid)
default = &decode_binary(info, extra, &1)
{array, ""} = decode_array(rest, info, extra, default, lengths)
array
end
defp decode_array("", _info, _extra, _default, []) do
{[], ""}
end
defp decode_array(rest, info, extra, default, [len]) do
array_elements(rest, info, extra, default, [], len)
end
defp decode_array(rest, info, extra, default, [len|lens]) do
Enum.map_reduce(1..len, rest, fn _, rest ->
decode_array(rest, info, extra, default, lens)
end)
end
defp array_elements(rest, _info, _extra, _default, acc, 0) do
{Enum.reverse(acc), rest}
end
defp array_elements(<< -1 :: int32, rest :: binary >>, info, extra, default, acc, count) do
array_elements(rest, info, extra, default, [nil|acc], count-1)
end
defp array_elements(<< length :: int32, elem :: binary(length), rest :: binary >>,
info, extra, default, acc, count) do
{_, decoder} = extra
value = decode_value(info, :binary, decoder, default, elem)
array_elements(rest, info, extra, default, [value|acc], count-1)
end
defp decode_record(<< num :: int32, rest :: binary >>, extra) do
record_elements(num, rest, extra) |> List.to_tuple
end
defp record_elements(0, <<>>, _extra) do
[]
end
defp record_elements(num, << _oid :: int32, -1 :: int32, rest :: binary >>, extra) do
[ nil | record_elements(num-1, rest, extra) ]
end
defp record_elements(num, << oid :: int32, length :: int32, elem :: binary(length), rest :: binary >>,
{types, decoder} = extra) do
info = Dict.fetch!(types, oid)
default = &decode_binary(info, extra, &1)
value = decode_value(info, :binary, decoder, default, elem)
[ value | record_elements(num-1, rest, extra) ]
end
### encode helpers ###
defp encode_numeric(dec) do
if Decimal.nan?(dec) do
<< 0 :: int16, 0 :: int16, 0xC000 :: uint16, 0 :: int16 >>
else
string = Decimal.to_string(dec, :normal) |> :binary.bin_to_list
if List.first(string) == ?- do
[_|string] = string
sign = 0x4000
else
sign = 0x0000
end
{int, float} = Enum.split_while(string, &(&1 != ?.))
{weight, int_digits} = Enum.reverse(int) |> encode_numeric_int(0, [])
if float != [] do
[_|float] = float
scale = length(float)
float_digits = encode_numeric_float(float, [])
else
scale = 0
float_digits = []
end
digits = int_digits ++ float_digits
bin = for digit <- digits, into: "", do: << digit :: uint16 >>
ndigits = div(byte_size(bin), 2)
<< ndigits :: int16, weight :: int16, sign :: uint16, scale :: int16, bin :: binary >>
end
end
defp encode_numeric_float([], [digit|acc]) do
[pad_float(digit)|acc]
|> trim_zeros
|> Enum.reverse
end
defp encode_numeric_float(list, acc) do
{list, rest} = Enum.split(list, 4)
digit = List.to_integer(list)
encode_numeric_float(rest, [digit|acc])
end
defp encode_numeric_int([], weight, acc) do
{weight, acc}
end
defp encode_numeric_int(list, weight, acc) do
{list, rest} = Enum.split(list, 4)
digit = Enum.reverse(list) |> List.to_integer
if rest != [], do: weight = weight + 1
encode_numeric_int(rest, weight, [digit|acc])
end
defp trim_zeros([0|tail]), do: trim_zeros(tail)
defp trim_zeros(list), do: list
defp pad_float(0) do
0
end
defp pad_float(num) do
num10 = num*10
if num10 >= @numeric_base do
num
else
pad_float(num10)
end
end
defp encode_date(date) do
<< :calendar.date_to_gregorian_days(date) - @gd_epoch :: int32 >>
end
defp encode_time(time) do
<< :calendar.time_to_seconds(time) * 1_000_000 :: int64 >>
end
defp encode_timestamp(timestamp) do
secs = :calendar.datetime_to_gregorian_seconds(timestamp) - @gs_epoch
<< secs * 1_000_000 :: int64 >>
end
defp encode_interval({months, days, secs}) do
microsecs = secs * 1_000_000
<< microsecs :: int64, days :: int32, months :: int32 >>
end
defp encode_array(list, oid, {types, _, _} = extra) do
%TypeInfo{array_elem: elem_oid} = Dict.fetch!(types, oid)
info = Dict.fetch!(types, elem_oid)
default = &encode(info, extra, &1)
{data, ndims, lengths} = encode_array(list, info, extra, default, 0, [])
bin = IO.iodata_to_binary(data)
lengths = for len <- Enum.reverse(lengths), into: "", do: << len :: int32, 1 :: int32 >>
<< ndims :: int32, 0 :: int32, elem_oid :: int32, lengths :: binary, bin :: binary >>
end
defp encode_array([], _info, _extra, _default, ndims, lengths) do
{"", ndims, lengths}
end
defp encode_array([head|tail]=list, info, extra, default, ndims, lengths)
when is_list(head) do
lengths = [length(list)|lengths]
{data, ndims, lengths} = encode_array(head, info, extra, default, ndims, lengths)
[dimlength|_] = lengths
rest = Enum.map(tail, fn sublist ->
{data, _, [len|_]} = encode_array(sublist, info, extra, default, ndims, lengths)
if len != dimlength do
throw {:postgrex_encode, "nested lists must have lists with matching lengths"}
end
data
end)
{[data|rest], ndims+1, lengths}
end
defp encode_array(list, info, extra, default, ndims, lengths) do
{data, length} = Enum.map_reduce(list, 0, fn elem, length ->
{:binary, bin} = encode_value(info, extra, default, elem)
{<< byte_size(bin) :: int32, bin :: binary >>, length + 1}
end)
{data, ndims+1, [length|lengths]}
end
defp encode_record(tuple, oid, {types, _, _} = extra) do
list = Tuple.to_list(tuple)
%TypeInfo{comp_elems: comp_oids} = Dict.fetch!(types, oid)
zipped = :lists.zip(list, comp_oids)
{data, count} = Enum.map_reduce(zipped, 0, fn {value, oid}, count ->
info = Dict.fetch!(types, oid)
default = &encode(info, extra, &1)
{:binary, bin} = encode_value(info, extra, default, value)
{<< oid :: int32, byte_size(bin) :: int32, bin :: binary >>, count + 1}
end)
<< count :: int32, IO.iodata_to_binary(data) :: binary >>
end
end