Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

implement support for integer_datetimes

--HG--
rename : src/pgsql_datetime.erl => src/pgsql_fdatetime.erl
  • Loading branch information...
commit 7f8eeb68c8c003ea72f15e0787f399d47dbff469 1 parent 51fb864
@wg authored
View
2  Makefile
@@ -30,7 +30,7 @@ clean:
@rm -rf $(NAME)-$(VERSION) $(NAME)-*.tar.gz
test: $(TESTS:test_src/%.erl=test_ebin/%.beam) $(BEAMS)
- dialyzer --src -c src
+ @dialyzer --src -c src
$(ERL) -pa ebin/ -pa test_ebin/ -noshell -s pgsql_tests run_tests -s init stop
# ------------------------------------------------------------------------
View
13 src/pgsql_binary.erl
@@ -5,6 +5,7 @@
-export([encode/2, decode/2, supports/1]).
-define(int32, 1/big-signed-unit:32).
+-define(datetime, (get(datetime_mod))).
encode(_Any, null) -> <<-1:?int32>>;
encode(bool, true) -> <<1:?int32, 1:1/big-signed-unit:8>>;
@@ -15,9 +16,9 @@ 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(Type, B) when Type == time; Type == timetz -> pgsql_datetime:encode(Type, B);
-encode(Type, B) when Type == date; Type == timestamp -> pgsql_datetime:encode(Type, B);
-encode(Type, B) when Type == timestamptz; Type == interval -> pgsql_datetime:encode(Type, B);
+encode(Type, B) when Type == time; Type == timetz -> ?datetime:encode(Type, B);
+encode(Type, B) when Type == date; Type == timestamp -> ?datetime:encode(Type, B);
+encode(Type, B) when Type == timestamptz; Type == interval -> ?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>>;
@@ -33,9 +34,9 @@ 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(Type, B) when Type == time; Type == timetz -> pgsql_datetime:decode(Type, B);
-decode(Type, B) when Type == date; Type == timestamp -> pgsql_datetime:decode(Type, B);
-decode(Type, B) when Type == timestamptz; Type == interval -> pgsql_datetime:decode(Type, B);
+decode(Type, B) when Type == time; Type == timetz -> ?datetime:decode(Type, B);
+decode(Type, B) when Type == date; Type == timestamp -> ?datetime:decode(Type, B);
+decode(Type, B) when Type == timestamptz; Type == interval -> ?datetime:decode(Type, B);
decode(_Other, Bin) -> Bin.
decode_record(<<>>, Acc) ->
View
7 src/pgsql_connection.erl
@@ -193,9 +193,14 @@ initializing({$E, Bin}, State) ->
%% ReadyForQuery
initializing({$Z, <<Status:8>>}, State) ->
+ #state{parameters = Parameters, reply_to = Reply_To} = State,
erase(username),
erase(password),
- gen_fsm:reply(State#state.reply_to, {ok, self()}),
+ case lists:keysearch(<<"integer_datetimes">>, 1, Parameters) of
+ {value, {_, <<"on">>}} -> put(datetime_mod, pgsql_idatetime);
+ {value, {_, <<"off">>}} -> put(datetime_mod, pgsql_fdatetime)
+ end,
+ gen_fsm:reply(Reply_To, {ok, self()}),
{next_state, ready, State#state{txstatus = Status}}.
ready(_Msg, State) ->
View
3  src/pgsql_datetime.erl → src/pgsql_fdatetime.erl
@@ -1,10 +1,9 @@
%%% Copyright (C) 2008 - Will Glozer. All rights reserved.
--module(pgsql_datetime).
+-module(pgsql_fdatetime).
-export([decode/2, encode/2]).
--define(int16, 1/big-signed-unit:16).
-define(int32, 1/big-signed-unit:32).
-define(postgres_epoc_jdate, 2451545).
View
96 src/pgsql_idatetime.erl
@@ -0,0 +1,96 @@
+%%% Copyright (C) 2008 - Will Glozer. All rights reserved.
+
+-module(pgsql_idatetime).
+
+-export([decode/2, encode/2]).
+
+-define(int32, 1/big-signed-unit:32).
+-define(int64, 1/big-signed-unit:64).
+
+-define(postgres_epoc_jdate, 2451545).
+
+-define(mins_per_hour, 60).
+-define(secs_per_minute, 60).
+
+-define(usecs_per_day, 86400000000).
+-define(usecs_per_hour, 3600000000).
+-define(usecs_per_minute, 60000000).
+-define(usecs_per_sec, 1000000).
+
+decode(date, <<J:?int32>>) -> j2date(?postgres_epoc_jdate + J);
+decode(time, <<N:?int64>>) -> i2time(N);
+decode(timetz, <<N:?int64, TZ:?int32>>) -> {i2time(N), TZ};
+decode(timestamp, <<N:?int64>>) -> i2timestamp(N);
+decode(timestamptz, <<N:?int64>>) -> i2timestamp(N);
+decode(interval, <<N:?int64, D:?int32, M:?int32>>) -> {i2time(N), D, M}.
+
+encode(date, D) -> <<4:?int32, (date2j(D) - ?postgres_epoc_jdate):?int32>>;
+encode(time, T) -> <<8:?int32, (time2i(T)):?int64>>;
+encode(timetz, {T, TZ}) -> <<12:?int32, (time2i(T)):?int64, TZ:?int32>>;
+encode(timestamp, TS) -> <<8:?int32, (timestamp2i(TS)):?int64>>;
+encode(timestamptz, TS) -> <<8:?int32, (timestamp2i(TS)):?int64>>;
+encode(interval, {T, D, M}) -> <<16:?int32, (time2i(T)):?int64, D:?int32, M:?int32>>.
+
+j2date(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,
+ case Y of
+ 0 -> J4 = ((J3 + 306) rem 366) + 123;
+ _ -> J4 = ((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}.
+
+date2j({Y, M, D}) ->
+ case M > 2 of
+ true ->
+ M2 = M + 1,
+ Y2 = Y + 4800;
+ false ->
+ M2 = M + 13,
+ Y2 = Y + 4799
+ end,
+ C = Y2 div 100,
+ J1 = Y2 * 365 - 32167,
+ J2 = J1 + (Y2 div 4 - C + C div 4),
+ J2 + 7834 * M2 div 256 + D.
+
+i2time(N) ->
+ Hour = N div ?usecs_per_hour,
+ R1 = N - Hour * ?usecs_per_hour,
+ Min = R1 div ?usecs_per_minute,
+ R2 = R1 - Min * ?usecs_per_minute,
+ Sec = R2 div ?usecs_per_sec,
+ US = R2 - Sec * ?usecs_per_sec,
+ {Hour, Min, Sec + US / ?usecs_per_sec}.
+
+time2i({H, M, S}) ->
+ US = trunc(round(S * ?usecs_per_sec)),
+ ((H * ?mins_per_hour + M) * ?secs_per_minute) * ?usecs_per_sec + US.
+
+i2timestamp(N) ->
+ case tmodulo(N, ?usecs_per_day) of
+ {T, D} when T < 0 -> i2timestamp2(D - 1 + ?postgres_epoc_jdate, T + ?usecs_per_day);
+ {T, D} -> i2timestamp2(D + ?postgres_epoc_jdate, T)
+ end.
+
+i2timestamp2(D, T) ->
+ {j2date(D), i2time(T)}.
+
+timestamp2i({Date, Time}) ->
+ D = date2j(Date) - ?postgres_epoc_jdate,
+ D * ?usecs_per_day + time2i(Time).
+
+tmodulo(T, U) ->
+ case T div U of
+ 0 -> {T, 0};
+ Q -> {T - (Q * U), Q}
+ end.
View
54 test_src/pgsql_tests.erl
@@ -6,6 +6,7 @@
-include("pgsql.hrl").
-define(host, "localhost").
+-define(port, 5432).
connect_test() ->
connect_only([[]]).
@@ -14,7 +15,7 @@ connect_to_db_test() ->
connect_only([[{database, "epgsql_test_db1"}]]).
connect_as_test() ->
- connect_only(["epgsql_test1", [{database, "epgsql_test_db1"}]]).
+ connect_only(["epgsql_test", [{database, "epgsql_test_db1"}]]).
connect_with_cleartext_test() ->
connect_only(["epgsql_test_cleartext",
@@ -31,7 +32,7 @@ connect_with_invalid_password_test() ->
pgsql:connect(?host,
"epgsql_test_md5",
"epgsql_test_sha1",
- [{database, "epgsql_test_db1"}]).
+ [{port, ?port}, {database, "epgsql_test_db1"}]).
select_test() ->
with_connection(
@@ -317,7 +318,7 @@ execute_function_test() ->
parameter_get_test() ->
with_connection(
fun(C) ->
- {ok, <<"off">>} = pgsql:get_parameter(C, "integer_datetimes")
+ {ok, <<"off">>} = pgsql:get_parameter(C, "is_superuser")
end).
parameter_set_test() ->
@@ -331,24 +332,39 @@ parameter_set_test() ->
{ok, _Cols, [{<<"02.01.2000">>}]} = pgsql:squery(C, "select '2000-01-02'::date")
end).
-type_test() ->
- check_type(bool, "true", true, [true, false]),
- check_type(bpchar, "'A'", $A, [1, $1, 255], "c_char"),
+numeric_type_test() ->
check_type(int2, "1", 1, [0, 256, -32768, +32767]),
check_type(int4, "1", 1, [0, 512, -2147483648, +2147483647]),
check_type(int8, "1", 1, [0, 1024, -9223372036854775808, +9223372036854775807]),
check_type(float4, "1.0", 1.0, [0.0, 1.23456, -1.23456]),
- check_type(float8, "1.0", 1.0, [0.0, 1.23456789012345, -1.23456789012345]),
- check_type(bytea, "E'\001\002'", <<1,2>>, [<<>>, <<0,128,255>>]),
+ check_type(float8, "1.0", 1.0, [0.0, 1.23456789012345, -1.23456789012345]).
+
+character_type_test() ->
+ check_type(bpchar, "'A'", $A, [1, $1, 255], "c_char"),
check_type(text, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]),
- check_type(varchar, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]),
- check_type(date, "'2008-01-02'", {2008,1,2}, [{-4712,1,1}, {5874897,1,1}]),
- check_type(time, "'00:01:02'", {0,1,2.0}, [{0,0,0.0}, {24,0,0.0}]),
- check_type(timetz, "'00:01:02-01'", {{0,1,2.0},1*60*60}, [{{0,0,0.0},0}, {{24,0,0.0},-13*60*60}]),
- check_type(timestamp, "'2008-01-02 03:04:05'", {{2008,1,2},{3,4,5.0}},
- [{{-4712,1,1},{0,0,0.0}}, {{5874897,12,31}, {23,59,59.0}}]),
- check_type(interval, "'1 hour 2 minutes 3.1 seconds'", {{1,2,3.1},0,0},
- [{{0,0,0.0},0,-178000000 * 12}, {{0,0,0.0},0,178000000 * 12}]).
+ check_type(varchar, "'hi'", <<"hi">>, [<<"">>, <<"hi">>]).
+
+date_time_type_test() ->
+ with_connection(
+ fun(C) ->
+ case pgsql:get_parameter(C, "integer_datetimes") of
+ {ok, <<"on">>} -> MaxTsDate = 294276;
+ {ok, <<"off">>} -> MaxTsDate = 5874897
+ end,
+
+ check_type(date, "'2008-01-02'", {2008,1,2}, [{-4712,1,1}, {5874897,1,1}]),
+ check_type(time, "'00:01:02'", {0,1,2.0}, [{0,0,0.0}, {24,0,0.0}]),
+ check_type(timetz, "'00:01:02-01'", {{0,1,2.0},1*60*60},
+ [{{0,0,0.0},0}, {{24,0,0.0},-13*60*60}]),
+ check_type(timestamp, "'2008-01-02 03:04:05'", {{2008,1,2},{3,4,5.0}},
+ [{{-4712,1,1},{0,0,0.0}}, {{MaxTsDate,12,31}, {23,59,59.0}}]),
+ check_type(interval, "'1 hour 2 minutes 3.1 seconds'", {{1,2,3.1},0,0},
+ [{{0,0,0.0},0,-178000000 * 12}, {{0,0,0.0},0,178000000 * 12}])
+ end).
+
+misc_type_test() ->
+ check_type(bool, "true", true, [true, false]),
+ check_type(bytea, "E'\001\002'", <<1,2>>, [<<>>, <<0,128,255>>]).
text_format_test() ->
with_connection(
@@ -373,12 +389,13 @@ run_tests() ->
%% -- internal functions --
connect_only(Args) ->
- {ok, C} = apply(pgsql, connect, [?host | Args]),
+ {ok, C} = apply(pgsql, connect, [?host, [{port, ?port} | Args]]),
pgsql:close(C),
flush().
with_connection(F) ->
- {ok, C} = pgsql:connect(?host, "epgsql_test1", [{database, "epgsql_test_db1"}]),
+ Args = [{port, ?port}, {database, "epgsql_test_db1"}],
+ {ok, C} = pgsql:connect(?host, "epgsql_test", Args),
try
F(C)
after
@@ -386,7 +403,6 @@ with_connection(F) ->
end,
flush().
-
with_rollback(F) ->
with_connection(
fun(C) ->
View
16 test_src/test_schema.sql
@@ -3,11 +3,15 @@
-- this script should be run as the same user the tests will be run as,
-- so that the test for connecting as the 'current user' succeeds
--
--- the following lines must be added to pg_hba.conf for the relevant
--- auth tests to succeed:
+-- the following lines must be added to pg_hba.conf for all tests to
+-- succeed:
--
+-- host epgsql_test_db1 epgsql_test 127.0.0.1/32 trust
-- host epgsql_test_db1 epgsql_test_md5 127.0.0.1/32 md5
-- host epgsql_test_db1 epgsql_test_cleartext 127.0.0.1/32 password
+--
+-- any 'trust all' must be commented out for the invalid password test
+-- to succeed.
CREATE USER epgsql_test;
@@ -65,7 +69,7 @@ begin
end
$$ language plpgsql;
-GRANT ALL ON TABLE test_table1 TO epgsql_test1;
-GRANT ALL ON TABLE test_table2 TO epgsql_test1;
-GRANT ALL ON FUNCTION insert_test1(integer, text) TO epgsql_test1;
-GRANT ALL ON FUNCTION do_nothing() TO epgsql_test1;
+GRANT ALL ON TABLE test_table1 TO epgsql_test;
+GRANT ALL ON TABLE test_table2 TO epgsql_test;
+GRANT ALL ON FUNCTION insert_test1(integer, text) TO epgsql_test;
+GRANT ALL ON FUNCTION do_nothing() TO epgsql_test;
Please sign in to comment.
Something went wrong with that request. Please try again.