diff options
Diffstat (limited to 'lib/odbc/src')
-rw-r--r-- | lib/odbc/src/Makefile | 122 | ||||
-rw-r--r-- | lib/odbc/src/odbc.app.src | 15 | ||||
-rw-r--r-- | lib/odbc/src/odbc.appup.src | 1 | ||||
-rw-r--r-- | lib/odbc/src/odbc.erl | 943 | ||||
-rw-r--r-- | lib/odbc/src/odbc_app.erl | 36 | ||||
-rw-r--r-- | lib/odbc/src/odbc_debug.erl | 43 | ||||
-rw-r--r-- | lib/odbc/src/odbc_internal.hrl | 164 | ||||
-rw-r--r-- | lib/odbc/src/odbc_sup.erl | 43 |
8 files changed, 1367 insertions, 0 deletions
diff --git a/lib/odbc/src/Makefile b/lib/odbc/src/Makefile new file mode 100644 index 0000000000..b48dd768c8 --- /dev/null +++ b/lib/odbc/src/Makefile @@ -0,0 +1,122 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# + +# +include $(ERL_TOP)/make/target.mk + +ifeq ($(TYPE),debug) +ERL_COMPILE_FLAGS += -Ddebug -W +endif + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(ODBC_VSN) +APP_VSN = "odbc-$(VSN)" +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/odbc-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULES= \ + odbc \ + odbc_app \ + odbc_sup + +HRLS = odbc_internal + +INCLUDE=../include +ODBC_HRL=$(INCLUDE)/odbc.hrl + +EXT_HRL_FILES= $(ODBC_HRL) +HRL_FILES = $(HRLS:%=%.hrl) +ERL_FILES= $(MODULES:%=%.erl) + +APP_FILE = odbc.app +APP_SRC = $(APP_FILE).src +APP_TARGET = $(EBIN)/$(APP_FILE) + +APPUP_FILE = odbc.appup +APPUP_SRC = $(APPUP_FILE).src +APPUP_TARGET = $(EBIN)/$(APPUP_FILE) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +# ---------------------------------------------------- +# ODBC FLAGS +# ---------------------------------------------------- +ODBC_FLAGS = -D'SERVER_SOFTWARE="odbc/$(VSN)"' \ + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += -I$(INCLUDE) \ + $(ODBC_FLAGS) \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +#debug: +# @${MAKE} TYPE=debug opt + +debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + +clean: + rm -f $(TARGET_FILES) + rm -f $(APP_TARGET) + rm -f $(APPUP_TARGET) + rm -f errs core *~ +# --------------------------------------------------- +# Special Target +# --------------------------------------------------- + + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(EXT_HRL_FILES) $(RELSYSDIR)/include + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) \ + $(RELSYSDIR)/ebin + + +release_docs_spec: + + diff --git a/lib/odbc/src/odbc.app.src b/lib/odbc/src/odbc.app.src new file mode 100644 index 0000000000..5229b28c08 --- /dev/null +++ b/lib/odbc/src/odbc.app.src @@ -0,0 +1,15 @@ +{application, odbc, + [{description, "Erlang ODBC application"}, + {vsn, "%VSN%"}, + {modules, [ + odbc, + odbc_app, + odbc_sup + ]}, + {registered, [ + odbc_sup + ]}, + {applications, [kernel, stdlib]}, + {env,[]}, + {mod, {odbc_app, []}}]}. + diff --git a/lib/odbc/src/odbc.appup.src b/lib/odbc/src/odbc.appup.src new file mode 100644 index 0000000000..e95e542ff5 --- /dev/null +++ b/lib/odbc/src/odbc.appup.src @@ -0,0 +1 @@ +{"%VSN%", [],[]} diff --git a/lib/odbc/src/odbc.erl b/lib/odbc/src/odbc.erl new file mode 100644 index 0000000000..8178accf6d --- /dev/null +++ b/lib/odbc/src/odbc.erl @@ -0,0 +1,943 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +% + +-module(odbc). + +-behaviour(gen_server). + +-include("odbc_internal.hrl"). + +%% API -------------------------------------------------------------------- + +-export([start/0, start/1, stop/0, + connect/2, disconnect/1, commit/2, commit/3, sql_query/2, + sql_query/3, select_count/2, select_count/3, first/1, first/2, + last/1, last/2, next/1, next/2, prev/1, prev/2, select/3, + select/4, param_query/3, param_query/4, describe_table/2, + describe_table/3]). + +%%------------------------------------------------------------------------- +%% supervisor callbacks +-export([start_link_sup/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%%-------------------------------------------------------------------------- +%% Internal state +-record(state, {erlang_port, % The port to the c-program + reply_to, % gen_server From parameter + owner, % Pid of the connection owner + result_set = undefined, % exists | undefined + auto_commit_mode = on, % on | off + %% Indicates if first, last and "select absolut" + %% is supported by the odbc driver. + absolute_pos, % true | false + %% Indicates if prev and "select relative" + %% is supported by the odbc driver. + relative_pos, % true | false + scrollable_cursors, % on | off + %% connecting | connected | disconnecting + state = connecting, + %% For timeout handling + pending_request, + num_timeouts = 0, + listen_sockets, + sup_socket, + odbc_socket + }). + +%%-------------------------------------------------------------------------- + +%%%========================================================================= +%%% API +%%%========================================================================= + + +%%-------------------------------------------------------------------- +%% Function: start([, Type]) -> ok +%% +%% Type = permanent | transient | temporary +%% +%% Description: Starts the inets application. Default type +%% is temporary. see application(3) +%%-------------------------------------------------------------------- +start() -> + application:start(odbc). + +start(Type) -> + application:start(odbc, Type). + +%%-------------------------------------------------------------------- +%% Function: stop() -> ok +%% +%% Description: Stops the odbc application. +%%-------------------------------------------------------------------- +stop() -> + application:stop(odbc). + +%%------------------------------------------------------------------------- +%% connect(ConnectionStr, Options) -> {ok, ConnectionReferense} | +%% {error, Reason} +%% Description: Spawns an erlang control process that will open a port +%% to a c-process that uses the ODBC API to open a connection +%% to the database. +%%------------------------------------------------------------------------- +connect(ConnectionStr, Options) when is_list(ConnectionStr), is_list(Options) -> + + %% Spawn the erlang control process. + try supervisor:start_child(odbc_sup, [[{client, self()}]]) of + {ok, Pid} -> + connect(Pid, ConnectionStr, Options); + {error, Reason} -> + {error, Reason} + catch + exit:{noproc, _} -> + {error, odbc_not_started} + end. + +%%-------------------------------------------------------------------------- +%% disconnect(ConnectionReferense) -> ok | {error, Reason} +%% +%% Description: Disconnects from the database and terminates both the erlang +%% control process and the database handling c-process. +%%-------------------------------------------------------------------------- +disconnect(ConnectionReference) when is_pid(ConnectionReference)-> + ODBCCmd = [?CLOSE_CONNECTION], + case call(ConnectionReference, {disconnect, ODBCCmd}, 5000) of + {error, connection_closed} -> + %% If the connection has already been closed the effect of + %% disconnect has already been acomplished + ok; + %% Note a time out of this call will return ok, as disconnect + %% will always succeed, the time out is to make sure + %% the connection is killed brutaly if it will not be shut down + %% gracefully. + ok -> + ok; + %% However you may receive an error message as result if you try to + %% disconnect a connection started by another process. + Other -> + Other + end. + +%%-------------------------------------------------------------------------- +%% commit(ConnectionReference, CommitMode, <TimeOut>) -> ok | {error,Reason} +%% +%% Description: Commits or rollbacks a transaction. Needed on connections +%% where automatic commit is turned off. +%%-------------------------------------------------------------------------- +commit(ConnectionReference, CommitMode) -> + commit(ConnectionReference, CommitMode, ?DEFAULT_TIMEOUT). + +commit(ConnectionReference, commit, infinity) + when is_pid(ConnectionReference) -> + ODBCCmd = [?COMMIT_TRANSACTION, ?COMMIT], + call(ConnectionReference, {commit, ODBCCmd}, infinity); + +commit(ConnectionReference, commit, TimeOut) + when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?COMMIT_TRANSACTION, ?COMMIT], + call(ConnectionReference, {commit, ODBCCmd}, TimeOut); + +commit(ConnectionReference, rollback, infinity) + when is_pid(ConnectionReference) -> + ODBCCmd = [?COMMIT_TRANSACTION, ?ROLLBACK], + call(ConnectionReference, {commit, ODBCCmd}, infinity); + +commit(ConnectionReference, rollback, TimeOut) + when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?COMMIT_TRANSACTION, ?ROLLBACK], + call(ConnectionReference, {commit, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% sql_query(ConnectionReference, SQLQuery, <TimeOut>) -> {updated, NRows} | +%% {selected, ColNames, Rows} | {error, Reason} +%% +%% Description: Executes a SQL query. If it is a SELECT query the +%% result set is returned, otherwise the number of affected +%% rows are returned. +%%-------------------------------------------------------------------------- +sql_query(ConnectionReference, SQLQuery) -> + sql_query(ConnectionReference, SQLQuery, ?DEFAULT_TIMEOUT). + +sql_query(ConnectionReference, SQLQuery, infinity) when + is_pid(ConnectionReference), is_list(SQLQuery) -> + ODBCCmd = [?QUERY, SQLQuery], + call(ConnectionReference, {sql_query, ODBCCmd}, infinity); + +sql_query(ConnectionReference, SQLQuery, TimeOut) + when is_pid(ConnectionReference),is_list(SQLQuery),integer(TimeOut),TimeOut>0 -> + ODBCCmd = [?QUERY, SQLQuery], + call(ConnectionReference, {sql_query, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% select_count(ConnectionReference, SQLQuery, <TimeOut>) -> {ok, NrRows} | +%% {error, Reason} +%% +%% Description: Executes a SQL SELECT query and associates the result set +%% with the connection. A cursor is positioned before +%% the first row in the result set and the number of +%% rows in the result set is returned. +%%-------------------------------------------------------------------------- +select_count(ConnectionReference, SQLQuery) -> + select_count(ConnectionReference, SQLQuery, ?DEFAULT_TIMEOUT). + +select_count(ConnectionReference, SQLQuery, infinity) when + is_pid(ConnectionReference), is_list(SQLQuery) -> + ODBCCmd = [?SELECT_COUNT, SQLQuery], + call(ConnectionReference, {select_count, ODBCCmd}, infinity); + +select_count(ConnectionReference, SQLQuery, TimeOut) when + is_pid(ConnectionReference), is_list(SQLQuery), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT_COUNT, SQLQuery], + call(ConnectionReference, {select_count, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% first(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} | +%% {error, Reason} +%% +%% Description: Selects the first row in the current result set. The cursor +%% : is positioned at this row. +%%-------------------------------------------------------------------------- +first(ConnectionReference) -> + first(ConnectionReference, ?DEFAULT_TIMEOUT). + +first(ConnectionReference, infinity) when is_pid(ConnectionReference) -> + ODBCCmd = [?SELECT, ?SELECT_FIRST], + call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, infinity); + +first(ConnectionReference, TimeOut) + when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT, ?SELECT_FIRST], + call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% last(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} | +%% {error, Reason} +%% +%% Description: Selects the last row in the current result set. The cursor +%% : is positioned at this row. +%%-------------------------------------------------------------------------- +last(ConnectionReference) -> + last(ConnectionReference, ?DEFAULT_TIMEOUT). + +last(ConnectionReference, infinity) when is_pid(ConnectionReference) -> + ODBCCmd = [?SELECT, ?SELECT_LAST], + call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, infinity); + +last(ConnectionReference, TimeOut) + when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT, ?SELECT_LAST], + call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, TimeOut). +%%-------------------------------------------------------------------------- +%% next(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} | +%% {error, Reason} +%% +%% Description: Selects the next row relative the current cursor position +%% : in the current result set. The cursor is positioned at +%% : this row. +%%-------------------------------------------------------------------------- +next(ConnectionReference) -> + next(ConnectionReference, ?DEFAULT_TIMEOUT). + +next(ConnectionReference, infinity) when is_pid(ConnectionReference) -> + ODBCCmd = [?SELECT, ?SELECT_NEXT], + call(ConnectionReference, {select_cmd, next, ODBCCmd}, infinity); + +next(ConnectionReference, TimeOut) + when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT, ?SELECT_NEXT], + call(ConnectionReference, {select_cmd, next, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% prev(ConnectionReference, <TimeOut>) -> {selected, ColNames, Rows} | +%% {error, Reason} +%% +%% Description: Selects the previous row relative the current cursor +%% : position in the current result set. The cursor is +%% : positioned at this row. +%%-------------------------------------------------------------------------- +prev(ConnectionReference) -> + prev(ConnectionReference, ?DEFAULT_TIMEOUT). + +prev(ConnectionReference, infinity) when is_pid(ConnectionReference) -> + ODBCCmd = [?SELECT, ?SELECT_PREV], + call(ConnectionReference, {select_cmd, relative, ODBCCmd}, infinity); + +prev(ConnectionReference, TimeOut) + when is_pid(ConnectionReference), is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT, ?SELECT_PREV], + call(ConnectionReference, {select_cmd, relative, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% select(ConnectionReference, <Timeout>) -> {selected, ColNames, Rows} | +%% {error, Reason} +%% +%% Description: Selects <N> rows. If <Position> is next it is +%% semanticly eqvivivalent of calling next/[1,2] <N> +%% times. If <Position> is {relative, Pos} <Pos> will be +%% used as an offset from the current cursor position to +%% determine the first selected row. If <Position> is +%% {absolute, Pos}, <Pos> will be the number of the first +%% row selected. After this function has returned the +%% cursor is positioned at the last selected row. +%%-------------------------------------------------------------------------- +select(ConnectionReference, Position, N) -> + select(ConnectionReference, Position, N, ?DEFAULT_TIMEOUT). + +select(ConnectionReference, next, N, infinity) + when is_pid(ConnectionReference), is_integer(N), N > 0 -> + ODBCCmd = [?SELECT, ?SELECT_N_NEXT, + integer_to_list(?DUMMY_OFFSET), ";", + integer_to_list(N), ";"], + call(ConnectionReference, {select_cmd, next, ODBCCmd}, + infinity); + +select(ConnectionReference, next, N, TimeOut) + when is_pid(ConnectionReference), is_integer(N), N > 0, + is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT, ?SELECT_N_NEXT, + integer_to_list(?DUMMY_OFFSET), ";", + integer_to_list(N), ";"], + call(ConnectionReference, {select_cmd, next, ODBCCmd}, + TimeOut); + +select(ConnectionReference, {relative, Pos} , N, infinity) + when is_pid(ConnectionReference), is_integer(Pos), Pos > 0, is_integer(N), N > 0 -> + ODBCCmd = [?SELECT, ?SELECT_RELATIVE, + integer_to_list(Pos), ";", integer_to_list(N), ";"], + call(ConnectionReference, {select_cmd, relative, ODBCCmd}, + infinity); + +select(ConnectionReference, {relative, Pos} , N, TimeOut) + when is_pid(ConnectionReference), is_integer(Pos), Pos >0, is_integer(N), N > 0, + is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT,?SELECT_RELATIVE, + integer_to_list(Pos), ";", integer_to_list(N), ";"], + call(ConnectionReference, {select_cmd, relative, ODBCCmd}, + TimeOut); + +select(ConnectionReference, {absolute, Pos} , N, infinity) + when is_pid(ConnectionReference), is_integer(Pos), Pos > 0, is_integer(N), N > 0 -> + ODBCCmd = [?SELECT, ?SELECT_ABSOLUTE, + integer_to_list(Pos), ";", integer_to_list(N), ";"], + call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, + infinity); + +select(ConnectionReference, {absolute, Pos} , N, TimeOut) + when is_pid(ConnectionReference), is_integer(Pos), Pos > 0, is_integer(N), N > 0, + is_integer(TimeOut), TimeOut > 0 -> + ODBCCmd = [?SELECT, ?SELECT_ABSOLUTE, + integer_to_list(Pos), ";", integer_to_list(N), ";"], + call(ConnectionReference, {select_cmd, absolute, ODBCCmd}, + TimeOut). +%%-------------------------------------------------------------------------- +%% param_query(ConnectionReference, SQLQuery, Params, <TimeOut>) -> +%% ok | {error, Reason} +%% +%% Description: Executes a parameterized update/delete/insert-query. +%%-------------------------------------------------------------------------- +param_query(ConnectionReference, SQLQuery, Params) -> + param_query(ConnectionReference, SQLQuery, Params, ?DEFAULT_TIMEOUT). + +param_query(ConnectionReference, SQLQuery, Params, infinity) + when is_pid(ConnectionReference), is_list(SQLQuery), is_list(Params) -> + Values = param_values(Params), + NoRows = length(Values), + NewParams = lists:map(fun fix_params/1, Params), + ODBCCmd = [?PARAM_QUERY, term_to_binary({SQLQuery ++ [?STR_TERMINATOR], + NoRows, NewParams})], + call(ConnectionReference, {param_query, ODBCCmd}, infinity); + +param_query(ConnectionReference, SQLQuery, Params, TimeOut) + when is_pid(ConnectionReference), is_list(SQLQuery), is_list(Params), + is_integer(TimeOut), TimeOut > 0 -> + Values = param_values(Params), + NoRows = length(Values), + NewParams = lists:map(fun fix_params/1, Params), + ODBCCmd = [?PARAM_QUERY, term_to_binary({SQLQuery ++ [?STR_TERMINATOR], + NoRows, NewParams})], + call(ConnectionReference, {param_query, ODBCCmd}, TimeOut). + +%%-------------------------------------------------------------------------- +%% describe_table(ConnectionReference, Table, <TimeOut>) -> {ok, Desc} +%% +%% Desc - [{ColName, Datatype}] +%% ColName - atom() +%% Datatype - atom() +%% Description: Queries the database to find out the datatypes of the +%% table <Table> +%%-------------------------------------------------------------------------- +describe_table(ConnectionReference, Table) -> + describe_table(ConnectionReference, Table, ?DEFAULT_TIMEOUT). + +describe_table(ConnectionReference, Table, infinity) when + is_pid(ConnectionReference), is_list(Table) -> + ODBCCmd = [?DESCRIBE, "SELECT * FROM " ++ Table], + call(ConnectionReference, {describe_table, ODBCCmd}, infinity); + +describe_table(ConnectionReference, Table, TimeOut) + when is_pid(ConnectionReference),is_list(Table),integer(TimeOut),TimeOut>0 -> + ODBCCmd = [?DESCRIBE, "SELECT * FROM " ++ Table], + call(ConnectionReference, {describe_table, ODBCCmd}, TimeOut). +%%%========================================================================= +%%% Start/stop +%%%========================================================================= +%%-------------------------------------------------------------------------- +%% start_link_sup(Args) -> {ok, Pid} | {error, Reason} +%% +%% Description: Callback function for the odbc supervisor. It is called +%% : when connect/2 calls supervisor:start_child/2 to start an +%% : instance of the erlang odbc control process. +%%-------------------------------------------------------------------------- +start_link_sup(Args) -> + gen_server:start_link(?MODULE, Args, []). + +%%% Stop functionality is handled by disconnect/1 + +%%%======================================================================== +%%% Callback functions from gen_server +%%%======================================================================== + +%%------------------------------------------------------------------------- +%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} +%% Description: Initiates the erlang process that manages the connection +%% and starts the port-program that use the odbc driver +%% to communicate with the database. +%%------------------------------------------------------------------------- +init(Args) -> + process_flag(trap_exit, true), + {value, {client, ClientPid}} = lists:keysearch(client, 1, Args), + + erlang:monitor(process, ClientPid), + + Inet = case gen_tcp:listen(0, [inet6]) of + {ok, Dummyport} -> + gen_tcp:close(Dummyport), + inet6; + _ -> + inet + end, + + {ok, ListenSocketSup} = + gen_tcp:listen(0, [Inet, binary, {packet, ?LENGTH_INDICATOR_SIZE}, + {active, false}, {nodelay, true}]), + {ok, ListenSocketOdbc} = + gen_tcp:listen(0, [Inet, binary, {packet, ?LENGTH_INDICATOR_SIZE}, + {active, false}, {nodelay, true}]), + + %% Start the port program (a c program) that utilizes the odbc driver + case os:find_executable(?SERVERPROG, ?SERVERDIR) of + FileName when is_list(FileName)-> + Port = open_port({spawn, FileName}, + [{packet, ?LENGTH_INDICATOR_SIZE}, binary, + exit_status]), + State = #state{listen_sockets = + [ListenSocketSup, ListenSocketOdbc], + erlang_port = Port, owner = ClientPid}, + {ok, State}; + false -> + {stop, port_program_executable_not_found} + end. + +%%-------------------------------------------------------------------------- +%% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, Reply, State} +%% Description: Handle incoming requests. Only requests from the process +%% that created the connection are allowed in order to preserve +%% the semantics of result sets. +%% Note: The order of the function clauses is significant. +%%-------------------------------------------------------------------------- +handle_call({Client, Msg, Timeout}, From, State = + #state{owner = Client, reply_to = undefined}) -> + handle_msg(Msg, Timeout, State#state{reply_to = From}); + +%% The client has caught the timeout and is sending a new request, but +%% we must preserve a synchronous communication with the port. This +%% request will be handled when we have received the answer to the +%% timed out request and thrown it away, if it has not already been +%% timed out itself in which case the request is thrown away. +handle_call(Request = {Client, _, Timeout}, From, + State = #state{owner = Client, reply_to = skip, + num_timeouts = N}) when N < ?MAX_SEQ_TIMEOUTS -> + {noreply, State#state{pending_request = {Request, From}}, Timeout}; + +%% The client has sent so many sequential requests that has timed out that +%% there might be something radically wrong causing the ODBC-driver to +%% hang. So we give up and close the connection. +handle_call({Client, _, _}, From, + State = #state{owner = Client, + num_timeouts = N}) when N >= ?MAX_SEQ_TIMEOUTS -> + gen_server:reply(From, {error, connection_closed}), + {stop, too_many_sequential_timeouts, State#state{reply_to = undefined}}; + +handle_call(_, _, State) -> + {reply, {error, process_not_owner_of_odbc_connection}, + State#state{reply_to = undefined}}. + +%%-------------------------------------------------------------------------- +%% Func: handle_msg(Msg, Timeout, State) -> same as handle_call/3. +%% Description: Sends requests to the port-program. +%% Note: The order of the function clauses is significant. +%%-------------------------------------------------------------------------- +handle_msg({connect, ODBCCmd, AutoCommitMode, SrollableCursors}, + Timeout, State) -> + + [ListenSocketSup, ListenSocketOdbc] = State#state.listen_sockets, + + %% Inform c-client so it knows where to send answers + {ok, InetPortSup} = inet:port(ListenSocketSup), + {ok, InetPortOdbc} = inet:port(ListenSocketOdbc), + + port_command(State#state.erlang_port, + [integer_to_list(InetPortSup), ";", + integer_to_list(InetPortOdbc) , ?STR_TERMINATOR]), + + NewState = State#state{auto_commit_mode = AutoCommitMode, + scrollable_cursors = SrollableCursors}, + + case gen_tcp:accept(ListenSocketSup, 5000) of + {ok, SupSocket} -> + gen_tcp:close(ListenSocketSup), + case gen_tcp:accept(ListenSocketOdbc, 5000) of + {ok, OdbcSocket} -> + gen_tcp:close(ListenSocketOdbc), + odbc_send(OdbcSocket, ODBCCmd), + {noreply, NewState#state{odbc_socket = OdbcSocket, + sup_socket = SupSocket}, + Timeout}; + {error, Reason} -> + {stop, Reason, {error, connection_closed}, NewState} + end; + {error, Reason} -> + {stop, Reason, {error, connection_closed}, NewState} + end; + +handle_msg({disconnect, ODBCCmd}, Timeout, State) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State#state{state = disconnecting}, Timeout}; + +handle_msg({commit, _ODBCCmd}, Timeout, + State = #state{auto_commit_mode = on}) -> + {reply, {error, not_an_explicit_commit_connection}, + State#state{reply_to = undefined}, Timeout}; + +handle_msg({commit, ODBCCmd}, Timeout, + State = #state{auto_commit_mode = off}) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State, Timeout}; + +handle_msg({sql_query, ODBCCmd}, Timeout, State) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State#state{result_set = undefined}, Timeout}; + +handle_msg({param_query, ODBCCmd}, Timeout, State) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State#state{result_set = undefined}, Timeout}; + +handle_msg({describe_table, ODBCCmd}, Timeout, State) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State#state{result_set = undefined}, Timeout}; + +handle_msg({select_count, ODBCCmd}, Timeout, State) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State#state{result_set = exists}, Timeout}; + +handle_msg({select_cmd, absolute, ODBCCmd}, Timeout, + State = #state{result_set = exists, absolute_pos = true}) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State, Timeout}; + +handle_msg({select_cmd, relative, ODBCCmd}, Timeout, + State = #state{result_set = exists, relative_pos = true}) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State, Timeout}; + +handle_msg({select_cmd, next, ODBCCmd}, Timeout, + State = #state{result_set = exists}) -> + odbc_send(State#state.odbc_socket, ODBCCmd), + {noreply, State, Timeout}; + +handle_msg({select_cmd, _Type, _ODBCCmd}, _Timeout, + State = #state{result_set = undefined}) -> + {reply, {error, result_set_does_not_exist}, + State#state{reply_to = undefined}}; + +handle_msg({select_cmd, _Type, _ODBCCmd}, _Timeout, State) -> + Reply = case State#state.scrollable_cursors of + on -> + {error, driver_does_not_support_function}; + off -> + {error, scrollable_cursors_disabled} + end, + + {reply, Reply, State#state{reply_to = undefined}}; + +%--------------------------------------------------------------------------- +%% Catch all - This can oly happen if the application programmer writes +%% really bad code that violates the API. +handle_msg(Request, _Timeout, State) -> + {stop, {'API_violation_connection_colsed', Request}, + {error, connection_closed}, State#state{reply_to = undefined}}. + +%%-------------------------------------------------------------------------- +%% handle_cast(Request, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handles cast messages. +%% Note: The order of the function clauses is significant. +%%------------------------------------------------------------------------- +%% Catch all - This can only happen if the application programmer writes +%% really bad code that violates the API. +handle_cast(Msg, State) -> + {stop, {'API_violation_connection_colsed', Msg}, State}. + +%%-------------------------------------------------------------------------- +%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handles timouts, replys from the port-program and EXIT and +%% down messages. +%% Note: The order of the function clauses is significant. +%%-------------------------------------------------------------------------- +handle_info({tcp, Socket, BinData}, State = #state{state = connecting, + reply_to = From, + odbc_socket = Socket}) -> + case binary_to_term(BinData) of + {ok, AbsolutSupport, RelativeSupport} -> + NewState = State#state{absolute_pos = AbsolutSupport, + relative_pos = RelativeSupport}, + gen_server:reply(From, ok), + {noreply, NewState#state{state = connected, + reply_to = undefined}}; + Error -> + gen_server:reply(From, Error), + {stop, normal, State#state{reply_to = undefined}} + end; + + +handle_info({tcp, Socket, _}, + State = #state{state = connected, + odbc_socket = Socket, + reply_to = skip, + pending_request = undefined}) -> + %% Disregard this message as it is a answer to a query that has timed + %% out. + {noreply, State#state{reply_to = undefined}}; + +handle_info({tcp, Socket, _}, + State = #state{state = connected, odbc_socket = Socket, + reply_to = skip}) -> + + %% Disregard this message as it is a answer to a query that has timed + %% out and process the pending request. + {{_, Msg, Timeout}, From} = State#state.pending_request, + handle_msg(Msg, Timeout, State#state{pending_request=undefined, + reply_to = From}); + +handle_info({tcp, Socket, BinData}, State = #state{state = connected, + reply_to = From, + odbc_socket = Socket}) -> + %% Send the reply from the database (received by the erlang control + %% process from the port program) to the waiting client. + gen_server:reply(From, BinData), + {noreply, State#state{reply_to = undefined, + num_timeouts = 0}}; + +handle_info({tcp, Socket, BinData}, State = #state{state = disconnecting, + reply_to = From, + odbc_socket = Socket}) -> + + %% The connection will always be closed + gen_server:reply(From, ok), + + case binary_to_term(BinData) of + ok -> + ok; + {error, Reason} -> + Report = + io_lib:format("ODBC could not end connection " + "gracefully due to ~p~n", [Reason]), + error_logger:error_report(Report) + end, + + {stop, normal, State#state{reply_to = undefined}}; + +handle_info(timeout, + State = #state{state = disconnecting, + reply_to = From}) when From /= undefined -> + gen_server:reply(From, ok), + {stop, {timeout, "Port program is not responding to disconnect, " + "will be killed"}, State}; + +handle_info(timeout, + State = #state{state = connecting, + reply_to = From}) when From /= undefined -> + gen_server:reply(From, timeout), + {stop, normal, State#state{reply_to = undefined}}; + +handle_info(timeout, + State = #state{state = connected, + pending_request = undefined, + reply_to = From}) when From /= undefined -> + gen_server:reply(From, timeout), + {noreply, State#state{reply_to = skip, + num_timeouts = State#state.num_timeouts + 1}}; + +handle_info(timeout, State = + #state{state = connected, + pending_request = {{_, {disconnect, _}, _}, + PendingFrom}}) -> + gen_server:reply(PendingFrom, ok), + {stop, {timeout, "Port-program busy when trying to disconnect, " + "will be killed"}, + State#state{pending_request = undefined, reply_to = undefined, + num_timeouts = State#state.num_timeouts + 1}}; + +handle_info(timeout, State = + #state{state = connected, + pending_request = {_, PendingFrom}}) -> + gen_server:reply(PendingFrom, timeout), + %% The state variable reply_to should continue to have the value skip + {noreply, State#state{pending_request = undefined, + num_timeouts = State#state.num_timeouts + 1}}; + +handle_info({Port, {exit_status, ?EXIT_SUCCESS}}, + State = #state{erlang_port = Port, state = disconnecting}) -> + {noreply, State}; % Ignore as this is perfectly normal in this case + +handle_info({Port, {exit_status, Status}}, + State = #state{erlang_port = Port}) -> + {stop, {port_exit, ?PORT_EXIT_REASON(Status)}, State}; + +handle_info({'EXIT', Port, _}, State = #state{erlang_port = Port, + state = disconnecting}) -> + {noreply, State}; % Ignore as this is perfectly normal in this case + +handle_info({'EXIT', Port, Reason}, State = #state{erlang_port = Port}) -> + {stop, Reason, State}; + +%%% If the owning process dies there is no reson to go on +handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) -> + {stop, normal, State#state{reply_to = undefined}}; + +handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) -> + {stop, normal, State#state{reply_to = undefined}}; + +handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) -> + {stop, {stopped, {'EXIT', Process, Reason}}, + State#state{reply_to = undefined}}; + +%--------------------------------------------------------------------------- +%% Catch all - throws away unknown messages (This could happen by "accident" +%% so we do not want to crash, but we make a log entry as it is an +%% unwanted behaviour.) +handle_info(Info, State) -> + Report = io_lib:format("ODBC: received unexpected info: ~p~n", [Info]), + error_logger:error_report(Report), + {noreply, State}. + +%%------------------------------------------------------------------------- +%% terminate/2 and code_change/3 +%%-------------------------------------------------------------------------- + +terminate({port_exit, _Reason}, State = #state{reply_to = undefined}) -> + %% Port program crashed + gen_tcp:close(State#state.odbc_socket), + gen_tcp:close(State#state.sup_socket), + ok; + +terminate(_Reason, State = #state{reply_to = undefined}) -> + + catch gen_tcp:send(State#state.sup_socket, + [?SHUTDOWN, ?STR_TERMINATOR]), + catch gen_tcp:close(State#state.odbc_socket), + catch gen_tcp:close(State#state.sup_socket), + catch port_close(State#state.erlang_port), + ok; + +terminate(Reason, State = #state{reply_to = From}) -> + gen_server:reply(From, {error, connection_closed}), + terminate(Reason, State#state{reply_to = undefined}). + +%--------------------------------------------------------------------------- +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== + +connect(ConnectionReferense, ConnectionStr, Options) -> + {C_AutoCommitMode, ERL_AutoCommitMode} = + connection_config(auto_commit, Options), + TimeOut = connection_config(timeout, Options), + {C_TraceDriver, _} = connection_config(trace_driver, Options), + {C_SrollableCursors, ERL_SrollableCursors} = + connection_config(scrollable_cursors, Options), + {C_TupleRow, _} = + connection_config(tuple_row, Options), + ODBCCmd = + [?OPEN_CONNECTION, C_AutoCommitMode, C_TraceDriver, + C_SrollableCursors, C_TupleRow, ConnectionStr], + + %% Send request, to open a database connection, to the control process. + case call(ConnectionReferense, + {connect, ODBCCmd, ERL_AutoCommitMode, ERL_SrollableCursors}, + TimeOut) of + ok -> + {ok, ConnectionReferense}; + Error -> + Error + end. + +%%------------------------------------------------------------------------- +odbc_send(Socket, Msg) -> %% Note currently all allowed messages are lists + NewMsg = Msg ++ [?STR_TERMINATOR], + ok = gen_tcp:send(Socket, NewMsg), + inet:setopts(Socket, [{active, once}]). + +%%-------------------------------------------------------------------------- +connection_config(Key, Options) -> + case lists:keysearch(Key, 1, Options) of + {value,{Key, on}} -> + {?ON, on}; + {value,{Key, off}} -> + {?OFF, off}; + {value,{Key, Value}} -> + Value; + _ -> + connection_default(Key) + end. + +%%-------------------------------------------------------------------------- +connection_default(auto_commit) -> + {?ON, on}; + +connection_default(timeout) -> + ?DEFAULT_TIMEOUT; + +connection_default(tuple_row) -> + {?ON, on}; + +connection_default(trace_driver) -> + {?OFF, off}; + +connection_default(scrollable_cursors) -> + {?ON, on}. + +%%------------------------------------------------------------------------- +call(ConnectionReference, Msg, Timeout) -> + + Result = (catch gen_server:call(ConnectionReference, + {self(), Msg, Timeout}, infinity)), + case Result of + %% Normal case, the result from the port-program has directly + %% been forwarded to the client + Binary when binary(Binary) -> + decode(Binary); + timeout -> + exit(timeout); + {'EXIT', _} -> + {error, connection_closed}; + %% At some occasions the erlang control process will have an + %% answer that was not directly received from the port-program. + Term -> + Term + end. + +%%------------------------------------------------------------------------- +decode(Binary) -> + case binary_to_term(Binary) of + [ResultSet | []] -> + ResultSet; + param_badarg -> + exit({badarg, odbc, param_query, 'Params'}); + MultipleResultSets_or_Other -> + MultipleResultSets_or_Other + end. + +%%------------------------------------------------------------------------- +param_values(Params) -> + case Params of + [{_, Values} | _] -> + Values; + [{_, _, Values} | _] -> + Values + end. + +%%------------------------------------------------------------------------- +fix_params({sql_integer, InOut, Values}) -> + {?USER_INT, fix_inout(InOut), [256 | Values]}; +fix_params({sql_smallint, InOut, Values}) -> + {?USER_SMALL_INT, fix_inout(InOut), [256 | Values]}; +fix_params({sql_tinyint, InOut, Values}) -> + {?USER_TINY_INT, fix_inout(InOut), [256 | Values]}; +fix_params({{sql_decimal, Precision, 0}, InOut, + Values}) when Precision >= 0, Precision =< 9 -> + {?USER_DECIMAL, Precision, 0, fix_inout(InOut), [256 | Values]}; +fix_params({{sql_decimal, Precision, Scale}, InOut, Values}) -> + {?USER_DECIMAL, Precision, Scale, fix_inout(InOut), Values}; +fix_params({{sql_numeric, Precision, 0}, InOut, + Values}) when Precision >= 0, Precision =< 9 -> + {?USER_NUMERIC, Precision, 0, fix_inout(InOut), [256 | Values]}; +fix_params({{sql_numeric, Precision, Scale}, InOut, Values}) -> + {?USER_NUMERIC, Precision, Scale, fix_inout(InOut), Values}; +fix_params({{sql_char, Max}, InOut, Values}) -> + NewValues = + case (catch + lists:map(fun(Str) -> Str ++ [?STR_TERMINATOR] end, Values)) of + Result -> + Result + end, + {?USER_CHAR, Max, fix_inout(InOut), NewValues}; +fix_params({{sql_varchar, Max}, InOut, Values}) -> + NewValues = + case (catch + lists:map(fun(Str) -> Str ++ [?STR_TERMINATOR] end, Values)) of + Result -> + Result + end, + {?USER_VARCHAR, Max, fix_inout(InOut), NewValues}; +fix_params({{sql_float, Precision}, InOut, Values}) -> + {?USER_FLOAT, Precision, fix_inout(InOut), Values}; +fix_params({sql_real, InOut, Values}) -> + {?USER_REAL, fix_inout(InOut), Values}; +fix_params({sql_double, InOut, Values}) -> + {?USER_DOUBLE, fix_inout(InOut), Values}; +fix_params({sql_bit, InOut, Values}) -> + {?USER_BOOLEAN, fix_inout(InOut), Values}; +%% default is IN %%% +fix_params({Type, Values}) -> + fix_params({Type, in, Values}). + +fix_inout(in) -> + ?IN; +fix_inout(out) -> + ?OUT; +fix_inout(inout) -> + ?INOUT. diff --git a/lib/odbc/src/odbc_app.erl b/lib/odbc/src/odbc_app.erl new file mode 100644 index 0000000000..76d8ec1a8c --- /dev/null +++ b/lib/odbc/src/odbc_app.erl @@ -0,0 +1,36 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%%---------------------------------------------------------------------- +%%% Purpose : The main application file of ODBC. +%%%---------------------------------------------------------------------- + +-module(odbc_app). + +-export([start/2, stop/1]). + + +start(_Type, Name) -> + supervisor:start_link({local, odbc_sup}, odbc_sup, [Name]). + + +stop([]) -> + ok. + diff --git a/lib/odbc/src/odbc_debug.erl b/lib/odbc/src/odbc_debug.erl new file mode 100644 index 0000000000..a2e5e5dff3 --- /dev/null +++ b/lib/odbc/src/odbc_debug.erl @@ -0,0 +1,43 @@ +%%%------------------------------------------------------------------- +%%% File : odbc_debug.erl +%%% Author : Ingela Anderton Andin <[email protected]> +%%% Description : Issuse standard tracing on an odbc connection process +%%% +%%% Created : 12 Dec 2003 by Ingela Anderton Andin <[email protected]> +%%%------------------------------------------------------------------- +-module(odbc_debug). + +-export([trace_odbc/2]). + +%%%======================================================================== +%%% Debug functions +%%%======================================================================== + +%%-------------------------------------------------------------------------- +%% trace_odbc(Process, OnOff, <Level>) -> ok +%% Process - pid() | Name | {global, Name} | {Name, Node} +%% OnOff - on | off +%% Level - exported | all +%% Description: Turns on tracing of messages sent and recived by +%% the server <Process> and tracing on all, or all exported +%% functions, according to level <Level>, in this module. +%% Result will be printed on stdout. +%%-------------------------------------------------------------------------- +trace_odbc(Process, OnOff) -> + trace_odbc(Process, OnOff, exported). + +trace_odbc(Process, on, exported) -> + dbg:tracer(), + dbg:tp(odbc, [{'_', [], [{return_trace}]}]), + dbg:p(Process, [call, m]), + ok; + +trace_odbc(Process, on, all) -> + dbg:tracer(), + dbg:tpl(odbc, [{'_', [], [{return_trace}]}]), + dbg:p(Process, [call, m]), + ok; + +trace_odbc(_Process, off, _Level) -> + dbg:stop(), + ok. diff --git a/lib/odbc/src/odbc_internal.hrl b/lib/odbc/src/odbc_internal.hrl new file mode 100644 index 0000000000..144e3cd176 --- /dev/null +++ b/lib/odbc/src/odbc_internal.hrl @@ -0,0 +1,164 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +% +%% + +%% Path to the c-program. +-define(SERVERDIR, filename:nativename( + filename:join(code:priv_dir(odbc), "bin"))). + +%% Name of the C program +-define(SERVERPROG, "odbcserver"). + +%% Constats defining the command protocol between the erlang control +%% process and the port program. These constants must also be defined +%% in the same way in the port program. + +-define(OPEN_CONNECTION, 1). +-define(CLOSE_CONNECTION, 2). +-define(COMMIT_TRANSACTION, 3). +-define(COMMIT, 4). +-define(ROLLBACK, 5). +-define(QUERY, 6). +-define(SELECT_COUNT, 7). +-define(SELECT_FIRST, 8). +-define(SELECT_LAST, 9). +-define(SELECT_NEXT, 10). +-define(SELECT_PREV, 11). +-define(SELECT, 12). +-define(SELECT_RELATIVE, 13). +-define(SELECT_ABSOLUTE, 14). +-define(SELECT_N_NEXT, 15). +-define(PARAM_QUERY, 16). +-define(DESCRIBE, 17). +-define(SHUTDOWN, 18). +-define(LENGTH_INDICATOR_SIZE, 4). +-define(INT_VALUE, 1). +-define(STR_VALUE, 2). +-define(ON, 1). +-define(OFF, 2). +-define(DUMMY_OFFSET, 0). + + +%% Types of parameters given to param_query +-define(USER_SMALL_INT, 1). +-define(USER_INT, 2). +-define(USER_DECIMAL, 3). +-define(USER_NUMERIC, 4). +-define(USER_CHAR, 5). +-define(USER_VARCHAR, 6). +-define(USER_FLOAT, 7). +-define(USER_REAL, 8). +-define(USER_DOUBLE, 9). +-define(USER_BOOLEAN, 10). +-define(USER_TINY_INT, 11). + +%% INPUT & OUTPUT TYPE +-define(IN, 0). +-define(OUT, 1). +-define(INOUT, 2). + +%% EXIT CODES +-define(EXIT_SUCCESS, 0). % As defined in c iso_stdlib +-define(EXIT_FAILURE, 1). % As defined in c iso_stdlib +-define(EXIT_ALLOC, 2). +-define(EXIT_ENV, 3). +-define(EXIT_CONNECTION, 4). +-define(EXIT_FREE, 5). +-define(EXIT_STDIN_HEADER, 6). +-define(EXIT_STDIN_BODY, 7). +-define(EXIT_BIN, 8). +-define(EXIT_THREAD, 9). +-define(EXIT_PARAM_ARRAY, 10). +-define(EXIT_OLD_WINSOCK, 11). +-define(EXIT_SOCKET_CONNECT, 12). +-define(EXIT_SOCKET_SEND_HEADER, 13). +-define(EXIT_SOCKET_SEND_BODY, 14). +-define(EXIT_SOCKET_RECV_MSGSIZE,15). +-define(EXIT_SOCKET_SEND_MSGSIZE,16). +-define(EXIT_SOCKET_RECV_HEADER, 17). +-define(EXIT_SOCKET_RECV_BODY, 18). +-define(EXIT_COLS, 19). +-define(EXIT_ROWS, 20). +-define(EXIT_DESC, 21). +-define(EXIT_BIND, 22). +-define(EXIT_DRIVER_INFO, 23). + +%% Misc constants +-define(DEFAULT_TIMEOUT, infinity). +-define(STR_TERMINATOR, 0). +-define(MAX_SEQ_TIMEOUTS, 10). + +%% Handling of C exit codes +-define(ENCODE_EXIT_FUN, + (fun(?EXIT_SUCCESS) -> + normal_exit; + (?EXIT_FAILURE) -> + abnormal_exit; + (?EXIT_ALLOC) -> + memory_allocation_failed; + (?EXIT_ENV) -> + setting_of_environment_attributes_failed; + (?EXIT_CONNECTION) -> + setting_of_connection_attributes_faild; + (?EXIT_FREE) -> + freeing_of_memory_failed; + (?EXIT_STDIN_HEADER) -> + receiving_port_message_header_failed; + (?EXIT_STDIN_BODY) -> + receiving_port_message_body_failed; + (?EXIT_BIN) -> + retrieving_of_binary_data_failed; + (?EXIT_THREAD) -> + failed_to_create_thread; + (?EXIT_PARAM_ARRAY) -> + does_not_support_param_arrays; + (?EXIT_OLD_WINSOCK) -> + too_old_verion_of_winsock; + (?EXIT_SOCKET_CONNECT) -> + socket_connect_failed; + (?EXIT_SOCKET_SEND_HEADER) -> + socket_send_message_header_failed; + (?EXIT_SOCKET_SEND_BODY) -> + socket_send_message_body_failed; + (?EXIT_SOCKET_RECV_MSGSIZE) -> + socket_received_too_large_message; + (?EXIT_SOCKET_SEND_MSGSIZE) -> + too_large_message_in_socket_send; + (?EXIT_SOCKET_RECV_HEADER) -> + socket_receive_message_header_failed; + (?EXIT_SOCKET_RECV_BODY) -> + socket_receive_message_body_failed; + (?EXIT_COLS) -> + could_not_access_column_count; + (?EXIT_ROWS) -> + could_not_access_row_count; + (?EXIT_DESC) -> + could_not_access_table_description; + (?EXIT_BIND) -> + could_not_bind_data_buffers; + (?EXIT_DRIVER_INFO) -> + collecting_of_driver_information_faild; + (_) -> + killed + end)). + +-define(PORT_EXIT_REASON(EXIT_STATUS), + ?ENCODE_EXIT_FUN(EXIT_STATUS)). diff --git a/lib/odbc/src/odbc_sup.erl b/lib/odbc/src/odbc_sup.erl new file mode 100644 index 0000000000..66b3e4de7c --- /dev/null +++ b/lib/odbc/src/odbc_sup.erl @@ -0,0 +1,43 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(odbc_sup). + +-behaviour(supervisor). + +-export([init/1]). + +init([Name]) -> + RestartStrategy = simple_one_for_one, + MaxR = 0, + MaxT = 3600, + StartFunc = {odbc, start_link_sup, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 7000, + Modules = [odbc], + Type = worker, + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + + + + + + |