|
@@ -2,79 +2,112 @@
|
|
|
|
|
|
-module(pgsql_binary).
|
|
-module(pgsql_binary).
|
|
|
|
|
|
--export([encode/2, decode/2, supports/1]).
|
|
|
|
|
|
+-export([new_codec/1,
|
|
|
|
+ update_type_cache/2,
|
|
|
|
+ type2oid/2, oid2type/2,
|
|
|
|
+ encode/3, decode/3, supports/1]).
|
|
|
|
+
|
|
|
|
+-record(codec, {
|
|
|
|
+ type2oid = [],
|
|
|
|
+ oid2type = []
|
|
|
|
+}).
|
|
|
|
|
|
-include("pgsql_binary.hrl").
|
|
-include("pgsql_binary.hrl").
|
|
|
|
|
|
-define(datetime, (get(datetime_mod))).
|
|
-define(datetime, (get(datetime_mod))).
|
|
|
|
|
|
-encode(_Any, null) -> <<-1:?int32>>;
|
|
|
|
-encode(bool, true) -> <<1:?int32, 1:1/big-signed-unit:8>>;
|
|
|
|
-encode(bool, false) -> <<1:?int32, 0:1/big-signed-unit:8>>;
|
|
|
|
-encode(int2, N) -> <<2:?int32, N:1/big-signed-unit:16>>;
|
|
|
|
-encode(int4, N) -> <<4:?int32, N:1/big-signed-unit:32>>;
|
|
|
|
-encode(int8, N) -> <<8:?int32, N:1/big-signed-unit:64>>;
|
|
|
|
-encode(float4, N) -> <<4:?int32, N:1/big-float-unit:32>>;
|
|
|
|
-encode(float8, N) -> <<8:?int32, N:1/big-float-unit:64>>;
|
|
|
|
-encode(bpchar, C) when is_integer(C) -> <<1:?int32, C:1/big-unsigned-unit:8>>;
|
|
|
|
-encode(bpchar, B) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
-encode(time = Type, B) -> ?datetime:encode(Type, B);
|
|
|
|
-encode(timetz = Type, B) -> ?datetime:encode(Type, B);
|
|
|
|
-encode(date = Type, B) -> ?datetime:encode(Type, B);
|
|
|
|
-encode(timestamp = Type, B) -> ?datetime:encode(Type, B);
|
|
|
|
-encode(timestamptz = Type, B) -> ?datetime:encode(Type, B);
|
|
|
|
-encode(interval = Type, B) -> ?datetime:encode(Type, B);
|
|
|
|
-encode(bytea, B) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
-encode(text, B) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
-encode(varchar, B) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
-encode(uuid, B) when is_binary(B) -> encode_uuid(B);
|
|
|
|
-encode(hstore, {L}) when is_list(L) -> encode_hstore(L);
|
|
|
|
-encode({array, char}, L) when is_list(L) -> encode_array(bpchar, L);
|
|
|
|
-encode({array, Type}, L) when is_list(L) -> encode_array(Type, L);
|
|
|
|
-encode(Type, L) when is_list(L) -> encode(Type, list_to_binary(L));
|
|
|
|
-encode(_Type, _Value) -> {error, unsupported}.
|
|
|
|
-
|
|
|
|
-decode(bool, <<1:1/big-signed-unit:8>>) -> true;
|
|
|
|
-decode(bool, <<0:1/big-signed-unit:8>>) -> false;
|
|
|
|
-decode(bpchar, <<C:1/big-unsigned-unit:8>>) -> C;
|
|
|
|
-decode(int2, <<N:1/big-signed-unit:16>>) -> N;
|
|
|
|
-decode(int4, <<N:1/big-signed-unit:32>>) -> N;
|
|
|
|
-decode(int8, <<N:1/big-signed-unit:64>>) -> N;
|
|
|
|
-decode(float4, <<N:1/big-float-unit:32>>) -> N;
|
|
|
|
-decode(float8, <<N:1/big-float-unit:64>>) -> N;
|
|
|
|
-decode(record, <<_:?int32, Rest/binary>>) -> list_to_tuple(decode_record(Rest, []));
|
|
|
|
-decode(time = Type, B) -> ?datetime:decode(Type, B);
|
|
|
|
-decode(timetz = Type, B) -> ?datetime:decode(Type, B);
|
|
|
|
-decode(date = Type, B) -> ?datetime:decode(Type, B);
|
|
|
|
-decode(timestamp = Type, B) -> ?datetime:decode(Type, B);
|
|
|
|
-decode(timestamptz = Type, B) -> ?datetime:decode(Type, B);
|
|
|
|
-decode(interval = Type, B) -> ?datetime:decode(Type, B);
|
|
|
|
-decode(uuid, B) -> decode_uuid(B);
|
|
|
|
-decode(hstore, Hstore) -> decode_hstore(Hstore);
|
|
|
|
-decode({array, _Type}, B) -> decode_array(B);
|
|
|
|
-decode(_Other, Bin) -> Bin.
|
|
|
|
-
|
|
|
|
-encode_array(Type, A) ->
|
|
|
|
- {Data, {NDims, Lengths}} = encode_array(Type, A, 0, []),
|
|
|
|
- Oid = pgsql_types:type2oid(Type),
|
|
|
|
|
|
+new_codec([]) -> #codec{}.
|
|
|
|
+
|
|
|
|
+update_type_cache(TypeInfos, Codec) ->
|
|
|
|
+ Type2Oid = lists:flatmap(
|
|
|
|
+ fun({NameBin, ElementOid, ArrayOid}) ->
|
|
|
|
+ Name = erlang:binary_to_atom(NameBin, utf8),
|
|
|
|
+ [{Name, ElementOid}, {{array, Name}, ArrayOid}]
|
|
|
|
+ end,
|
|
|
|
+ TypeInfos),
|
|
|
|
+ Oid2Type = [{Oid, Type} || {Type, Oid} <- Type2Oid],
|
|
|
|
+ Codec#codec{type2oid = Type2Oid, oid2type = Oid2Type}.
|
|
|
|
+
|
|
|
|
+oid2type(Oid, #codec{oid2type = Oid2Type}) ->
|
|
|
|
+ case pgsql_types:oid2type(Oid) of
|
|
|
|
+ {unknown_oid, _} ->
|
|
|
|
+ proplists:get_value(Oid, Oid2Type, {unknown_oid, Oid});
|
|
|
|
+ Type -> Type
|
|
|
|
+ end.
|
|
|
|
+
|
|
|
|
+type2oid(Type, #codec{type2oid = Type2Oid}) ->
|
|
|
|
+ case pgsql_types:type2oid(Type) of
|
|
|
|
+ {unknown_type, _} ->
|
|
|
|
+ proplists:get_value(Type, Type2Oid, {unknown_type, Type});
|
|
|
|
+ Oid -> Oid
|
|
|
|
+ end.
|
|
|
|
+
|
|
|
|
+encode(_Any, null, _) -> <<-1:?int32>>;
|
|
|
|
+encode(bool, true, _) -> <<1:?int32, 1:1/big-signed-unit:8>>;
|
|
|
|
+encode(bool, false, _) -> <<1:?int32, 0:1/big-signed-unit:8>>;
|
|
|
|
+encode(int2, N, _) -> <<2:?int32, N:1/big-signed-unit:16>>;
|
|
|
|
+encode(int4, N, _) -> <<4:?int32, N:1/big-signed-unit:32>>;
|
|
|
|
+encode(int8, N, _) -> <<8:?int32, N:1/big-signed-unit:64>>;
|
|
|
|
+encode(float4, N, _) -> <<4:?int32, N:1/big-float-unit:32>>;
|
|
|
|
+encode(float8, N, _) -> <<8:?int32, N:1/big-float-unit:64>>;
|
|
|
|
+encode(bpchar, C, _) when is_integer(C) -> <<1:?int32, C:1/big-unsigned-unit:8>>;
|
|
|
|
+encode(bpchar, B, _) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
+encode(time = Type, B, _) -> ?datetime:encode(Type, B);
|
|
|
|
+encode(timetz = Type, B, _) -> ?datetime:encode(Type, B);
|
|
|
|
+encode(date = Type, B, _) -> ?datetime:encode(Type, B);
|
|
|
|
+encode(timestamp = Type, B, _) -> ?datetime:encode(Type, B);
|
|
|
|
+encode(timestamptz = Type, B, _) -> ?datetime:encode(Type, B);
|
|
|
|
+encode(interval = Type, B, _) -> ?datetime:encode(Type, B);
|
|
|
|
+encode(bytea, B, _) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
+encode(text, B, _) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
+encode(varchar, B, _) when is_binary(B) -> <<(byte_size(B)):?int32, B/binary>>;
|
|
|
|
+encode(uuid, B, _) when is_binary(B) -> encode_uuid(B);
|
|
|
|
+encode({array, char}, L, Codec) when is_list(L) -> encode_array(bpchar, type2oid(bpchar, Codec), L, Codec);
|
|
|
|
+encode({array, Type}, L, Codec) when is_list(L) -> encode_array(Type, type2oid(Type, Codec), L, Codec);
|
|
|
|
+encode(hstore, {L}, _) when is_list(L) -> encode_hstore(L);
|
|
|
|
+encode(Type, L, Codec) when is_list(L) -> encode(Type, list_to_binary(L), Codec);
|
|
|
|
+encode(_Type, _Value, _) -> {error, unsupported}.
|
|
|
|
+
|
|
|
|
+decode(bool, <<1:1/big-signed-unit:8>>, _) -> true;
|
|
|
|
+decode(bool, <<0:1/big-signed-unit:8>>, _) -> false;
|
|
|
|
+decode(bpchar, <<C:1/big-unsigned-unit:8>>, _) -> C;
|
|
|
|
+decode(int2, <<N:1/big-signed-unit:16>>, _) -> N;
|
|
|
|
+decode(int4, <<N:1/big-signed-unit:32>>, _) -> N;
|
|
|
|
+decode(int8, <<N:1/big-signed-unit:64>>, _) -> N;
|
|
|
|
+decode(float4, <<N:1/big-float-unit:32>>, _) -> N;
|
|
|
|
+decode(float8, <<N:1/big-float-unit:64>>, _) -> N;
|
|
|
|
+decode(record, <<_:?int32, Rest/binary>>, Codec) -> list_to_tuple(decode_record(Rest, [], Codec));
|
|
|
|
+decode(time = Type, B, _) -> ?datetime:decode(Type, B);
|
|
|
|
+decode(timetz = Type, B, _) -> ?datetime:decode(Type, B);
|
|
|
|
+decode(date = Type, B, _) -> ?datetime:decode(Type, B);
|
|
|
|
+decode(timestamp = Type, B, _) -> ?datetime:decode(Type, B);
|
|
|
|
+decode(timestamptz = Type, B, _) -> ?datetime:decode(Type, B);
|
|
|
|
+decode(interval = Type, B, _) -> ?datetime:decode(Type, B);
|
|
|
|
+decode(uuid, B, _) -> decode_uuid(B);
|
|
|
|
+decode(hstore, Hstore, _) -> decode_hstore(Hstore);
|
|
|
|
+decode({array, _Type}, B, Codec) -> decode_array(B, Codec);
|
|
|
|
+decode(_Other, Bin, _) -> Bin.
|
|
|
|
+
|
|
|
|
+encode_array(Type, Oid, A, Codec) ->
|
|
|
|
+ {Data, {NDims, Lengths}} = encode_array(Type, A, 0, [], Codec),
|
|
Lens = [<<N:?int32, 1:?int32>> || N <- lists:reverse(Lengths)],
|
|
Lens = [<<N:?int32, 1:?int32>> || N <- lists:reverse(Lengths)],
|
|
Hdr = <<NDims:?int32, 0:?int32, Oid:?int32>>,
|
|
Hdr = <<NDims:?int32, 0:?int32, Oid:?int32>>,
|
|
Bin = iolist_to_binary([Hdr, Lens, Data]),
|
|
Bin = iolist_to_binary([Hdr, Lens, Data]),
|
|
<<(byte_size(Bin)):?int32, Bin/binary>>.
|
|
<<(byte_size(Bin)):?int32, Bin/binary>>.
|
|
|
|
|
|
-encode_array(_Type, [], NDims, Lengths) ->
|
|
|
|
|
|
+encode_array(_Type, [], NDims, Lengths, _Codec) ->
|
|
{<<>>, {NDims, Lengths}};
|
|
{<<>>, {NDims, Lengths}};
|
|
-encode_array(Type, [H | _] = Array, NDims, Lengths) when not is_list(H) ->
|
|
|
|
- F = fun(E, Len) -> {encode(Type, E), Len + 1} end,
|
|
|
|
|
|
+encode_array(Type, [H | _] = Array, NDims, Lengths, Codec) when not is_list(H) ->
|
|
|
|
+ F = fun(E, Len) -> {encode(Type, E, Codec), Len + 1} end,
|
|
{Data, Len} = lists:mapfoldl(F, 0, Array),
|
|
{Data, Len} = lists:mapfoldl(F, 0, Array),
|
|
{Data, {NDims + 1, [Len | Lengths]}};
|
|
{Data, {NDims + 1, [Len | Lengths]}};
|
|
-encode_array(uuid, [_H | _] = Array, NDims, Lengths) ->
|
|
|
|
- F = fun(E, Len) -> {encode(uuid, E), Len + 1} end,
|
|
|
|
|
|
+encode_array(uuid, [_H | _] = Array, NDims, Lengths, Codec) ->
|
|
|
|
+ F = fun(E, Len) -> {encode(uuid, E, Codec), Len + 1} end,
|
|
{Data, Len} = lists:mapfoldl(F, 0, Array),
|
|
{Data, Len} = lists:mapfoldl(F, 0, Array),
|
|
{Data, {NDims + 1, [Len | Lengths]}};
|
|
{Data, {NDims + 1, [Len | Lengths]}};
|
|
-encode_array(Type, Array, NDims, Lengths) ->
|
|
|
|
|
|
+encode_array(Type, Array, NDims, Lengths, Codec) ->
|
|
Lengths2 = [length(Array) | Lengths],
|
|
Lengths2 = [length(Array) | Lengths],
|
|
- F = fun(A2, {_NDims, _Lengths}) -> encode_array(Type, A2, NDims, Lengths2) end,
|
|
|
|
|
|
+ F = fun(A2, {_NDims, _Lengths}) -> encode_array(Type, A2, NDims, Lengths2, Codec) end,
|
|
{Data, {NDims2, Lengths3}} = lists:mapfoldl(F, {NDims, Lengths2}, Array),
|
|
{Data, {NDims2, Lengths3}} = lists:mapfoldl(F, {NDims, Lengths2}, Array),
|
|
{Data, {NDims2 + 1, Lengths3}}.
|
|
{Data, {NDims2 + 1, Lengths3}}.
|
|
|
|
|
|
@@ -105,36 +138,36 @@ encode_hstore_string(Str) when is_float(Str) ->
|
|
encode_hstore_string(iolist_to_binary(io_lib:format("~w", [Str])));
|
|
encode_hstore_string(iolist_to_binary(io_lib:format("~w", [Str])));
|
|
encode_hstore_string(Str) when is_binary(Str) -> <<(byte_size(Str)):?int32, Str/binary>>.
|
|
encode_hstore_string(Str) when is_binary(Str) -> <<(byte_size(Str)):?int32, Str/binary>>.
|
|
|
|
|
|
-decode_array(<<NDims:?int32, _HasNull:?int32, Oid:?int32, Rest/binary>>) ->
|
|
|
|
|
|
+decode_array(<<NDims:?int32, _HasNull:?int32, Oid:?int32, Rest/binary>>, Codec) ->
|
|
{Dims, Data} = erlang:split_binary(Rest, NDims * 2 * 4),
|
|
{Dims, Data} = erlang:split_binary(Rest, NDims * 2 * 4),
|
|
Lengths = [Len || <<Len:?int32, _LBound:?int32>> <= Dims],
|
|
Lengths = [Len || <<Len:?int32, _LBound:?int32>> <= Dims],
|
|
- Type = pgsql_types:oid2type(Oid),
|
|
|
|
- {Array, <<>>} = decode_array(Data, Type, Lengths),
|
|
|
|
|
|
+ Type = oid2type(Oid, Codec),
|
|
|
|
+ {Array, <<>>} = decode_array(Data, Type, Lengths, Codec),
|
|
Array.
|
|
Array.
|
|
|
|
|
|
-decode_array(Data, _Type, []) ->
|
|
|
|
|
|
+decode_array(Data, _Type, [], _Codec) ->
|
|
{[], Data};
|
|
{[], Data};
|
|
-decode_array(Data, Type, [Len]) ->
|
|
|
|
- decode_elements(Data, Type, [], Len);
|
|
|
|
-decode_array(Data, Type, [Len | T]) ->
|
|
|
|
- F = fun(_N, Rest) -> decode_array(Rest, Type, T) end,
|
|
|
|
|
|
+decode_array(Data, Type, [Len], Codec) ->
|
|
|
|
+ decode_elements(Data, Type, [], Len, Codec);
|
|
|
|
+decode_array(Data, Type, [Len | T], Codec) ->
|
|
|
|
+ F = fun(_N, Rest) -> decode_array(Rest, Type, T, Codec) end,
|
|
lists:mapfoldl(F, Data, lists:seq(1, Len)).
|
|
lists:mapfoldl(F, Data, lists:seq(1, Len)).
|
|
|
|
|
|
-decode_elements(Rest, _Type, Acc, 0) ->
|
|
|
|
|
|
+decode_elements(Rest, _Type, Acc, 0, _Codec) ->
|
|
{lists:reverse(Acc), Rest};
|
|
{lists:reverse(Acc), Rest};
|
|
-decode_elements(<<-1:?int32, Rest/binary>>, Type, Acc, N) ->
|
|
|
|
- decode_elements(Rest, Type, [null | Acc], N - 1);
|
|
|
|
-decode_elements(<<Len:?int32, Value:Len/binary, Rest/binary>>, Type, Acc, N) ->
|
|
|
|
- Value2 = decode(Type, Value),
|
|
|
|
- decode_elements(Rest, Type, [Value2 | Acc], N - 1).
|
|
|
|
|
|
+decode_elements(<<-1:?int32, Rest/binary>>, Type, Acc, N, Codec) ->
|
|
|
|
+ decode_elements(Rest, Type, [null | Acc], N - 1, Codec);
|
|
|
|
+decode_elements(<<Len:?int32, Value:Len/binary, Rest/binary>>, Type, Acc, N, Codec) ->
|
|
|
|
+ Value2 = decode(Type, Value, Codec),
|
|
|
|
+ decode_elements(Rest, Type, [Value2 | Acc], N - 1, Codec).
|
|
|
|
|
|
-decode_record(<<>>, Acc) ->
|
|
|
|
|
|
+decode_record(<<>>, Acc, _Codec) ->
|
|
lists:reverse(Acc);
|
|
lists:reverse(Acc);
|
|
-decode_record(<<_Type:?int32, -1:?int32, Rest/binary>>, Acc) ->
|
|
|
|
- decode_record(Rest, [null | Acc]);
|
|
|
|
-decode_record(<<Type:?int32, Len:?int32, Value:Len/binary, Rest/binary>>, Acc) ->
|
|
|
|
- Value2 = decode(pgsql_types:oid2type(Type), Value),
|
|
|
|
- decode_record(Rest, [Value2 | Acc]).
|
|
|
|
|
|
+decode_record(<<_Type:?int32, -1:?int32, Rest/binary>>, Acc, Codec) ->
|
|
|
|
+ decode_record(Rest, [null | Acc], Codec);
|
|
|
|
+decode_record(<<Type:?int32, Len:?int32, Value:Len/binary, Rest/binary>>, Acc, Codec) ->
|
|
|
|
+ Value2 = decode(oid2type(Type, Codec), Value, Codec),
|
|
|
|
+ decode_record(Rest, [Value2 | Acc], Codec).
|
|
|
|
|
|
decode_uuid(<<U0:32, U1:16, U2:16, U3:16, U4:48>>) ->
|
|
decode_uuid(<<U0:32, U1:16, U2:16, U3:16, U4:48>>) ->
|
|
Format = "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b",
|
|
Format = "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b",
|