Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
{erl_opts, [debug_info]}.
{deps, []}.

{xref_ignores, [{pg_types, format_error, 1},
{xref_ignores, [{pg_codec, behaviour_info, 1},
{pg_codec, decode, 4},
{pg_codec, decode_text, 4},
{pg_codec, encode, 4},
{pg_codec, init_mods, 2},
{pg_types, format_error, 1},
{pg_types, update_map, 3}]}.

%% `rebar3 as test proper`
{profiles,
[{test,
[{deps, [{proper, "1.3.0"}]},
{plugins, [rebar3_proper]}
]}]
}.
23 changes: 21 additions & 2 deletions src/pg_date.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,29 @@ init(_Opts) ->
{[<<"date_send">>], []}.

encode(Date, _TypeInfo) ->
<<4:?int32, (encode_date(Date)):?int32>>.
[<<4:?int32>>, <<(encode_date(Date) - ?POSTGRESQL_GD_EPOCH):?int32>>].

decode(<<Date:?int32>>, _TypeInfo) ->
calendar:gregorian_days_to_date(Date + ?POSTGRESQL_GD_EPOCH).
decode_date(Date + ?POSTGRESQL_GD_EPOCH).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to verify that over the real postgresql server. But that's how it's done in epgsql.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean "verify over the real postgresql server"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, we are converting datetime to some integer and send it to PostgreSQL; then we read it back as integer and convert back to datetime using the same algorithm. So, what we send and what we will receive will be the same, but what should be verified is that this integer is interpreted as the same YYYY-MM-DD_HH:mm:ss as in Erlang.
Say, we have {{2019, 11, 9}, {19, 59, 0}} in erlang. We encode it to some integer, send it to postgres, but what if postgres interprets this integer as some other date? Are we sure ituses gregorian_days_to_date?


%% Julian <-> Gregorian
decode_date(N) ->
J = N + 32044,
Q1 = J div 146097,
Extra = (J - Q1 * 146097) * 4 + 3,
J2 = J + 60 + Q1 * 3 + Extra div 146097,
Q2 = J2 div 1461,
J3 = J2 - Q2 * 1461,
Y = J3 * 4 div 1461,
J4 = case Y of
0 -> ((J3 + 306) rem 366) + 123;
_ -> ((J3 + 305) rem 365) + 123
end,
Year = (Y + Q2 * 4) - 4800,
Q3 = J4 * 2141 div 65536,
Day = J4 - 7834 * Q3 div 256,
Month = (Q3 + 10) rem 12 + 1,
{Year, Month, Day}.

encode_date(Date) ->
calendar:date_to_gregorian_days(Date) - ?POSTGRESQL_GD_EPOCH.
Expand Down
2 changes: 1 addition & 1 deletion src/pg_uuid.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ decode(Uuid, #type_info{config=binary}) ->
decode(<<U0:32, U1:16, U2:16, U3:16, U4:48>>, #type_info{config=string}) ->
Format = "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b",
iolist_to_binary(io_lib:format(Format, [U0, U1, U2, U3, U4]));
decode(<<Uuid:128>>, #type_info{config=integer}) ->
decode(<<Uuid:?int128>>, #type_info{config=integer}) ->
Uuid.

type_spec() ->
Expand Down
11 changes: 11 additions & 0 deletions test/prop_bin.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-module(prop_bin).
-include_lib("proper/include/proper.hrl").


prop_raw_codec() ->
?FORALL(Val, proper_types:binary(),
proper_lib:codec(pg_raw, [], Val)).

prop_bitstring_codec() ->
?FORALL(Val, proper_types:bitstring(),
proper_lib:codec(pg_bit_string, [], Val)).
7 changes: 7 additions & 0 deletions test/prop_boolean.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-module(prop_boolean).
-include_lib("proper/include/proper.hrl").


prop_codec() ->
?FORALL(Val, proper_types:boolean(),
proper_lib:codec(pg_boolean, [], Val)).
6 changes: 6 additions & 0 deletions test/prop_date.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(prop_date).
-include_lib("proper/include/proper.hrl").

prop_codec() ->
?FORALL(Val, proper_lib:date_gen(),
proper_lib:codec(pg_date, [], Val)).
21 changes: 21 additions & 0 deletions test/prop_float.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-module(prop_float).
-include_lib("proper/include/proper.hrl").

%% TODO: fix truncation problem
%% prop_float4_codec() ->
%% ?FORALL(Val, proper_types:float(-999.99, 990.99),
%% proper_lib:codec(pg_float4, [], Val, fun canonical_float4/1)).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess can be fixed by smth like:

float32_gen() ->
  ?LET(F, proper_types:float(),
        begin
          <<F1:32/float>> = <<F:32/float>>,
          F1
        end).

so, truncation is done inside the generator, not inside the codec? Or is there any better solution?

%%
%% canonical_float4(N) ->
%% round(N * 100).

prop_float8_codec() ->
?FORALL(Val, float64(),
proper_lib:codec(pg_float8, [], Val)).

float64() ->
proper_types:frequency(
[{85, proper_types:float()},
{5, plus_infinity},
{5, minus_infinity},
{5, nan}]).
58 changes: 58 additions & 0 deletions test/prop_geometry.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
%% https://www.postgresql.org/docs/current/datatype-geometric.html
-module(prop_geometry).
-include_lib("proper/include/proper.hrl").

prop_point_codec() ->
?FORALL(Val, point(),
proper_lib:codec(pg_point, [], Val)).


prop_line_codec() ->
?FORALL(Val, line(),
proper_lib:codec(pg_line, [], Val)).


prop_line_segment_codec() ->
?FORALL({P1, P2}, {point(), point()},
proper_lib:codec(pg_line_segment, [],
#{point1 => P1, point2 => P2})).

%% prop_box_codec() ->
%% not_implemented.

prop_path_codec() ->
?FORALL({Open, Points},
{proper_types:boolean(),
proper_types:list(point())},
proper_lib:codec(pg_path, [],
#{open => Open, points => Points})).


prop_polygon_codec() ->
?FORALL(Vertices,
proper_types:list(point()),
proper_lib:codec(pg_polygon, [],
#{vertices => Vertices})).

prop_circle_codec() ->
?FORALL({Center, Radius},
{point(), coord()},
proper_lib:codec(pg_circle, [],
#{center => Center, radius => Radius})).
%%
%% Types
%%

point() ->
?LET({X, Y}, {coord(), coord()},
#{x => X, y => Y}).

line() ->
?LET({A, B, C},
{coord(),
coord(),
coord()},
#{a => A, b => B, c => C}).

coord() ->
proper_types:float().
39 changes: 39 additions & 0 deletions test/prop_hstore.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-module(prop_hstore).
-include_lib("proper/include/proper.hrl").

prop_binary_codec() ->
?FORALL(Val, bin_map(),
proper_lib:codec(pg_hstore, [], Val)).

bin_map() ->
?LET(KV,
proper_types:list(
{proper_types:binary(),
proper_types:binary()}
),
maps:from_list(KV)).

prop_strkey_codec() ->
?FORALL(Val, strkey_map(),
proper_lib:codec(pg_hstore, [], Val, fun canonical_keys/1)).

strkey_map() ->
?LET(KV,
proper_types:list(
{proper_types:oneof(
[proper_types:atom(),
proper_types:list(proper_types:byte()),
proper_types:binary()]),
proper_types:binary()}
),
maps:from_list(KV)).

canonical_keys(Map) ->
maps:fold(
fun(K, V, Acc) when is_list(K) ->
Acc#{list_to_binary(K) => V};
(K, V, Acc) when is_atom(K) ->
Acc#{atom_to_binary(K, utf8) => V};
(K, V, Acc) ->
Acc#{K => V}
end, #{}, Map).
30 changes: 30 additions & 0 deletions test/prop_inet.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-module(prop_inet).
-include_lib("proper/include/proper.hrl").

prop_ip4_codec() ->
?FORALL(Val, ipv4(),
proper_lib:codec(pg_inet, [], Val)).

prop_ip6_codec() ->
?FORALL(Val, ipv6(),
proper_lib:codec(pg_inet, [], Val)).

prop_ip4_cidr_codec() ->
?FORALL(Val, {ipv4(), proper_types:integer(0, 32)},
proper_lib:codec(pg_inet, [], Val)).

prop_ip6_cidr_codec() ->
?FORALL(Val, {ipv6(), proper_types:integer(4, 128)},
proper_lib:codec(pg_inet, [], Val)).


ipv4() ->
{proper_types:byte(),
proper_types:byte(),
proper_types:byte(),
proper_types:byte()}.

ipv6() ->
E = proper_types:integer(0, 65535),
{E, E, E, E,
E, E, E, E}.
14 changes: 14 additions & 0 deletions test/prop_int.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-module(prop_int).
-include_lib("proper/include/proper.hrl").

prop_int2_codec() ->
?FORALL(Val, proper_lib:int16(),
proper_lib:codec(pg_int2, [], Val)).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking now maybe we should add a guards to integer encoders that ensure that the value is within the allowed range, instead of silently truncating when overflown?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, agreed.


prop_int4_codec() ->
?FORALL(Val, proper_lib:int32(),
proper_lib:codec(pg_int4, [], Val)).

prop_int8_codec() ->
?FORALL(Val, proper_lib:int64(),
proper_lib:codec(pg_int8, [], Val)).
53 changes: 53 additions & 0 deletions test/prop_json.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-module(prop_json).
-include_lib("proper/include/proper.hrl").

%% Fake JSON encoder
-export([decode/2, encode/2]).

prop_json_codec() ->
?FORALL(Val, json_str(),
proper_lib:codec(pg_json, [], Val)).

prop_jsonb_codec() ->
?FORALL(Val, json_str(),
proper_lib:codec(pg_jsonb, [], Val)).

prop_custom_json_codec() ->
Config = #{json_config => {?MODULE, [compressed], []}},
?FORALL(Val, json_struct(),
proper_lib:codec(pg_json, Config, Val)).

prop_custom_jsonb_codec() ->
Config = #{json_config => {?MODULE, [compressed], []}},
?FORALL(Val, json_struct(),
proper_lib:codec(pg_jsonb, Config, Val)).


%% Fake JSON codec
decode(Bin, _) ->
binary_to_term(Bin).

encode(Term, Opts) ->
term_to_binary(Term, Opts).

%% Generators

json_str() ->
proper_types:binary().


json_struct() ->
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to implement recursive generator for JSON, but failed to do so =) But it isn't needed really, because we are just doing term_to_binary anyway.

?LET(KV,
proper_types:list({j_string(), j_literal()}),
maps:from_list(KV)).

j_string() ->
proper_types:string().

j_literal() ->
proper_types:oneof(
[j_string(),
proper_types:integer(1, 10),
proper_types:float(),
proper_types:boolean(),
null]).
10 changes: 10 additions & 0 deletions test/prop_macaddr.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-module(prop_macaddr).
-include_lib("proper/include/proper.hrl").


prop_codec() ->
B = proper_types:byte(),
?FORALL(Val,
{B, B, B,
B, B, B},
proper_lib:codec(pg_macaddr, [], Val)).
13 changes: 13 additions & 0 deletions test/prop_name.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-module(prop_name).
-include_lib("proper/include/proper.hrl").


prop_codec() ->
?FORALL(Val, bin64(),
proper_lib:codec(pg_name, [], Val)).


bin64() ->
?LET(Size,
proper_types:integer(0, 63),
proper_types:binary(Size)).
6 changes: 6 additions & 0 deletions test/prop_time.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-module(prop_time).
-include_lib("proper/include/proper.hrl").

prop_int_sec_codec() ->
?FORALL(Val, proper_lib:int_time_gen(),
proper_lib:codec(pg_time, [], Val)).
13 changes: 13 additions & 0 deletions test/prop_timestamp.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-module(prop_timestamp).
-include_lib("proper/include/proper.hrl").

prop_int_sec_codec() ->
?FORALL(Val, int_timestamp(),
proper_lib:codec(pg_timestamp, [], Val)).

prop_tz_int_sec_codec() ->
?FORALL(Val, int_timestamp(),
proper_lib:codec(pg_timestampz, [], Val)).

int_timestamp() ->
{proper_lib:date_gen(), proper_lib:int_time_gen()}.
25 changes: 25 additions & 0 deletions test/prop_tsvector.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-module(prop_tsvector).
-include_lib("proper/include/proper.hrl").


prop_codec() ->
?FORALL(Val, tsvector(),
proper_lib:codec(pg_tsvector, [], Val)).

tsvector() ->
proper_types:list(lexeme()).

lexeme() ->
{str(), % word
proper_types:list(position())}. % positions

position() ->
{proper_types:integer(0, 8192), % position
proper_types:oneof(['A', 'B', 'C', null])}. % weight

str() ->
?LET(
NoZeroStr,
?SUCHTHAT(S, proper_types:list(proper_types:byte()),
not lists:member(0, S)),
list_to_binary(NoZeroStr)).
Loading