1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1999-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%
22
23-module(odbc).
24
25-behaviour(gen_server).
26
27-include("odbc_internal.hrl").
28
29-define(ODBC_PORT_TIMEOUT, 5000).
30
31%% API --------------------------------------------------------------------
32
33-export([start/0, start/1, stop/0,
34	 connect/2, disconnect/1, commit/2, commit/3, sql_query/2,
35	 sql_query/3, select_count/2, select_count/3, first/1, first/2,
36	 last/1, last/2, next/1, next/2, prev/1, prev/2, select/3,
37	 select/4, param_query/3, param_query/4, describe_table/2,
38	 describe_table/3]).
39
40%%-------------------------------------------------------------------------
41%% supervisor callbacks
42-export([start_link_sup/1]).
43
44%% gen_server callbacks
45-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
46	 terminate/2, code_change/3]).
47
48%%--------------------------------------------------------------------------
49%% Internal state
50-record(state, {erlang_port,                 % The port to the c-program
51		reply_to,		     % gen_server From parameter
52		owner,                       % Pid of the connection owner
53		result_set = undefined,      % exists | undefined
54		auto_commit_mode = on,       % on | off
55		%% Indicates if first, last and "select absolut"
56		%% is supported by the odbc driver.
57		absolute_pos,                % true | false
58		%% Indicates if prev and "select relative"
59		%% is supported by the odbc driver.
60		relative_pos,                % true | false
61		scrollable_cursors,      % on | off
62		%% connecting | connected | disconnecting
63		state = connecting,
64		%% For timeout handling
65		pending_request,
66		num_timeouts = 0,
67		listen_sockets,
68		sup_socket,
69		odbc_socket
70	       }).
71
72%%--------------------------------------------------------------------------
73
74%%%=========================================================================
75%%%  API
76%%%=========================================================================
77
78
79%%--------------------------------------------------------------------
80%% Function: start([, Type]) -> ok
81%%
82%%  Type =  permanent | transient | temporary
83%%
84%% Description: Starts the inets application. Default type
85%% is temporary. see application(3)
86%%--------------------------------------------------------------------
87start() ->
88    application:start(odbc).
89
90start(Type) ->
91    application:start(odbc, Type).
92
93%%--------------------------------------------------------------------
94%% Function: stop() -> ok
95%%
96%% Description: Stops the odbc application.
97%%--------------------------------------------------------------------
98stop() ->
99    application:stop(odbc).
100
101%%-------------------------------------------------------------------------
102%% connect(ConnectionStr, Options) -> {ok, ConnectionReferense} |
103%%                                    {error, Reason}
104%% Description: Spawns an erlang control process that will open a port
105%%              to a c-process that uses the ODBC API to open a connection
106%%              to the database.
107%%-------------------------------------------------------------------------
108connect(ConnectionStr, Options) when is_list(ConnectionStr), is_list(Options) ->
109
110    %% Spawn the erlang control process.
111    try  supervisor:start_child(odbc_sup, [[{client, self()}]]) of
112	 {ok, Pid} ->
113	    connect(Pid, ConnectionStr, Options);
114	 {error, Reason} ->
115	    {error, Reason}
116    catch
117	exit:{noproc, _} ->
118            {error, odbc_not_started}
119    end.
120
121%%--------------------------------------------------------------------------
122%% disconnect(ConnectionReferense) -> ok | {error, Reason}
123%%
124%% Description: Disconnects from the database and terminates both the erlang
125%%              control process and the database handling c-process.
126%%--------------------------------------------------------------------------
127disconnect(ConnectionReference) when is_pid(ConnectionReference)->
128    ODBCCmd = [?CLOSE_CONNECTION],
129    case call(ConnectionReference, {disconnect, ODBCCmd}, 5000) of
130	{error, connection_closed} ->
131	    %% If the connection has already been closed the effect of
132	    %% disconnect has already been acomplished
133	    ok;
134	%% Note a time out of this call will return ok, as disconnect
135	%% will always succeed, the time out is to make sure
136	%% the connection is killed brutaly if it will not be shut down
137	%% gracefully.
138	ok ->
139	    ok;
140	%% However you may receive an error message as result if you try to
141	%% disconnect a connection started by another process.
142	Other ->
143	    Other
144    end.
145
146%%--------------------------------------------------------------------------
147%% commit(ConnectionReference, CommitMode, <TimeOut>) -> ok | {error,Reason}
148%%
149%% Description: Commits or rollbacks a transaction. Needed on connections
150%%              where automatic commit is turned off.
151%%--------------------------------------------------------------------------
152commit(ConnectionReference, CommitMode) ->
153    commit(ConnectionReference, CommitMode, ?DEFAULT_TIMEOUT).
154
155commit(ConnectionReference, commit, infinity)
156  when is_pid(ConnectionReference) ->
157    ODBCCmd = [?COMMIT_TRANSACTION, ?COMMIT],
158    call(ConnectionReference, {commit, ODBCCmd}, infinity);
159
160commit(ConnectionReference, commit, TimeOut)
161  when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0  ->
162    ODBCCmd = [?COMMIT_TRANSACTION, ?COMMIT],
163    call(ConnectionReference, {commit, ODBCCmd}, TimeOut);
164
165commit(ConnectionReference, rollback, infinity)
166  when is_pid(ConnectionReference) ->
167    ODBCCmd = [?COMMIT_TRANSACTION, ?ROLLBACK],
168    call(ConnectionReference, {commit, ODBCCmd}, infinity);
169
170commit(ConnectionReference, rollback, TimeOut)
171  when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0  ->
172    ODBCCmd = [?COMMIT_TRANSACTION, ?ROLLBACK],
173    call(ConnectionReference, {commit, ODBCCmd}, TimeOut).
174
175%%--------------------------------------------------------------------------
176%% sql_query(ConnectionReference, SQLQuery, <TimeOut>) -> {updated, NRows} |
177%%			       {selected, ColNames, Rows} | {error, Reason}
178%%
179%% Description: Executes a SQL query. If it is a SELECT query the
180%%              result set is returned, otherwise the number of affected
181%%       	rows are returned.
182%%--------------------------------------------------------------------------
183sql_query(ConnectionReference, SQLQuery) ->
184    sql_query(ConnectionReference, SQLQuery, ?DEFAULT_TIMEOUT).
185
186sql_query(ConnectionReference, SQLQuery, infinity) when
187  is_pid(ConnectionReference), is_list(SQLQuery) ->
188    ODBCCmd = [?QUERY, SQLQuery],
189    call(ConnectionReference, {sql_query, ODBCCmd}, infinity);
190
191sql_query(ConnectionReference, SQLQuery, TimeOut)
192  when is_pid(ConnectionReference),is_list(SQLQuery),is_integer(TimeOut),TimeOut>0 ->
193    ODBCCmd = [?QUERY, SQLQuery],
194    call(ConnectionReference, {sql_query, ODBCCmd}, TimeOut).
195
196%%--------------------------------------------------------------------------
197%% select_count(ConnectionReference, SQLQuery, <TimeOut>) -> {ok, NrRows} |
198%%							    {error, Reason}
199%%
200%% Description: Executes a SQL SELECT query and associates the result set
201%%              with the connection. A cursor is positioned before
202%%        	the first row in the result set and the number of
203%%	        rows in the result set is returned.
204%%--------------------------------------------------------------------------
205select_count(ConnectionReference, SQLQuery) ->
206    select_count(ConnectionReference, SQLQuery, ?DEFAULT_TIMEOUT).
207
208select_count(ConnectionReference, SQLQuery, infinity) when
209  is_pid(ConnectionReference), is_list(SQLQuery) ->
210    ODBCCmd = [?SELECT_COUNT, SQLQuery],
211    call(ConnectionReference, {select_count, ODBCCmd}, infinity);
212
213select_count(ConnectionReference, SQLQuery, TimeOut) when
214  is_pid(ConnectionReference), is_list(SQLQuery), is_integer(TimeOut), TimeOut > 0 ->
215    ODBCCmd = [?SELECT_COUNT, SQLQuery],
216    call(ConnectionReference, {select_count, ODBCCmd}, TimeOut).
217
218%%--------------------------------------------------------------------------
219%% first(ConnectionReference, <TimeOut>) ->  {selected, ColNames, Rows} |
220%%					     {error, Reason}
221%%
222%% Description: Selects the first row in the current result set. The cursor
223%%            : is positioned at this row.
224%%--------------------------------------------------------------------------
225first(ConnectionReference) ->
226    first(ConnectionReference, ?DEFAULT_TIMEOUT).
227
228first(ConnectionReference, infinity) when is_pid(ConnectionReference) ->
229    ODBCCmd = [?SELECT, ?SELECT_FIRST],
230    call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, infinity);
231
232first(ConnectionReference, TimeOut)
233  when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 ->
234    ODBCCmd = [?SELECT, ?SELECT_FIRST],
235    call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, TimeOut).
236
237%%--------------------------------------------------------------------------
238%% last(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} |
239%%					   {error, Reason}
240%%
241%% Description: Selects the last row in the current result set. The cursor
242%%            : is positioned at this row.
243%%--------------------------------------------------------------------------
244last(ConnectionReference) ->
245    last(ConnectionReference, ?DEFAULT_TIMEOUT).
246
247last(ConnectionReference, infinity) when is_pid(ConnectionReference) ->
248    ODBCCmd = [?SELECT, ?SELECT_LAST],
249    call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, infinity);
250
251last(ConnectionReference, TimeOut)
252  when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 ->
253    ODBCCmd = [?SELECT, ?SELECT_LAST],
254    call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, TimeOut).
255%%--------------------------------------------------------------------------
256%% next(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} |
257%%					   {error, Reason}
258%%
259%% Description: Selects the next row relative the current cursor position
260%%            : in the current result set. The cursor is positioned at
261%%            : this row.
262%%--------------------------------------------------------------------------
263next(ConnectionReference) ->
264    next(ConnectionReference, ?DEFAULT_TIMEOUT).
265
266next(ConnectionReference, infinity) when is_pid(ConnectionReference) ->
267    ODBCCmd = [?SELECT, ?SELECT_NEXT],
268    call(ConnectionReference, {select_cmd, next, ODBCCmd}, infinity);
269
270next(ConnectionReference, TimeOut)
271  when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 ->
272    ODBCCmd = [?SELECT, ?SELECT_NEXT],
273    call(ConnectionReference, {select_cmd, next, ODBCCmd}, TimeOut).
274
275%%--------------------------------------------------------------------------
276%% prev(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} |
277%%					   {error, Reason}
278%%
279%% Description: Selects the previous row relative the current cursor
280%%            : position in the current result set. The cursor is
281%%            : positioned at this row.
282%%--------------------------------------------------------------------------
283prev(ConnectionReference) ->
284    prev(ConnectionReference, ?DEFAULT_TIMEOUT).
285
286prev(ConnectionReference, infinity) when is_pid(ConnectionReference) ->
287    ODBCCmd = [?SELECT, ?SELECT_PREV],
288    call(ConnectionReference, {select_cmd, relative, ODBCCmd}, infinity);
289
290prev(ConnectionReference, TimeOut)
291  when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 ->
292    ODBCCmd = [?SELECT, ?SELECT_PREV],
293    call(ConnectionReference, {select_cmd, relative, ODBCCmd}, TimeOut).
294
295%%--------------------------------------------------------------------------
296%% select(ConnectionReference, <Timeout>) -> {selected, ColNames, Rows} |
297%%					     {error, Reason}
298%%
299%% Description: Selects <N> rows. If <Position> is next it is
300%%              semanticly eqvivivalent of calling next/[1,2] <N>
301%%              times. If <Position> is {relative, Pos} <Pos> will be
302%%              used as an offset from the current cursor position to
303%%              determine the first selected row. If <Position> is
304%%              {absolute, Pos}, <Pos> will be the number of the first
305%%              row selected. After this function has returned the
306%%              cursor is positioned at the last selected row.
307%%--------------------------------------------------------------------------
308select(ConnectionReference, Position, N) ->
309    select(ConnectionReference, Position, N, ?DEFAULT_TIMEOUT).
310
311select(ConnectionReference, next, N, infinity)
312  when is_pid(ConnectionReference), is_integer(N), N > 0 ->
313    ODBCCmd = [?SELECT, ?SELECT_N_NEXT,
314	       integer_to_list(?DUMMY_OFFSET), ";",
315	       integer_to_list(N), ";"],
316    call(ConnectionReference, {select_cmd, next, ODBCCmd},
317	 infinity);
318
319select(ConnectionReference, next, N, TimeOut)
320  when is_pid(ConnectionReference), is_integer(N), N > 0,
321  is_integer(TimeOut), TimeOut > 0 ->
322    ODBCCmd = [?SELECT, ?SELECT_N_NEXT,
323	       integer_to_list(?DUMMY_OFFSET), ";",
324	       integer_to_list(N), ";"],
325    call(ConnectionReference, {select_cmd, next, ODBCCmd},
326	 TimeOut);
327
328select(ConnectionReference, {relative, Pos} , N, infinity)
329  when is_pid(ConnectionReference), is_integer(Pos), Pos > 0, is_integer(N), N > 0 ->
330    ODBCCmd = [?SELECT, ?SELECT_RELATIVE,
331	       integer_to_list(Pos), ";", integer_to_list(N), ";"],
332    call(ConnectionReference, {select_cmd, relative, ODBCCmd},
333	 infinity);
334
335select(ConnectionReference, {relative, Pos} , N, TimeOut)
336  when is_pid(ConnectionReference), is_integer(Pos), Pos >0, is_integer(N),  N > 0,
337  is_integer(TimeOut), TimeOut > 0 ->
338    ODBCCmd = [?SELECT,?SELECT_RELATIVE,
339	       integer_to_list(Pos), ";", integer_to_list(N), ";"],
340    call(ConnectionReference, {select_cmd, relative, ODBCCmd},
341	 TimeOut);
342
343select(ConnectionReference, {absolute, Pos} , N, infinity)
344  when is_pid(ConnectionReference), is_integer(Pos), Pos > 0, is_integer(N), N > 0 ->
345    ODBCCmd = [?SELECT, ?SELECT_ABSOLUTE,
346	       integer_to_list(Pos), ";", integer_to_list(N), ";"],
347    call(ConnectionReference, {select_cmd, absolute, ODBCCmd},
348	 infinity);
349
350select(ConnectionReference, {absolute, Pos} , N, TimeOut)
351  when is_pid(ConnectionReference), is_integer(Pos), Pos > 0, is_integer(N),  N > 0,
352  is_integer(TimeOut), TimeOut > 0  ->
353    ODBCCmd = [?SELECT, ?SELECT_ABSOLUTE,
354	       integer_to_list(Pos), ";", integer_to_list(N), ";"],
355    call(ConnectionReference, {select_cmd, absolute, ODBCCmd},
356	 TimeOut).
357%%--------------------------------------------------------------------------
358%% param_query(ConnectionReference, SQLQuery, Params, <TimeOut>) ->
359%%                             ok | {error, Reason}
360%%
361%% Description: Executes a parameterized update/delete/insert-query.
362%%--------------------------------------------------------------------------
363param_query(ConnectionReference, SQLQuery, Params) ->
364    param_query(ConnectionReference, SQLQuery, Params, ?DEFAULT_TIMEOUT).
365
366param_query(ConnectionReference, SQLQuery, Params, infinity)
367  when is_pid(ConnectionReference), is_list(SQLQuery), is_list(Params) ->
368    Values = param_values(Params),
369    NoRows = length(Values),
370    NewParams = lists:map(fun fix_params/1, Params),
371    ODBCCmd = [?PARAM_QUERY, term_to_binary({SQLQuery ++ [?STR_TERMINATOR],
372					     NoRows, NewParams})],
373    call(ConnectionReference, {param_query, ODBCCmd}, infinity);
374
375param_query(ConnectionReference, SQLQuery, Params, TimeOut)
376  when is_pid(ConnectionReference), is_list(SQLQuery), is_list(Params),
377       is_integer(TimeOut), TimeOut > 0 ->
378    Values = param_values(Params),
379    NoRows = length(Values),
380    NewParams = lists:map(fun fix_params/1, Params),
381    ODBCCmd = [?PARAM_QUERY, term_to_binary({SQLQuery ++ [?STR_TERMINATOR],
382					     NoRows, NewParams})],
383    call(ConnectionReference, {param_query, ODBCCmd}, TimeOut).
384
385%%--------------------------------------------------------------------------
386%% describe_table(ConnectionReference, Table, <TimeOut>) -> {ok, Desc}
387%%
388%% Desc - [{ColName, Datatype}]
389%% ColName - atom()
390%% Datatype - atom()
391%% Description: Queries the database to find out the datatypes of the
392%%              table <Table>
393%%--------------------------------------------------------------------------
394describe_table(ConnectionReference, Table) ->
395    describe_table(ConnectionReference, Table, ?DEFAULT_TIMEOUT).
396
397describe_table(ConnectionReference, Table, infinity) when
398  is_pid(ConnectionReference), is_list(Table) ->
399    ODBCCmd = [?DESCRIBE, "SELECT * FROM " ++ Table],
400    call(ConnectionReference, {describe_table, ODBCCmd}, infinity);
401
402describe_table(ConnectionReference, Table, TimeOut)
403  when is_pid(ConnectionReference),is_list(Table),is_integer(TimeOut),TimeOut>0 ->
404    ODBCCmd = [?DESCRIBE, "SELECT * FROM " ++ Table],
405    call(ConnectionReference, {describe_table, ODBCCmd}, TimeOut).
406%%%=========================================================================
407%%% Start/stop
408%%%=========================================================================
409%%--------------------------------------------------------------------------
410%% start_link_sup(Args) -> {ok, Pid} | {error, Reason}
411%%
412%% Description: Callback function for the odbc supervisor. It is called
413%%            : when connect/2 calls supervisor:start_child/2 to start an
414%%            : instance of the erlang odbc control process.
415%%--------------------------------------------------------------------------
416start_link_sup(Args) ->
417    gen_server:start_link(?MODULE, Args, []).
418
419%%% Stop functionality is handled by disconnect/1
420
421%%%========================================================================
422%%% Callback functions from gen_server
423%%%========================================================================
424
425%%-------------------------------------------------------------------------
426%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
427%% Description: Initiates the erlang process that manages the connection
428%%              and starts the port-program that use the odbc driver
429%%		to communicate with the database.
430%%-------------------------------------------------------------------------
431init(Args) ->
432    process_flag(trap_exit, true),
433    {value, {client, ClientPid}} = lists:keysearch(client, 1, Args),
434
435    erlang:monitor(process, ClientPid),
436
437    Inet = case gen_tcp:listen(0, [inet6, {ip, loopback}]) of
438	       {ok, Dummyport} ->
439		   gen_tcp:close(Dummyport),
440		   inet6;
441	       _ ->
442		   inet
443	   end,
444
445    {ok, ListenSocketSup} =
446	gen_tcp:listen(0, [Inet, binary, {packet, ?LENGTH_INDICATOR_SIZE},
447			   {active, false}, {nodelay, true},
448			   {ip, loopback}]),
449    {ok, ListenSocketOdbc} =
450	gen_tcp:listen(0, [Inet, binary, {packet, ?LENGTH_INDICATOR_SIZE},
451			   {active, false}, {nodelay, true},
452			   {ip, loopback}]),
453
454    %% Start the port program (a c program) that utilizes the odbc driver
455    case os:find_executable(?SERVERPROG, ?SERVERDIR) of
456	FileName when is_list(FileName)->
457	    Port  = open_port({spawn, "\""++FileName++"\""},
458			      [{packet, ?LENGTH_INDICATOR_SIZE}, binary,
459			       exit_status]),
460	    State = #state{listen_sockets =
461			   [ListenSocketSup, ListenSocketOdbc],
462			   erlang_port = Port, owner = ClientPid},
463	    {ok, State};
464	false ->
465	    {stop, port_program_executable_not_found}
466    end.
467
468%%--------------------------------------------------------------------------
469%% handle_call(Request, From, State) -> {reply, Reply, State} |
470%%                                      {reply, Reply, State, Timeout} |
471%%                                      {noreply, State}               |
472%%                                      {noreply, State, Timeout}      |
473%%                                      {stop, Reason, Reply, State}   |
474%%                                      {stop, Reason, Reply, State}
475%% Description: Handle incoming requests. Only requests from the process
476%%              that created the connection are allowed in order to preserve
477%%              the semantics of result sets.
478%% Note: The order of the function clauses is significant.
479%%--------------------------------------------------------------------------
480handle_call({Client, Msg, Timeout}, From, State =
481	    #state{owner = Client, reply_to = undefined})  ->
482    handle_msg(Msg, Timeout, State#state{reply_to = From});
483
484%% The client has caught the timeout and is sending a new request, but
485%% we must preserve a synchronous communication with the port. This
486%% request will be handled when we have received the answer to the
487%% timed out request and thrown it away, if it has not already been
488%% timed out itself in which case the request is thrown away.
489handle_call(Request = {Client, _, Timeout}, From,
490	    State = #state{owner = Client, reply_to = skip,
491			   num_timeouts = N}) when N < ?MAX_SEQ_TIMEOUTS ->
492    {noreply, State#state{pending_request = {Request, From}}, Timeout};
493
494%% The client has sent so many sequential requests that has timed out that
495%% there might be something radically wrong causing the ODBC-driver to
496%% hang. So we give up and close the connection.
497handle_call({Client, _, _}, From,
498	    State = #state{owner = Client,
499			   num_timeouts = N}) when N >= ?MAX_SEQ_TIMEOUTS ->
500    gen_server:reply(From, {error, connection_closed}),
501    {stop, too_many_sequential_timeouts, State#state{reply_to = undefined}};
502
503handle_call(_, _, State) ->
504    {reply, {error, process_not_owner_of_odbc_connection},
505     State#state{reply_to = undefined}}.
506
507%%--------------------------------------------------------------------------
508%% Func: handle_msg(Msg, Timeout, State) -> same as handle_call/3.
509%% Description: Sends requests to the port-program.
510%% Note: The order of the function clauses is significant.
511%%--------------------------------------------------------------------------
512handle_msg({connect, ODBCCmd, AutoCommitMode, SrollableCursors},
513	   Timeout, State) ->
514
515    [ListenSocketSup, ListenSocketOdbc] = State#state.listen_sockets,
516
517    %% Inform c-client so it knows where to send answers
518    {ok, InetPortSup} = inet:port(ListenSocketSup),
519    {ok, InetPortOdbc} = inet:port(ListenSocketOdbc),
520
521    port_command(State#state.erlang_port,
522		 [integer_to_list(InetPortSup), ";",
523		  integer_to_list(InetPortOdbc) , ?STR_TERMINATOR]),
524
525    NewState = State#state{auto_commit_mode = AutoCommitMode,
526			   scrollable_cursors = SrollableCursors},
527
528    case gen_tcp:accept(ListenSocketSup, port_timeout()) of
529	{ok, SupSocket} ->
530	    gen_tcp:close(ListenSocketSup),
531	    case gen_tcp:accept(ListenSocketOdbc, port_timeout()) of
532		{ok, OdbcSocket} ->
533		    gen_tcp:close(ListenSocketOdbc),
534		    odbc_send(OdbcSocket, ODBCCmd),
535		    {noreply, NewState#state{odbc_socket = OdbcSocket,
536					     sup_socket = SupSocket},
537		     Timeout};
538		{error, Reason} ->
539		    {stop, Reason, {error, connection_closed}, NewState}
540	    end;
541	{error, Reason} ->
542	    {stop, Reason, {error, connection_closed}, NewState}
543    end;
544
545handle_msg({disconnect, ODBCCmd}, Timeout, State) ->
546    odbc_send(State#state.odbc_socket, ODBCCmd),
547    {noreply, State#state{state = disconnecting}, Timeout};
548
549handle_msg({commit, _ODBCCmd}, Timeout,
550	   State = #state{auto_commit_mode = on}) ->
551    {reply, {error, not_an_explicit_commit_connection},
552     State#state{reply_to = undefined}, Timeout};
553
554handle_msg({commit, ODBCCmd}, Timeout,
555	   State = #state{auto_commit_mode = off}) ->
556    odbc_send(State#state.odbc_socket, ODBCCmd),
557    {noreply, State, Timeout};
558
559handle_msg({sql_query, ODBCCmd}, Timeout, State) ->
560    odbc_send(State#state.odbc_socket, ODBCCmd),
561    {noreply, State#state{result_set = undefined}, Timeout};
562
563handle_msg({param_query, ODBCCmd}, Timeout, State) ->
564    odbc_send(State#state.odbc_socket, ODBCCmd),
565    {noreply, State#state{result_set = undefined}, Timeout};
566
567handle_msg({describe_table, ODBCCmd}, Timeout, State) ->
568    odbc_send(State#state.odbc_socket, ODBCCmd),
569    {noreply, State#state{result_set = undefined}, Timeout};
570
571handle_msg({select_count, ODBCCmd}, Timeout, State) ->
572    odbc_send(State#state.odbc_socket, ODBCCmd),
573    {noreply, State#state{result_set = exists}, Timeout};
574
575handle_msg({select_cmd, absolute, ODBCCmd}, Timeout,
576	   State = #state{result_set = exists, absolute_pos = true}) ->
577    odbc_send(State#state.odbc_socket, ODBCCmd),
578    {noreply, State, Timeout};
579
580handle_msg({select_cmd, relative, ODBCCmd}, Timeout,
581	   State = #state{result_set = exists, relative_pos = true}) ->
582    odbc_send(State#state.odbc_socket, ODBCCmd),
583    {noreply, State, Timeout};
584
585handle_msg({select_cmd, next, ODBCCmd}, Timeout,
586	   State = #state{result_set = exists}) ->
587    odbc_send(State#state.odbc_socket, ODBCCmd),
588    {noreply, State, Timeout};
589
590handle_msg({select_cmd, _Type, _ODBCCmd}, _Timeout,
591	   State = #state{result_set = undefined}) ->
592    {reply, {error, result_set_does_not_exist},
593     State#state{reply_to = undefined}};
594
595handle_msg({select_cmd, _Type, _ODBCCmd}, _Timeout, State) ->
596    Reply = case State#state.scrollable_cursors of
597		on ->
598		    {error, driver_does_not_support_function};
599		off ->
600		    {error, scrollable_cursors_disabled}
601	    end,
602
603    {reply, Reply, State#state{reply_to = undefined}};
604
605%---------------------------------------------------------------------------
606%% Catch all -  This can oly happen if the application programmer writes
607%% really bad code that violates the API.
608handle_msg(Request, _Timeout, State) ->
609    {stop, {'API_violation_connection_colsed', Request},
610     {error, connection_closed}, State#state{reply_to = undefined}}.
611
612%%--------------------------------------------------------------------------
613%% handle_cast(Request, State) -> {noreply, State} |
614%%                                {noreply, State, Timeout} |
615%%                                {stop, Reason, State}
616%% Description: Handles cast messages.
617%% Note: The order of the function clauses is significant.
618%%-------------------------------------------------------------------------
619%% Catch all - This can only happen if the application programmer writes
620%% really bad code that violates the API.
621handle_cast(Msg, State) ->
622    {stop, {'API_violation_connection_colsed', Msg}, State}.
623
624%%--------------------------------------------------------------------------
625%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
626%%			      {stop, Reason, State}
627%% Description: Handles timouts, replys from the port-program and EXIT and
628%%		down messages.
629%% Note: The order of the function clauses is significant.
630%%--------------------------------------------------------------------------
631handle_info({tcp, Socket, BinData}, State = #state{state = connecting,
632						reply_to = From,
633						odbc_socket = Socket}) ->
634    case binary_to_term(BinData) of
635	{ok, AbsolutSupport, RelativeSupport} ->
636	    NewState = State#state{absolute_pos = AbsolutSupport,
637				   relative_pos = RelativeSupport},
638	    gen_server:reply(From, ok),
639	    {noreply, NewState#state{state = connected,
640				     reply_to = undefined}};
641	Error ->
642	    gen_server:reply(From, Error),
643	    {stop, normal, State#state{reply_to = undefined}}
644    end;
645
646
647handle_info({tcp, Socket, _},
648	    State = #state{state = connected,
649 			   odbc_socket = Socket,
650 			   reply_to = skip,
651 			   pending_request = undefined}) ->
652    %% Disregard this message as it is a answer to a query that has timed
653    %% out.
654    {noreply, State#state{reply_to = undefined}};
655
656handle_info({tcp, Socket, _},
657 	    State = #state{state = connected, odbc_socket = Socket,
658 			   reply_to = skip}) ->
659
660    %% Disregard this message as it is a answer to a query that has timed
661    %% out and process the pending request.
662    {{_, Msg, Timeout}, From} = State#state.pending_request,
663    handle_msg(Msg, Timeout, State#state{pending_request=undefined,
664					 reply_to = From});
665
666handle_info({tcp, Socket, BinData}, State = #state{state = connected,
667						   reply_to = From,
668						   odbc_socket = Socket}) ->
669    %% Send the reply from the database (received by the erlang control
670    %% process from the port program) to the waiting client.
671    gen_server:reply(From, BinData),
672    {noreply, State#state{reply_to = undefined,
673			  num_timeouts = 0}};
674
675handle_info({tcp, Socket, BinData}, State = #state{state = disconnecting,
676						   reply_to = From,
677						   odbc_socket = Socket}) ->
678
679    %% The connection will always be closed
680    gen_server:reply(From, ok),
681
682    case binary_to_term(BinData) of
683 	ok ->
684 	    ok;
685 	{error, Reason} ->
686	    Report =
687		io_lib:format("ODBC could not end connection "
688			      "gracefully due to ~p~n", [Reason]),
689 	    error_logger:error_report(Report)
690    end,
691
692    {stop, normal, State#state{reply_to = undefined}};
693
694handle_info(timeout,
695	    State = #state{state = disconnecting,
696			   reply_to = From}) when From /= undefined ->
697    gen_server:reply(From, ok),
698    {stop, {timeout, "Port program is not responding to disconnect, "
699 	    "will be killed"}, State};
700
701handle_info(timeout,
702	    State = #state{state = connecting,
703			   reply_to = From}) when From /= undefined ->
704    gen_server:reply(From, timeout),
705    {stop, normal, State#state{reply_to = undefined}};
706
707handle_info(timeout,
708	    State = #state{state = connected,
709			   pending_request = undefined,
710			   reply_to = From}) when From /= undefined ->
711    gen_server:reply(From, timeout),
712    {noreply, State#state{reply_to = skip,
713			  num_timeouts = State#state.num_timeouts + 1}};
714
715handle_info(timeout, State =
716	    #state{state = connected,
717		   pending_request = {{_, {disconnect, _}, _},
718				      PendingFrom}}) ->
719    gen_server:reply(PendingFrom, ok),
720    {stop, {timeout, "Port-program busy when trying to disconnect,  "
721	    "will be killed"},
722     State#state{pending_request = undefined, reply_to = undefined,
723		 num_timeouts = State#state.num_timeouts + 1}};
724
725handle_info(timeout, State =
726	    #state{state = connected,
727		   pending_request = {_, PendingFrom}}) ->
728    gen_server:reply(PendingFrom, timeout),
729    %% The state variable reply_to should continue to have the value skip
730    {noreply, State#state{pending_request = undefined,
731 			  num_timeouts = State#state.num_timeouts + 1}};
732
733handle_info({Port, {exit_status, ?EXIT_SUCCESS}},
734	    State = #state{erlang_port = Port, state = disconnecting}) ->
735    {noreply, State}; % Ignore as this is perfectly normal in this case
736
737handle_info({Port, {exit_status, Status}},
738	    State = #state{erlang_port = Port}) ->
739    {stop, {port_exit, ?PORT_EXIT_REASON(Status)}, State};
740
741handle_info({'EXIT', Port, _}, State = #state{erlang_port = Port,
742					      state = disconnecting}) ->
743    {noreply, State}; % Ignore as this is perfectly normal in this case
744
745handle_info({'EXIT', Port, Reason}, State = #state{erlang_port = Port}) ->
746    {stop, Reason, State};
747
748%%% If the owning process dies there is no reson to go on
749handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
750    {stop, normal, State#state{reply_to = undefined}};
751
752handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
753    {stop, normal, State#state{reply_to = undefined}};
754
755handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
756    {stop, normal, State#state{reply_to = undefined}};
757
758handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
759    {stop, {stopped, {'EXIT', Process, Reason}},
760     State#state{reply_to = undefined}};
761
762handle_info({tcp_closed, Socket}, State = #state{odbc_socket=Socket,
763						 state = disconnecting}) ->
764    {stop, normal, State};
765%---------------------------------------------------------------------------
766%% Catch all - throws away unknown messages (This could happen by "accident"
767%% so we do not want to crash, but we make a log entry as it is an
768%% unwanted behaviour.)
769handle_info(Info, State) ->
770    Report = io_lib:format("ODBC: received unexpected info: ~p~n", [Info]),
771    error_logger:error_report(Report),
772    {noreply, State}.
773
774%%-------------------------------------------------------------------------
775%% terminate/2 and code_change/3
776%%--------------------------------------------------------------------------
777
778terminate({port_exit, _Reason}, State = #state{reply_to = undefined}) ->
779    %% Port program crashed
780    gen_tcp:close(State#state.odbc_socket),
781    gen_tcp:close(State#state.sup_socket),
782    ok;
783
784terminate(_Reason,  State = #state{reply_to = undefined}) ->
785
786    catch gen_tcp:send(State#state.sup_socket,
787		       [?SHUTDOWN, ?STR_TERMINATOR]),
788    catch gen_tcp:close(State#state.odbc_socket),
789    catch gen_tcp:close(State#state.sup_socket),
790    catch port_close(State#state.erlang_port),
791    ok;
792
793terminate(Reason, State = #state{reply_to = From}) ->
794    gen_server:reply(From, {error, connection_closed}),
795    terminate(Reason, State#state{reply_to = undefined}).
796
797%---------------------------------------------------------------------------
798code_change(_Vsn, State, _Extra) ->
799    {ok, State}.
800
801
802%%%========================================================================
803%%% Internal functions
804%%%========================================================================
805
806connect(ConnectionReferense, ConnectionStr, Options) ->
807    {C_AutoCommitMode, ERL_AutoCommitMode} =
808	connection_config(auto_commit, Options),
809    TimeOut = connection_config(timeout, Options),
810    {C_TraceDriver, _} = connection_config(trace_driver, Options),
811    {C_SrollableCursors, ERL_SrollableCursors} =
812	connection_config(scrollable_cursors, Options),
813    {C_TupleRow, _} =
814	connection_config(tuple_row, Options),
815    {BinaryStrings, _} = connection_config(binary_strings, Options),
816    {ExtendedErrors, _} = connection_config(extended_errors, Options),
817
818    ODBCCmd =
819	[?OPEN_CONNECTION, C_AutoCommitMode, C_TraceDriver,
820	 C_SrollableCursors, C_TupleRow, BinaryStrings, ExtendedErrors, ConnectionStr],
821
822    %% Send request, to open a database connection, to the control process.
823    case call(ConnectionReferense,
824	      {connect, ODBCCmd, ERL_AutoCommitMode, ERL_SrollableCursors},
825	      TimeOut) of
826	ok ->
827	    {ok, ConnectionReferense};
828	Error ->
829	    Error
830    end.
831
832%%-------------------------------------------------------------------------
833odbc_send(Socket, Msg) -> %% Note currently all allowed messages are lists
834    NewMsg = Msg ++ [?STR_TERMINATOR],
835    ok = gen_tcp:send(Socket, NewMsg),
836    ok = inet:setopts(Socket, [{active, once}]).
837
838%%--------------------------------------------------------------------------
839connection_config(Key, Options) ->
840    case lists:keysearch(Key, 1, Options) of
841	{value,{Key, on}} ->
842	    {?ON, on};
843	{value,{Key, off}} ->
844	    {?OFF, off};
845	{value,{Key, Value}} ->
846	    Value;
847	_ ->
848	    connection_default(Key)
849    end.
850
851%%--------------------------------------------------------------------------
852connection_default(auto_commit) ->
853    {?ON, on};
854
855connection_default(timeout) ->
856    ?DEFAULT_TIMEOUT;
857
858connection_default(tuple_row) ->
859  {?ON, on};
860
861connection_default(trace_driver) ->
862    {?OFF, off};
863
864connection_default(scrollable_cursors) ->
865    {?ON, on};
866connection_default(binary_strings) ->
867    {?OFF, off};
868connection_default(extended_errors) ->
869    {?OFF, off}.
870
871%%-------------------------------------------------------------------------
872call(ConnectionReference, Msg, Timeout) ->
873
874    Result = (catch gen_server:call(ConnectionReference,
875				    {self(), Msg, Timeout}, infinity)),
876    case Result of
877	%% Normal case, the result from the port-program has directly
878	%% been forwarded to the client
879	Binary when is_binary(Binary) ->
880	     decode(Binary);
881	timeout ->
882	    exit(timeout);
883	{'EXIT', _} ->
884	    {error, connection_closed};
885	%% At some occasions the erlang control process will have an
886	%% answer that was not directly received from the port-program.
887	Term ->
888	    Term
889    end.
890
891%%-------------------------------------------------------------------------
892decode(Binary) ->
893    case binary_to_term(Binary) of
894	[ResultSet | []] ->
895	    ResultSet;
896	param_badarg ->
897	    exit({badarg, odbc, param_query, 'Params'});
898	MultipleResultSets_or_Other ->
899	    MultipleResultSets_or_Other
900    end.
901
902%%-------------------------------------------------------------------------
903param_values(Params) ->
904    case Params of
905	[{_, Values} | _] ->
906	    Values;
907	[{_, _, Values} | _] ->
908	    Values;
909	[] ->
910	    []
911    end.
912
913%%-------------------------------------------------------------------------
914fix_params({sql_integer, InOut, Values}) ->
915    {?USER_INT, fix_inout(InOut), [256 | Values]};
916fix_params({sql_smallint, InOut, Values}) ->
917    {?USER_SMALL_INT, fix_inout(InOut), [256 | Values]};
918fix_params({sql_tinyint, InOut, Values}) ->
919    {?USER_TINY_INT, fix_inout(InOut), [256 | Values]};
920fix_params({{sql_decimal, Precision, 0}, InOut,
921 	    Values}) when Precision >= 0, Precision =< 9 ->
922    {?USER_DECIMAL, Precision, 0, fix_inout(InOut), [256 | Values]};
923fix_params({{sql_decimal, Precision, Scale}, InOut, Values}) ->
924    {?USER_DECIMAL, Precision, Scale, fix_inout(InOut), Values};
925fix_params({{sql_numeric, Precision, 0}, InOut,
926 	    Values}) when Precision >= 0, Precision =< 9 ->
927    {?USER_NUMERIC, Precision, 0, fix_inout(InOut), [256 | Values]};
928fix_params({{sql_numeric, Precision, Scale}, InOut, Values}) ->
929        {?USER_NUMERIC, Precision, Scale, fix_inout(InOut), Values};
930fix_params({{sql_char, Max}, InOut, Values}) ->
931     NewValues = string_terminate(Values),
932    {?USER_CHAR, Max, fix_inout(InOut), NewValues};
933fix_params({{sql_varchar, Max}, InOut, Values}) ->
934     NewValues = string_terminate(Values),
935    {?USER_VARCHAR, Max, fix_inout(InOut), NewValues};
936fix_params({{sql_wchar, Max}, InOut, Values}) ->
937    NewValues = string_terminate(Values),
938    {?USER_WCHAR, Max, fix_inout(InOut), NewValues};
939fix_params({{sql_wvarchar, Max}, InOut, Values}) ->
940    NewValues = string_terminate(Values),
941    {?USER_WVARCHAR, Max, fix_inout(InOut), NewValues};
942fix_params({{sql_wlongvarchar, Max}, InOut, Values}) ->
943    NewValues = string_terminate(Values),
944    {?USER_WLONGVARCHAR, Max, fix_inout(InOut), NewValues};
945fix_params({{sql_float, Precision}, InOut, Values}) ->
946    {?USER_FLOAT, Precision, fix_inout(InOut), Values};
947fix_params({sql_real, InOut, Values}) ->
948    {?USER_REAL, fix_inout(InOut), Values};
949fix_params({sql_double, InOut, Values}) ->
950    {?USER_DOUBLE, fix_inout(InOut), Values};
951fix_params({sql_bit, InOut, Values}) ->
952    {?USER_BOOLEAN, fix_inout(InOut), Values};
953fix_params({'sql_timestamp', InOut, Values}) ->
954    NewValues =
955 	case (catch
956		  lists:map(
957		    fun({{Year,Month,Day},{Hour,Minute,Second}}) ->
958			    {Year,Month,Day,Hour,Minute,Second};
959		       (null) -> null
960		    end, Values)) of
961 	    Result ->
962 		Result
963 	end,
964    {?USER_TIMESTAMP, fix_inout(InOut), NewValues};
965%% default is IN %%%
966fix_params({Type, Values}) ->
967    fix_params({Type, in, Values}).
968
969fix_inout(in) ->
970    ?IN;
971fix_inout(out) ->
972    ?OUT;
973fix_inout(inout) ->
974    ?INOUT.
975
976string_terminate(Values) ->
977    case (catch lists:map(fun string_terminate_value/1, Values)) of
978	Result ->
979	    Result
980    end.
981
982string_terminate_value(String) when is_list(String) ->
983    String ++ [?STR_TERMINATOR];
984string_terminate_value(Binary) when is_binary(Binary) ->
985    <<Binary/binary,0:16>>;
986string_terminate_value(null) ->
987    null.
988
989port_timeout() ->
990  application:get_env(?MODULE, port_timeout, ?ODBC_PORT_TIMEOUT).
991