psql-0.1.3/0000755000175000001440000000000011014774320011702 5ustar martinuserspsql-0.1.3/doc/0000755000175000001440000000000011014774320012447 5ustar martinuserspsql-0.1.3/doc/overview.edoc0000644000175000001440000000560311014771305015155 0ustar martinusers PostgresSQL protocol driver @author Martin Carlson [http://www.erlang-consulting.com] @copyright 2006 Erlang Training & Consulting Ltd. @version 0.2.0 @doc Implementation of the PostgeSQL tcp protocol v3.0 [http://www.postgresql.org/docs/8.1/interactive/protocol.html] Please send patches to support@erlang-consulting.com Bugs reports bugs@erlang-consulting.com OBS The postgres database MUST be configured to support MD5 authentication (pg_hba.conf) === Example ===


sys.config: {psql, [{default, {"192.168.1.101", 5432, "dev", "secret", "sql_test"}},
            {pools, [{default, 1}]}]}

15> P = psql:allocate().
<0.60.0>
16> psql:parse(P, "test", "select * from test where id = $1", []). {[],[]} 17> psql:transaction(P).
[{<<66,69,71,73,78,0>>,[]}] 18> psql:bind(P, "test", "test", ["1"]). {[],[]} 19> {Desc, []} = psql:describe(P, statement, "test"). {{{field,"id",17232,1,int,4,0},{field,"name",17232,2,string,-1,0}},[]} 20> psql:execute(P, "test", 10000, Desc). {{{field,"id",17232,1,int,4,0},{field,"name",17232,2,string,-1,0}}, [{<<83,69,76,69,67,84,0>>,[{1,"dev"}]}]} 21> psql:commit(P). [{<<67,79,77,77,73,84,0>>,[]}] 22>
=== Limitations === SSL encryption not supported Only MD5 authentication is supported. COPY Operations not implemented. === Copyright === Copyright (c) 2006, Erlang Training & Consulting Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Erlang Training & Consulting Ltd. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @end psql-0.1.3/src/0000755000175000001440000000000011014774320012471 5ustar martinuserspsql-0.1.3/src/psql_app.erl0000644000175000001440000000355010515431764015026 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -module(psql_app). -behaviour(application). %% Application callbacks -export([start/2, stop/1]). %%==================================================================== %% Application callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: start(Type, StartArgs) -> {ok, Pid} | %% {ok, Pid, State} | %% {error, Reason} %% Description: This function is called whenever an application %% is started using application:start/1,2, and should start the processes %% of the application. If the application is structured according to the %% OTP design principles as a supervision tree, this means starting the %% top supervisor of the tree. %%-------------------------------------------------------------------- start(normal, []) -> psql_sup:start_link(). %%-------------------------------------------------------------------- %% Function: stop(State) -> void() %% Description: This function is called whenever an application %% has stopped. It is intended to be the opposite of Module:start/2 and %% should do any necessary cleaning up. The return value is ignored. %%-------------------------------------------------------------------- stop(_State) -> ok. %%==================================================================== %% Internal functions %%==================================================================== psql-0.1.3/src/psql_connection.erl0000644000175000001440000001437410541472637016416 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -module(psql_connection). %% API -export([start_link/1, command/2, sync/1]). %% Internal Import -export([init/2]). %% System exports -export([system_continue/3, system_terminate/4, system_code_change/4, print_event/3]). -record(state, {parent, debug, logic, host = undefined, port, socket = undefined, buffer = []}). -define(TCP_OPTIONS, [binary, {active, true}, {packet, 0}]). -define(RECONNECT_TIMEOUT, 1000). %%==================================================================== %% API %%==================================================================== start_link(Logic) -> proc_lib:start_link(?MODULE, init, [self(), Logic]). command(Pid, Data) -> Pid ! {logic, self(), Data}, ok. sync(Pid) -> Pid ! {logic, self(), {send, psql_protocol:sync()}}, ok. %%==================================================================== %% Server %%==================================================================== init(Parent, Logic) -> Debug = sys:debug_options([]), proc_lib:init_ack(Parent, {ok, self()}), loop(#state{parent = Parent, debug = Debug, logic = Logic}). %% %% This make sure we get into a reconnect loop if the connection goes down %% by setting the socket to 'undefined' %% loop(State) when State#state.socket == undefined, State#state.host /= undefined -> case gen_tcp:connect(State#state.host, State#state.port, ?TCP_OPTIONS) of {ok, Socket} -> psql_logic:connection_event(State#state.logic, {psql, connection_established, []}), loop(State#state{socket = Socket}); {error, Reason} -> error_logger:error_report({connection_failed, Reason}), receive Message -> handle_receive(Message, State) after ?RECONNECT_TIMEOUT -> loop(State) end end; loop(State) -> receive Message -> handle_receive(Message, State) end. %%==================================================================== %% Receive Handling %%==================================================================== handle_receive({system, From, Req}, State) -> sys:handle_system_msg(Req, From, State#state.parent, ?MODULE, State#state.debug, State); handle_receive({logic, From, Data}, State) when From == State#state.logic -> DState = handle_debug({logic, Data, State}, State), case handle_message(Data, DState) of {ok, NewState} -> loop(NewState); {stop, _State} -> ok end; handle_receive({tcp, Socket, Data}, State) when Socket == State#state.socket -> DState = handle_debug({socket, Data, State}, State), {ok, NewState} = handle_message(Data, DState), loop(NewState); handle_receive({tcp_closed, Socket}, State) when Socket == State#state.socket -> DState = handle_debug({tcp_closed, Socket, State}, State), psql_logic:connection_closed(State#state.logic), loop(DState#state{socket = undefined}); handle_receive(Message, State) -> loop(handle_debug({unknown_message, Message, State}, State)). %%==================================================================== %% Message Handling %%==================================================================== handle_message({connect, Host, Port}, State) -> {ok, State#state{host = Host, port = Port}}; handle_message(disconnect, State) -> gen_tcp:close(State#state.socket), {ok, State#state{socket = undefined, host = undefined, port = undefined}}; handle_message(shutdown, State) -> gen_tcp:close(State#state.socket), {stop, State}; handle_message({send, Message}, State) -> Data = psql_protocol:encode(Message), case gen_tcp:send(State#state.socket, Data) of ok -> {ok, State}; {error, Reason} -> psql_logic:connection_event(State#state.logic, {error, Reason}), gen_tcp:close(State#state.socket), {ok, State#state{socket = undefined}} end; handle_message({send, Type, Message}, State) -> Data = psql_protocol:encode(Type, Message), case gen_tcp:send(State#state.socket, Data) of ok -> {ok, State}; {error, Reason} -> psql_logic:connection_event(State#state.logic, {error, Reason}), gen_tcp:close(State#state.socket), {ok, State#state{socket = undefined}} end; handle_message(Fragment, State) -> if State#state.buffer == []; State#state.buffer == <<>> -> Msg = Fragment; true -> Msg = <<(State#state.buffer)/binary, Fragment/binary>> end, case dispatch_messages(State#state.logic, psql_protocol:decode(Msg)) of {fragment, Tail} -> {ok, State#state{buffer = Tail}}; done -> {ok, State#state{buffer = <<>>}} end. %%==================================================================== %% System Callbacks %%==================================================================== system_continue(Parent, Debug, State) -> loop(State#state{parent = Parent, debug = Debug}). system_terminate(Reason, _Parent, _Debug, _State) -> exit(Reason). system_code_change(State, _Module, _OldVsn, _Extra) -> {ok, State}. %%==================================================================== %% Debug Callbacks %%==================================================================== print_event(Dev, Event, []) -> io:format(Dev, "*DBG* ~p dbg ~p~n", [self(), Event]); print_event(Dev, Event, Name) -> io:format(Dev, "*DBG* ~p dbg ~p~n", [Name, Event]). %%==================================================================== %% Internal functions %%==================================================================== handle_debug(Data, State) -> Debug = sys:handle_debug(State#state.debug, {?MODULE, print_event}, [], Data), State#state{debug = Debug}. dispatch_messages(_Logic, {next, Fragment}) -> {fragment, Fragment}; dispatch_messages(Logic, {{Type, Data}, <<>>}) -> psql_logic:connection_event(Logic, {psql, Type, Data}), done; dispatch_messages(Logic, {{Type, Data}, Tail}) -> psql_logic:connection_event(Logic, {psql, Type, Data}), dispatch_messages(Logic, psql_protocol:decode(Tail)). psql-0.1.3/src/psql_con_sup.erl0000644000175000001440000000427210541472637015721 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -module(psql_con_sup). -behaviour(supervisor). %% API -export([start_link/0, start_connection/0, start_connection/1]). %% Supervisor callbacks -export([init/1]). -define(SERVER, ?MODULE). %%==================================================================== %% API functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the supervisor %%-------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). start_connection() -> supervisor:start_child(?SERVER, []). start_connection(PoolSpec) -> supervisor:start_child(?SERVER, [PoolSpec]). %%==================================================================== %% Supervisor callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Func: init(Args) -> {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %% Description: Whenever a supervisor is started using %% supervisor:start_link/[2,3], this function is called by the new process %% to find out about restart strategy, maximum restart frequency and child %% specifications. %%-------------------------------------------------------------------- init([]) -> Logic = {psql_logic, {psql_logic, start_link, []}, permanent, 2000, worker, [psql_logic]}, {ok, {{simple_one_for_one, 10, 60}, [Logic]}}. %%==================================================================== %% Internal functions %%==================================================================== psql-0.1.3/src/psql_protocol.erl0000644000175000001440000001526710560147212016107 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -module(psql_protocol). %% API -export([decode_packet_header/1, decode/1, encode/1, encode/2, type/1, data_type/1, error/1, md5/1, to_int8/1, to_int16/1, to_int32/1, to_string/1]). -export([authenticate/4, md5digest/2, copy_done/0, q/1, parse/3, bind/3, describe/2, execute/2, close/2, sync/0]). %%==================================================================== %% Encode/Decode %%==================================================================== decode_packet_header(<>) -> {type(Type), Size}. decode(<>) when S - 4 =< size(R) -> MsgSize = S - 4, <> = R, {{type(Type), Message}, Tail}; decode(Acc) -> {next, Acc}. encode({Type, Message}) -> encode(Type, Message); encode(Message) when is_list(Message) -> encode(list_to_binary(Message)); encode(Message) when is_binary(Message) -> Size = to_int32(size(Message) + 4), [Size, Message]. encode(Type, Message) when is_list(Message) -> encode(Type, list_to_binary(Message)); encode(Type, Message) when is_binary(Message) -> Size = to_int32(size(Message) + 4), [type(Type), Size, Message]. %%==================================================================== %% Messages %%==================================================================== authenticate(VSN, Usr, Pwd, Db) -> Msg = [to_int32(VSN), to_string("user"), to_string(Usr), to_string("database"), to_string(Db), null()], Digest = psql_protocol:md5([Pwd, Usr]), {Msg, Digest}. md5digest(Digest, Salt) -> Auth = md5([Digest, Salt]), {password_message, <<"md5", Auth/binary, 0:8>>}. copy_done() -> {'copy_done', []}. q(Query) -> {'query', to_string(Query)}. parse(Name, Query, Types) -> {'parse', list_to_binary([to_string(Name), to_string(Query), to_int16(length(Types)), [to_int32(T) || T <- Types]])}. bind(Portal, Statement, Parameters) -> {bind, list_to_binary([to_string(Portal), to_string(Statement), to_int16(0), % Denotes that all prameters are in text to_int16(length(Parameters)), map_parameters(Parameters, []), to_int16(0)])}. % Denotes that all prameters are in text describe(Name, Type) -> if Type == statement -> T = to_int8($S); true -> T = to_int8($P) end, {describe, [T, to_string(Name)]}. execute(Portal, ResultSetSize) -> {execute, list_to_binary([to_string(Portal), to_int32(ResultSetSize)])}. close(Name, Type) -> if Type == statement -> T = to_int8($S); true -> T = to_int8($P) end, {close, [T, to_string(Name)]}. sync() -> {sync, []}. %%==================================================================== %% Bind Parameter Map %%==================================================================== map_parameters([], Acc) -> lists:reverse(Acc); map_parameters([nil|T], Acc) -> map_parameters(T, [to_int32(-1)|Acc]); map_parameters([H|T], Acc) when is_list(H) -> map_parameters(T, [H, to_int32(length(H))|Acc]). %%==================================================================== %% Message Type Map %%==================================================================== type($R) -> authentication; type($K) -> backend_key_data; type($2) -> bind_complete; type($3) -> close_complete; type($C) -> command_complete; type($d) -> copy_data; type($c) -> copy_done; type($G) -> copy_in_response; type($H) -> copy_out_response; type($D) -> data_row; type($I) -> empty_query_response; type($E) -> error; type($V) -> function_call_response; type($n) -> no_data; type($N) -> notice_response; type($A) -> notification_response; type($t) -> parameter_description; type($S) -> parameter_status; type($1) -> parse_complete; type($s) -> portal_suspended; type($Z) -> ready_for_query; type($T) -> row_description; type(bind) -> $B; type(close) -> $C; type(copy_data) -> $d; type(copy_done) -> $c; type(copy_fail) -> $f; type(describe) -> $D; type(execute) -> $E; type(flush) -> $H; type(function_call) -> $F; type(parse) -> $P; type(password_message) -> $p; type('query') -> $Q; type(ssl_request) -> 8; type(sync) -> $S; type(terminate) -> $X. %%==================================================================== %% Datatype Map %%==================================================================== %% Datatypes are specified in pg_type data_type(16) -> bool; data_type(17) -> binary; data_type(18) -> string; data_type(19) -> string; data_type(20) -> int; data_type(21) -> int; data_type(23) -> int; data_type(24) -> string; data_type(25) -> string; data_type(26) -> binary; data_type(896) -> ip; data_type(1007) -> integer_array; data_type(1014) -> char_array; data_type(1015) -> varchar_array; data_type(1042) -> string; data_type(1043) -> string; data_type(1082) -> date; data_type(1083) -> time; data_type(1114) -> datetime; data_type(1184) -> datetime; data_type(1266) -> time; data_type(1700) -> float. %%==================================================================== %% Errors %%==================================================================== error(<<"SFATAL",0, "C28000", 0, Rest/binary>>) -> {authentication, Rest}; error(<<"SFATAL",0, "C57P01", 0, Rest/binary>>) -> {shutdown, Rest}; error(<<"SFATAL",0, "C57P02", 0, Rest/binary>>) -> {shutdown, Rest}; error(<<"SFATAL",0, "C57P03", 0, Rest/binary>>) -> {shutdown, Rest}; error(<<"SERROR", 0, $C, P, C, O, D, E, 0, Rest/binary>>) -> {_, Desc} = lists:foldl(fun(0, {Acc0, Acc}) -> {[], lists:reverse(Acc0) ++ [$ |Acc]}; (Chr, {Acc0, Acc}) -> {[Chr|Acc0], Acc} end, {[], []}, binary_to_list(Rest)), {sql_error, [P,C,O,D,E], string:strip(Desc)}. %%==================================================================== %% Utility functions %%==================================================================== to_int8(N) -> <>. to_int16(N) -> <>. to_int32(N) -> <>. to_string(Str) -> <<(list_to_binary(Str))/binary, 0:8>>. null() -> <<0:8>>. md5(Data) -> Digest = erlang:md5(Data), list_to_binary(to_hex(Digest)). to_hex(<>) -> [hex(H), hex(L) | to_hex(Rest)]; to_hex(<<>>) -> []. hex(Dec) when Dec < 10 -> $0 + Dec; hex(Dec) -> $a - 10 + Dec. psql-0.1.3/src/psql_logic.erl0000644000175000001440000003143111014771305015333 0ustar martinusers%%-------------------------------------------------------------------- %%% @copyright (C) 2006, Erlang Traingin & Consulting, Inc. %%% @version 0.2.0 %%% @author %%% @doc %%% @end %%-------------------------------------------------------------------- -module(psql_logic). -author("support@erlang-consulting.com"). -copyright("2006 (C) Erlang Trining & Consulting Ltd."). -vsn("$Rev$"). %% API -export([start_link/0, start_link/1, command/2]). -export([connection_event/2, connection_closed/1]). -export([init/1, system_continue/3, system_terminate/4, system_code_change/4]). -record(state, {connection, host, port, user, password, database, connected = false}). -define(PROTOCOL_VERSION, 196608). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% @spec start_link() -> {ok, pid()} | {error, term()} %% @doc Start the special process %% @end %%-------------------------------------------------------------------- start_link() -> proc_lib:start_link(?MODULE, init, [self()]). %%-------------------------------------------------------------------- %% @spec start_link(Pool::atom()) -> {ok, pid()} | {error, term()} %% @doc Start the special process and connect to `Pool' %% @end %%-------------------------------------------------------------------- start_link({_PoolName, {Host, Port, Usr, Pwd, Db}}) -> {ok, Pid} = proc_lib:start_link(?MODULE, init, [self()]), command(Pid, {connect, self(), Host, Port, Usr, Pwd, Db}), receive {psql_server, ready_for_query} -> {ok, Pid}; {psql_server, {sql_error, Error}} -> erlang:error({sql_error, psql_lib:error(Error)}) end. %%-------------------------------------------------------------------- %% @spec command(Logic::pid(), Command::term()) -> ok %% @doc send a command to the logic process %% @end %%-------------------------------------------------------------------- command(Logic, Command) -> Logic ! Command, ok. %%-------------------------------------------------------------------- %% @spec init(Parent) -> exit() %% @doc Start the special process %% @end %%-------------------------------------------------------------------- init(Parent) -> Debug = sys:debug_options([]), {ok, Connection} = psql_connection:start_link(self()), psql_pool:register(self()), proc_lib:init_ack(Parent, {ok, self()}), loop(Parent, Debug, #state{connection = Connection}). %%-------------------------------------------------------------------- %% @spec loop(Parent::term(), Debug::term(), State::#state{}) -> exit() %% @doc Server Loop %% @end %%-------------------------------------------------------------------- loop(Parent, Debug, State) -> receive {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, State); {get_modules, From} = Msg -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), reply(From, {modules, [?MODULE]}), loop(Parent, Debug2, State); {'EXIT', Parent, Reason} -> exit(Reason); {psql, connection_closed, _} = Msg -> %% Connection drops Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), loop(Parent, Debug2, State#state{connected = false}); {psql, connection_established, _} = Msg -> %% Reconnections Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = authenticate(self(), State), loop(Parent, Debug2, State0); {connect, From, Host, Port, Usr, Pwd, DB} = Msg when State#state.connected == false -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = connect(From, State#state{host = Host, port = Port, user = Usr, password = Pwd, database = DB}), loop(Parent, Debug2, State0); {parse, From, Statement, Query, Types} = Msg when State#state.connected == true -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = parse(From, Statement, Query, Types, State), loop(Parent, Debug2, State0); {bind, From, Portal, Statement, Parameters} = Msg when State#state.connected == true -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = bind(From, Portal, Statement, Parameters, State), loop(Parent, Debug2, State0); {describe, From, Type, Name} = Msg when State#state.connected == true -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = describe(From, Type, Name, State), loop(Parent, Debug2, State0); {execute, From, Name, ResultSet} = Msg when State#state.connected == true -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = execute(From, Name, ResultSet, State), loop(Parent, Debug2, State0); {close, From, Type, Name} = Msg when State#state.connected == true -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = close(From, Name, Type, State), loop(Parent, Debug2, State0); {simple_query, From, Query} = Msg when State#state.connected == true -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), State0 = simple_query(From, Query, State), loop(Parent, Debug2, State0); Msg -> Debug2 = sys:handle_debug(Debug, fun write_debug/3, State, Msg), loop(Parent, Debug2, State) end. %% =================================================================== %% States %% =================================================================== %% ------------------------------------------------------------------- %% Connect to the database server %% ------------------------------------------------------------------- connect(From, #state{host = Host, port = Port} = State) -> psql_connection:command(State#state.connection, {connect, Host, Port}), receive {psql, connection_established, _} -> authenticate(From, State); dissconnect -> psql_connection:command(State#state.connection, disconnect), State end. %% ------------------------------------------------------------------- %% Authenticate with the database server %% ------------------------------------------------------------------- authenticate(From, State) -> {Auth, Digest} = psql_protocol:authenticate(?PROTOCOL_VERSION, State#state.user, State#state.password, State#state.database), psql_connection:command(State#state.connection, {send, Auth}), receive {psql, authentication, <<0,0,0,5, Salt/binary>>} -> AuthDigest = psql_protocol:md5digest(Digest, Salt), psql_connection:command(State#state.connection, {send, AuthDigest}), receive {psql, authentication, <<0,0,0,0>>} -> setup(From, State); {psql, error, Error} -> reply(From, {sql_error, Error}), State end; {psql, connection_closed, _} -> reply(From, disconnected), State; {psql, error, Error} -> reply(From, {sql_error, Error}), State end. %% ------------------------------------------------------------------- %% Set parameters to match the servers %% ------------------------------------------------------------------- setup(From, State) -> receive {psql, ready_for_query, _} -> reply(From, ready_for_query), State#state{connected = true}; {psql, error, Error} -> reply(From, {sql_error, Error}), State; _Parameter -> setup(From, State) % TODO: Parse and set the parameter end. parse(From, Statement, Query, Types, State) -> QueryMsg = psql_protocol:parse(Statement, Query, Types), psql_connection:command(State#state.connection, {send, QueryMsg}), psql_connection:sync(State#state.connection), receive {psql, parse_complete, _} -> reply(From, parse_complete), receive {psql, ready_for_query, _} -> reply(From, ready_for_query), State end; {psql, error, Error} -> reply(From, {sql_error, Error}), State end. bind(From, Portal, Statement, Parameters, State) -> QueryMsg = psql_protocol:bind(Portal, Statement, Parameters), psql_connection:command(State#state.connection, {send, QueryMsg}), psql_connection:sync(State#state.connection), receive {psql, bind_complete, _} -> reply(From, bind_complete), receive {psql, ready_for_query, _} -> reply(From, ready_for_query), State end; {psql, error, Error} -> reply(From, {sql_error, Error}), State end. %% There be dragons here.... describe(From, Type, Name, State) -> QueryMsg = psql_protocol:describe(Name, Type), psql_connection:command(State#state.connection, {send, QueryMsg}), psql_connection:sync(State#state.connection), receive {psql, row_description, Data} -> reply(From, {row_description, Data}), reply(From, ready_for_query), %% Should really be fetch_result State; {psql, error, Error} -> reply(From, {sql_error, Error}), State after 1000 -> reply(From, disconnected), State end. execute(From, Name, ResultSet, State) -> QueryMsg = psql_protocol:execute(Name, ResultSet), psql_connection:command(State#state.connection, {send, QueryMsg}), psql_connection:sync(State#state.connection), fetch_result(From, [], State). close(From, Name, Type, State) -> QueryMsg = psql_protocol:close(Name, Type), psql_connection:command(State#state.connection, {send, QueryMsg}), psql_connection:sync(State#state.connection), fetch_result(From, [], State). simple_query(From, Query, State) -> QueryMsg = psql_protocol:q(Query), psql_connection:command(State#state.connection, {send, QueryMsg}), psql_connection:sync(State#state.connection), fetch_result(From, [], State). fetch_result(From, Result, State) -> receive {psql, row_description, Data} -> reply(From, {row_description, Data}), fetch_result(From, [], State); {psql, data_row, Data} -> fetch_result(From, [Data|Result], State); {psql, no_data, _} -> fetch_result(From, no_data, State); {psql, command_complete, Command} -> reply(From, {command_complete, Command, Result}), fetch_result(From, [], State); {psql, error, Error} -> reply(From, {sql_error, Error}), fetch_result(From, Result, State); {psql, portal_suspended, _} -> reply(From, fetch_more), State; {psql, ready_for_query, _} -> reply(From, ready_for_query), State end. %% =================================================================== %% Connection Callbacks %% =================================================================== connection_event(Logic, Message) -> Logic ! Message, ok. connection_closed(Logic) -> Logic ! {psql, connection_closed, self()}. %% =================================================================== %% System Callbacks %% =================================================================== %%-------------------------------------------------------------------- %% @spec system_continue(Parent::pid(), %% Debug::list(), %% State::term()) -> exit() %% @doc Called by sys %% @end %% @see //sys. sys %%-------------------------------------------------------------------- system_continue(Parent, Debug, State) -> loop(Parent, Debug, State). %%-------------------------------------------------------------------- %% @spec system_terminate(Reason::atom(), Parent::pid(), %% Debug::list(), State::term()) -> exit() %% @doc Terminate the process called by sys %% @end %% @see //sys. sys %%-------------------------------------------------------------------- system_terminate(Reason, _Parent, _Debug, _State) -> psql_pool:unregister(self()), if Reason == normal; Reason == shutdown -> exit(Reason); true -> error_logger:error_report({unknown_reason, self(), Reason}), exit(Reason) end. %%-------------------------------------------------------------------- %% @spec (State::term(), Module::atom(), Vsn::term(), Extra::term()) -> %% {ok, term()} %% @doc Updates the process state called by sys %% @end %% @see //sys. sys %%-------------------------------------------------------------------- system_code_change(State, _Module, _OldVsn, _Extra) -> io:format("Updating code...~n"), {ok, State}. %%-------------------------------------------------------------------- %% @spec write_debug(Device::ref(), Event::term(), State::term()) -> ok %% @doc Write debug messages called by sys %% @end %% @see //sys. sys %%-------------------------------------------------------------------- write_debug(Device, Event, State) -> io:format(Device, "*DBG* ~p, ~w", [Event, State]). %%==================================================================== %% Internal functions %%==================================================================== reply(From, Message) -> From ! {psql_server,Message}, ok. psql-0.1.3/src/psql.app.src0000644000175000001440000000107011014771305014736 0ustar martinusers%% copyright 2006 Reliance Commnication inc %% author Martin Carlson %% version $Rev$ %% psql spec %% OBS: Make sure the postgres server uses MD5 authentication %%------------------------------------------------------------------- {application, %NAME%, [{description, "%NAME% $Rev$"}, {vsn, "%VSN%"}, {modules, [%MODULES%]}, {registered, [psql_sup]}, {applications, [kernel, stdlib]}, {mod, {psql, []}}, {env, [{default, {"127.0.0.1", 5432, "user", "password", "db"}}, {pools, [{default, 1}]}]}]}. psql-0.1.3/src/psql_lib.erl0000644000175000001440000001166410751604232015013 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -module(psql_lib). %% API -export([type_cast/2, row_description/1, row/2, error/1, command/1]). -include("psql.hrl"). %%==================================================================== %% API %%==================================================================== type_cast(SqlValue, Desc) when Desc#field.type == binary -> SqlValue; type_cast(SqlValue, Desc) when Desc#field.type == list -> binary_to_list(SqlValue); type_cast(SqlValue, Desc) when Desc#field.type == string -> binary_to_list(SqlValue); type_cast(SqlValue, Desc) when Desc#field.type == int -> List = binary_to_list(SqlValue), case catch list_to_integer(List) of {'EXIT', _Reason} -> list_to_float(List); Res -> Res end; type_cast(SqlValue, Desc) when Desc#field.type == float -> List = binary_to_list(SqlValue), case catch list_to_float(List) of {'EXIT', _Reason} ->list_to_integer(List); Res -> Res end; type_cast(SqlValue, Desc) when Desc#field.type == bool -> if SqlValue == <<"f">> -> false; true -> true end; type_cast(SqlValue, Desc) when Desc#field.type == date -> List = binary_to_list(SqlValue), [Yr,Mh,Dy] = string:tokens(List, "-"), {list_to_integer(Yr), list_to_integer(Mh), list_to_integer(Dy)}; type_cast(SqlValue, Desc) when Desc#field.type == time -> List = binary_to_list(SqlValue), [Hr,Mt,Sd] = string:tokens(List, "-: "), {list_to_integer(Hr), list_to_integer(Mt), case lists:member($., Sd) of false -> list_to_integer(hd(string:tokens(Sd, "+"))); true -> list_to_integer(hd(string:tokens(Sd, "."))) end}; type_cast(SqlValue, Desc) when Desc#field.type == datetime -> List = binary_to_list(SqlValue), [Yr,Mh,Dy,Hr,Mt,Sd] = string:tokens(List, "-: "), {{list_to_integer(Yr), list_to_integer(Mh), list_to_integer(Dy)}, {list_to_integer(Hr), list_to_integer(Mt), case lists:member($., Sd) of false -> list_to_integer(hd(string:tokens(Sd, "+"))); true -> list_to_integer(hd(string:tokens(Sd, "."))) end}}; type_cast(SqlValue, #field{type = T}) when T == char_array; T == varchar_array -> List = binary_to_list(SqlValue), Values = string:substr(List, 2, length(List) - 2), Tokens = string:tokens(Values, ","), F = fun([$"|Rest] = String) -> case lists:reverse(Rest) of [$"|RRest] -> lists:reverse(RRest); _Otherwise -> String end; (String) -> String end, list_to_tuple(lists:map(F, Tokens)); type_cast(SqlValue, Desc) when Desc#field.type == integer_array -> List = binary_to_list(SqlValue), {_, Tokens, _} = erl_scan:string(List ++ "."), {ok, Res} = erl_parse:parse_term(Tokens), Res; type_cast(SqlValue, Desc) -> Res = binary_to_list(SqlValue), io:format("SQL: Unknown code: ~p (~p)", [Desc#field.type, Res]), Res. %% %% Row description %% row_description(<<_:1/big-unit:16, Fields/binary>>) -> list_to_tuple(field_desc_parser(Fields, [])). field_desc_parser(<<>>, Acc) -> lists:reverse(Acc); field_desc_parser(Fields, Acc) -> {Name, <>} = field_name_parser(Fields, []), Field = #field{name = Name, table_code = Table, field_code = Col, type = psql_protocol:data_type(Type), max_length = Size, format = Format}, field_desc_parser(Tail, [Field|Acc]). field_name_parser(<<0, Tail/binary>>, Acc) -> {lists:reverse(Acc), Tail}; field_name_parser(<>, Acc) -> field_name_parser(Tail, [C|Acc]). %% %% Row Parser %% row(<<_:1/big-signed-unit:16, Cols/binary>>, Desc) -> col_parser(Cols, Desc, 1, []); row(_, no_result) -> no_result. col_parser(<<>>, _, _, Acc) -> list_to_tuple(lists:reverse(Acc)); col_parser(<>, Desc, N, Acc) -> if Length == -1 -> Value = [], Tail = Rest; Length > size(Rest) -> Value = [], Tail = Rest, erlang:error({badarg, Length, N, Rest}); true -> <> = Rest, Value = psql_lib:type_cast(SQLValue, element(N, Desc)) end, col_parser(Tail, Desc, N + 1, [Value|Acc]). %% %% Error parsing (This should be moved here) %% error(Error) -> psql_protocol:error(Error). %% %% Parse command %% command(Command) -> Command. %%==================================================================== %% Internal functions %%==================================================================== psql-0.1.3/src/psql_pool.erl0000644000175000001440000001750610541472637015230 0ustar martinusers%%%------------------------------------------------------------------- %%% @author Martin Carlson %%% @doc %%% @end %%% Revisions: %%%------------------------------------------------------------------- -module(psql_pool). -author('code@erlang-consulting.com'). -copyright('Erlang Training & Consulting Ltd.'). -vsn("$Rev$"). -behaviour(gen_server). %% API -export([start_link/0, register/1, unregister/1, alloc/1, free/1, ref/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include_lib("stdlib/include/qlc.hrl"). -define(SERVER, ?MODULE). -record(state, {pool}). -record(pool, {pid, type, ref}). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?SERVER, [], []). %%-------------------------------------------------------------------- %% @spec register(Resource::pid()) -> ok %% @doc Register a resource %% @end %%-------------------------------------------------------------------- register(Resource) -> gen_server:call(?SERVER, {register, Resource}). %%-------------------------------------------------------------------- %% @spec unregister(Resource::pid()) -> ok %% @doc Unregisters a resource %% @end %%-------------------------------------------------------------------- unregister(Resource) -> gen_server:call(?SERVER, {unregister, Resource}). %%-------------------------------------------------------------------- %% @spec alloc(Pid::pid()) -> pid() %% @doc Allocates a resource %% @end %%-------------------------------------------------------------------- alloc(Pid) -> case ref(Pid) of nomatch -> gen_server:call(?SERVER, {alloc, Pid}); Res -> Res end. %%-------------------------------------------------------------------- %% @spec free(Pid::pid()) -> pid() %% @doc Frees a pid %% @end %%-------------------------------------------------------------------- free(Pid) -> case ref(Pid) of nomatch -> ok; _ -> gen_server:call(?SERVER, {free, Pid}) end. %%-------------------------------------------------------------------- %% @spec ref(Pid) -> pid() | nomatch %% @doc Get the resource bound to pid %% @end %%-------------------------------------------------------------------- ref(Pid) -> gen_server:call(?SERVER, {ref, Pid}). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([]) -> process_flag(trap_exit, true), {ok, #state{pool = ets:new(pool, [{keypos, 2}])}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call({register, Res}, _From, #state{pool = P} = State) -> link(Res), ets:insert(P, #pool{type = free, pid = Res}), {reply, ok, State}; handle_call({unregister, Res}, _From, #state{pool = P} = State) -> unlink(Res), case find_pid(Res, P) of #pool{type = free} -> ets:delete(P, Res); #pool{type = alloc, pid = Client} -> exit(Client, {error, psql_resource_deregistered}), ets:delete(P, Client) end, {reply, ok, State}; handle_call({alloc, Pid}, From, #state{pool = P} = State) -> link(Pid), case first(free, ets:first(P), P) of nomatch -> ets:insert(P, #pool{type = queue, pid = Pid, ref = From}), {noreply, State}; #pool{pid = Res} -> ets:delete(P, Res), ets:insert(P, #pool{type = alloc, pid = Pid, ref = Res}), {reply, Res, State} end; handle_call({free, Pid}, _From, #state{pool = P} = State) -> unlink(Pid), [#pool{ref = Res}] = ets:lookup(P, Pid), ets:delete(P, Pid), case first(queue, ets:first(P), P) of nomatch -> ets:insert(P, #pool{type = free, pid = Res}); #pool{pid = QPid, ref = QFrom} -> ets:insert(P, #pool{type = alloc, pid = QPid, ref = Res}), gen_server:reply(QFrom, Res) end, {reply, ok, State}; handle_call({ref, Pid}, _From, #state{pool = P} = State) -> case find_pid(Pid, P) of #pool{type = alloc, ref = Res} -> {reply, Res, State}; _ -> {reply, nomatch, State} end. %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({'EXIT', Pid, Reason}, #state{pool = P} = State) -> case find_pid(Pid, P) of #pool{type = free} -> ets:delete(P, Pid); #pool{type = queue} -> ets:delete(P, Pid); #pool{type = alloc, pid = Pid, ref = Res} -> ets:delete(P, Pid), unlink(Res), exit(Res, Reason); #pool{type = alloc, pid = Key} -> ets:delete(P, Key), unlink(Key), exit(Key, Reason) end, {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- first(_, '$end_of_table', _) -> nomatch; first(Type, Key, TID) -> case ets:lookup(TID, Key) of [#pool{type = Type} = P] -> P; _ -> first(Type, ets:next(TID, Key), TID) end. find_pid(Pid, TID) -> QH = qlc:q([P || P <- ets:table(TID), (P#pool.pid == Pid) or (P#pool.ref == Pid)]), case qlc:e(QH) of [] -> nomatch; [Pool] -> Pool end. psql-0.1.3/src/psql_sup.erl0000644000175000001440000000415010541472637015055 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -module(psql_sup). -behaviour(supervisor). %% API -export([start_link/0]). %% Supervisor callbacks -export([init/1]). -define(SERVER, ?MODULE). %%==================================================================== %% API functions %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the supervisor %%-------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). %%==================================================================== %% Supervisor callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Func: init(Args) -> {ok, {SupFlags, [ChildSpec]}} | %% ignore | %% {error, Reason} %% Description: Whenever a supervisor is started using %% supervisor:start_link/[2,3], this function is called by the new process %% to find out about restart strategy, maximum restart frequency and child %% specifications. %%-------------------------------------------------------------------- init([]) -> ConSup = {psql_con_sup, {psql_con_sup, start_link, []}, permanent, 2000, supervisor, [psql_con_sup]}, Pool = {psql_pool, {psql_pool, start_link, []}, permanent, 2000, worker, [psql_pool]}, {ok, {{one_for_one, 10, 60}, [ConSup, Pool]}}. %%==================================================================== %% Internal functions %%==================================================================== psql-0.1.3/src/Emakefile0000644000175000001440000000007710515431764014311 0ustar martinusers{'*', [{outdir, "../ebin"}, debug_info, strict_record_tests]}. psql-0.1.3/src/psql.erl0000644000175000001440000002212411014771305014155 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.2.0 %%% @doc Interface module for the PostgreSQL driver %%% @end %%%------------------------------------------------------------------- -module(psql). -author("support@erlang-consulting.com"). -copyright("Erlang Training & Consulting Ltd"). -vsn("$Rev"). -behaviour(application). %% Application callbacks -export([start/2, stop/1]). %% API -export([connect/4, connect/6, allocate/0, free/0, sql_query/2, parse/4, bind/4, describe/3, execute/3, execute/4, close/3, transaction/1, commit/1, rollback/1]). -define(REREQUEST_FREQ, 1000). -define(DEFAULT_PORT, 5432). %% Global types %% @type query() = string(). %% SQL conformant query with omitted semicolon %% %% @type result() = [] | [{Command::binary(), Data::rows()}] | %% {sql_error, term()} | term(). %% Query results, rows are represented as tuples and %% values are converted to the closest erlang type %% %% @type rows() = [row()] | []. %% %% @type row() = tuple() | binary(). %% A tuple with one element per column converted to erlang terms %% if the sql_query is used or if a description is passed to execute %% else a binary representation for each column %% %% @type portal() = string() | []. %% A string representation of a postgre portal, see postgres documentation %% %% @type statement() = string() | []. %% A string representation of a postgre statement, see postgres documentation %% %% @type sql_type() = integer(). %% A integer representing the OID of the type, see postgres documentation %%==================================================================== %% Application callbacks %%==================================================================== start(normal, []) -> case psql_sup:start_link() of {ok, Pid} -> {ok, Pools} = application:get_env(psql, pools), F = fun({Pool, N}) -> {ok, PS} = application:get_env(psql, Pool), lists:foreach(fun(_) -> {ok, _} = psql_con_sup:start_connection({Pool, PS}) end, lists:seq(1, N)) end, lists:foreach(F, Pools), {ok, Pid}; Error -> Error end. stop(_State) -> ok. %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% @spec connect(Pid::pid(), Host::string(), %% Usr::string(), Pwd::string()) -> ok %% @doc Connect to database %% @end %%-------------------------------------------------------------------- connect(Pid, Host, Usr, Pwd) -> connect(Pid, Host, ?DEFAULT_PORT, Usr, Pwd, Usr). %%-------------------------------------------------------------------- %% @spec connect(Pid::pid(), Host::string(), Port::integer(), %% Usr::string(), Pwd::string(), DB::string) -> ok %% @doc Connect to database %% @end %%-------------------------------------------------------------------- connect(Pid, Host, Port, Usr, Pwd, DB) -> psql_logic:command(Pid, {connect, self(), Host, Port, Usr, Pwd, DB}), receive {psql_server, ready_for_query} -> ok; {psql_server, {sql_error, Error}} -> {sql_error, psql_lib:error(Error)} end. %%-------------------------------------------------------------------- %% @spec allocate() -> pid() %% @doc Allocate a connection from the pool %% @end %%-------------------------------------------------------------------- allocate() -> psql_pool:alloc(self()). %%-------------------------------------------------------------------- %% @spec free() -> ok %% @doc Free a connection back to the pool %% @end %%-------------------------------------------------------------------- free() -> psql_pool:free(self()). %%-------------------------------------------------------------------- %% @spec sql_query(Pid::pid(), Query::query()) -> result() %% @doc Simple sql query %% @end %%-------------------------------------------------------------------- sql_query(Pid, Query) -> psql_logic:command(Pid, {simple_query, self(), Query}), result([], []). %%-------------------------------------------------------------------- %% @spec parse(Pid::pid(), Name::statement(), %% Query::query(), Args::[sql_type()]) -> {[],[]} %% @doc Parse a sql query, can contain placeholders, i.e $N %% @end %%-------------------------------------------------------------------- parse(Pid, Name, Query, Args) -> psql_logic:command(Pid, {parse, self(), Name, Query, Args}), result([], []). %%-------------------------------------------------------------------- %% @spec bind(Pid::pid(), Portal::portal(), %% Statement::statement(), Args::[string()]) -> {[], []} %% @doc Bind variables to placeholders. Variables must be strings %% @end %%-------------------------------------------------------------------- bind(Pid, Portal, Statement, Args) -> psql_logic:command(Pid, {bind, self(), Portal, Statement, Args}), result([], []). %%-------------------------------------------------------------------- %% @spec describe(Pid, Type::statement|portal, %% Name::statement()|portal()) -> result() %% @doc Describes a resultset from a statement or a portal. %% @end %%-------------------------------------------------------------------- describe(Pid, Type, Name) -> psql_logic:command(Pid, {describe, self(), Type, Name}), result([], []). %%-------------------------------------------------------------------- %% @spec execute(Pid::pid(), Portal::portal(), Size::integer()) -> result() %% @doc Execute a portal returns at most Size rows %% @end %%-------------------------------------------------------------------- execute(Pid, Portal, Size) -> psql_logic:command(Pid, {execute, self(), Portal, Size}), result([], []). %%-------------------------------------------------------------------- %% @spec execute(Pid::pid(), Portal::portal(), %% Size::integer(), Description::term()) -> %% result() %% @doc Execute a portal returns at most Size rows converted to erlang terms. %% @end %%-------------------------------------------------------------------- execute(Pid, Portal, Size, Desc) -> psql_logic:command(Pid, {execute, self(), Portal, Size}), result(Desc, []). %%-------------------------------------------------------------------- %% @spec close(Pid, Type::statement|portal, Name::portal()|statement()) -> %% result() %% @doc Closes a statement or portal %% @end %%-------------------------------------------------------------------- close(Pid, Type, Name) -> psql_logic:command(Pid, {close, self(), Type, Name}), result([], []). %%-------------------------------------------------------------------- %% @spec transaction(Pid::pid()) -> result() %% @doc Starts a transaction %% @end %%-------------------------------------------------------------------- transaction(Pid) -> sql_query(Pid, "BEGIN"). %%-------------------------------------------------------------------- %% @spec commit(Pid::pid()) -> result() %% @doc Commits a transaction %% @end %%-------------------------------------------------------------------- commit(Pid) -> sql_query(Pid, "COMMIT"). %%-------------------------------------------------------------------- %% @spec rollback(Pid::pid()) -> result() %% @doc Roll back a transaction %% @end %%-------------------------------------------------------------------- rollback(Pid) -> sql_query(Pid, "ROLLBACK"). %%==================================================================== %% Internal functions %%==================================================================== collect_result(Desc, Acc) -> handle_result(receive_loop(infinite), Desc, Acc). handle_result({row_description, Data}, _Desc, Acc) -> collect_result(psql_lib:row_description(Data), Acc); handle_result({command_complete, Command, []}, Desc, Acc) -> collect_result(Desc, [{Command, []}|Acc]); handle_result({command_complete, Command, Rows}, Desc, Acc) when Desc /= [] -> collect_result(Desc, [{Command, [psql_lib:row(Row, Desc) || Row <- Rows]}|Acc]); handle_result({command_complete, Command, Rows}, Desc, Acc) -> collect_result(Desc, [{Command, Rows}|Acc]); handle_result({sql_error, Error}, _Desc, _Acc) -> psql_lib:error(Error); handle_result(parse_complete, Desc, Acc) -> collect_result(Desc, Acc); handle_result(bind_complete, Desc, Acc) -> collect_result(Desc, Acc); handle_result(fetch_more, Desc, Acc) -> {Desc, Acc}; %% TODO: Handle this differently handle_result(ready_for_query, Desc, Acc) -> case receive_loop(5) of %% TODO: Clean this up timeout -> {Desc, Acc}; Msg -> handle_result(Msg, Desc, Acc) end. result(Desc, Acc) -> case collect_result(Desc, Acc) of {[], Result} -> Result; {Result, []} -> Result; Result -> Result end. receive_loop(infinite) -> receive {psql_server,Msg} -> Msg end; receive_loop(Timeout) -> receive {psql_server,Msg} -> Msg after Timeout -> timeout end. psql-0.1.3/src/psql.hrl0000644000175000001440000000166110541472637014175 0ustar martinusers%%%------------------------------------------------------------------- %%% BASIC INFORMATION %%%------------------------------------------------------------------- %%% @copyright 2006 Erlang Training & Consulting Ltd %%% @author Martin Carlson %%% @version 0.0.1 %%% @doc %%% @end %%%------------------------------------------------------------------- -record(field, {name, table_code, field_code, type, max_length, format}). -define(SQL_BOOLEAN, 16). -define(SQL_BINARY, 17). -define(SQL_CHAR, 18). -define(SQL_BIGINT, 20). -define(SQL_SMALLINT, 21). -define(SQL_INT, 23). -define(SQL_TEXT, 25). -define(SQL_OID, 26). -define(SQL_INET, 896). -define(SQL_INTARRAY, 1007). -define(SQL_CHARARRAY, 1014). -define(SQL_VARCHARARRAY, 1015). -define(SQL_VARCHAR, 1043). -define(SQL_DATE, 1082). -define(SQL_TIME, 1083). -define(SQL_DATETIME, 1114). -define(SQL_TIMESTAMP, 1184). -define(SQL_FLOAT, 1700). psql-0.1.3/ebin/0000755000175000001440000000000011014774320012617 5ustar martinuserspsql-0.1.3/priv/0000755000175000001440000000000011014774320012662 5ustar martinuserspsql-0.1.3/Makefile0000644000175000001440000000162010751606171013346 0ustar martinusers## Makefile for building the skel application ## Author: Martin Carlson ## Usage: make Will build the source code ## make docs Will build the docs ## make app Will build the application resource file include vsn.mk APPLICATION=psql ifndef ($(ERL_TOP)) ERL=erl $(ERL_ARGS) else ERL=$(ERL_TOP)/bin/erl $(ERL_ARGS) endif all: app @(cd src && $(ERL) -make) test: @(cd tests && $(ERL) -DTEST -make) docs: $(ERL) -noshell -pa `pwd`/ebin -run edoc_run application \ "'$(APPLICATION)'" '"."' '[]' -s erlang halt app: src/$(APPLICATION).app.src @(cd src && sed "s|%MODULES%|`ls -x -m *.erl | sed 's|.erl||g' | tr \\\n ' '`|g" `basename $<` | sed "s|%VSN%|$(VSN)|g" | sed "s|%NAME%|$(APPLICATION)|g" > ../ebin/`basename $< .src`) clean: rm -fv ebin/*.beam rm -fv ebin/*.app rm -fv doc/*.html rm -fv doc/edoc-info rm -fv doc/*.css cleanapp: rm -fv ebin/*.app psql-0.1.3/vsn.mk0000644000175000001440000000001211014771305013032 0ustar martinusersVSN=0.1.3