Browse Source

Merge pull request #166 from eryx67/master

support tsrange, tstzrange, daterange types
Sergey Prokhorov 6 years ago
parent
commit
447417513f

+ 6 - 1
README.md

@@ -425,7 +425,9 @@ PG type       | Representation
   cidr        | `{ip_address(), Mask :: 0..32}`
   macaddr(8)  | tuple of 6 or 8 `byte()`
   geometry    | `ewkb:geometry()`
-
+  tsrange     | `{{Hour, Minute, Second.Microsecond}, {Hour, Minute, Second.Microsecond}}`
+  tstzrange   | `{{Hour, Minute, Second.Microsecond}, {Hour, Minute, Second.Microsecond}}`
+  daterange   | `{{Year, Month, Day}, {Year, Month, Day}}`
 
   `timestamp` and `timestamptz` parameters can take `erlang:now()` format: `{MegaSeconds, Seconds, MicroSeconds}`
 
@@ -433,6 +435,9 @@ PG type       | Representation
   bracket and parentheses respectively. Additionally, infinities are represented by the atoms `minus_infinity`
   and `plus_infinity`
 
+  `tsrange`, `tstzrange`, `daterange` are range types for `timestamp`, `timestamptz` and `date`
+  respectively. They can return `empty` atom as the result from a database if bounds are equal
+
 ## Errors
 
 Errors originating from the PostgreSQL backend are returned as `{error, #error{}}`,

+ 61 - 0
src/datatypes/epgsql_codec_timerange.erl

@@ -0,0 +1,61 @@
+%%% @doc
+%%% Codec for `tsrange', `tstzrange', `daterange' types.
+%%% https://www.postgresql.org/docs/current/static/rangetypes.html#rangetypes-builtin
+%%% $PG$/src/backend/utils/adt/rangetypes.c
+%%% @end
+%%% Created : 16 Jul 2018 by Vladimir Sekissov <eryx67@gmail.com>
+%%% TODO: universal range, based on pg_range table
+%%% TODO: inclusive/exclusive ranges `[]' `[)' `(]' `()'
+
+-module(epgsql_codec_timerange).
+-behaviour(epgsql_codec).
+
+-export([init/2, names/0, encode/3, decode/3, decode_text/3]).
+
+-include("protocol.hrl").
+
+-export_type([data/0]).
+
+-type data() :: {epgsql_codec_datetime:data(), epgsql_codec_datetime:data()} | empty.
+
+init(_, Sock) ->
+    case epgsql_sock:get_parameter_internal(<<"integer_datetimes">>, Sock) of
+        <<"on">>  -> epgsql_idatetime;
+        <<"off">> -> epgsql_fdatetime
+    end.
+
+names() ->
+    [tsrange, tstzrange, daterange].
+
+encode(empty, _T, _CM) ->
+    <<1>>;
+encode({From, To}, Type, EncMod) ->
+    FromBin = encode_member(Type, From, EncMod),
+    ToBin = encode_member(Type, To, EncMod),
+    <<2:1/big-signed-unit:8,
+      (byte_size(FromBin)):?int32, FromBin/binary,
+      (byte_size(ToBin)):?int32, ToBin/binary>>.
+
+decode(<<1>>, _, _) ->
+    empty;
+decode(<<2:1/big-signed-unit:8,
+         FromLen:?int32, FromBin:FromLen/binary,
+         ToLen:?int32, ToBin:ToLen/binary>>,
+       Type, EncMod) ->
+    {decode_member(Type, FromBin, EncMod), decode_member(Type, ToBin, EncMod)}.
+
+decode_text(V, _, _) -> V.
+
+encode_member(Type, Val, epgsql_idatetime) ->
+    epgsql_idatetime:encode(member_type(Type), Val);
+encode_member(Type, Val, epgsql_fdatetime) ->
+    epgsql_fdatetime:encode(member_type(Type), Val).
+
+decode_member(Type, Bin, epgsql_idatetime) ->
+    epgsql_idatetime:decode(member_type(Type), Bin);
+decode_member(Type, Bin, epgsql_fdatetime) ->
+    epgsql_fdatetime:decode(member_type(Type), Bin).
+
+member_type(tsrange) -> timestamp;
+member_type(tstzrange) -> timestamptz;
+member_type(daterange) -> date.

+ 8 - 2
src/epgsql_binary.erl

@@ -281,7 +281,9 @@ default_codecs() ->
      {epgsql_codec_net, []},
      %% {epgsql_codec_postgis,[]},
      {epgsql_codec_text, []},
-     {epgsql_codec_uuid, []}].
+     {epgsql_codec_timerange, []},
+     {epgsql_codec_uuid, []}
+    ].
 
 -spec default_oids() -> [epgsql_oid_db:oid_entry()].
 default_oids() ->
@@ -291,6 +293,7 @@ default_oids() ->
      {char, 18, 1002},
      {cidr, 650, 651},
      {date, 1082, 1182},
+     {daterange, 3912, 3913},
      {float4, 700, 1021},
      {float8, 701, 1022},
      %% {geometry, 17063, 17071},
@@ -312,5 +315,8 @@ default_oids() ->
      {timestamp, 1114, 1115},
      {timestamptz, 1184, 1185},
      {timetz, 1266, 1270},
+     {tsrange, 3908, 3909},
+     {tstzrange, 3910, 3911},
      {uuid, 2950, 2951},
-     {varchar, 1043, 1015}].
+     {varchar, 1043, 1015}
+    ].

+ 5 - 1
test/data/test_schema.sql

@@ -68,7 +68,11 @@ CREATE TABLE test_table2 (
   c_int4range int4range,
   c_int8range int8range,
   c_json json,
-  c_jsonb jsonb);
+  c_jsonb jsonb,
+  c_tsrange tsrange,
+  c_tstzrange tstzrange,
+  c_daterange daterange
+  );
 
 -- CREATE LANGUAGE plpgsql;
 

+ 11 - 0
test/epgsql_SUITE.erl

@@ -61,6 +61,7 @@ groups() ->
             array_type,
             range_type,
             range8_type,
+            date_time_range_type,
             custom_types
         ]},
         {generic, [parallel], [
@@ -1096,6 +1097,16 @@ range8_type(Config) ->
         ])
     end, []).
 
+date_time_range_type(Config) ->
+    epgsql_ct:with_min_version(Config, [9, 2], fun(_C) ->
+        check_type(Config, tsrange, "tsrange('2008-01-02 03:04:05', '2008-02-02 03:04:05')", {{{2008,1,2},{3,4,5.0}}, {{2008,2,2},{3,4,5.0}}}, []),
+       check_type(Config, tsrange, "tsrange('2008-01-02 03:04:05', '2008-01-02 03:04:05')", empty, []),
+
+       check_type(Config, daterange, "daterange('2008-01-02', '2008-02-02')", {{2008,1,2}, {2008, 2, 2}}, [{{-4712,1,1}, {5874897,1,1}}
+]),
+      check_type(Config, tstzrange, "tstzrange('2011-01-02 03:04:05+3', '2011-01-02 04:04:05+3')", {{{2011, 1, 2}, {0, 4, 5.0}}, {{2011, 1, 2}, {1, 4, 5.0}}}, [{{{2011, 1, 2}, {0, 4, 5.0}}, {{2011, 1, 2}, {1, 4, 5.0}}}])
+
+   end, []).
 
 with_transaction(Config) ->
     Module = ?config(module, Config),

+ 9 - 1
test/epgsql_cth.erl

@@ -209,7 +209,15 @@ write_pg_hba_config(Config) ->
         "host    epgsql_test_db1 epgsql_test             127.0.0.1/32    trust\n",
         "host    epgsql_test_db1 epgsql_test_md5         127.0.0.1/32    md5\n",
         "host    epgsql_test_db1 epgsql_test_cleartext   127.0.0.1/32    password\n",
-        "hostssl epgsql_test_db1 epgsql_test_cert        127.0.0.1/32    cert clientcert=1\n" |
+        "hostssl epgsql_test_db1 epgsql_test_cert        127.0.0.1/32    cert clientcert=1\n",
+        "host    template1       ", User, "              ::1/128    trust\n",
+        "host    ", User, "      ", User, "              ::1/128    trust\n",
+        "hostssl postgres        ", User, "              ::1/128    trust\n",
+        "host    epgsql_test_db1 ", User, "              ::1/128    trust\n",
+        "host    epgsql_test_db1 epgsql_test             ::1/128    trust\n",
+        "host    epgsql_test_db1 epgsql_test_md5         ::1/128    md5\n",
+        "host    epgsql_test_db1 epgsql_test_cleartext   ::1/128    password\n",
+        "hostssl epgsql_test_db1 epgsql_test_cert        ::1/128    cert clientcert=1\n" |
         case Version >= [10] of
             true ->
                 %% See