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