diff options
Diffstat (limited to 'lib/odbc')
52 files changed, 7439 insertions, 0 deletions
diff --git a/lib/odbc/AUTHORS b/lib/odbc/AUTHORS new file mode 100644 index 0000000000..d1ed32bde1 --- /dev/null +++ b/lib/odbc/AUTHORS @@ -0,0 +1,8 @@ +Original Authors: + Ingela Anderton - version 1.0 + Ingvar Meyer - version 0.9.1 + Joakim Hirsch - first version + +Contributors: +Scott Lystig Fritchie - input/output variables for stored procedures [email protected] - Some 64 bits adjustments
\ No newline at end of file diff --git a/lib/odbc/Makefile b/lib/odbc/Makefile new file mode 100644 index 0000000000..4cc48cb559 --- /dev/null +++ b/lib/odbc/Makefile @@ -0,0 +1,115 @@ +# +# %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 +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +include subdirs.mk + +include vsn.mk + +VSN = $(ODBC_VSN) + +SPECIAL_TARGETS = + +DIR_NAME = odbc_c_src-$(VSN) + +ifndef APP_RELEASE_DIR + APP_RELEASE_DIR = /tmp +endif + +ifndef APP_TAR_FILE + APP_TAR_FILE = $(APP_RELEASE_DIR)/$(DIR_NAME).tgz +endif + +APP_DIR = $(APP_RELEASE_DIR)/$(DIR_NAME) + +ifdef OTP_INSTALL_DIR + APP_INSTALL_DIR = $(OTP_INSTALL_DIR)/lib/erlang +else + # If installing into an OTP structure created + # by installing an source OTP build, the '/tmp' + # shall be replaced with the value of ERL_TOP + APP_INSTALL_DIR = /tmp/lib/erlang +endif + + +# ---------------------------------------------------- +# Default Subdir Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_subdir.mk + +conf: do_configure + +do_configure: configure + ./configure + +configure: configure.in + autoconf + +.PHONY: info + +info: + @echo "ODBC_VSN: $(ODBC_VSN)" + + +# ---------------------------------------------------- +# Application (source) release targets +# ---------------------------------------------------- +app_release: app_doc tar + +app_clean: + rm -rf $(APP_TAR_FILE) $(APP_DIR) + +app_doc: + cd doc/src; $(MAKE) html man + +app_dir: $(APP_DIR) + +$(APP_DIR): + cat TAR.exclude | grep -v "odbc/doc" > TAR.exclude2; \ + echo "odbc/doc/src" >> TAR.exclude2; \ + echo "odbc/TAR.exclude2" >> TAR.exclude2; \ + echo "odbc/doc/internal" >> TAR.exclude2 + (cd ..; find odbc -name 'findmerge.*' >> odbc/TAR.exclude2) + (cd ..; find odbc -name '*.contrib*' >> odbc/TAR.exclude2) + (cd ..; find odbc -name '*.keep*' >> odbc/TAR.exclude2) + (cd ..; find odbc -name '*~' >> odbc/TAR.exclude2) + (cd ..; find odbc -name '*.log' >> odbc/TAR.exclude2) + (cd ..; find odbc -name 'erl_crash.dump' >> odbc/TAR.exclude2) + mkdir $(APP_DIR); \ + (cd ..; tar cfX - odbc/TAR.exclude2 odbc) | \ + (cd $(APP_DIR); tar xf -); \ + mv $(APP_DIR)/odbc/* $(APP_DIR)/; \ + find $(APP_DIR)/odbc -name '.cmake.state' | xargs rm -f; \ + mkdir $(APP_DIR)/autoconf; \ + cp autoconf/config.guess $(APP_DIR)/autoconf/; \ + cp autoconf/config.sub $(APP_DIR)/autoconf/; \ + cp autoconf/install-sh $(APP_DIR)/autoconf/; \ + rmdir $(APP_DIR)/odbc + +tar: $(APP_TAR_FILE) + +$(APP_TAR_FILE): $(APP_DIR) + (cd $(APP_RELEASE_DIR); gtar zcf $(APP_TAR_FILE) $(DIR_NAME)) diff --git a/lib/odbc/c_src/Makefile b/lib/odbc/c_src/Makefile new file mode 100644 index 0000000000..acc79e33d5 --- /dev/null +++ b/lib/odbc/c_src/Makefile @@ -0,0 +1,25 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2005-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% +# + +# +# Invoke with GNU make or clearmake -C gnu. +# + +include $(ERL_TOP)/make/run_make.mk + diff --git a/lib/odbc/c_src/Makefile.in b/lib/odbc/c_src/Makefile.in new file mode 100644 index 0000000000..6a9a174417 --- /dev/null +++ b/lib/odbc/c_src/Makefile.in @@ -0,0 +1,142 @@ +# +# %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 +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +ifeq ($(TYPE),debug) +TYPEMARKER = .debug +TYPEFLAGS = -g +else +TYPEMARKER = +TYPEFLAGS = +endif + +WIN_BIN_DIR = ../priv/bin +BIN_DIR = ../priv/bin/$(TARGET) +OBJ_DIR = ../priv/obj/$(TARGET) +INCLUDE_DIR= ../include + +.PHONY: create_dirs + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(ODBC_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/odbc-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +EI_ROOT = $(ERL_TOP)/lib/erl_interface +EI_INCLUDE = -I$(EI_ROOT)/include +ifeq ($(findstring win32,$(TARGET)),win32) +EI_LIB = -lerl_interface_md -lei_md +ENTRY_OBJ=$(ERL_TOP)/erts/obj/$(TARGET)/port_entry.o +PORT_ENTRY_POINT=erl_port_entry +ENTRY_LDFLAGS=-entry:$(PORT_ENTRY_POINT) +WIN32_TARGET = $(WIN_BIN_DIR)/odbcserver.exe +EXE_TARGET = $(WIN32_TARGET) +else +EI_LIB = -lerl_interface -lei +UNIX_TARGET = $(BIN_DIR)/odbcserver +EXE_TARGET = $(UNIX_TARGET) +endif + +C_FILES = odbcserver.c +H_FILES = odbcserver.h + +# ---------------------------------------------------- +# ODBC locations and include options from configure +# ---------------------------------------------------- +ODBC_LIB = @ODBC_LIB@ +ODBC_INCLUDE = @ODBC_INCLUDE@ + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +CC = @CC@ +CFLAGS = $(TYPEFLAGS) @CFLAGS@ +EI_LDFLAGS = -L$(EI_ROOT)/obj$(TYPEMARKER)/$(TARGET) +LD = @DED_LD@ +LDFLAGS = $(ODBC_LIB) $(EI_LDFLAGS) +LIBS = @LIBS@ $(EI_LIB) +INCLUDES = -I. $(ODBC_INCLUDE) $(EI_INCLUDE) +TARGET_FLAGS = @TARGET_FLAGS@ + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +ifdef EXE_TARGET +opt debug: create_dirs $(EXE_TARGET) +else +opt debug: +endif + +clean: + rm -f core *~ + rm -f $(OBJ_DIR)/* +docs: + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +ifdef UNIX_TARGET +$(UNIX_TARGET): $(BIN_DIR) $(OBJ_DIR)/odbcserver.o + $(CC) $(CFLAGS) -o $@ $(OBJ_DIR)/odbcserver.o $(LDFLAGS) $(LIBS) +endif + +ifdef WIN32_TARGET +$(WIN32_TARGET): $(BIN_DIR) $(OBJ_DIR)/odbcserver.o + $(LD) $(LDFLAGS) -o $@ $(OBJ_DIR)/odbcserver.o $(ENTRY_OBJ) \ + $(LIBS) $(ENTRY_LDFLAGS) +endif + +$(OBJ_DIR)/odbcserver.o: $(OBJ_DIR) odbcserver.c + $(CC) $(CFLAGS) $(INCLUDES) $(TARGET_FLAGS) -o $@ -c odbcserver.c + +create_dirs: + $(INSTALL_DIR) $(OBJ_DIR) + $(INSTALL_DIR) $(BIN_DIR) + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- + +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt +ifdef EXE_TARGET + $(INSTALL_DIR) $(RELSYSDIR)/c_src + $(INSTALL_DATA) $(C_FILES) $(H_FILES) $(RELSYSDIR)/c_src + $(INSTALL_DIR) $(RELSYSDIR)/priv + $(INSTALL_DIR) $(RELSYSDIR)/priv/bin + $(INSTALL_DIR) $(RELSYSDIR)/priv/obj + $(INSTALL_PROGRAM) $(EXE_TARGET) $(RELSYSDIR)/priv/bin +endif + +release_docs_spec: diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c new file mode 100644 index 0000000000..63177121bc --- /dev/null +++ b/lib/odbc/c_src/odbcserver.c @@ -0,0 +1,2502 @@ +/* + * %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% + * + + */ + +/* + DESCRIPTION: Erlang ODBC (Open Database Connectivity) application. An + erlang control process sends request to the c-process that queries the + database using the Microsoft ODBC API. The c-process is implemented + using two threads the supervisor thread and the database handler thread. + If the database thread should hang erlang can close the c-process down + by sendig a shutdown request to the supervisor thread. + + Erlang will start this c-process as a port-program and send information + regarding inet-port nummbers through the erlang-port. + After that c-process will communicate via sockets with erlang. The + reason for this is that some odbc-drivers do unexpected things with + stdin/stdout messing up the erlang-port communication. + + + Command protocol between Erlang and C + ------------------------------------- + The requests from Erlang to C are byte lists composed as [CommandByte, + Bytes, StringTerminator] + + CommandByte - constants between 0 and 255 + identifing the request defined in odbc_internal.hrl and odbcserver.h + + Bytes - How to interpret this sequence of bytes depends on the + CommandByte. + + StringTerminator - 0 + + When the C-program processed the request from erlang it will use the + ei-interface to create an Erlang term. This term will be sent the + erlang via a socket. The Erlang control process, will forward + it to the client that does binary_to_term before returning the result + to the client program. + + Here follows a list of [CommandByte, Bytes] that describes the possible + values. Note the Bytes part may be empty as in the case + of ?CLOSE_CONNECTION and if integer values may be larger than 255 + they are converted to string values. + + [?OPEN_CONNECTION, C_AutoCommitMode, C_TraceDriver, C_SrollableCursors, + C_TupelRow, ConnectionStr] + [?CLOSE_CONNECTION] + [?COMMIT_TRANSACTION, CommitMode] + [?QUERY, SQLQuery] + [?SELECT_COUNT, SQLQuery] + [?SELECT, ?SELECT_FIRST] + [?SELECT, ?SELECT_LAST] + [?SELECT, ?SELECT_NEXT] + [?SELECT, ?SELECT_PREV] + [?SELECT, CursorRelation, integer_to_list(OffSet), ";", + integer_to_list(N), ";"] + [?PARAM_QUERY, Binary] + + C_AutoCommitMode - ?ON | ?OFF + C_TraceDriver - ?ON | ?OFF + C_SrollableCursors - ?ON | ?OFF + C_TupelRow - - ?ON | ?OFF + ConnectionStr - String + CommitMode - ?COMMIT | ?ROLLBACK + SQLQuery - String + CursorRelation - ?SELECT_RELATIVE | ?SELECT_ABSOLUTE | ?SELECT_N_NEXT + OffSet - integer + N - integer + Binary - binary encodede tuple of {SQLQuery, NoRows, Parameters} + NoRows - integer + Parameters - [{Datatype, InOrOut, Value}] + InOrOut = [ERL_ODBC_IN | ERL_ODBC_OUT | ERL_ODBC_INOUT] + Datatype - USER_INT | USER_SMALL_INT | {USER_DECIMAL, Precision, Scale} | + {USER_NMERIC, Precision, Scale} | {USER_CHAR, Max} | {USER_VARCHAR, Max} | + {USER_FLOAT, Precision} | USER_REAL | USER_DOUBLE + Scale - integer + Precision - integer + Max - integer +*/ + +/* ----------------------------- INCLUDES ------------------------------*/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#ifdef UNIX +#include <unistd.h> +#endif + +#if defined WIN32 +#include <winsock2.h> +/* #include <ws2tcpip.h > When we can support a newer c-compiler*/ +#include <windows.h> +#include <fcntl.h> +#include <sql.h> +#include <sqlext.h> +#else +#include "sql.h" +#include "sqlext.h" +#include <pthread.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netdb.h> +#endif + +#include <limits.h> + +#include "ei.h" +#include "odbcserver.h" + +/* ---------------- Main functions ---------------------------------------*/ +static void spawn_sup(const char *port); +#ifdef WIN32 +DWORD WINAPI database_handler(const char *port); +#else +void database_handler(const char *port); +#endif +static db_result_msg handle_db_request(byte *reqstring, db_state *state); +static void supervise(const char *port); +/* ----------------- ODBC functions --------------------------------------*/ + +static db_result_msg db_connect(byte *connStrIn, db_state *state); +static db_result_msg db_close_connection(db_state *state); +static db_result_msg db_end_tran(byte compleationtype, db_state *state); +static db_result_msg db_query(byte *sql, db_state *state); +static db_result_msg db_select_count(byte *sql,db_state *state); +static db_result_msg db_select(byte *args, db_state *state); +static db_result_msg db_param_query(byte *buffer, db_state *state); +static db_result_msg db_describe_table(byte *sql, db_state *state); + +/* ------------- Encode/decode functions -------- ------------------------*/ + +static db_result_msg encode_empty_message(void); +static db_result_msg encode_error_message(char *reason); +static db_result_msg encode_atom_message(char *atom); +static db_result_msg encode_result(db_state *state); +static db_result_msg encode_result_set(SQLSMALLINT num_of_columns, + db_state *state); +static db_result_msg encode_out_params(db_state *state, + int cols, + param_array *params, + int num_param_values); +static db_result_msg encode_column_name_list(SQLSMALLINT num_of_columns, + db_state *state); +static db_result_msg encode_value_list(SQLSMALLINT num_of_columns, + db_state *state); +static db_result_msg encode_value_list_scroll(SQLSMALLINT num_of_columns, + SQLSMALLINT Orientation, + SQLINTEGER OffSet, int N, + db_state *state); +static db_result_msg encode_row_count(SQLINTEGER num_of_rows, + db_state *state); +static void encode_column_dyn(db_column column, int column_nr, + db_state *state); +static void encode_data_type(SQLINTEGER sql_type, SQLINTEGER size, + SQLSMALLINT decimal_digits, db_state *state); +static Boolean decode_params(byte *buffer, int *index, param_array **params, + int i, int j); + +/*------------- Erlang port communication functions ----------------------*/ + +static int read_exact(byte *buf, int len); +static byte * receive_erlang_port_msg(void); + +/* ------------- Socket communication functions --------------------------*/ + +#ifdef WIN32 +static SOCKET connect_to_erlang(const char *port); +static void send_msg(db_result_msg *msg, SOCKET socket); +static byte *receive_msg(SOCKET socket); +static Boolean receive_msg_part(SOCKET socket, + byte * buffer, size_t msg_len); +static Boolean send_msg_part(SOCKET socket, byte * buffer, size_t msg_len); +static void close_socket(SOCKET socket); +static void init_winsock(void); +#elif defined(UNIX) +static int connect_to_erlang(const char *port); +static void send_msg(db_result_msg *msg, int socket); +static byte *receive_msg(int socket); +static Boolean receive_msg_part(int socket, byte * buffer, size_t msg_len); +static Boolean send_msg_part(int socket, byte * buffer, size_t msg_len); +static void close_socket(int socket); +#endif +static void clean_socket_lib(void); + +/*------------- Memory handling funtions --------------------------------*/ + +static void * safe_malloc(int size); +static void * safe_realloc(void * ptr, int size); +static db_column * alloc_column_buffer(int n); +static void free_column_buffer(db_column **columns, int n); +static void free_params(param_array **params, int cols); +static void clean_state(db_state *state); + +/* ------------- Init/map/bind/retrive functions -------------------------*/ + +static void init_driver(int erl_auto_commit_mode, int erl_trace_driver, + db_state *state); +static void init_param_column(param_array *params, byte *buffer, int *index, + int num_param_values, db_state* state); + +static void init_param_statement(int cols, + int num_param_values, + db_state *state, + param_status *status); + +static void map_dec_num_2_c_column(col_type *type, int precision, + int scale); +static db_result_msg map_sql_2_c_column(db_column* column); + + +static param_array * bind_parameter_arrays(byte *buffer, int *index, + int cols, + int num_param_values, + db_state *state); +static void * retrive_param_values(param_array *Param); + +static db_column retrive_binary_data(db_column column, int column_nr, + db_state *state); +static db_result_msg retrive_scrollable_cursor_support_info(db_state + *state); +static int num_out_params(int num_of_params, param_array* params); +/* ------------- Error handling functions --------------------------------*/ + +static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle); + +/* ------------- Boolean functions ---------------------------------------*/ + +static db_result_msg more_result_sets(db_state *state); +static Boolean sql_success(SQLRETURN result); +static void str_tolower(char *str, int len); + +/* ----------------------------- CODE ------------------------------------*/ + +#if defined(WIN32) +# define DO_EXIT(code) do { ExitProcess((code)); exit((code));} while (0) +/* exit() called only to avoid a warning */ +#else +# define DO_EXIT(code) exit((code)) +#endif + +/* ----------------- Main functions --------------------------------------*/ + +int main(void) +{ + byte *msg = NULL; + char *temp = NULL, *supervisor_port = NULL, *odbc_port = NULL; + size_t length; +#ifdef WIN32 + _setmode(_fileno( stdin), _O_BINARY); +#endif + + msg = receive_erlang_port_msg(); + + temp = strtok(msg, ";"); + length = strlen(temp); + supervisor_port = safe_malloc(length + 1); + strcpy(supervisor_port, temp); + + temp = strtok(NULL, ";"); + length = strlen(temp); + odbc_port = safe_malloc(length + 1); + strcpy(odbc_port, temp); + + free(msg); + + spawn_sup(supervisor_port); + database_handler(odbc_port); + + return 0; +} + +#ifdef WIN32 +static void spawn_sup(const char *port) +{ + DWORD threadId; + (HANDLE)_beginthreadex(NULL, 0, supervise, port, 0, &threadId); +} +#elif defined(UNIX) +static void spawn_sup(const char *port) +{ + pthread_t thread; + int result; + + result = pthread_create(&thread, NULL, + (void *(*)(void *))supervise, + (void *)port); + if (result != 0) + DO_EXIT(EXIT_THREAD); +} +#endif + +void supervise(const char *port) { + byte *msg = NULL; + int reason; +#ifdef WIN32 + SOCKET socket; + init_winsock(); +#elif defined(UNIX) + int socket; +#endif + + socket = connect_to_erlang(port); + msg = receive_msg(socket); + + if(msg[0] == SHUTDOWN) { + reason = EXIT_SUCCESS; + } else { + reason = EXIT_FAILURE; /* Should not happen */ + } + + free(msg); + close_socket(socket); + clean_socket_lib(); + DO_EXIT(reason); +} + +#ifdef WIN32 +DWORD WINAPI database_handler(const char *port) +#else + void database_handler(const char *port) +#endif +{ + db_result_msg msg; + byte *request_buffer = NULL; + db_state state = + {NULL, NULL, NULL, NULL, 0, {NULL, 0, 0}, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}; + byte request_id; +#ifdef WIN32 + SOCKET socket; + init_winsock(); +#elif defined(UNIX) + int socket; +#endif + + socket = connect_to_erlang(port); + + do { + request_buffer = receive_msg(socket); + + request_id = request_buffer[0]; + msg = handle_db_request(request_buffer, &state); + + send_msg(&msg, socket); /* Send answer to erlang */ + + if (msg.dyn_alloc) { + ei_x_free(&(state.dynamic_buffer)); + } else { + free(msg.buffer); + msg.buffer = NULL; + } + + free(request_buffer); + request_buffer = NULL; + + } while(request_id != CLOSE_CONNECTION); + + shutdown(socket, 2); + close_socket(socket); + clean_socket_lib(); + DO_EXIT(EXIT_SUCCESS); +} + +/* Description: Calls the appropriate function to handle the database + request recived from the erlang-process. Returns a message to send back + to erlang. */ +static db_result_msg handle_db_request(byte *reqstring, db_state *state) +{ + byte *args; + byte request_id; + + /* First byte is an index that identifies the requested command the + rest is the argument string. */ + request_id = reqstring[0]; + args = reqstring + sizeof(byte); + + switch(request_id) { + case OPEN_CONNECTION: + return db_connect(args, state); + case CLOSE_CONNECTION: + return db_close_connection(state); + case COMMIT_TRANSACTION: + if(args[0] == COMMIT) { + return db_end_tran((byte)SQL_COMMIT, state); + } else { /* args[0] == ROLLBACK */ + return db_end_tran((byte)SQL_ROLLBACK, state); + } + case QUERY: + return db_query(args, state); + case SELECT_COUNT: + return db_select_count(args, state); + case SELECT: + return db_select(args, state); + case PARAM_QUERY: + return db_param_query(args, state); + case DESCRIBE: + return db_describe_table(args, state); + default: + DO_EXIT(EXIT_FAILURE); /* Should not happen */ + } +} + +/* ----------------- ODBC-functions ----------------------------------*/ + +/* Description: Tries to open a connection to the database using + <connStrIn>, returns a message indicating the outcome. */ +static db_result_msg db_connect(byte *args, db_state *state) +{ + SQLCHAR connStrOut[MAX_CONN_STR_OUT + 1] = {0}; + SQLRETURN result; + SQLSMALLINT stringlength2ptr = 0, connlen; + db_result_msg msg; + diagnos diagnos; + byte *connStrIn; + int erl_auto_commit_mode, erl_trace_driver, + use_srollable_cursors, tuple_row_state; + + erl_auto_commit_mode = args[0]; + erl_trace_driver = args[1]; + use_srollable_cursors = args[2]; + tuple_row_state = args[3]; + connStrIn = args + 4 * sizeof(byte); + + if(tuple_row_state == ON) { + tuple_row(state) = TRUE; + } else { + tuple_row(state) = FALSE; + } + + if(use_srollable_cursors == ON) { + use_srollable_cursors(state) = TRUE; + } else { + use_srollable_cursors(state) = FALSE; + } + + init_driver(erl_auto_commit_mode, erl_trace_driver, state); + + connlen = (SQLSMALLINT)strlen((const char*)connStrIn); + result = SQLDriverConnect(connection_handle(state), NULL, + (SQLCHAR *)connStrIn, + connlen, + connStrOut, (SQLSMALLINT)MAX_CONN_STR_OUT, + &stringlength2ptr, SQL_DRIVER_NOPROMPT); + + if (!sql_success(result)) { + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + strcat((char *)diagnos.error_msg, + " Connection to database failed."); + msg = encode_error_message(diagnos.error_msg); + + if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC, + connection_handle(state)))) + DO_EXIT(EXIT_FREE); + if(!sql_success(SQLFreeHandle(SQL_HANDLE_ENV, + environment_handle(state)))) + DO_EXIT(EXIT_FREE); + + return msg; + } + + msg = retrive_scrollable_cursor_support_info(state); + + return msg; +} + +/* Close the connection to the database. Returns an ok or error message. */ +static db_result_msg db_close_connection(db_state *state) +{ + int index; + SQLRETURN result; + diagnos diagnos; + + if (associated_result_set(state)) { + clean_state(state); + } + + result = SQLDisconnect(connection_handle(state)); + + if (!sql_success(result)) { + diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state)); + return encode_error_message(diagnos.error_msg); + } + + if(!sql_success(SQLFreeHandle(SQL_HANDLE_DBC, + connection_handle(state)))) + DO_EXIT(EXIT_FREE); + if(!sql_success(SQLFreeHandle(SQL_HANDLE_ENV, + environment_handle(state)))) + DO_EXIT(EXIT_FREE); + + return encode_atom_message("ok"); +} + + +/* Description: Requests a commit or rollback operation for all active + operations on all statements associated with the connection + handle <connection_handle(state)>. Returns an ok or error message. */ +static db_result_msg db_end_tran(byte compleationtype, db_state *state) +{ + SQLRETURN result; + diagnos diagnos; + + result = SQLEndTran(SQL_HANDLE_DBC, connection_handle(state), + (SQLSMALLINT)compleationtype); + + if (!sql_success(result)) { + diagnos = get_diagnos(SQL_HANDLE_DBC, connection_handle(state)); + return encode_error_message(diagnos.error_msg); + } else { + return encode_atom_message("ok"); + } +} + +/* Description: Executes an sql query and encodes the result set as an + erlang term into the message buffer of the returned message-struct. */ +static db_result_msg db_query(byte *sql, db_state *state) +{ + char *atom; + int num_of_rows, elements, update; + SQLSMALLINT num_of_columns; + SQLRETURN result; + SQLINTEGER RowCountPtr; + db_result_msg msg; + diagnos diagnos; + byte is_error[6]; + + if (associated_result_set(state)) { + clean_state(state); + } + associated_result_set(state) = FALSE; + + msg = encode_empty_message(); + + if(!sql_success(SQLAllocHandle(SQL_HANDLE_STMT, + connection_handle(state), + &statement_handle(state)))) + DO_EXIT(EXIT_ALLOC); + + result = SQLExecDirect(statement_handle(state), sql, SQL_NTS); + + /* SQL_SUCCESS_WITH_INFO at this point may indicate an error in user input. */ + if (result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND) { + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + if(strcmp((char *)diagnos.sqlState, INFO) == 0) { + is_error[0] = 0; + strncat((char *)is_error, (char *)diagnos.error_msg, + 5); + str_tolower((char *)&is_error, 5); + /* The ODBC error handling could have been more + predictable but alas ... we try to make the best of + it as we want a nice and clean Erlang API */ + if((strcmp((char *)is_error, "error") == 0)) + { + msg = encode_error_message((char *)diagnos.error_msg); + clean_state(state); + return msg; + } + } else { + msg = encode_error_message((char *)diagnos.error_msg); + clean_state(state); + return msg; + } + } + + ei_x_new_with_version(&dynamic_buffer(state)); + + /* OTP-5759, fails when 0 rows deleted */ + if (result == SQL_NO_DATA_FOUND) { + msg = encode_result(state); + } else { + /* Handle multiple result sets */ + do { + ei_x_encode_list_header(&dynamic_buffer(state), 1); + msg = encode_result(state); + /* We don't want to continue if an error occured */ + if (msg.length != 0) { + break; + } + msg = more_result_sets(state); + /* We don't want to continue if an error occured */ + if (msg.length != 0) { + break; + } + } while (exists_more_result_sets(state)); + + ei_x_encode_empty_list(&dynamic_buffer(state)); + } + + clean_state(state); + + if (msg.length != 0) { /* An error has occurred */ + ei_x_free(&(dynamic_buffer(state))); + return msg; + } else { + msg.buffer = dynamic_buffer(state).buff; + msg.length = dynamic_buffer(state).index; + msg.dyn_alloc = TRUE; + return msg; + } +} + +/* Description: Executes an sql query. Returns number of rows in the result + set. */ +static db_result_msg db_select_count(byte *sql, db_state *state) +{ + SQLSMALLINT num_of_columns, intresult; + SQLLEN num_of_rows; + SQLRETURN result; + diagnos diagnos; + db_result_msg msg; + int index; + + if (associated_result_set(state)) { + clean_state(state); + } + associated_result_set(state) = TRUE; + + if(!sql_success(SQLAllocHandle(SQL_HANDLE_STMT, + connection_handle(state), + &statement_handle(state)))) + DO_EXIT(EXIT_ALLOC); + + if(use_srollable_cursors(state)) { + /* This function will fail if the driver does not support scrollable + cursors, this is expected and will not cause any damage*/ + SQLSetStmtAttr(statement_handle(state), + (SQLINTEGER)SQL_ATTR_CURSOR_SCROLLABLE, + (SQLPOINTER)SQL_SCROLLABLE, (SQLINTEGER)0); + } + + if(!sql_success(SQLExecDirect(statement_handle(state), sql, SQL_NTS))) { + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + clean_state(state); + return encode_error_message(diagnos.error_msg); + } + + if(!sql_success(SQLNumResultCols(statement_handle(state), + &num_of_columns))) + DO_EXIT(EXIT_COLS); + + nr_of_columns(state) = (int)num_of_columns; + columns(state) = alloc_column_buffer(nr_of_columns(state)); + + if(!sql_success(SQLRowCount(statement_handle(state), &num_of_rows))) + DO_EXIT(EXIT_ROWS); + + return encode_row_count(num_of_rows, state); +} + +/* Description: Fetches rows from the result set associated with the + connection by db_select_count. The method of seletion will be according + too <args> */ +static db_result_msg db_select(byte *args, db_state *state) +{ + db_result_msg msg; + SQLSMALLINT num_of_columns; + int offset, n, orientation; + byte erlOrientation; + + erlOrientation = args[0]; + + switch(erlOrientation) { + case SELECT_FIRST: + orientation = SQL_FETCH_FIRST; + offset = DUMMY_OFFSET; + n = 1; + break; + case SELECT_LAST: + orientation = SQL_FETCH_LAST; + offset = DUMMY_OFFSET; + n = 1; + break; + case SELECT_NEXT: + orientation = SQL_FETCH_NEXT; + offset = DUMMY_OFFSET; + n = 1; + break; + case SELECT_PREV: + orientation = SQL_FETCH_PRIOR; + offset = DUMMY_OFFSET; + n = 1; + break; + case SELECT_ABSOLUTE: + orientation = SQL_FETCH_ABSOLUTE; + offset = atoi(strtok((char *)(args + sizeof(byte)), ";")); + n = atoi(strtok(NULL, ";")); + break; + case SELECT_RELATIVE: + orientation = SQL_FETCH_RELATIVE; + offset = atoi(strtok((char *)(args + sizeof(byte)), ";")); + n = atoi(strtok(NULL, ";")); + break; + case SELECT_N_NEXT: + orientation = SQL_FETCH_NEXT; + offset = atoi(strtok((char *)(args + sizeof(byte)), ";")); + n = atoi(strtok(NULL, ";")); + } + + msg = encode_empty_message(); + + ei_x_new(&dynamic_buffer(state)); + ei_x_new_with_version(&dynamic_buffer(state)); + ei_x_encode_tuple_header(&dynamic_buffer(state), 3); + ei_x_encode_atom(&dynamic_buffer(state), "selected"); + + num_of_columns = nr_of_columns(state); + msg = encode_column_name_list(num_of_columns, state); + if (msg.length == 0) { /* If no error has occurred */ + msg = encode_value_list_scroll(num_of_columns, + (SQLSMALLINT)orientation, + (SQLINTEGER)offset, + n, state); + } + + if (msg.length != 0) { /* An error has occurred */ + ei_x_free(&(dynamic_buffer(state))); + return msg; + } else { + msg.buffer = dynamic_buffer(state).buff; + msg.length = dynamic_buffer(state).index; + msg.dyn_alloc = TRUE; + return msg; + } +} + +/* Description: Handles parameterized queries ex: + INSERT INTO FOO VALUES(?, ?) */ +static db_result_msg db_param_query(byte *buffer, db_state *state) +{ + byte *sql; + db_result_msg msg; + int i, num_param_values, ver = 0, + erl_type = 0, index = 0, size = 0, cols = 0; + long long_num_param_values; + param_status param_status; + diagnos diagnos; + param_array *params; + + if (associated_result_set(state)) { + clean_state(state); + } + associated_result_set(state) = FALSE; + param_query(state) = TRUE; + + msg = encode_empty_message(); + + ei_decode_version(buffer, &index, &ver); + + ei_decode_tuple_header(buffer, &index, &size); + + ei_get_type(buffer, &index, &erl_type, &size); + + sql = (byte*)safe_malloc((sizeof(byte) * (size + 1))); + ei_decode_string(buffer, &index, sql); + + ei_decode_long(buffer, &index, &long_num_param_values); + + num_param_values = (int)long_num_param_values; + ei_decode_list_header(buffer, &index, &cols); + + + init_param_statement(cols, num_param_values, state, ¶m_status); + + params = bind_parameter_arrays(buffer, &index, cols, + num_param_values, state); + + if(params != NULL) { + if(!sql_success(SQLExecDirect(statement_handle(state), + sql, SQL_NTS))) { + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + msg = encode_error_message(diagnos.error_msg); + } else { + for (i = 0; i < param_status.params_processed; i++) { + switch (param_status.param_status_array[i]) { + case SQL_PARAM_SUCCESS: + case SQL_PARAM_SUCCESS_WITH_INFO: + /* SQL_PARAM_DIAG_UNAVAILABLE is entered when the + * driver treats arrays of parameters as a monolithic + * unit, so it does not generate this individual + * parameter level of error information. */ + case SQL_PARAM_DIAG_UNAVAILABLE: + break; + default: + diagnos = + get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + msg = encode_error_message(diagnos.error_msg); + i = param_status.params_processed; + break; + } + } + if(msg.length == 0) { + ei_x_new_with_version(&dynamic_buffer(state)); + if(out_params(state)){ + msg = encode_out_params(state, cols, params, num_param_values); + }else{ + msg = encode_result(state); + } + if(msg.length == 0) { + msg.buffer = dynamic_buffer(state).buff; + msg.length = dynamic_buffer(state).index; + msg.dyn_alloc = TRUE; + } else { /* Error occurred */ + ei_x_free(&(dynamic_buffer(state))); + } + } + } + + if(!sql_success(SQLFreeStmt(statement_handle(state), + SQL_RESET_PARAMS))) { + DO_EXIT(EXIT_FREE); + } + } else { + msg = encode_atom_message("param_badarg"); + } + + free(sql); + + free_params(¶ms, cols); + + free(param_status.param_status_array); + + if(!sql_success(SQLFreeHandle(SQL_HANDLE_STMT, + statement_handle(state)))){ + DO_EXIT(EXIT_FREE); + } + statement_handle(state) = NULL; + param_query(state) = FALSE; + return msg; +} + + +static db_result_msg db_describe_table(byte *sql, db_state *state) +{ + db_result_msg msg; + SQLSMALLINT num_of_columns; + SQLCHAR name[MAX_NAME]; + SQLSMALLINT name_len, sql_type, dec_digits, nullable; + SQLLEN size; + diagnos diagnos; + int i; + + if (associated_result_set(state)) { + clean_state(state); + } + associated_result_set(state) = FALSE; + + msg = encode_empty_message(); + + if(!sql_success(SQLAllocHandle(SQL_HANDLE_STMT, + connection_handle(state), + &statement_handle(state)))) + DO_EXIT(EXIT_ALLOC); + + if (!sql_success(SQLPrepare(statement_handle(state), sql, SQL_NTS))){ + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + msg = encode_error_message(diagnos.error_msg); + clean_state(state); + return msg; + } + + if(!sql_success(SQLNumResultCols(statement_handle(state), + &num_of_columns))) { + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + msg = encode_error_message(diagnos.error_msg); + clean_state(state); + return msg; + } + + ei_x_new_with_version(&dynamic_buffer(state)); + ei_x_encode_tuple_header(&dynamic_buffer(state), 2); + ei_x_encode_atom(&dynamic_buffer(state), "ok"); + ei_x_encode_list_header(&dynamic_buffer(state), num_of_columns); + + for (i = 0; i < num_of_columns; ++i) { + + if(!sql_success(SQLDescribeCol(statement_handle(state), + (SQLUSMALLINT)(i+1), + name, sizeof(name), &name_len, + &sql_type, &size, &dec_digits, + &nullable))) + DO_EXIT(EXIT_DESC); + + ei_x_encode_tuple_header(&dynamic_buffer(state), 2); + ei_x_encode_string_len(&dynamic_buffer(state), + (char *)name, name_len); + encode_data_type(sql_type, size, dec_digits, state); + } + + ei_x_encode_empty_list(&dynamic_buffer(state)); + + clean_state(state); + msg.buffer = dynamic_buffer(state).buff; + msg.length = dynamic_buffer(state).index; + msg.dyn_alloc = TRUE; + return msg; +} + + +/* ----------------- Encode/decode functions -----------------------------*/ + +static db_result_msg encode_empty_message(void) +{ + db_result_msg msg; + + msg.length = 0; + msg.buffer = NULL; + msg.dyn_alloc = FALSE; + + return msg; +} + +/* Description: Encode an error-message to send back to erlang*/ +static db_result_msg encode_error_message(char *reason) +{ + int index; + db_result_msg msg; + + index = 0; + ei_encode_version(NULL, &index); + ei_encode_tuple_header(NULL, &index, 2); + ei_encode_atom(NULL, &index, "error"); + ei_encode_string(NULL, &index, reason); + + msg.length = index; + msg.buffer = (byte *)safe_malloc(index); + msg.dyn_alloc = FALSE; + + index = 0; + ei_encode_version((char *)msg.buffer, &index); + ei_encode_tuple_header((char *)msg.buffer, &index, 2); + ei_encode_atom((char *)msg.buffer, &index, "error"); + ei_encode_string((char *)msg.buffer, &index, reason); + + return msg; +} + +/* Description: Encode a messge that is a erlang atom */ +static db_result_msg encode_atom_message(char* atom) +{ + int index; + db_result_msg msg; + + index = 0; + ei_encode_version(NULL, &index); + ei_encode_atom(NULL, &index, atom); + + msg.length = index; + msg.buffer = (byte *)safe_malloc(index); + msg.dyn_alloc = FALSE; + + index = 0; + ei_encode_version((char *)msg.buffer, &index); + ei_encode_atom((char *)msg.buffer, &index, atom); + + return msg; +} + + +/* Top encode function for db_query that encodes the resulting erlang + term to be returned to the erlang client. */ +static db_result_msg encode_result(db_state *state) +{ + SQLSMALLINT num_of_columns = 0; + SQLLEN RowCountPtr = 0; + SQLINTEGER paramBatch = 0; + db_result_msg msg; + int elements, update, num_of_rows = 0; + char *atom; + + msg = encode_empty_message(); + + if(!sql_success(SQLNumResultCols(statement_handle(state), + &num_of_columns))) { + DO_EXIT(EXIT_COLS); + } + + if (num_of_columns == 0) { + elements = 2; + atom = "updated"; + update = TRUE; + } else { + elements = 3; + atom = "selected"; + update = FALSE; + } + + if(!sql_success(SQLRowCount(statement_handle(state), &RowCountPtr))) { + DO_EXIT(EXIT_ROWS); + } + + if(param_query(state) && update) { + if(!sql_success(SQLGetInfo(connection_handle(state), + SQL_PARAM_ARRAY_ROW_COUNTS, + (SQLPOINTER)¶mBatch, + sizeof(paramBatch), + NULL))) { + DO_EXIT(EXIT_DRIVER_INFO); + } + + if(paramBatch == SQL_PARC_BATCH ) { + /* Individual row counts (one for each parameter set) + are available, sum them up */ + do { + num_of_rows = num_of_rows + (int)RowCountPtr; + msg = more_result_sets(state); + /* We don't want to continue if an error occured */ + if (msg.length != 0) { + return msg; + } + if(exists_more_result_sets(state)) { + if(!sql_success(SQLRowCount(statement_handle(state), + &RowCountPtr))) { + DO_EXIT(EXIT_ROWS); + } + } + } while (exists_more_result_sets(state)); + } else { + /* Row counts are rolled up into one (SQL_PARC_NO_BATCH) */ + num_of_rows = (int)RowCountPtr; + } + } else { + num_of_rows = (int)RowCountPtr; + } + ei_x_encode_tuple_header(&dynamic_buffer(state), elements); + ei_x_encode_atom(&dynamic_buffer(state), atom); + if (update) { + if(num_of_rows < 0 ) { + ei_x_encode_atom(&dynamic_buffer(state), "undefined"); + } else { + ei_x_encode_long(&dynamic_buffer(state), num_of_rows); + } + } else { + msg = encode_result_set(num_of_columns, state); + } + return msg; +} + +static db_result_msg encode_out_params(db_state *state, + int num_of_params, + param_array* params, + int num_param_values) +{ + int num_of_columns = 0; + int i = 0; + int j = 0; + param_array column; + db_result_msg msg; + msg = encode_empty_message(); + + ei_x_encode_tuple_header(&dynamic_buffer(state), 3); + ei_x_encode_atom(&dynamic_buffer(state), "executed"); + + num_of_columns = num_out_params(num_of_params, params); + ei_x_encode_long(&dynamic_buffer(state), num_of_columns); + + ei_x_encode_list_header(&dynamic_buffer(state), num_param_values); + for(j =0; j < num_param_values; j ++){ + + if(tuple_row(state)) { + ei_x_encode_tuple_header(&dynamic_buffer(state), num_of_columns); + + } else { + ei_x_encode_list_header(&dynamic_buffer(state), num_of_columns); + } + + for (i = 0; i< num_of_params; i++) { + if(params[i].input_output_type==SQL_PARAM_INPUT){ + continue; + } + column = params[i]; + if (column.type.len == 0 || + column.type.strlen_or_indptr == SQL_NULL_DATA) { + ei_x_encode_atom(&dynamic_buffer(state), "null"); + } else { + void* values = retrive_param_values(&column); + switch(column.type.c) { + case SQL_C_CHAR: + ei_x_encode_string(&dynamic_buffer(state), ((char*)values)+j*column.type.len); + break; + case SQL_C_SLONG: + ei_x_encode_long(&dynamic_buffer(state), ((long*)values)[j]); + break; + case SQL_C_DOUBLE: + ei_x_encode_double(&dynamic_buffer(state), + ((double*)values)[j]); + break; + case SQL_C_BIT: + ei_x_encode_atom(&dynamic_buffer(state), + ((Boolean*)values)[j]==TRUE?"true":"false"); + break; + default: + ei_x_encode_atom(&dynamic_buffer(state), "error"); + break; + } + } + } + if(!tuple_row(state)) { + ei_x_encode_empty_list(&dynamic_buffer(state)); + } + } + ei_x_encode_empty_list(&dynamic_buffer(state)); + return msg; +} + +static int num_out_params(int num_of_params, param_array* params) +{ + int ret = 0; + int i = 0; + for(i=0; i < num_of_params; i++){ + if(params[i].input_output_type==SQL_PARAM_INPUT_OUTPUT || + params[i].input_output_type==SQL_PARAM_OUTPUT) + ret++; + } + return ret; +} + +/* Description: Encodes the result set into the "ei_x" - dynamic_buffer + held by the state variable */ +static db_result_msg encode_result_set(SQLSMALLINT num_of_columns, + db_state *state) +{ + db_result_msg msg; + + columns(state) = alloc_column_buffer(num_of_columns); + + msg = encode_column_name_list(num_of_columns, state); + if (msg.length == 0) { /* If no error has occurred */ + msg = encode_value_list(num_of_columns, state); + } + + free_column_buffer(&(columns(state)), num_of_columns); + + return msg; +} + +/* Description: Encodes the list of column names into the "ei_x" - + dynamic_buffer held by the state variable */ +static db_result_msg encode_column_name_list(SQLSMALLINT num_of_columns, + db_state *state) +{ + int i; + db_result_msg msg; + SQLCHAR name[MAX_NAME]; + SQLSMALLINT name_len, sql_type, dec_digits, nullable; + SQLLEN size; + SQLRETURN result; + + msg = encode_empty_message(); + + ei_x_encode_list_header(&dynamic_buffer(state), num_of_columns); + + for (i = 0; i < num_of_columns; ++i) { + + if(!sql_success(SQLDescribeCol(statement_handle(state), + (SQLSMALLINT)(i+1), + name, sizeof(name), &name_len, + &sql_type, &size, &dec_digits, + &nullable))) + DO_EXIT(EXIT_DESC); + + if(sql_type == SQL_LONGVARCHAR || sql_type == SQL_LONGVARBINARY) + size = MAXCOLSIZE; + + (columns(state)[i]).type.decimal_digits = dec_digits; + (columns(state)[i]).type.sql = sql_type; + (columns(state)[i]).type.col_size = size; + + msg = map_sql_2_c_column(&columns(state)[i]); + if (msg.length > 0) { + return msg; /* An error has occurred */ + } else { + if (columns(state)[i].type.len > 0) { + columns(state)[i].buffer = + (char *)safe_malloc(columns(state)[i].type.len); + + if (columns(state)[i].type.c == SQL_C_BINARY) { + /* retrived later by retrive_binary_data */ + }else { + if(!sql_success( + SQLBindCol + (statement_handle(state), + (SQLSMALLINT)(i+1), + columns(state)[i].type.c, + columns(state)[i].buffer, + columns(state)[i].type.len, + &columns(state)[i].type.strlen_or_indptr))) + DO_EXIT(EXIT_BIND); + } + ei_x_encode_string_len(&dynamic_buffer(state), + name, name_len); + } + else { + columns(state)[i].type.len = 0; + columns(state)[i].buffer = NULL; + } + } + } + ei_x_encode_empty_list(&dynamic_buffer(state)); + + return msg; +} + +/* Description: Encodes the list(s) of row values fetched by SQLFetch into + the "ei_x" - dynamic_buffer held by the state variable */ +static db_result_msg encode_value_list(SQLSMALLINT num_of_columns, + db_state *state) +{ + int i, msg_len; + SQLRETURN result; + db_result_msg list_result; + db_result_msg msg; + + msg = encode_empty_message(); + + for (;;) { + /* fetch the next row */ + result = SQLFetch(statement_handle(state)); + + if (result == SQL_NO_DATA) /* Reached end of result set */ + { + break; + } + + ei_x_encode_list_header(&dynamic_buffer(state), 1); + + if(tuple_row(state)) { + ei_x_encode_tuple_header(&dynamic_buffer(state), + num_of_columns); + } else { + ei_x_encode_list_header(&dynamic_buffer(state), num_of_columns); + } + + for (i = 0; i < num_of_columns; i++) { + encode_column_dyn(columns(state)[i], i, state); + } + + if(!tuple_row(state)) { + ei_x_encode_empty_list(&dynamic_buffer(state)); + } + } + ei_x_encode_empty_list(&dynamic_buffer(state)); + return msg; +} + +/* Description: Encodes the list(s) of row values fetched with + SQLFetchScroll into the "ei_x" - dynamic_buffer held by the state + variable */ +static db_result_msg encode_value_list_scroll(SQLSMALLINT num_of_columns, + SQLSMALLINT Orientation, + SQLINTEGER OffSet, int N, + db_state *state) +{ + int i, j, msg_len; + SQLRETURN result; + db_result_msg list_result; + db_result_msg msg; + + msg = encode_empty_message(); + + for (j = 0; j < N; j++) { + if((j > 0) && (Orientation == SQL_FETCH_ABSOLUTE)) { + OffSet++; + } + + if((j == 1) && (Orientation == SQL_FETCH_RELATIVE)) { + OffSet = 1; + } + + result = SQLFetchScroll(statement_handle(state), Orientation, + OffSet); + + if (result == SQL_NO_DATA) /* Reached end of result set */ + { + break; + } + ei_x_encode_list_header(&dynamic_buffer(state), 1); + + if(tuple_row(state)) { + ei_x_encode_tuple_header(&dynamic_buffer(state), + num_of_columns); + } else { + ei_x_encode_list_header(&dynamic_buffer(state), num_of_columns); + } + for (i = 0; i < num_of_columns; i++) { + encode_column_dyn(columns(state)[i], i, state); + } + if(!tuple_row(state)) { + ei_x_encode_empty_list(&dynamic_buffer(state)); + } + } + ei_x_encode_empty_list(&dynamic_buffer(state)); + return msg; +} + +/* Encodes row count result for erlang */ +static db_result_msg encode_row_count(SQLINTEGER num_of_rows, + db_state *state) +{ + db_result_msg msg; + int index; + + index = 0; + ei_encode_version(NULL, &index); + ei_encode_tuple_header(NULL, &index, 2); + ei_encode_atom(NULL, &index, "ok"); + if(num_of_rows == -1) + { + ei_encode_atom(NULL, &index, "undefined"); + } else { + ei_encode_long(NULL, &index, num_of_rows); + } + msg.length = index; + msg.buffer = (byte *)safe_malloc(index); + msg.dyn_alloc = FALSE; + + index = 0; + ei_encode_version((char *)msg.buffer, &index); + ei_encode_tuple_header((char *)msg.buffer, &index, 2); + ei_encode_atom((char *)msg.buffer, &index, "ok"); + + if(num_of_rows == -1) + { + ei_encode_atom((char *)msg.buffer, &index, "undefined"); + } else { + ei_encode_long((char *)msg.buffer, &index, num_of_rows); + } + return msg; +} + +/* Description: Encodes the a column value into the "ei_x" - dynamic_buffer + held by the state variable */ +static void encode_column_dyn(db_column column, int column_nr, + db_state *state) +{ + if (column.type.len == 0 || + column.type.strlen_or_indptr == SQL_NULL_DATA) { + ei_x_encode_atom(&dynamic_buffer(state), "null"); + } else { + switch(column.type.c) { + case SQL_C_CHAR: + ei_x_encode_string(&dynamic_buffer(state), column.buffer); + break; + case SQL_C_SLONG: + ei_x_encode_long(&dynamic_buffer(state), + *(SQLINTEGER*)column.buffer); + break; + case SQL_C_DOUBLE: + ei_x_encode_double(&dynamic_buffer(state), + *(double*)column.buffer); + break; + case SQL_C_BIT: + ei_x_encode_atom(&dynamic_buffer(state), + column.buffer[0]?"true":"false"); + break; + case SQL_C_BINARY: + column = retrive_binary_data(column, column_nr, state); + ei_x_encode_string(&dynamic_buffer(state), (void *)column.buffer); + break; + default: + ei_x_encode_atom(&dynamic_buffer(state), "error"); + break; + } + } +} + +static void encode_data_type(SQLINTEGER sql_type, SQLINTEGER size, + SQLSMALLINT decimal_digits, db_state *state) +{ + switch(sql_type) { + case SQL_CHAR: + ei_x_encode_tuple_header(&dynamic_buffer(state), 2); + ei_x_encode_atom(&dynamic_buffer(state), "sql_char"); + ei_x_encode_long(&dynamic_buffer(state), size); + break; + case SQL_VARCHAR: + ei_x_encode_tuple_header(&dynamic_buffer(state), 2); + ei_x_encode_atom(&dynamic_buffer(state), "sql_varchar"); + ei_x_encode_long(&dynamic_buffer(state), size); + break; + case SQL_NUMERIC: + ei_x_encode_tuple_header(&dynamic_buffer(state), 3); + ei_x_encode_atom(&dynamic_buffer(state), "sql_numeric"); + ei_x_encode_long(&dynamic_buffer(state), size); + ei_x_encode_long(&dynamic_buffer(state), decimal_digits); + break; + case SQL_DECIMAL: + ei_x_encode_tuple_header(&dynamic_buffer(state), 3); + ei_x_encode_atom(&dynamic_buffer(state), "sql_decimal"); + ei_x_encode_long(&dynamic_buffer(state), size); + ei_x_encode_long(&dynamic_buffer(state), decimal_digits); + break; + case SQL_INTEGER: + ei_x_encode_atom(&dynamic_buffer(state), "sql_integer"); + break; + case SQL_TINYINT: + ei_x_encode_atom(&dynamic_buffer(state), "sql_tinyint"); + break; + case SQL_SMALLINT: + ei_x_encode_atom(&dynamic_buffer(state), "sql_smallint"); + break; + case SQL_REAL: + ei_x_encode_atom(&dynamic_buffer(state), "sql_real"); + break; + case SQL_FLOAT: + ei_x_encode_tuple_header(&dynamic_buffer(state), 2); + ei_x_encode_atom(&dynamic_buffer(state), "sql_float"); + ei_x_encode_long(&dynamic_buffer(state), size); + break; + case SQL_DOUBLE: + ei_x_encode_atom(&dynamic_buffer(state), "sql_double"); + break; + case SQL_BIT: + ei_x_encode_atom(&dynamic_buffer(state), "sql_bit"); + break; + case SQL_TYPE_DATE: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_TYPE_DATE"); + break; + case SQL_TYPE_TIME: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_TYPE_TIME"); + break; + case SQL_TYPE_TIMESTAMP: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_TYPE_TIMESTAMP"); + break; + case SQL_BIGINT: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_BIGINT"); + break; + case SQL_BINARY: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_BINARY"); + break; + case SQL_LONGVARCHAR: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_LONGVARCHAR"); + break; + case SQL_VARBINARY: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_VARBINARY"); + break; + case SQL_LONGVARBINARY: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_LONGVARBINARY"); + break; + case SQL_INTERVAL_MONTH: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_INTERVAL_MONTH"); + break; + case SQL_INTERVAL_YEAR: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_INTERVAL_YEAR"); + break; + case SQL_INTERVAL_DAY: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_INTERVAL_DAY"); + break; + case SQL_INTERVAL_MINUTE: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_INTERVAL_MINUTE"); + break; + case SQL_INTERVAL_HOUR_TO_SECOND: + ei_x_encode_atom(&dynamic_buffer(state), + "SQL_INTERVAL_HOUR_TO_SECOND"); + break; + case SQL_INTERVAL_MINUTE_TO_SECOND: + ei_x_encode_atom(&dynamic_buffer(state), + "SQL_INTERVAL_MINUTE_TO_SECOND"); + break; + case SQL_UNKNOWN_TYPE: + ei_x_encode_atom(&dynamic_buffer(state), "SQL_UNKNOWN_TYPE"); + break; + default: /* Will probably never happen */ + ei_x_encode_atom(&dynamic_buffer(state), "ODBC_UNSUPPORTED_TYPE"); + break; + } +} + +static Boolean decode_params(byte *buffer, int *index, param_array **params, + int i, int j) +{ + int erl_type, size; + long bin_size, l64; + param_array* param; + + ei_get_type(buffer, index, &erl_type, &size); + param = &(*params)[i]; + + switch (param->type.c) { + case SQL_C_CHAR: + if(erl_type != ERL_STRING_EXT) { + return FALSE; + } + + ei_decode_string(buffer, index, &(param->values.string[param->offset])); + param->offset += param->type.len; + param->type.strlen_or_indptr_array[j] = SQL_NTS; + break; + + case SQL_C_SLONG: + if(!((erl_type == ERL_SMALL_INTEGER_EXT) || + (erl_type == ERL_INTEGER_EXT) || + (erl_type == ERL_SMALL_BIG_EXT) || + (erl_type == ERL_LARGE_BIG_EXT))) { + return FALSE; + } + + if(ei_decode_long(buffer, index, &l64)) { + return FALSE; + } + + /* For 64-bit platforms we downcast 8-byte long + * to 4-byte SQLINTEGER, checking for overflow */ + + if(l64>INT_MAX || l64<INT_MIN) { + return FALSE; + } + + param->values.integer[j]=(SQLINTEGER)l64; + break; + + case SQL_C_DOUBLE: + if((erl_type != ERL_FLOAT_EXT)) { + return FALSE; + } + ei_decode_double(buffer, index, &(param->values.floating[j])); + break; + + case SQL_C_BIT: + if((erl_type != ERL_ATOM_EXT)) { + return FALSE; + } + ei_decode_boolean(buffer, index, &(param->values.bool[j])); + break; + + default: + return FALSE; + } + + return TRUE; +} + +/*------------- Erlang port communication functions ----------------------*/ + +/* read from stdin */ +#ifdef WIN32 +static int read_exact(byte *buffer, int len) +{ + HANDLE standard_input = GetStdHandle(STD_INPUT_HANDLE); + + unsigned read_result; + unsigned sofar = 0; + + if (!len) { /* Happens for "empty packages */ + return 0; + } + for (;;) { + if (!ReadFile(standard_input, buffer + sofar, + len - sofar, &read_result, NULL)) { + return -1; /* EOF */ + } + if (!read_result) { + return -2; /* Interrupted while reading? */ + } + sofar += read_result; + if (sofar == len) { + return len; + } + } +} +#elif defined(UNIX) +static int read_exact(byte *buffer, int len) { + int i, got = 0; + + do { + if ((i = read(0, buffer + got, len - got)) <= 0) + return(i); + got += i; + } while (got < len); + return len; + +} +#endif + + +/* Recieive (read) data from erlang on stdin */ +static byte * receive_erlang_port_msg(void) +{ + int i, len = 0; + byte *buffer; + byte lengthstr[LENGTH_INDICATOR_SIZE]; + + if(read_exact(lengthstr, LENGTH_INDICATOR_SIZE) != + LENGTH_INDICATOR_SIZE) + { + DO_EXIT(EXIT_STDIN_HEADER); + } + for(i=0; i < LENGTH_INDICATOR_SIZE; i++) { + len <<= 8; + len |= lengthstr[i]; + } + + buffer = (byte *)safe_malloc(len); + + if (read_exact(buffer, len) <= 0) { + DO_EXIT(EXIT_STDIN_BODY); + } + + return buffer; +} + +/* ------------- Socket communication functions --------------------------*/ +#define USE_IPV4 +#ifdef UNIX +#define SOCKET int +#endif + +#if defined WIN32 || defined USE_IPV4 +/* Currently only an old windows compiler is supported so we do not have ipv6 + capabilities */ +static SOCKET connect_to_erlang(const char *port) +{ + SOCKET sock; + struct sockaddr_in sin; + + sock = socket(AF_INET, SOCK_STREAM, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_port = htons ((unsigned short)atoi(port)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + close_socket(sock); + DO_EXIT(EXIT_SOCKET_CONNECT); + } + return sock; +} +#elif defined(UNIX) +static int connect_to_erlang(const char *port) +{ + int sock; + + struct addrinfo hints; + struct addrinfo *erlang_ai, *first; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; /* PF_INET or PF_INET6 */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo("localhost", port, &hints, &first) != 0) { + DO_EXIT(EXIT_FAILURE); + } + + for (erlang_ai = first; erlang_ai; erlang_ai = erlang_ai->ai_next) { + + sock = socket(erlang_ai->ai_family, erlang_ai->ai_socktype, + erlang_ai->ai_protocol); + if (sock < 0) + continue; + if (connect(sock, (struct sockaddr*)erlang_ai->ai_addr, + erlang_ai->ai_addrlen) < 0) { + close(sock); + sock = -1; + continue; + } else { + break; + } + } + freeaddrinfo(first); + + if (sock < 0){ + close_socket(sock); + DO_EXIT(EXIT_SOCKET_CONNECT); + } + + return sock; +} +#endif + +#ifdef WIN32 +static void close_socket(SOCKET socket) +{ + closesocket(socket); +} +#elif defined(UNIX) +static void close_socket(int socket) +{ + close(socket); +} +#endif + +#ifdef WIN32 +static byte * receive_msg(SOCKET socket) +#elif defined(UNIX) +static byte * receive_msg(int socket) +#endif +{ + byte lengthstr[LENGTH_INDICATOR_SIZE]; + size_t msg_len = 0; + int i; + byte *buffer = NULL; + + if(!receive_msg_part(socket, lengthstr, LENGTH_INDICATOR_SIZE)) { + close_socket(socket); + DO_EXIT(EXIT_SOCKET_RECV_HEADER); + } + + for(i = 0; i < LENGTH_INDICATOR_SIZE; i++) { + msg_len <<= 8; + msg_len |= lengthstr[i]; + } + + buffer = (byte *)safe_malloc(msg_len); + + if(!receive_msg_part(socket, buffer, msg_len)) { + close_socket(socket); + DO_EXIT(EXIT_SOCKET_RECV_BODY); + } + + return buffer; +} + +#ifdef WIN32 +static Boolean receive_msg_part(SOCKET socket, byte * buffer, size_t msg_len) +#elif defined(UNIX) +static Boolean receive_msg_part(int socket, byte * buffer, size_t msg_len) +#endif +{ + int nr_bytes_received = 0; + + nr_bytes_received = recv(socket, (void *)buffer, msg_len, 0); + + if(nr_bytes_received == msg_len) { + return TRUE; + } else if(nr_bytes_received > 0 && nr_bytes_received < msg_len) { + return receive_msg_part(socket, buffer + nr_bytes_received, + msg_len - nr_bytes_received); + } else if(nr_bytes_received == -1) { + return FALSE; + } else { /* nr_bytes_received > msg_len */ + close_socket(socket); + DO_EXIT(EXIT_SOCKET_RECV_MSGSIZE); + } +} + +#ifdef WIN32 +static void send_msg(db_result_msg *msg, SOCKET socket) +#elif defined(UNIX) +static void send_msg(db_result_msg *msg, int socket) +#endif +{ + byte lengthstr[LENGTH_INDICATOR_SIZE]; + int len; + len = msg ->length; + + lengthstr[0] = (len >> 24) & 0x000000FF; + lengthstr[1] = (len >> 16) & 0x000000FF; + lengthstr[2] = (len >> 8) & 0x000000FF; + lengthstr[3] = len & 0x000000FF; + + if(!send_msg_part(socket, lengthstr, LENGTH_INDICATOR_SIZE)) { + close_socket(socket); + DO_EXIT(EXIT_SOCKET_SEND_HEADER); + } + + if(!send_msg_part(socket, msg->buffer, len)) { + close_socket(socket); + DO_EXIT(EXIT_SOCKET_SEND_BODY); + } +} + +#ifdef WIN32 +static Boolean send_msg_part(SOCKET socket, byte * buffer, size_t msg_len) +#elif defined(UNIX) +static Boolean send_msg_part(int socket, byte * buffer, size_t msg_len) +#endif +{ + int nr_bytes_sent = 0; + + nr_bytes_sent = send(socket, (void *)buffer, msg_len, 0); + + if(nr_bytes_sent == msg_len) { + return TRUE; + } else if(nr_bytes_sent > 0 && nr_bytes_sent < msg_len) { + return send_msg_part(socket, buffer + nr_bytes_sent, + msg_len - nr_bytes_sent); + } else if(nr_bytes_sent == -1) { + return FALSE; + } else { /* nr_bytes_sent > msg_len */ + close_socket(socket); + DO_EXIT(EXIT_SOCKET_SEND_MSGSIZE); + } +} + +#ifdef WIN32 +static void init_winsock(void) +{ + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 0 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + DO_EXIT(EXIT_OLD_WINSOCK); + } + + if ( LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 0 ) { + clean_socket_lib(); + DO_EXIT(EXIT_OLD_WINSOCK); + } +} +#endif + +static void clean_socket_lib(void) +{ +#ifdef WIN32 + WSACleanup(); +#endif +} + + +/*------------- Memmory handling funtions -------------------------------*/ +static void *safe_malloc(int size) +{ + void *memory; + + memory = (void *)malloc(size); + if (memory == NULL) + DO_EXIT(EXIT_ALLOC); + + return memory; +} + +static void *safe_realloc(void *ptr, int size) +{ + void *memory; + + memory = (void *)realloc(ptr, size); + + if (memory == NULL) + { + free(ptr); + DO_EXIT(EXIT_ALLOC); + } + return memory; +} + +/* Description: Allocate memory for n columns */ +static db_column * alloc_column_buffer(int n) +{ + int i; + db_column *columns; + + columns = (db_column *)safe_malloc(n * sizeof(db_column)); + for(i = 0; i < n; i++) + columns[i].buffer = NULL; + + return columns; +} + +/* Description: Deallocate memory allocated by alloc_column_buffer */ +static void free_column_buffer(db_column **columns, int n) +{ + int i; + if(*columns != NULL) { + for (i = 0; i < n; i++) { + if((*columns)[i].buffer != NULL) { + free((*columns)[i].buffer); + } + } + free(*columns); + *columns = NULL; + } +} + +static void free_params(param_array **params, int cols) +{ + int i; + if(*params != NULL) { + for (i = 0; i < cols; i++) { + if((*params)[i].type.strlen_or_indptr_array != NULL){ + free((*params)[i].type.strlen_or_indptr_array); + } + free(retrive_param_values(&((*params)[i]))); + } + free(*params); + *params = NULL; + } +} + +/* Description: Frees resources associated with the current statement handle + keeped in the state.*/ +static void clean_state(db_state *state) +{ + if(statement_handle(state) != NULL) { + if(!sql_success(SQLFreeHandle(SQL_HANDLE_STMT, + statement_handle(state)))) { + DO_EXIT(EXIT_FREE); + } + statement_handle(state) = NULL; + } + free_column_buffer(&(columns(state)), nr_of_columns(state)); + columns(state) = NULL; + nr_of_columns(state) = 0; +} + +/* ------------- Init/map/bind/retrive functions ------------------------*/ + +/* Prepare the state for a connection */ +static void init_driver(int erl_auto_commit_mode, int erl_trace_driver, + db_state *state) +{ + + int auto_commit_mode, trace_driver, use_srollable_cursors; + + if(erl_auto_commit_mode == ON) { + auto_commit_mode = SQL_AUTOCOMMIT_ON; + } else { + auto_commit_mode = SQL_AUTOCOMMIT_OFF; + } + + if(erl_trace_driver == ON) { + trace_driver = SQL_OPT_TRACE_ON; + } else { + trace_driver = SQL_OPT_TRACE_OFF; + } + + if(!sql_success(SQLAllocHandle(SQL_HANDLE_ENV, + SQL_NULL_HANDLE, + &environment_handle(state)))) + DO_EXIT(EXIT_ALLOC); + if(!sql_success(SQLSetEnvAttr(environment_handle(state), + SQL_ATTR_ODBC_VERSION, + (SQLPOINTER)SQL_OV_ODBC3, 0))) + DO_EXIT(EXIT_ENV); + if(!sql_success(SQLAllocHandle(SQL_HANDLE_DBC, + environment_handle(state), + &connection_handle(state)))) + DO_EXIT(EXIT_ALLOC); + if(!sql_success(SQLSetConnectAttr(connection_handle(state), + SQL_ATTR_CONNECTION_TIMEOUT, + (SQLPOINTER)TIME_OUT, 0))) + DO_EXIT(EXIT_CONNECTION); + if(!sql_success(SQLSetConnectAttr(connection_handle(state), + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)auto_commit_mode, 0))) + DO_EXIT(EXIT_CONNECTION); + if(!sql_success(SQLSetConnectAttr(connection_handle(state), + SQL_ATTR_TRACE, + (SQLPOINTER)trace_driver, 0))) + DO_EXIT(EXIT_CONNECTION); +} + +static void init_param_column(param_array *params, byte *buffer, int *index, + int num_param_values, db_state* state) +{ + int size, erl_type; + long user_type, precision, scale, length, dummy; + long in_or_out; + + ei_decode_long(buffer, index, &user_type); + + params->type.strlen_or_indptr = (SQLINTEGER)NULL; + params->type.strlen_or_indptr_array = NULL; + params->type.decimal_digits = (SQLINTEGER)0; + + switch (user_type) { + case USER_SMALL_INT: + params->type.sql = SQL_SMALLINT; + params->type.c = SQL_C_SLONG; + params->type.len = sizeof(SQLINTEGER); + params->type.col_size = COL_SQL_SMALLINT; + params->values.integer = + (SQLINTEGER*)safe_malloc(num_param_values * params->type.len); + break; + case USER_INT: + params->type.sql = SQL_INTEGER; + params->type.c = SQL_C_SLONG; + params->type.len = sizeof(SQLINTEGER); + params->type.col_size = COL_SQL_INTEGER; + params->values.integer = + (SQLINTEGER*)safe_malloc(num_param_values * params->type.len); + break; + case USER_TINY_INT: + params->type.sql = SQL_TINYINT; + params->type.c = SQL_C_SLONG; + params->type.len = sizeof(SQLINTEGER); + params->type.col_size = COL_SQL_TINYINT; + params->values.integer = + (SQLINTEGER*)safe_malloc(num_param_values * params->type.len); + break; + case USER_DECIMAL: + case USER_NMERIC: + if(user_type == USER_NMERIC) { + params->type.sql = SQL_NUMERIC; + } else { + params->type.sql = SQL_DECIMAL; + } + ei_decode_long(buffer, index, &precision); + ei_decode_long(buffer, index, &scale); + map_dec_num_2_c_column(¶ms->type, (int)precision, (int)scale); + if( params->type.c == SQL_C_SLONG) { + params->values.integer = + (SQLINTEGER *)safe_malloc(num_param_values * params->type.len); + } else if( params->type.c == SQL_C_DOUBLE) { + params->values.floating = + (double *)safe_malloc(num_param_values * params->type.len); + } else if(params->type.c == SQL_C_CHAR) { + params->type.strlen_or_indptr_array + = (SQLLEN*)safe_malloc(num_param_values * sizeof(SQLINTEGER)); + params->values.string = + (byte *)safe_malloc(num_param_values * + sizeof(byte)* params->type.len); + } + break; + case USER_CHAR: + case USER_VARCHAR: + if(user_type == USER_CHAR) { + params->type.sql = SQL_CHAR; + } else { + params->type.sql = SQL_VARCHAR; + } + ei_decode_long(buffer, index, &length); + /* Max string length + string terminator */ + params->type.len = length+1; + params->type.c = SQL_C_CHAR; + params->type.col_size = (SQLUINTEGER)length; + params->type.strlen_or_indptr_array = + (SQLLEN*)safe_malloc(num_param_values * sizeof(SQLINTEGER)); + params->values.string = + (byte *)safe_malloc(num_param_values * + sizeof(byte)* params->type.len); + + break; + case USER_FLOAT: + params->type.sql = SQL_FLOAT; + params->type.c = SQL_C_DOUBLE; + params->type.len = sizeof(double); + ei_decode_long(buffer, index, &length); + params->type.col_size = (SQLUINTEGER)length; + params->values.floating = + (double *)safe_malloc(num_param_values * params->type.len); + break; + case USER_REAL: + params->type.sql = SQL_REAL; + params->type.c = SQL_C_DOUBLE; + params->type.len = sizeof(double); + params->type.col_size = COL_SQL_REAL; + params->values.floating = + (double *)safe_malloc(num_param_values * params->type.len); + break; + case USER_DOUBLE: + params->type.sql = SQL_DOUBLE; + params->type.c = SQL_C_DOUBLE; + params->type.len = sizeof(double); + params->type.col_size = COL_SQL_DOUBLE; + params->values.floating = + (double *)safe_malloc(num_param_values * params->type.len); + break; + case USER_BOOLEAN: + params->type.sql = SQL_BIT; + params->type.c = SQL_C_BIT; + params->type.len = sizeof(Boolean); + params->type.col_size = params->type.len; + params->values.bool = + (Boolean *)safe_malloc(num_param_values * params->type.len); + break; + } + params->offset = 0; + + ei_decode_long(buffer, index, &in_or_out); + switch((in_or_out_type)in_or_out){ + case(ERL_ODBC_OUT): + out_params(state) = TRUE; + params->input_output_type = SQL_PARAM_OUTPUT; break; + case(ERL_ODBC_INOUT): + out_params(state) = TRUE; + params->input_output_type = SQL_PARAM_INPUT_OUTPUT; break; + case(ERL_ODBC_IN): + default: + params->input_output_type = SQL_PARAM_INPUT; break; + } + +} + +static void init_param_statement(int cols, int num_param_values, + db_state *state, param_status *status) +{ + int i; + + status -> param_status_array = + (SQLUSMALLINT *)safe_malloc(num_param_values * sizeof(SQLUSMALLINT)); + + for(i=0; i<num_param_values; i++) { + status -> param_status_array[i] = SQL_PARAM_PROCEED; + } + + status -> params_processed = 0; + + if(!sql_success(SQLAllocHandle(SQL_HANDLE_STMT, + connection_handle(state), + &statement_handle(state)))) { + DO_EXIT(EXIT_ALLOC); + } + + if(num_param_values <= 1) return; + + if(!sql_success(SQLSetStmtAttr(statement_handle(state), + SQL_ATTR_PARAM_BIND_TYPE, + SQL_PARAM_BIND_BY_COLUMN, 0))) { + DO_EXIT(EXIT_PARAM_ARRAY); + } + + /* Note the (int *) cast is correct as the API function SQLSetStmtAttr + takes either an interger or a pointer depending on the attribute */ + if(!sql_success(SQLSetStmtAttr(statement_handle(state), + SQL_ATTR_PARAMSET_SIZE, + (int *)num_param_values, + 0))) { + DO_EXIT(EXIT_PARAM_ARRAY); + } + + if(!sql_success(SQLSetStmtAttr(statement_handle(state), + SQL_ATTR_PARAM_STATUS_PTR, + (status -> param_status_array), 0))) { + DO_EXIT(EXIT_PARAM_ARRAY); + } + + if(!sql_success(SQLSetStmtAttr(statement_handle(state), + SQL_ATTR_PARAMS_PROCESSED_PTR, + &(status -> params_processed), 0))) { + DO_EXIT(EXIT_PARAM_ARRAY); + } +} + +static void map_dec_num_2_c_column(col_type *type, int precision, int scale) +{ + type -> col_size = (SQLINTEGER)precision; + type -> decimal_digits = (SQLSMALLINT)scale; + + if(precision >= 0 && precision <= 4 && scale == 0) { + type->len = sizeof(SQLINTEGER); + type->c = SQL_C_SLONG; + } else if(precision >= 5 && precision <= 9 && scale == 0) { + type->len = sizeof(SQLINTEGER); + type->c = SQL_C_SLONG; + } else if((precision >= 10 && precision <= 15 && scale == 0) + || (precision <= 15 && scale > 0)) { + type->len = sizeof(double); + type->c = SQL_C_DOUBLE; + } else { + type->len = DEC_NUM_LENGTH; + type->c = SQL_C_CHAR; + } +} + +/* Description: Transform SQL columntype to C columntype. Returns a dummy + db_result_msg with length 0 on success and an errormessage otherwise.*/ +static db_result_msg map_sql_2_c_column(db_column* column) +{ + db_result_msg msg; + + msg = encode_empty_message(); + + switch(column -> type.sql) { + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_BINARY: + case SQL_LONGVARCHAR: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + column -> type.len = (column -> type.col_size) + + /* Make place for NULL termination */ + sizeof(byte); + column -> type.c = SQL_C_CHAR; + column -> type.strlen_or_indptr = SQL_NTS; + break; + case SQL_NUMERIC: + case SQL_DECIMAL: + map_dec_num_2_c_column(&(column -> type), column -> type.col_size, + column -> type.decimal_digits); + column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + break; + case SQL_TINYINT: + case SQL_INTEGER: + case SQL_SMALLINT: + column -> type.len = sizeof(SQLINTEGER); + column -> type.c = SQL_C_SLONG; + column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + break; + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + column -> type.len = sizeof(double); + column -> type.c = SQL_C_DOUBLE; + column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + break; + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + column -> type.len = (column -> type.col_size) + + sizeof(byte); + column -> type.c = SQL_C_CHAR; + column -> type.strlen_or_indptr = SQL_NTS; + break; + case SQL_BIGINT: + column -> type.len = DEC_NUM_LENGTH; + column -> type.c = SQL_C_CHAR; + column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + break; + case SQL_BIT: + column -> type.len = sizeof(byte); + column -> type.c = SQL_C_BIT; + column -> type.strlen_or_indptr = (SQLINTEGER)NULL; + break; + case SQL_UNKNOWN_TYPE: + msg = encode_error_message("Unknown column type"); + break; + default: + msg = encode_error_message("Column type not supported"); + break; + } + return msg; +} + +static param_array * bind_parameter_arrays(byte *buffer, int *index, + int cols, int num_param_values, + db_state *state) +{ + int i, j, k, size, erl_type; + db_result_msg msg; + long dummy; + void *Values; + param_array *params; + + params = (param_array *)safe_malloc(cols * sizeof(param_array)); + + for (i = 0; i < cols; i++) { + + ei_get_type(buffer, index, &erl_type, &size); + + if(erl_type == ERL_NIL_EXT) { + /* End of previous list of column values when i > 0 */ + ei_decode_list_header(buffer, index, &size); + } + + ei_decode_tuple_header(buffer, index, &size); + + init_param_column(¶ms[i], buffer, index, num_param_values, state); + + ei_decode_list_header(buffer, index, &size); + + if(params[i].type.c == SQL_C_SLONG) { + /* Get rid of the dummy value 256 that is added as the first value + of all integer parameter value lists. This is to avoid that the + list will be encoded as a string if all values are less + than 256 */ + ei_decode_long(buffer, index, &dummy); + } + + for (j = 0; j < num_param_values; j++) { + if(!decode_params(buffer, index, ¶ms, i, j)) { + /* An input parameter was not of the expected type */ + free_params(¶ms, i); + return params; + } + } + + Values = retrive_param_values(¶ms[i]); + + if(!sql_success( + SQLBindParameter(statement_handle(state), i + 1, + params[i].input_output_type, + params[i].type.c, + params[i].type.sql, + params[i].type.col_size, + params[i].type.decimal_digits, Values, + params[i].type.len, + params[i].type.strlen_or_indptr_array))) { + DO_EXIT(EXIT_BIND); + } + } + + return params; +} + +static void * retrive_param_values(param_array *Param) +{ + switch(Param->type.c) { + case SQL_C_CHAR: + return (void *)Param->values.string; + case SQL_C_SLONG: + return (void *)Param->values.integer; + case SQL_C_DOUBLE: + return (void *)Param->values.floating; + case SQL_C_BIT: + return (void *)Param->values.bool; + default: + DO_EXIT(EXIT_FAILURE); /* Should not happen */ + } +} + +/* Description: More than one call to SQLGetData may be required to retrieve + data from a single column with binary data. SQLGetData then returns + SQL_SUCCESS_WITH_INFO nd the SQLSTATE will have the value 01004 (Data + truncated). The application can then use the same column number to + retrieve subsequent parts of the data until SQLGetData returns + SQL_SUCCESS, indicating that all data for the column has been retrieved. +*/ + +static db_column retrive_binary_data(db_column column, int column_nr, + db_state *state) +{ + char *outputptr; + char *sqlState; + int blocklen, outputlen, result; + diagnos diagnos; + + blocklen = column.type.len; + outputptr = column.buffer; + result = SQLGetData(statement_handle(state), (SQLSMALLINT)(column_nr+1), + SQL_C_CHAR, outputptr, + blocklen, &column.type.strlen_or_indptr); + + while (result == SQL_SUCCESS_WITH_INFO) { + + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + + if(strcmp((char *)diagnos.sqlState, TRUNCATED) == 0) { + outputlen = column.type.len - 1; + column.type.len = outputlen + blocklen; + column.buffer = + safe_realloc((void *)column.buffer, column.type.len); + outputptr = column.buffer + outputlen; + result = SQLGetData(statement_handle(state), + (SQLSMALLINT)(column_nr+1), SQL_C_CHAR, + outputptr, blocklen, + &column.type.strlen_or_indptr); + } + } + + if (result == SQL_SUCCESS) { + return column; + } else { + DO_EXIT(EXIT_BIN); + } +} + +/* Description: Returns information about support for scrollable cursors */ +static db_result_msg retrive_scrollable_cursor_support_info(db_state *state) +{ + db_result_msg msg; + SQLUINTEGER supportMask; + + ei_x_new_with_version(&dynamic_buffer(state)); + ei_x_encode_tuple_header(&dynamic_buffer(state), 3); + ei_x_encode_atom(&dynamic_buffer(state), "ok"); + + if(use_srollable_cursors(state)) { + + if(!sql_success(SQLGetInfo(connection_handle(state), + SQL_DYNAMIC_CURSOR_ATTRIBUTES1, + (SQLPOINTER)&supportMask, + sizeof(supportMask), + NULL))) { + DO_EXIT(EXIT_DRIVER_INFO); + } + + if ((supportMask & SQL_CA1_ABSOLUTE)) { + ei_x_encode_atom(&dynamic_buffer(state), "true"); + } + else { + ei_x_encode_atom(&dynamic_buffer(state), "false"); + } + + if ((supportMask & SQL_CA1_RELATIVE)) { + ei_x_encode_atom(&dynamic_buffer(state), "true"); + } + else { + ei_x_encode_atom(&dynamic_buffer(state), "false"); + } + } else { /* Scrollable cursors disabled by the user */ + ei_x_encode_atom(&dynamic_buffer(state), "false"); + ei_x_encode_atom(&dynamic_buffer(state), "false"); + } + msg.buffer = dynamic_buffer(state).buff; + msg.length = dynamic_buffer(state).index; + msg.dyn_alloc = TRUE; + return msg; +} + +/* ------------- Boolean functions ---------------------------------------*/ + +/* Check if there are any more result sets */ +static db_result_msg more_result_sets(db_state *state) +{ + SQLRETURN result; + diagnos diagnos; + db_result_msg msg; + + msg = encode_empty_message(); + result = SQLMoreResults(statement_handle(state)); + + if(sql_success(result)){ + exists_more_result_sets(state) = TRUE; + return msg; + } else if(result == SQL_NO_DATA) { + exists_more_result_sets(state) = FALSE; + return msg; + } else { + /* As we found an error we do not care about any potential more result + sets */ + exists_more_result_sets(state) = FALSE; + diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state)); + strcat((char *)diagnos.error_msg, + "Failed to create on of the result sets"); + msg = encode_error_message(diagnos.error_msg); + return msg; + } +} + +static Boolean sql_success(SQLRETURN result) +{ + return result == SQL_SUCCESS || result == SQL_SUCCESS_WITH_INFO; +} + +/* ------------- Error handling functions --------------------------------*/ + +/* Description: An ODBC function can post zero or more diagnostic records + each time it is called. This function loops through the current set of + diagnostic records scaning for error messages and the sqlstate. + If this function is called when no error has ocurred only the sqlState + field may be referenced.*/ +static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle) +{ + diagnos diagnos; + SQLINTEGER nativeError; + SQLSMALLINT errmsg_buffer_size, record_nr, errmsg_size; + int acc_errmsg_size; + byte *current_errmsg_pos; + SQLCHAR current_sql_state[SQL_STATE_SIZE]; + + diagnos.error_msg[0] = 0; + + current_errmsg_pos = (byte *)diagnos.error_msg; + + /* number bytes free in error message buffer */ + errmsg_buffer_size = MAX_ERR_MSG - ERRMSG_HEADR_SIZE; + acc_errmsg_size = 0; /* number bytes used in the error message buffer */ + + /* Foreach diagnostic record in the current set of diagnostic records + the error message is obtained */ + for(record_nr = 1; ;record_nr++) { + if(SQLGetDiagRec(handleType, handle, record_nr, current_sql_state, + &nativeError, current_errmsg_pos, + (SQLSMALLINT)errmsg_buffer_size, &errmsg_size) + != SQL_SUCCESS) { + + + break; + } else { + /* update the sqlstate in the diagnos record, because the SQLGetDiagRec + call succeeded */ + memcpy(diagnos.sqlState, current_sql_state, SQL_STATE_SIZE); + errmsg_buffer_size = errmsg_buffer_size - errmsg_size; + acc_errmsg_size = acc_errmsg_size + errmsg_size; + current_errmsg_pos = current_errmsg_pos + errmsg_size; + } + } + + if(acc_errmsg_size == 0) { + strcat((char *)diagnos.error_msg, + "No SQL-driver information available."); + } + else { + strcat(strcat((char *)diagnos.error_msg, " SQLSTATE IS: "), + (char *)diagnos.sqlState); + } + return diagnos; +} + +static void str_tolower(char *str, int len) +{ + int i; + + for(i = 0; i <= len; i++) { + str[i] = tolower(str[i]); + } +} diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h new file mode 100644 index 0000000000..ccd694a985 --- /dev/null +++ b/lib/odbc/c_src/odbcserver.h @@ -0,0 +1,193 @@ +/* + * %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% + * + + */ + +/* ----------------------------- CONSTANTS ------------------------------*/ + +#define MAXCOLSIZE 8001 +#define MAX_ERR_MSG 1024 +#define ERRMSG_HEADR_SIZE 20 +#define MAX_CONN_STR_OUT 1024 +#define MAX_NAME 255 +#define TRUNCATED "01004" +#define INFO "00000" +#define SQL_STATE_SIZE 6 +#define TRUE 1 +#define FALSE 0 +#define WAIT_FOR_NEW_MSG 0 +#define NEW_MSG_ARRIVED 1 +#define DEC_NUM_LENGTH 50 + +/* 0 in this case (SQL_ATTR_CONNECTION_TIMEOUT) corresponds to erlang + infinity. Erlang will handle all timeouts so we do not want any in the + portprogram. */ +#define TIME_OUT 0 + +/* 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 Erlang. */ +#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 + +/* EXIT CODES */ +#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 + +/* COL_SIZE */ +#define COL_SQL_SMALLINT 5 +#define COL_SQL_INTEGER 10 +#define COL_SQL_REAL 7 +#define COL_SQL_DOUBLE 15 +#define COL_SQL_TINYINT 4 + +/* Types of parameters given to param_query*/ +#define USER_SMALL_INT 1 +#define USER_INT 2 +#define USER_DECIMAL 3 +#define USER_NMERIC 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 + +/*------------------------ TYPDEFS ----------------------------------*/ + +typedef unsigned char byte; +typedef int Boolean; + +typedef struct { + SQLSMALLINT c; + SQLSMALLINT sql; + SQLUINTEGER col_size; + SQLSMALLINT decimal_digits; + SQLLEN len; + SQLLEN strlen_or_indptr; + SQLLEN *strlen_or_indptr_array; +} col_type; + +typedef struct { + char *buffer; + col_type type; +} db_column; + +typedef struct { + int length; + byte *buffer; + Boolean dyn_alloc; +} db_result_msg; + +typedef struct { + SQLCHAR sqlState[SQL_STATE_SIZE]; + byte error_msg[MAX_ERR_MSG]; +} diagnos; + +typedef struct { + col_type type; + int offset; + SQLUSMALLINT input_output_type; + union { + byte *string; + SQLINTEGER *integer; + double *floating; + Boolean *bool; + }values; +} param_array; + +typedef struct { + SQLUSMALLINT params_processed; + SQLUSMALLINT *param_status_array; +} param_status; + +typedef struct { + SQLHDBC connection_handle; + SQLHENV environment_handle; + SQLHSTMT statement_handle; + db_column *columns; + int number_of_columns; + ei_x_buff dynamic_buffer; + Boolean associated_result_set; + Boolean use_srollable_cursors; + Boolean tuple_row; + Boolean exists_more_result_sets; + Boolean param_query; + Boolean out_params; +} db_state; + +typedef enum { + ERL_ODBC_IN, ERL_ODBC_OUT, ERL_ODBC_INOUT +} in_or_out_type; + +#define connection_handle(db_state) (db_state -> connection_handle) +#define environment_handle(db_state) (db_state -> environment_handle) +#define statement_handle(db_state) (db_state -> statement_handle) +#define columns(db_state) (db_state -> columns) +#define nr_of_columns(db_state) (db_state -> number_of_columns) +#define dynamic_buffer(db_state) (db_state -> dynamic_buffer) +#define associated_result_set(db_state) (db_state -> associated_result_set) +#define use_srollable_cursors(db_state) (db_state -> use_srollable_cursors) +#define tuple_row(db_state) (db_state -> tuple_row) +#define exists_more_result_sets(db_state) (db_state -> exists_more_result_sets) +#define param_query(db_state) (db_state -> param_query) +#define out_params(db_state) (db_state -> out_params) diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in new file mode 100644 index 0000000000..77b576ee88 --- /dev/null +++ b/lib/odbc/configure.in @@ -0,0 +1,217 @@ + +dnl define([AC_CACHE_LOAD], )dnl +dnl define([AC_CACHE_SAVE], )dnl + +if test "x$no_recursion" != "xyes" -a "x$OVERRIDE_CONFIG_CACHE" = "x"; then + # We do not want to use a common cache! + cache_file=/dev/null +fi + + + +dnl Process this file with autoconf to produce a configure script. +AC_INIT(c_src/odbcserver.c) + +if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then + AC_CONFIG_AUX_DIRS(autoconf) +else + erl_top=${ERL_TOP} + AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf) +fi + +if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then + AC_CANONICAL_HOST +else + host_os=win32 +fi + +dnl Checks for programs. +AC_PROG_CC + +dnl --------------------------------------------------------------------- +dnl Special windows stuff regarding CFLAGS and details in the environment... +dnl --------------------------------------------------------------------- +AC_MSG_CHECKING(for mixed cygwin and native VC++ environment) +if test "X$CC" = "Xcc.sh" -a "X$host" = "Xwin32"; then + if test -x /usr/bin/cygpath; then + CFLAGS="-O2" + AC_MSG_RESULT([yes]) + MIXED_CYGWIN_VC=yes + else + AC_MSG_RESULT([undeterminable]) + AC_MSG_ERROR(Seems to be mixed windows but not with cygwin, cannot handle this!) + fi +else + AC_MSG_RESULT([no]) + MIXED_CYGWIN_VC=no +fi +AC_SUBST(MIXED_CYGWIN_VC) + +AC_PROG_MAKE_SET +AC_CHECK_PROGS(DED_LD, [ld.sh ld], '$(CC)') +AC_SUBST(DED_LD) + +# Sockets +#-------------------------------------------------------------------- +# Check for the existence of the -lsocket and -lnsl libraries. +# The order here is important, so that they end up in the right +# order in the command line generated by make. Here are some +# special considerations: +# 1. Use "connect" and "accept" to check for -lsocket, and +# "gethostbyname" to check for -lnsl. +# 2. Use each function name only once: can't redo a check because +# autoconf caches the results of the last check and won't redo it. +# 3. Use -lnsl and -lsocket only if they supply procedures that +# aren't already present in the normal libraries. This is because +# IRIX 5.2 has libraries, but they aren't needed and they're +# bogus: they goof up name resolution if used. +# 4. On some SVR4 systems, can't use -lsocket without -lnsl too. +# To get around this problem, check for both libraries together +# if -lsocket doesn't work by itself. +#-------------------------------------------------------------------- +erl_checkBoth=0 +AC_CHECK_FUNC(connect, erl_checkSocket=0, erl_checkSocket=1) +if test "$erl_checkSocket" = 1; then + AC_CHECK_LIB(socket, socket, LIBS="$LIBS -lsocket", erl_checkBoth=1) +fi +if test "$erl_checkBoth" = 1; then + tk_oldLibs=$LIBS + LIBS="$LIBS -lsocket -lnsl" + AC_CHECK_FUNC(accept, odbc_erl_checkNsl=0, [LIBS=$tk_oldLibs]) +fi +AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, main, [LIBS="$LIBS -lnsl"])) + +dnl Checks for header files. +AC_HEADER_STDC +case $have_pthread_lib-$host_os in + yes-linux*) + dnl NPTL test stolen from $ERL_TOP/erts/aclocal.m4 + AC_MSG_CHECKING(for Native POSIX Thread Library) + case `getconf GNU_LIBPTHREAD_VERSION 2>/dev/null` in + nptl*) nptl=yes;; + NPTL*) nptl=yes;; + *) nptl=no;; + esac + AC_MSG_RESULT($nptl) + if test $nptl = yes; then + need_nptl_incldir=no + AC_CHECK_HEADER(nptl/pthread.h, need_nptl_incldir=yes) + if test $need_nptl_incldir = yes; then + # Ahh... + nptl_path="$C_INCLUDE_PATH:$CPATH:/usr/local/include:/usr/include" + nptl_ws_path= + save_ifs="$IFS"; IFS=":" + for dir in $nptl_path; do + if test "x$dir" != "x"; then + nptl_ws_path="$nptl_ws_path $dir" + fi + done + IFS=$save_ifs + nptl_incldir= + for dir in $nptl_ws_path; do + AC_CHECK_HEADER($dir/nptl/pthread.h, + nptl_incldir=$dir/nptl) + if test "x$nptl_incldir" != "x"; then + CFLAGS="$CFLAGS -isystem $nptl_incldir" + dnl CPPFLAGS is for configure internal use + CPPFLAGS="$CPPFLAGS -isystem $nptl_incldir" + break + fi + done + if test "x$nptl_incldir" = "x"; then + AC_MSG_ERROR(Failed to locate nptl system include directory) + fi + fi + fi + ;; + *) + ;; +esac +AC_CHECK_HEADERS([fcntl.h netdb.h stdlib.h string.h sys/socket.h]) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T + +dnl Checks for library functions. +AC_CHECK_FUNCS([memset socket]) + +# ODBC +/bin/rm -f $ERL_TOP/lib/odbc/SKIP + +have_pthread_lib=no +have_odbc_lib=no + +AC_SUBST(TARGET_FLAGS) + case $host_os in + darwin*) + TARGET_FLAGS="-DUNIX" + AC_CHECK_LIB(pthread, pthread_create, + [AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if you have the pthread library (-lpthread).]) + LIBS="$LIBS -lpthread" + have_pthread_lib=yes]) + if test ! -d "$with_odbc"; then + ODBC_LIB= -L"/usr/lib" + ODBC_INCLUDE="-I/usr/lib/include" + else + ODBC_LIB=-L"$with_odbc/lib" + ODBC_INCLUDE="-I$with_odbc/include" + fi + + AC_CHECK_LIB(iodbc, SQLAllocHandle,[ODBC_LIB="$ODBC_LIB -liodbc" odbc_lib_link_sucess=yes]) + ;; + win32|cygwin) + TARGET_FLAGS="-DWIN32" + AC_CHECK_LIB(ws2_32, main) + if test ! -d "$with_odbc"; then + ODBC_LIB="" + ODBC_INCLUDE="" + else + ODBC_LIB=-L"$with_odbc/lib" + ODBC_INCLUDE="-I$with_odbc/include" + fi + AC_CHECK_LIB(odbc32, main, [ODBC_LIB="$ODBC_LIB -lodbc32" odbc_lib_link_sucess=yes]) + ;; + *) + TARGET_FLAGS="-DUNIX" + AC_CHECK_LIB(pthread, pthread_create, + [AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if you have the pthread library (-lpthread).]) + LIBS="$LIBS -lpthread" + have_pthread_lib=yes]) + if test ! -d "$with_odbc"; then + AC_MSG_CHECKING([for odbc in standard locations]) + for dir in /usr/local/odbc /usr/local /usr/odbc \ + /usr /opt/local/pgm/odbc /usr/local/pgm/odbc + do + if test -f "$dir/include/sql.h"; then + is_odbc_std_location=yes + ODBC_LIB=-L"$dir/lib" + ODBC_INCLUDE="-I$dir/include" + break + fi + done + if test "x$is_odbc_std_location" != "xyes"; then + AC_MSG_RESULT(no) + AC_MSG_WARN([No odbc library found skipping odbc]) + echo "No odbc library found" > $ERL_TOP/lib/odbc/SKIP + else + AC_MSG_RESULT($ODBC_LIB) + AC_CHECK_LIB(odbc, SQLAllocHandle,[ODBC_LIB="$ODBC_LIB -lodbc" odbc_lib_link_sucess=yes]) + fi + else + ODBC_LIB=-L"$with_odbc/lib" + ODBC_INCLUDE="-I$with_odbc/include" + AC_CHECK_LIB(odbc, SQLAllocHandle,[ODBC_LIB="$ODBC_LIB -lodbc" odbc_lib_link_sucess=yes]) + fi + ;; +esac + +if test "x$odbc_lib_link_sucess" != "xyes"; then + AC_MSG_WARN(["ODBC library - link check failed"]) + echo "ODBC library - link check failed" > $ERL_TOP/lib/odbc/SKIP +fi + +AC_SUBST(ODBC_LIB) +AC_SUBST(ODBC_INCLUDE) + +AC_OUTPUT(c_src/$host/Makefile:c_src/Makefile.in) diff --git a/lib/odbc/doc/html/.gitignore b/lib/odbc/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/doc/html/.gitignore diff --git a/lib/odbc/doc/man3/.gitignore b/lib/odbc/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/doc/man3/.gitignore diff --git a/lib/odbc/doc/man6/.gitignore b/lib/odbc/doc/man6/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/doc/man6/.gitignore diff --git a/lib/odbc/doc/pdf/.gitignore b/lib/odbc/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/doc/pdf/.gitignore diff --git a/lib/odbc/doc/src/Makefile b/lib/odbc/doc/src/Makefile new file mode 100644 index 0000000000..136ddfb980 --- /dev/null +++ b/lib/odbc/doc/src/Makefile @@ -0,0 +1,221 @@ +# +# %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 +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(ODBC_VSN) +APPLICATION=odbc + +# ---------------------------------------------------- +# Include dependency +# ---------------------------------------------------- + +ifndef DOCSUPPORT +include make.dep +endif + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_APPLICATION_FILES = ref_man.xml +XML_REF3_FILES = odbc.xml + +XML_PART_FILES = part.xml \ + part_notes.xml \ + part_notes_history.xml + +XML_HTML_FILES = \ + notes_history.xml + +XML_CHAPTER_FILES = \ + introduction.xml \ + getting_started.xml \ + databases.xml \ + error_handling.xml \ + notes.xml + +BOOK_FILES = book.xml + +GIF_FILES = \ + book.gif \ + odbc.gif \ + note.gif \ + notes.gif \ + ref_man.gif \ + user_guide.gif \ + odbc_app_arc.gif + +# ---------------------------------------------------- + +HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) \ + +INFO_FILE = ../../info +EXTRA_FILES = $(DEFAULT_GIF_FILES) \ + $(DEFAULT_HTML_FILES) \ + $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) + +MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) + +ifdef DOCSUPPORT + +HTML_REF_MAN_FILE = $(HTMLDIR)/index.html + +TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf + +else + +TEX_FILES_BOOK = \ + $(BOOK_FILES:%.xml=%.tex) +TEX_FILES_REF_MAN = $(XML_REF3_FILES:%.xml=%.tex) \ + $(XML_APPLICATION_FILES:%.xml=%.tex) +TEX_FILES_USERS_GUIDE = \ + $(XML_CHAPTER_FILES:%.xml=%.tex) + +TOP_PDF_FILE = $(APPLICATION)-$(VSN).pdf +TOP_PS_FILE = $(APPLICATION)-$(VSN).ps + +$(TOP_PDF_FILE): book.dvi ../../vsn.mk + $(DVI2PS) $(DVIPS_FLAGS) -f $< | $(DISTILL) $(DISTILL_FLAGS) > $@ + +$(TOP_PS_FILE): book.dvi ../../vsn.mk + $(DVI2PS) $(DVIPS_FLAGS) -f $< > $@ + +endif + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += +DVIPS_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +$(HTMLDIR)/%.gif: %.gif # Copy them to ../html + $(INSTALL_DATA) $< $@ + +ifdef DOCSUPPORT + +docs: pdf html man + +$(TOP_PDF_FILE): $(XML_FILES) + +pdf: $(TOP_PDF_FILE) + +html: gifs $(HTML_REF_MAN_FILE) + +clean clean_docs: + rm -rf $(HTMLDIR)/* + rm -f $(MAN3DIR)/* + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f errs core *~ + +else + +ifeq ($(DOCTYPE),pdf) +docs: pdf +else +ifeq ($(DOCTYPE),ps) +docs: ps +else +docs: html gifs man +endif +endif + +pdf: $(TOP_PDF_FILE) + +ps: $(TOP_PS_FILE) + +html: $(HTML_FILES) + +clean clean_docs clean_tex: + rm -f $(TEX_FILES_USERS_GUIDE) $(TEX_FILES_REF_MAN) $(TEX_FILES_BOOK) + rm -f $(HTML_FILES) $(MAN3_FILES) + rm -f $(TOP_PDF_FILE) $(TOP_PS_FILE) + rm -f errs core *~ *xmls_output *xmls_errs $(LATEX_CLEAN) + +endif + +man: $(MAN3_FILES) + +gifs: $(GIF_FILES:%=$(HTMLDIR)/%) # We depend just to copy them to ../html + +debug opt: + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +ifdef DOCSUPPORT + +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf + $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(HTMLDIR)/* \ + $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 + $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3 +else + +ifeq ($(DOCTYPE),pdf) +release_docs_spec: pdf + $(INSTALL_DIR) $(RELEASE_PATH)/pdf + $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELEASE_PATH)/pdf +else +ifeq ($(DOCTYPE),ps) +release_docs_spec: ps + $(INSTALL_DIR) $(RELEASE_PATH)/ps + $(INSTALL_DATA) $(TOP_PS_FILE) $(RELEASE_PATH)/ps +else +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \ + $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 + $(INSTALL_DATA) $(MAN3_FILES) $(RELEASE_PATH)/man/man3 +endif +endif + +endif + +release_spec: + + + + diff --git a/lib/odbc/doc/src/book.gif b/lib/odbc/doc/src/book.gif Binary files differnew file mode 100644 index 0000000000..94b3868792 --- /dev/null +++ b/lib/odbc/doc/src/book.gif diff --git a/lib/odbc/doc/src/book.xml b/lib/odbc/doc/src/book.xml new file mode 100644 index 0000000000..05e16b512a --- /dev/null +++ b/lib/odbc/doc/src/book.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE book SYSTEM "book.dtd"> + +<book xmlns:xi="http://www.w3.org/2001/XInclude"> + <header titlestyle="normal"> + <copyright> + <year>1999</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Erlang ODBC</title> + <prepared>Ingvar Meyer</prepared> + <docno></docno> + <date></date> + <rev>A</rev> + <file>book.sgml</file> + </header> + <insidecover> + </insidecover> + <pagetext>Erlang ODBC</pagetext> + <preamble> + <contents level="2"></contents> + </preamble> + <parts lift="no"> + <xi:include href="part.xml"/> + </parts> + <applications> + <xi:include href="ref_man.xml"/> + </applications> + <releasenotes> + <xi:include href="notes.xml"/> + </releasenotes> + <index></index> +</book> + + diff --git a/lib/odbc/doc/src/databases.xml b/lib/odbc/doc/src/databases.xml new file mode 100644 index 0000000000..c06327e11d --- /dev/null +++ b/lib/odbc/doc/src/databases.xml @@ -0,0 +1,275 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2002</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Databases</title> + <prepared>Ingela Anderton</prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date></date> + <rev></rev> + <file>databases.xml</file> + </header> + + <section> + <title>Databases</title> + <p>If you need to access a relational database such as + <c>sqlserver</c>, <c>mysql</c>, <c>postgres</c>, <c>oracle</c>, + <c>cybase</c> etc. from your erlang application using the Erlang + ODBC interface is a good way to go about it.</p> + <p></p> + <p>The Erlang ODBC application should work for any relational + database that has an ODBC driver. But currently it is only + regularly tested for <c>sqlserver</c> and <c>postgres</c>.</p> + </section> + + <section> + <title>Database independence </title> + <p>The Erlang ODBC interface is in principal database + independent, e.i. an erlang program using the interface could be + run without changes towards different databases. But as SQL is + used it is alas possible to write database dependent + programs. Even though SQL is an ANSI-standard meant to be + database independent, different databases have proprietary + extensions to SQL defining their own data types. If you keep to + the ANSI data types you will minimize the problem. But + unfortunately there is no guarantee that all databases actually + treats the ANSI data types equivalently. For instance an + installation of <c>Oracle Enterprise release 8.0.5.0.0 for unix</c> will accept that you create a table column with the + ANSI data type <c>integer</c>, but when retrieving values from + this column the driver reports that it is of type + <c>SQL_DECIMAL(0, 38)</c> and not <c>SQL_INTEGER</c> as you may have + expected. </p> + <p>Another obstacle is that some drivers do not support scrollable + cursors which has the effect that the only way to traverse the + result set is sequentially, with next, from the first row to the + last, and once you pass a row you can not go back. This means + that some functions in the interface will not work together with + certain drivers. A similar problem is that not all drivers + support "row count" for select queries, hence resulting in that + the function <c>select_count/[3,4]</c> will return <c>{ok, undefined}</c> instead of <c>{ok, NrRows}</c> where + <c>NrRows</c> is the number of rows in the result set.</p> + </section> + + <section> + <title>Data types </title> + <p>The following is a list of the ANSI data types. For details + turn to the ANSI standard documentation. Usage of other data types + is of course possible, but you should be aware that this makes your + application dependent on the database you are using at the moment.</p> + <list type="bulleted"> + <item>CHARACTER (size), CHAR (size)</item> + <item>NUMERIC (precision, scale), DECIMAL (precision, scale), DEC + (precision, scale ) precision - total number of digits, scale + - total number of decimal places</item> + <item>INTEGER, INT, SMALLINT</item> + <item>FLOAT (precision)</item> + <item>REAL</item> + <item>DOUBLE PRECISION</item> + <item>CHARACTER VARYING(size), CHAR VARYING(size)</item> + </list> + <p>When inputting data using sql_query/[2,3] the values will + always be in string format as they are part of an SQL-query. + Example:</p> + <code type="none"> + odbc:sql_query(Ref, "INSERT INTO TEST VALUES(1, 2, 3)"). + </code> + <note> + <p>Note that when the value of the data to input is a string, it + has to be quoted with <c>'</c>. Example: </p> + <code type="none"> +\011odbc:sql_query(Ref, "INSERT INTO EMPLOYEE VALUES(1, 'Jane', 'Doe', 'F')"). + </code> + </note> + <p>You may also input data using <seealso marker="odbc#param_query">param_query/[3,4]</seealso> and then + the input data will have the Erlang type corresponding to the + ODBC type of the column.<seealso marker="#type">See ODBC to Erlang mapping</seealso></p> + <p> <marker id="type"></marker> + When selecting data from a table, all data + types are returned from the database to the ODBC driver as an + ODBC data type. The tables below shows the mapping between those + data types and what is returned by the Erlang API.</p> + <table> + <row> + <cell align="left" valign="middle">ODBC Data Type </cell> + <cell align="left" valign="middle">Erlang Data Type </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_CHAR(size)</cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_NUMERIC(p,s) <br></br> +when (p >= 0 and p <= 9 and s == 0) </cell> + <cell align="left" valign="middle">Integer </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_NUMERIC(p,s) <br></br> +when (p >= 10 and p <= 15 and s == 0) or (s <= 15 and s > 0)</cell> + <cell align="left" valign="middle">Float </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_NUMERIC(p,s) <br></br> +when p >= 16 </cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_DECIMAL(p,s) <br></br> +when (p >= 0 and p <= 9 and s == 0) </cell> + <cell align="left" valign="middle">Integer </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_DECIMAL(p,s) <br></br> +when (p >= 10 and p <= 15 and s == 0) or (s <= 15 and s > 0)</cell> + <cell align="left" valign="middle">Float </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_DECIMAL(p,s) <br></br> +when p >= 16 </cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_INTEGER </cell> + <cell align="left" valign="middle">Integer </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_SMALLINT </cell> + <cell align="left" valign="middle">Integer </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_FLOAT </cell> + <cell align="left" valign="middle">Float </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_REAL </cell> + <cell align="left" valign="middle">Float </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_DOUBLE</cell> + <cell align="left" valign="middle">Float</cell> + </row> + <row> + <cell align="left" valign="middle">SQL_VARCHAR(size) </cell> + <cell align="left" valign="middle">String </cell> + </row> + <tcaption>Mapping of ODBC data types to the Erlang data types returned to the Erlang application.</tcaption> + </table> + <table> + <row> + <cell align="left" valign="middle">ODBC Data Type </cell> + <cell align="left" valign="middle">Erlang Data Type </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_TYPE_DATE </cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_TYPE_TIME </cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_TYPE_TIMESTAMP </cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_LONGVARCHAR </cell> + <cell align="left" valign="middle">String</cell> + </row> + <row> + <cell align="left" valign="middle">SQL_BINARY</cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_VARBINARY</cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_LONGVARBINARY</cell> + <cell align="left" valign="middle">String </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_TINYINT </cell> + <cell align="left" valign="middle">Integer </cell> + </row> + <row> + <cell align="left" valign="middle">SQL_BIT</cell> + <cell align="left" valign="middle">Boolean </cell> + </row> + <tcaption>Mapping of extended ODBC data types to the Erlang data types returned to the Erlang application.</tcaption> + </table> + <note> + <p>To find out which data types will be returned for the + columns in a table use the function <seealso marker="odbc#describe_table">describe_table/[2,3]</seealso></p> + </note> + </section> + + <section> + <title>Batch handling</title> + <p>Grouping of SQL queries can be desirable in order to reduce + network traffic. Another benefit can be that the data source + sometimes can optimize execution of a batch of SQL queries.</p> + <p>Explicit batches an procedures described below will result + in multiple results being returned from sql_query/[2,3]. + while with parameterized queries only one result will be returned + from param_query/[2,3].</p> + + <section> + <title>Explicit batches</title> + <p>The most basic form of a batch is created by semicolons + separated SQL queries, for example:</p> + <code type="none"> +"SELECT * FROM FOO; SELECT * FROM BAR" or +"INSERT INTO FOO VALUES(1,'bar'); SELECT * FROM FOO" + </code> + </section> + + <section> + <title>Procedures </title> + <p>Different databases may also support creating of procedures + that contains more than one SQL query. For example, the + following SQLServer-specific statement creates a procedure that + returns a result set containing information about employees + that work at the department and and a result set listing the + customers of that department. </p> + <code type="none"> + CREATE PROCEDURE DepartmentInfo (@DepartmentID INT) AS +\011SELECT * FROM Employee WHERE department = @DepartmentID +\011SELECT * FROM Customers WHERE department = @DepartmentID + </code> + </section> + + <section> + <title>Parameterized queries</title> + <p>To effectively perform a batch of similar queries, you can use + parameterized queries. This means that you in your SQL query + string will mark the places that usually would contain values + with question marks and then provide lists of values for each + parameter. For instance you can use this to insert multiple + rows into the <c>EMPLOYEE</c> table while executing only a + single SQL statement, for example code see <seealso marker="getting_started#param_query">"Using the Erlang API"</seealso> section in the "Getting Started" chapter.</p> + </section> + </section> +</chapter> + + diff --git a/lib/odbc/doc/src/error_handling.xml b/lib/odbc/doc/src/error_handling.xml new file mode 100644 index 0000000000..26ad7f9848 --- /dev/null +++ b/lib/odbc/doc/src/error_handling.xml @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Error handling</title> + <prepared>Ingela Anderton</prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date></date> + <rev></rev> + <file>error_handling.xml</file> + </header> + + <section> + <title>Strategy </title> + <p>On a conceptual level starting a database connection using the + Erlang ODBC API is a basic client server application. The client + process uses the API to start and communicate with the server + process that manages the connection. The strategy of the Erlang + ODBC application is that programming faults in the application + itself will cause the connection process to terminate + abnormally.(When a process terminates abnormally its supervisor + will log relevant error reports.) Calls to API functions during or + after termination of the connection process, will return <c>{error, connection_closed}</c>. Contextual errors on the other + hand will not terminate the connection it will only return + <c>{error, Reason} </c> to the client, where <c>Reason</c> may be + any erlang term.</p> + + <section> + <title>Clients </title> + <p>The connection is associated with the process that created it + and can only be accessed through it. The reason for this is to + preserve the semantics of result sets and transactions when + select_count/[2,3] is called or auto_commit is turned off. + Attempts to use the connection from another process will + fail. This will not effect the connection. On the other hand, if + the client process dies the connection will be terminated.</p> + </section> + + <section> + <title>Timeouts </title> + <p>All request made by the client to the connection are + synchronous. If the timeout is used and expires the client + process will exit with reason timeout. Proably the right thing + to do is let the client die and perhaps be restarted by its + supervisor. But if the client chooses to catch this timeout, + it is a good idea to wait a little while before trying + again. If there are too many consecutive timeouts that are + caught the connection process will conclude that there is + something radically wrong and terminate the connection.</p> + </section> + + <section> + <title>Gaurds </title> + <p>All API-functions are guarded and if you pass an argument of + the wrong type a runtime error will occur. All input parameters + to internal functions are trusted to be correct. It is a good + programming practise to only distrust input from truly external + sources. You are not supposed to catch these errors, it will + only make the code very messy and much more complex, which + introduces more bugs and in the worst case also covers up the + actual faults. Put your effort on testing instead, you should + trust your own input.</p> + </section> + </section> + + <section> + <title>The whole picture </title> + <p>As the Erlang ODBC application relies on third party products + and communicates with a database that probably runs on an other + computer in the network there are plenty of things that might go + wrong. To fully understand the things that might happen it + facilitate to know the design of the Erlang ODBC application, + hence here follows a short description of the current design.</p> + <note> + <p>Please note that design is something, that not + necessarily will, but might change in future releases. While the + semantics of the API will not change as it is independent of the + implementation.</p> + </note> + <image file="odbc_app_arc.gif"> + <icaption>Architecture of the Erlang odbc application</icaption> + </image> + <p>When you do application:start(odbc) the only thing that + happens is that a supervisor process is started. For each call + to the API function connect/2 a process is spawned and added as + a child to the Erlang ODBC supervisor. The supervisors only + tasks are to provide error-log reports, if a child process should + die abnormally, and the possibility to do a code change. Only + the client process has the knowledge to decide if this + connection managing process should be restarted.</p> + <p>The erlang connection process spawned by connect/2, will open a + port to a c-process that handles the communication with the + database through Microsoft's ODBC API. The erlang port will be + kept open for exit signal propagation, if something goes wrong + in the c-process and it exits we want know as mush as possible + about the reason. The main communication with the c-process is + done through sockets. The C-process consists of two threads, + the supervisor thread and the database handler thread. The + supervisor thread checks for shutdown messages on the supervisor + socket and the database handler thread receives requests and sends + answers on the database socket. If the database thread seems to + hang on some database call, the erlang control process will send + a shutdown message on the supervisor socket, in this case the + c-process will exit. If the c-process crashes/exits it will + bring the erlang-process down too and vice versa i.e. the + connection is terminated.</p> + <note> + <p>The function connect/2 will start the odbc application if + that is not already done. In this case a supervisor information + log will be produced stating that the odbc application was started + as a temporary application. It is really the responsibility of the + application that uses the API too make sure it is started in the + desired way.</p> + </note> + + <section> + <title>Error types</title> + <p>The types of errors that may occur can be divide into the + following categories.</p> + <list type="bulleted"> + <item>Configuration problems - Everything from that the + database was not set up right to that the c-program that + should be run through the erlang port was not compiled for + your platform.</item> + <item>Errors discovered by the ODBC driver - If calls to the + ODBC-driver fails due to circumstances that can not be + controlled by the Erlang ODBC application programmer, an + error string will be dug up from the driver. This string + will be the <c>Reason</c> in the <c>{error, Reason} </c> + return value. How good this error message is will of course + be driver dependent. Examples of such circumstances are + trying to insert the same key twice, invalid SQL-queries and + that the database has gone off line.</item> + <item>Connection termination - If a connection is terminated + in an abnormal way, or if you try to use a connection that + you have already terminated in a normal way by calling + disconnect/1, the return value will be<c>{error, connection_closed}</c>. A connection could end abnormally + because of an programming error in the Erlang ODBC + application, but also if the ODBC driver crashes.</item> + <item>Contextual errors - If API functions are used in the + wrong context, the <c>Reason</c> in the error tuple will + be a descriptive atom. For instance if you try to call the + function <c>last/[1,2] </c> without first calling <c>select_count/[2,3] </c> to associate a result set with the + connection. If the ODBC-driver does not support some + functions, or if you disabled some functionality for a + connection and then try to use it.</item> + </list> + </section> + </section> +</chapter> + + diff --git a/lib/odbc/doc/src/fascicules.xml b/lib/odbc/doc/src/fascicules.xml new file mode 100644 index 0000000000..91d81d2ac1 --- /dev/null +++ b/lib/odbc/doc/src/fascicules.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE fascicules SYSTEM "fascicules.dtd"> + +<fascicules> + <fascicule file="part" href="part_frame.html" entry="no"> + User's Guide + </fascicule> + <fascicule file="ref_man" href="ref_man_frame.html" entry="yes"> + Reference Manual + </fascicule> + <fascicule file="part_notes" href="part_notes_frame.html" entry="no"> + Release Notes + </fascicule> + <fascicule file="" href="../../../../doc/print.html" entry="no"> + Off-Print + </fascicule> +</fascicules> + + diff --git a/lib/odbc/doc/src/getting_started.xml b/lib/odbc/doc/src/getting_started.xml new file mode 100644 index 0000000000..864c3a7b65 --- /dev/null +++ b/lib/odbc/doc/src/getting_started.xml @@ -0,0 +1,248 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2002</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Getting started</title> + <prepared>Ingela Anderton Andin</prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date></date> + <rev></rev> + <file>getting_started.xml</file> + </header> + + <section> + <title>Setting things up </title> + <p>As the Erlang ODBC application is dependent on third party + products there are a few administrative things that needs to be + done before you can get things up and running.</p> + <p></p> + <list type="bulleted"> + <item>The first thing you need to do, is to make sure you + have an ODBC driver installed for the database that you + want to access. Both the client machine where you plan to + run your erlang node and the server machine running the + database needs the the ODBC driver. (In some cases the + client and the server may be the same machine).</item> + <item>Secondly you might need to set environment variables + and paths to appropriate values. This may differ a lot + between different os's, databases and ODBC drivers. This + is a configuration problem related to the third party product + and hence we can not give you a standard solution in this guide.</item> + <item>The Erlang ODBC application consists of both <c>Erlang</c> + and <c>C</c> code. The <c>C</c> code is delivered as a + precompiled executable for windows, solaris and linux (SLES10) in the commercial + build. In the open source distribution it is built the + same way as all other application using configure and make. + You may want to provide the the path to your ODBC libraries + using --with-odbc=PATH. </item> + </list> + <note> + <p>The Erlang ODBC application should run on all Unix + dialects including Linux, Windows 2000, Windows XP and + NT. But currently it is only tested for Solaris, Windows + 2000, Windows XP and NT.</p> + </note> + </section> + + <section> + <title>Using the Erlang API</title> + <p>The following dialog within the Erlang shell illustrates the + functionality of the Erlang ODBC interface. The table used in + the example does not have any relevance to anything that exist + in reality, it is just a simple example. The example was created + using <c>sqlserver 7.0 with servicepack 1</c> as database and + the ODBC driver for <c>sqlserver</c> with version + <c>2000.80.194.00</c>.</p> + <code type="none"> + 1 > odbc:start(). + ok </code> + <p>Connect to the database </p> + <code type="none"><![CDATA[ + 2 > {ok, Ref} = odbc:connect("DSN=sql-server;UID=aladdin;PWD=sesame", []). + {ok,<0.342.0>} ]]></code> + <p>Create a table </p> + <code type="none"> + 3 > odbc:sql_query(Ref, "CREATE TABLE EMPLOYEE (NR integer, + FIRSTNAME char varying(20), LASTNAME char varying(20), GENDER char(1), + PRIMARY KEY(NR))"). + {updated,undefined} </code> + <p>Insert some data </p> + <code type="none"> + 4 > odbc:sql_query(Ref, "INSERT INTO EMPLOYEE VALUES(1, 'Jane', 'Doe', 'F')"). + {updated,1} </code> + <p>Check what data types the database assigned for the columns. + Hopefully this is not a surprise, some times it can be! These + are the data types that you should use if you want to do a + parameterized query.</p> + <code type="none"> + 5 > odbc:describe_table(Ref, "EMPLOYEE"). + {ok, [{"NR", sql_integer}, + {"FIRSTNAME", {sql_varchar, 20}}, + {"LASTNAME", {sql_varchar, 20}} + {"GENDER", {sql_char, 1}}]} + </code> + <p> <marker id="param_query"></marker> + Use a parameterized query + to insert many rows in one go. </p> + <code type="none"> + 6 > odbc:param_query(Ref,"INSERT INTO EMPLOYEE (NR, FIRSTNAME, " +\011 "LASTNAME, GENDER) VALUES(?, ?, ?, ?)", +\011 [{sql_integer,[2,3,4,5,6,7,8]}, +\011 {{sql_varchar, 20}, + ["John", "Monica", "Ross", "Rachel", + "Piper", "Prue", "Louise"]}, +\011 {{sql_varchar, 20}, + ["Doe","Geller","Geller", "Green", + "Halliwell", "Halliwell", "Lane"]}, +\011 {{sql_char, 1}, ["M","F","M","F","F","F","F"]}]). + {updated, 7} + </code> + <p>Fetch all data in the table employee </p> + <code type="none"> + 7> odbc:sql_query(Ref, "SELECT * FROM EMPLOYEE"). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"], + [{1,"Jane","Doe","F"}, + {2,"John","Doe","M"}, + {3,"Monica","Geller","F"}, + {4,"Ross","Geller","M"}, + {5,"Rachel","Green","F"}, + {6,"Piper","Halliwell","F"}, + {7,"Prue","Halliwell","F"}, + {8,"Louise","Lane","F"}]]} </code> + <p>Associate a result set containing the whole table + <c>EMPLOYEE</c> to the connection. The number of rows in the + result set is returned.</p> + <code type="none"> + 8 > odbc:select_count(Ref, "SELECT * FROM EMPLOYEE"). + {ok,8} </code> + <p>You can always traverse the result set sequential by using next</p> + <code type="none"> + 9 > odbc:next(Ref). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"],[{1,"Jane","Doe","F"}]} + </code> + <code type="none"> + 10 > odbc:next(Ref). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"],[{2,"John","Doe","M"}]} + </code> + <p>If your driver supports scrollable cursors you have a little + more freedom, and can do things like this. </p> + <code type="none"> + 11 > odbc:last(Ref). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"],[{8,"Louise","Lane","F"}]} </code> + <code type="none"> + 12 > odbc:prev(Ref). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"],[{7,"Prue","Halliwell","F"}]} </code> + <code type="none"> + 13 > odbc:first(Ref). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"],[{1,"Jane","Doe","F"}]} </code> + <code type="none"> + 14 > odbc:next(Ref). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"],[{2,"John","Doe","M"}]} + </code> + <p>Fetch the fields <c>FIRSTNAME </c> and <c>NR </c> for all female + employees</p> + <code type="none"> + 15 > odbc:sql_query(Ref, "SELECT FIRSTNAME, NR FROM EMPLOYEE WHERE GENDER = 'F'"). + {selected,["FIRSTNAME","NR"], + [{"Jane",1}, + {"Monica",3}, + {"Rachel",5}, + {"Piper",6}, + {"Prue",7}, + {"Louise",8}]} </code> + <p>Fetch the fields <c>FIRSTNAME </c> and <c>NR </c> for all female + employees and sort them on the field <c>FIRSTNAME </c>. </p> + <code type="none"> + 16 > odbc:sql_query(Ref, "SELECT FIRSTNAME, NR FROM EMPLOYEE WHERE GENDER = 'F' + ORDER BY FIRSTNAME"). + {selected,["FIRSTNAME","NR"], + [{"Jane",1}, + {"Louise",8}, + {"Monica",3}, + {"Piper",6}, + {"Prue",7}, + {"Rachel",5}]} + </code> + <p>Associate a result set that contains the fields <c>FIRSTNAME</c> and <c>NR </c> for all female employees to the + connection. The number of rows in the result set is + returned.</p> + <code type="none"> + 17 > odbc:select_count(Ref, "SELECT FIRSTNAME, NR FROM EMPLOYEE WHERE GENDER = 'F'"). + {ok,6} </code> + <p>A few more ways of retrieving parts of the result set when the + driver supports scrollable cursors. Note that next will work even + without support for scrollable cursors. </p> + <code type="none"> + 18 > odbc:select(Ref, {relative, 2}, 3). + {selected,["FIRSTNAME","NR"],[{"Monica",3},{"Rachel",5},{"Piper",6}]} + </code> + <code type="none"> + 19 > odbc:select(Ref, next, 2). + {selected,["FIRSTNAME","NR"],[{"Prue",7},{"Louise",8}]} + </code> + <code type="none"> + 20 > odbc:select(Ref, {absolute, 1}, 2). + {selected,["FIRSTNAME","NR"],[{"Jane",1},{"Monica",3}]} + </code> + <code type="none"> + 21 > odbc:select(Ref, next, 2). + {selected,["FIRSTNAME","NR"],[{"Rachel",5},{"Piper",6}]} + </code> + <code type="none"> + 22 > odbc:select(Ref, {absolute, 1}, 4). + {selected,["FIRSTNAME","NR"], + [{"Jane",1},{"Monica",3},{"Rachel",5},{"Piper",6}]} + </code> + <p>Select, using a parameterized query. </p> + <code type="none"> + 23 > odbc:param_query(Ref, "SELECT * FROM EMPLOYEE WHERE GENDER=?", + [{{sql_char, 1}, ["M"]}]). + {selected,["NR","FIRSTNAME","LASTNAME","GENDER"], + [{2,"John", "Doe", "M"},{4,"Ross","Geller","M"}]} + </code> + <p>Delete the table <c>EMPLOYEE</c>.</p> + <code type="none"> + 24 > odbc:sql_query(Ref, "DROP TABLE EMPLOYEE"). + {updated,undefined} + </code> + <p>Shut down the connection. </p> + <code type="none"> + 25 > odbc:disconnect(Ref). + ok + </code> + <p>Shut down the application. </p> + <code type="none"> + 26 > odbc:stop(). + =INFO REPORT==== 7-Jan-2004::17:00:59 === + application: odbc + exited: stopped + type: temporary + + ok + </code> + </section> +</chapter> + + diff --git a/lib/odbc/doc/src/introduction.xml b/lib/odbc/doc/src/introduction.xml new file mode 100644 index 0000000000..8e9688c407 --- /dev/null +++ b/lib/odbc/doc/src/introduction.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2002</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Introduction</title> + <prepared>Ingela Anderton</prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date>2002-09-17</date> + <rev>A</rev> + <file>introduction.xml</file> + </header> + + <section> + <title>Purpose</title> + <p>The purpose of the Erlang ODBC application is to provide the + programmer with an ODBC interface that has a Erlang/OTP touch and + feel. So that the programmer may concentrate on solving his/her + actual problem instead of struggling with pointers and memory + allocation which is not very relevant for Erlang. This user guide + will give you some information about technical issues and provide + some examples of how to use the Erlang ODBC interface.</p> + </section> + + <section> + <title>Prerequisites</title> + <p>It is assumed that the reader is familiar with the Erlang + programming language, concepts of OTP and has a basic + understanding of relational databases and SQL.</p> + </section> + + <section> + <title>About ODBC</title> + <p>Open Database Connectivity (ODBC) is a Microsoft standard for + accessing relational databases that has become widely used. The + ODBC standard provides a c-level application programming + interface (API) for database access. It uses Structured Query + Language (SQL) as its database access language.</p> + </section> + + <section> + <title>About the Erlang ODBC application</title> + <p>Provides an Erlang interface to communicate with relational + SQL-databases. It is built on top of Microsofts ODBC interface and + therefore requires that you have an ODBC driver to the database + that you want to connect to. The Erlang ODBC application is designed + using the version 3.0 of the ODBC-standard, however using the option + <c>{scrollable_cursors, off} </c> for a connection has been known + to make it work for at least some 2.X drivers.</p> + </section> +</chapter> + + diff --git a/lib/odbc/doc/src/make.dep b/lib/odbc/doc/src/make.dep new file mode 100644 index 0000000000..d62e8dd8f0 --- /dev/null +++ b/lib/odbc/doc/src/make.dep @@ -0,0 +1,27 @@ +# ---------------------------------------------------- +# >>>> Do not edit this file <<<< +# This file was automaticly generated by +# /home/otp/bin/docdepend +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# TeX files that the DVI file depend on +# ---------------------------------------------------- + +book.dvi: book.tex databases.tex error_handling.tex \ + getting_started.tex introduction.tex odbc.tex \ + part.tex ref_man.tex + +# ---------------------------------------------------- +# Source inlined when transforming from source to LaTeX +# ---------------------------------------------------- + +book.tex: ref_man.xml + +# ---------------------------------------------------- +# Pictures that the DVI file depend on +# ---------------------------------------------------- + +book.dvi: odbc_app_arc.ps + diff --git a/lib/odbc/doc/src/note.gif b/lib/odbc/doc/src/note.gif Binary files differnew file mode 100644 index 0000000000..6fffe30419 --- /dev/null +++ b/lib/odbc/doc/src/note.gif diff --git a/lib/odbc/doc/src/notes.gif b/lib/odbc/doc/src/notes.gif Binary files differnew file mode 100644 index 0000000000..e000cca26a --- /dev/null +++ b/lib/odbc/doc/src/notes.gif diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml new file mode 100644 index 0000000000..1f9b3b4290 --- /dev/null +++ b/lib/odbc/doc/src/notes.xml @@ -0,0 +1,492 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2004</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>ODBC Release Notes</title> + <prepared>otp_appnotes</prepared> + <docno>nil</docno> + <date>nil</date> + <rev>nil</rev> + <file>notes.xml</file> + </header> + <p>This document describes the changes made to the odbc application. + </p> + + <section><title>ODBC 2.10.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Applied a patch from Andrew Thompson, which fixes some + error cases.</p> + <p> + Own Id: OTP-8291</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The documentation is now built with open source tools + (xsltproc and fop) that exists on most platforms. One + visible change is that the frames are removed.</p> + <p> + Own Id: OTP-8250</p> + </item> + </list> + </section> + + </section> + + <section><title>ODBC 2.10.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A missing return statement in a non void function has been fixed in <c>odbc</c>. (Thanks to Nico Kruber) + </p> + <p> + Own Id: OTP-7978</p> + </item> + </list> + </section> + + </section> + + + <section><title>ODBC 2.10.4</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + param_query now handles the in_or_out parameter + correctly.</p> + <p> + Own Id: OTP-7720</p> + </item> + <item> + <p> + Changed the internal socket use so that it will become + more robust to non-functional ipv6 and fallback on ipv4.</p> + <p> + Own Id: OTP-7721</p> + </item> + </list> + </section> + + </section> + + <section><title>ODBC 2.10.3</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Configure update for mac.</p> + <p> + Own Id: OTP-7418</p> + </item> + </list> + </section> + + + <section><title>Known Bugs and Problems</title> + <list> + <item> + <p> + describe_table/[2,3] on mac gives an empty result</p> + <p> + Own Id: OTP-7478</p> + </item> + </list> + </section> + +</section> + + <section><title>ODBC 2.10.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + SQLINTEGERs where not retrieved correctly on 64 bit + platforms as an SQLINTEGER is defined to be a 32 bit + integer and not a true long.</p> + <p> + Own Id: OTP-7297</p> + </item> + </list> + </section> + + </section> + + <section><title>ODBC 2.10.1</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Now supports out and input parameters for stored + procedures.</p> + <p> + Own Id: OTP-7019</p> + </item> + <item> + <p> + ODBC is now prebuilt for SLES10 in the commercial build + and parameters to error_logger:error_report/1 has been + corrected.</p> + <p> + Own Id: OTP-7294</p> + </item> + <item> + <p> + Parametrized queries will now work correctly when using + Erlang R12B-2 on Linux (SuSE 10.3), MySQL 5.0.45, myodbc + 3.51 and unixODBC 2.2.12. Earlier it could happen that an + error was returned even though data was correctly + inserted into the database.</p> + <p> + Own Id: OTP-7307</p> + </item> + </list> + </section> + + + <section><title>Known Bugs and Problems</title> + <list> + <item> + <p> + SQLINTEGERs are not retrieved correctly on 64 bit + platforms as an SQLINTEGER seems to be defined to be a 32 + bit integer and not a true long.</p> + <p> + Own Id: OTP-7297</p> + </item> + </list> + </section> + + </section> + + <section><title>ODBC 2.10</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Enhanced configure to among other things work better when + there is a library found but it is not usable e.i. 32 bit + library in 64 bit build.</p> + <p> + Own Id: OTP-7062</p> + </item> + </list> + </section> + + </section> + + <section><title>ODBC 2.0.9</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The odbc application now has to be explicitly started and + stopped e.i. it will not automatically be started as a + temporary application as it did before. Although a + practical feature when testing things in the shell, it is + not desirable that people take advantage of this and not + start the odbc application in a correct way in their + products. Added functions to the odbc API that calls + application:start/stop.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-6984</p> + </item> + <item> + <p> + Changed Makefile.in so that odbc is not disabled on + 64-bits architectures. It was earlier disabled due to + that it had never been tested in that environment.</p> + <p> + Own Id: OTP-6987</p> + </item> + </list> + </section> + + </section> + + <section> + <title>ODBC 2.0.8</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Minor Makefile changes.</p> + <p>Own Id: OTP-6689</p> + </item> + </list> + </section> + </section> + + <section> + <title>ODBC 2.0.7</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>When using a parameterized query on a windows platform + the data was inserted in the table on the sql-server but + the connection was lost, this seems to be due to a + compiler error that has now been worked around, but + further investigation is ongoing to verify that that + really was the problem.</p> + <p>Own Id: OTP-5504</p> + </item> + <item> + <p>param_query/[3,4] could return an unexpected row count + for some drivers, in this case a postgresdriver.</p> + <p>Own Id: OTP-6363</p> + </item> + </list> + </section> + </section> + + <section> + <title>ODBC 2.0.6</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>pthread header and library mismatch on linux systems (at + least some SuSE and Debian) with both NPTL and + Linuxthreads libraries installed.</p> + <p>Own Id: OTP-5981</p> + </item> + </list> + </section> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Changed configure to find odbc in /usr/local too</p> + <p>Own Id: OTP-5966</p> + </item> + </list> + </section> + + <section> + <title>Known Bugs and Problems</title> + <list type="bulleted"> + <item> + <p>When using a parameterized query on a windows platform + the data is inserted in the table on the sql-server but + for some reason the connection is lost.</p> + <p>Own Id: OTP-5504</p> + </item> + </list> + </section> + </section> + + <section> + <title>ODBC 2.0.5</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>Fixed bug, reported error when deleting nonexisting rows, + thanks to Laura M. Castro for reporting this.</p> + <p>Own Id: OTP-5759</p> + </item> + </list> + </section> + + <section> + <title>Known Bugs and Problems</title> + <list type="bulleted"> + <item> + <p>When using a parameterized query on a windows platform + the data is inserted in the table on the sql-server but + for some reason the connection is lost.</p> + <p>Own Id: OTP-5504</p> + </item> + </list> + </section> + </section> + + <section> + <title>Odbc 2.0.4</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>/usr was added as a default place for configure to look + for the odbc library on unix/linux platforms.</p> + <p>Own Id: OTP-5501</p> + </item> + <item> + <p>A legacy timer in the c port program was set to infinity. + All timeout handling is handled by the erlang code and a + extra timeout in the c code will just lead to confusion + if it is released.</p> + <p>Own Id: OTP-5502</p> + </item> + </list> + </section> + + <section> + <title>Known Bugs and Problems</title> + <list type="bulleted"> + <item> + <p>When using a parameterized query on a windows platform + the data is inserted in the table on the sql-server but + for some reason the connection is lost.</p> + <p>Own Id: OTP-5504</p> + </item> + </list> + </section> + </section> + + <section> + <title>Odbc 2.0.3</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>odbc now uses configure as all "normal" applications + instead of providing special Makefiles for each + commercial supported platform. This also makes it easier + to build odbc on non supported platforms.</p> + <p>Own Id: OTP-5437</p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 2.0.2</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>When issuing a batch of queries and one of the queries + fail the odbc port program crashed. This is no longer the + case.</p> + <p>Own Id: OTP-5176</p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 2.0.1</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Added use of the socket option TCP_NODELAY, as in the + case of Erlang odbc the Nagel algorithm will never help, + but always cause an unnecessary delay.</p> + <p>Own Id: OTP-5100</p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 2.0</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Erlang ODBC now handles batches of queries and can return + multiple result sets.</p> + <p>Own Id: OTP-4642 Aux Id: seq7766 </p> + </item> + <item> + <p>The old interface that became deprecated in odbc 1.0 has + now been removed.</p> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + <p>Own Id: OTP-4794</p> + </item> + <item> + <p>The port program now sends different exit codes to Erlang + when it exits due to failure. This instead of sending the + same exit code and then trying to write to stderr. Erlang + encodes the exit code to a descriptive atom.</p> + <p>Own Id: OTP-4813</p> + </item> + <item> + <p>Erlang ODBC now supports parameterized queries for the + most common ODBC data types.</p> + <p>Own Id: OTP-4821</p> + </item> + <item> + <p>SQL_NUMERIC and SQL_DECIMAL columns are converted to + integer and float values if possible.</p> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + <p>Own Id: OTP-4826</p> + </item> + <item> + <p>Result sets are now by default returned as a list of + tuples which is the most intuitive and useful mapping. To + keep some degree of backwards compatibility you may turn + this off to get the old behavior that result sets are + returned as lists of lists. However do not use this in + new code as it is considered a deprecated feature that + eventually will disappear.</p> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + <p>Own Id: OTP-4850</p> + </item> + <item> + <p>The odbc implementation now mostly uses sockets to + communicate between the c and the erlang process, this is + to avoid a lot of problems arising from different + odbc-drivers doing strange things that disturbed the + port-program communication mechanism.</p> + <p>Own Id: OTP-4875</p> + </item> + </list> + </section> + <!-- p>For information about older versions see + <url href="part_notes_history_frame.html">release notes history</url>.</p --> + </section> +</chapter> + + diff --git a/lib/odbc/doc/src/notes_history.xml b/lib/odbc/doc/src/notes_history.xml new file mode 100644 index 0000000000..2ce6b18201 --- /dev/null +++ b/lib/odbc/doc/src/notes_history.xml @@ -0,0 +1,235 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2004</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Older ODBC Release Notes</title> + <prepared>otp_appnotes</prepared> + <docno>nil</docno> + <date>nil</date> + <rev>nil</rev> + </header> + + <section> + <title>odbc 1.0.6</title> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Error handling has been enhanced and better documented</p> + <p>Own Id: OTP-4661</p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 1.0.5</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>Some error messages originating from the sql-driver where + corrupted so that odbc only returned the end of the error + message. This has now been fixed.</p> + <p>Own Id: OTP-4636 Aux Id: seq7766 </p> + </item> + </list> + </section> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>The erlang odbc process will now die normally if a + connection can not be established. No connection no + process it is expected. And as the client has already + received the error message that would be the reason with + which the erlang process would be stopped, the supervisor + report will be superfluous. Naturally you may still get + supervisor reports if the erlang process is stopped for + any other reason than the above.</p> + <p>Own Id: OTP-4639 Aux Id: seq7766 </p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 1.0.4</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>API function commit/3 is now exported.</p> + <p>Own Id: OTP-4593 Aux Id: seq7766 </p> + </item> + <item> + <p>If an ODBC API function times out it will cause the + client to exit with reason timeout. If this exit was + caught and the client continued using the connection, the + next request could get the answer meant for the + previously timed out request, this is no longer the case.</p> + <p>Own Id: OTP-4606 Aux Id: seq7766 </p> + </item> + </list> + </section> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>Improved error messages so that the implementation not + shall shine through.</p> + <p>Added possibility to let a connection return rows as + tuples instead of lists.</p> + <p>Own Id: OTP-4605 Aux Id: seq7766 </p> + </item> + <item> + <p>Scrollable cursors are nice but causes some overhead. For + some connections speed might be more important than + flexible data access and then you can disable scrollable + cursor for a connection, limiting the API but gaining + speed.</p> + <p>Own Id: OTP-4624</p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 1.0</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>API function commit/3 is now exported.</p> + <p>Own Id: OTP-4593 Aux Id: seq7766 </p> + </item> + <item> + <p>If an ODBC API function times out it will cause the + client to exit with reason timeout. If this exit was + caught and the client continued using the connection, the + next request could get the answer meant for the + previously timed out request, this is no longer the case.</p> + <p>Own Id: OTP-4606 Aux Id: seq7766 </p> + </item> + <item> + <p>Some error messages originating from the sql-driver where + corrupted so that odbc only returned the end of the error + message. This has now been fixed.</p> + <p>Own Id: OTP-4636 Aux Id: seq7766 </p> + </item> + </list> + </section> + + <section> + <title>Improvements and New Features</title> + <list type="bulleted"> + <item> + <p>The Erlang ODBC application consists of both Erlang and C + code. The C code is delivered as a precompiled executable + for windows and solaris. For the purpose of recompilation + or compiling on some other platform there is a Makefile + included.</p> + <p>Own Id: OTP-4193 Aux Id: seq7190 </p> + </item> + <item> + <p>Expensive constructs such as lists:flatten/1, double + buffering and string compare has been replaced by more + efficient constructs or when possible totally eliminated.</p> + <p>Own Id: OTP-4362</p> + </item> + <item> + <p>New API that has an Erlang/OTP touch and feel instead of + being a C-interface with Erlang syntax. The old interface + is deprecated and will be removed in release R10.</p> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + <p>Own Id: OTP-4498</p> + </item> + <item> + <p>API functions that became deprecated in version 0.9.1 has + been removed.</p> + <p>*** POTENTIAL INCOMPATIBILITY ***</p> + <p>Own Id: OTP-4499</p> + </item> + <item> + <p>Improved error messages so that the implementation not + shall shine through.</p> + <p>Added possibility to let a connection return rows as + tuples instead of lists.</p> + <p>Own Id: OTP-4605 Aux Id: seq7766 </p> + </item> + <item> + <p>Scrollable cursors are nice but causes some overhead. For + some connections speed might be more important than + flexible data access and then you can disable scrollable + cursor for a connection, limiting the API but gaining + speed.</p> + <p>Own Id: OTP-4624</p> + </item> + <item> + <p>The erlang odbc process will now die normally if a + connection can not be established. No connection no + process it is expected. And as the client has already + received the error message that would be the reason with + which the erlang process would be stopped, the supervisor + report will be superfluous. Naturally you may still get + supervisor reports if the erlang process is stopped for + any other reason than the above.</p> + <p>Own Id: OTP-4639 Aux Id: seq7766 </p> + </item> + <item> + <p>Error handling has been enhanced and better documented</p> + <p>Own Id: OTP-4661</p> + </item> + </list> + </section> + </section> + + <section> + <title>odbc 0.9.3</title> + + <section> + <title>Fixed Bugs and Malfunctions</title> + <list type="bulleted"> + <item> + <p>Erlang ODBC application can handle floating point number + and has been improved with null value handling. The error + messages has been improved with STATE-information from SQL + function SQLGetDiagRec.</p> + <p>Own Id: OTP-4192 Aux Id: seq7195 </p> + </item> + <item> + <p>sqlCloseCursor have been replaced with sqlCloseHandle </p> + <p>(*** POTENTIAL INCOMPATIBILITY ***) </p> + <p>Aux Id: seq7283 </p> + </item> + </list> + </section> + </section> +</chapter> + + diff --git a/lib/odbc/doc/src/odbc.gif b/lib/odbc/doc/src/odbc.gif Binary files differnew file mode 100644 index 0000000000..fbbabee5aa --- /dev/null +++ b/lib/odbc/doc/src/odbc.gif diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml new file mode 100644 index 0000000000..450531c81c --- /dev/null +++ b/lib/odbc/doc/src/odbc.xml @@ -0,0 +1,455 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>1999</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>odbc</title> + <prepared>Ingela Anderton Andin</prepared> + <responsible></responsible> + <docno></docno> + <date></date> + <rev></rev> + </header> + <module>odbc</module> + <modulesummary>Erlang ODBC application</modulesummary> + <description> + <p>This application provides an Erlang interface to communicate + with relational SQL-databases. It is built on top of Microsofts + ODBC interface and therefore requires that you have an ODBC driver + to the database that you want to connect to.</p> + <note> + <p>The functions <c>first/[1,2]</c>, <c>last/[1,2]</c>, + <c>next/[1,2]</c>, <c>prev[1,2]</c> and <c>select/[3,4]</c> + assumes there is a result set associated with the connection to + work on. Calling the function <c>select_count/[2,3]</c> + associates such a result set with the connection. Calling + select_count again will remove the current result set + association and create a new one. Calling a function which dose + not operate on an associated result sets, such as + <c>sql_query/[2,3]</c>, will remove the current result set + association.</p> + <p>Alas some drivers only support sequential traversal of the + result set, e.i. they do not support what in the ODBC world is + known as scrollable cursors. This will have the effect that + functions such as <c>first/[1,2]</c>, <c>last/[1,2]</c>, + <c>prev[1,2]</c>, etc will return <c>{error, driver_does_not_support_function}</c></p> + </note> + </description> + + <section> + <title>COMMON DATA TYPES </title> + <p>Here follows type definitions that are used by more than one + function in the ODBC API. </p> + <note> + <p>The type <c>TimeOut</c> has the default value + <c>infinity</c>, so for instance: <br></br> + + commit(Ref, CommitMode) is the same as + commit(Ref, CommitMode, infinity). If the + timeout expires the client will exit with the reason + timeout. </p> + </note> + <code type="none"> + connection_reference() - as returned by connect/2 </code> + <code type="none"> + time_out() = milliseconds() | infinity </code> + <code type="none"> + milliseconds() = integer() >= 0 </code> + <code type="none"> + common_reason() = connection_closed | term() - some kind of + explanation of what went wrong </code> + <code type="none"> + string() = list of ASCII characters </code> + <code type="none"> + col_name() = string() - Name of column in the result set </code> + <code type="none"> + col_names() - [col_name()] - e.g. a list of the names of the + selected columns in the result set. </code> + <code type="none"> + row() = {value()} - Tuple of column values e.g. one row of the + result set. </code> + <code type="none"> + value() = null | term() - A column value. </code> + <code type="none"> + rows() = [row()] - A list of rows from the result set. </code> + <code type="none"> + result_tuple() = + {updated, n_rows()} | {selected, col_names(), rows()} </code> + <code type="none"> + n_rows() = integer() - The number of affected rows for UPDATE, + INSERT, or DELETE queries. For other query types the value + is driver defined, and hence should be ignored. </code> + <code type="none"> + odbc_data_type() = sql_integer | sql_smallint | sql_tinyint | + {sql_decimal, precision(), scale()} | + {sql_numeric, precision(), scale()} | + {sql_char, size()} | {sql_varchar, size()} | {sql_float, precision()} | + {sql_float, precision()} | sql_real | sql_double | sql_bit | atom() + </code> + <code type="none"> + precision() = integer() </code> + <code type="none"> + scale() = integer() </code> + <code type="none"> + size() = integer() </code> + </section> + + <section> + <title>ERROR HANDLING </title> + <p>The error handling strategy and possible errors sources are + described in the Erlang ODBC <seealso marker="error_handling">User's Guide.</seealso></p> + </section> + <funcs> + <func> + <name>commit(Ref, CommitMode) -></name> + <name>commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name> + <fsummary>Commits or rollbacks a transaction. </fsummary> + <type> + <v>Ref = connection_reference() </v> + <v>CommitMode = commit | rollback</v> + <v>TimeOut = time_out()</v> + <v>Reason = not_an_explicit_commit_connection | process_not_owner_of_odbc_connection | common_reason()</v> + </type> + <desc> + <p>Commits or rollbacks a transaction. Needed on connections + where automatic commit is turned off.</p> + </desc> + </func> + <func> + <name>connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name> + <fsummary>Opens a connection to the database. </fsummary> + <type> + <v>ConnectStr = string()</v> + <d>An example of a connection string:<c>"DSN=sql-server;UID=aladdin;PWD=sesame"</c>where DSN is your ODBC Data Source Name, UID is a database user id and PWD is the password for that user. These are usually the attributes required in the connection string, but some drivers have other driver specific attributes, for example<c>"DSN=Oracle8;DBQ=gandalf;UID=aladdin;PWD=sesame"</c>where DBQ is your TNSNAMES.ORA entry name e.g. some Oracle specific configuration attribute.</d> + <v>Options = [] | [option()]</v> + <d>All options has default values. </d> + <v>option() = {auto_commit, auto_commit_mode()} | {timeout, milliseconds()} | {tuple_row, tuple_mode()} | {scrollable_cursors, use_scrollable_cursors()} | {trace_driver, trace_mode()} </v> + <d>The default timeout is infinity </d> + <v>auto_commit_mode() = on | off </v> + <d>Default is on.</d> + <v>tuple_mode() = on | off </v> + <d>Default is on. The option is deprecated and should not be used in new code.</d> + <v>use_scrollable_cursors() = on | off </v> + <d>Default is on.</d> + <v>trace_mode() = on | off </v> + <d>Default is off.</d> + <v>Ref = connection_reference() - should be used to access the connection. </v> + <v>Reason = port_program_executable_not_found | common_reason()</v> + </type> + <desc> + <p>Opens a connection to the database. The connection is + associated with the process that created it and can only be + accessed through it. This function may spawn new processes + to handle the connection. These processes will terminate if + the process that created the connection dies or if you call + disconnect/1.</p> + <p>If automatic commit mode is turned on, each query will be + considered as an individual transaction and will be + automatically committed after it has been executed. If you want + more than one query to be part of the same transaction the automatic + commit mode should be turned off. Then you will have to call + commit/3 explicitly to end a transaction. </p> + <p>As default result sets are returned as a lists of + tuples. The <c>TupleMode</c> option still exists to keep some + degree of backwards compatibility. If the option is set to + off, result sets will be returned as a lists of lists + instead of a lists of tuples.</p> + <p>Scrollable cursors are nice but causes some overhead. For + some connections speed might be more important than flexible + data access and then you can disable scrollable cursor for a + connection, limiting the API but gaining speed</p> + <p>If trace mode is turned on this tells the ODBC driver to + write a trace log to the file SQL.LOG that is placed in the + current directory of the erlang emulator. This information + may be useful if you suspect there might be a bug in the + erlang ODBC application, and it might be relevant for you to + send this file to our support. Otherwise you will probably + not have much use of this.</p> + <note> + <p>For more information about the <c>ConnectStr</c> see + description of the function SQLDriverConnect in [1].</p> + </note> + </desc> + </func> + <func> + <name>disconnect(Ref) -> ok | {error, Reason} </name> + <fsummary>Closes a connection to a database. </fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>Reason = process_not_owner_of_odbc_connection</v> + </type> + <desc> + <p>Closes a connection to a database. This will also + terminate all processes that may have been spawned + when the connection was opened. This call will always succeed. + If the connection can not be disconnected gracefully it will + be brutally killed. However you may receive an error message + as result if you try to disconnect a connection started by another + process. + <marker id="describe_table"></marker> +</p> + </desc> + </func> + <func> + <name>describe_table(Ref, Table) -> </name> + <name>describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name> + <fsummary>Queries the database to find out the data types of the columns of the table <c>Table</c>. </fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>Table = string() - Name of databas table.</v> + <v>TimeOut = time_out()</v> + <v>Description = [{col_name(), odbc_data_type()}]</v> + <v>Reason = common_reason()</v> + </type> + <desc> + <p>Queries the database to find out the ODBC data types of the + columns of the table <c>Table</c>. </p> + </desc> + </func> + <func> + <name>first(Ref) -></name> + <name>first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name> + <fsummary>Returns the first row of the result set and positions a cursor at this row.</fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>TimeOut = time_out()</v> + <v>ColNames = col_names() </v> + <v>Rows = rows()</v> + <v>Reason = result_set_does_not_exist | driver_does_not_support_function | scrollable_cursors_disabled | process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>Returns the first row of the result set and positions a + cursor at this row.</p> + </desc> + </func> + <func> + <name>last(Ref) -></name> + <name>last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <fsummary>Returns the last row of the result set and positions a cursor at this row. </fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>TimeOut = time_out()</v> + <v>ColNames = col_names() </v> + <v>Rows = rows()</v> + <v>Reason = result_set_does_not_exist | driver_does_not_support_function | scrollable_cursors_disabled | process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>Returns the last row of the result set and positions a + cursor at this row.</p> + </desc> + </func> + <func> + <name>next(Ref) -> </name> + <name>next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <fsummary>Returns the next row of the result set relative the current cursor position and positions the cursor at this row. </fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>TimeOut = time_out()</v> + <v>ColNames = col_names() </v> + <v>Rows = rows()</v> + <v>Reason = result_set_does_not_exist | process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>Returns the next row of the result set relative the + current cursor position and positions the cursor at this + row. If the cursor is positioned at the last row of the + result set when this function is called the returned value + will be <c>{selected, ColNames,[]}</c> e.i. the list of row + values is empty indicating that there is no more data to fetch. + <marker id="param_query"></marker> +</p> + </desc> + </func> + <func> + <name>param_query(Ref, SQLQuery, Params) -> </name> + <name>param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name> + <fsummary>Executes a parameterized SQL query.</fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>SQLQuery = string() - a SQL query with parameter markers/place holders in form of question marks.</v> + <v>Params = [{odbc_data_type(), [value()]}] |[{odbc_data_type(), in_or_out(), [value()]}] </v> + <v>in_or_out = in | out | inout</v> + <d>Defines IN, OUT, and IN OUT Parameter Modes for stored procedures.</d> + <v>TimeOut = time_out()</v> + <v>Values = term() - Must be consistent with the Erlang data type that corresponds to the ODBC data type ODBCDataType</v> + </type> + <desc> + <p>Executes a parameterized SQL query. For an + example see the <seealso marker="getting_started#param_query">"Using the Erlang API"</seealso> in the Erlang ODBC + User's Guide.</p> + <note> + <p>Use the function describe_table/[2,3] to find out which + ODBC data type that is expected for each column of that + table. If a column has a data type that is described with + capital letters, alas it is not currently supported by the + param_query function. Too know which Erlang data type + corresponds to an ODBC data type see the Erlang to ODBC + data type<seealso marker="databases#type">mapping</seealso> in the User's Guide.</p> + </note> + </desc> + </func> + <func> + <name>prev(Ref) -> </name> + <name>prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <fsummary>Returns the previous row of the result set relative the current cursor position and positions the cursor at this row. </fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>TimeOut = time_out()</v> + <v>ColNames = col_names() </v> + <v>Rows = rows()</v> + <v>Reason = result_set_does_not_exist | driver_does_not_support_function | scrollable_cursors_disabled | process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>Returns the previous row of the result set relative the + current cursor position and positions the + cursor at this row.</p> + </desc> + </func> + + <func> + <name>start() -> </name> + <name>start(Type) -> ok | {error, Reason}</name> + <fsummary>Starts the odb application. </fsummary> + + <type> + <v>Type = permanent | transient | temporary + </v> + </type> + + <desc> + <p> Starts the odbc application. Default type + is temporary. + <seealso marker="kernel:application">See application(3)</seealso> + </p> + </desc> + </func> + + <func> + <name>stop() -> ok </name> + <fsummary> Stops the odbc application.</fsummary> + + <desc> + <p> Stops the odbc application. + <seealso marker="kernel:application">See application(3)</seealso> + </p> + </desc> + </func> + + <func> + <name>sql_query(Ref, SQLQuery) -> </name> + <name>sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name> + <fsummary>Executes a SQL query or a batch of SQL queries. If it is a SELECT query the result set is returned, on the format<c>{selected, ColNames, Rows}</c>. For other query types the tuple <c>{updated, NRows}</c>is returned, and for batched queries, if the driver supports them, this function can also return a list of result tuples.</fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>SQLQuery = string() - The string may be composed by several SQL-queries separated by a ";", this is called a batch. </v> + <v>TimeOut = time_out()</v> + <v>ResultTuple = result_tuple() </v> + <v>Reason = process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>Executes a SQL query or a batch of SQL queries. If it + is a SELECT query the result set is returned, on the format + <c>{selected, ColNames, Rows}</c>. For other query types the + tuple <c>{updated, NRows}</c> is returned, and for batched + queries, if the driver supports them, this function can also + return a list of result tuples.</p> + <note> + <p>Some drivers may not have the information of the number + of affected rows available and then the return value may + be <c>{updated, undefined} </c>. </p> + <p>The list of column names is ordered in the same way as the + list of values of a row, e.g. the first <c>ColName</c> is + associated with the first <c>Value</c> in a <c>Row</c>.</p> + </note> + <br></br> + </desc> + </func> + <func> + <name>select_count(Ref, SelectQuery) -> </name> + <name>select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name> + <fsummary>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 tuple <c>{ok, NrRows}</c>is returned. </fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>SelectQuery = string()</v> + <d>SQL SELECT query.</d> + <v>TimeOut = time_out()</v> + <v>NrRows = n_rows()</v> + <v>Reason = process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>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 tuple <c>{ok, NrRows}</c> is + returned. </p> + <note> + <p>Some drivers may not have the information of the number of + rows in the result set, then <c>NrRows</c> will have the value + <c>undefined</c>. </p> + </note> + </desc> + </func> + <func> + <name>select(Ref, Position, N) -></name> + <name>select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <fsummary>Selects <c>N</c>consecutive rows of the result set.</fsummary> + <type> + <v>Ref = connection_reference()</v> + <v>Position = next | {relative, Pos} | {absolute, Pos} </v> + <d>Selection strategy, determines at which row in the result set to start the selection.</d> + <v>Pos = integer() </v> + <d>Should indicate a row number in the result set. When used together with the option <c>relative</c>it will be used as an offset from the current cursor position, when used together with the option <c>absolute</c>it will be interpreted as a row number.</d> + <v>N = integer() </v> + <v>TimeOut = time_out()</v> + <v>Reason = result_set_does_not_exist | driver_does_not_support_function | scrollable_cursors_disabled | process_not_owner_of_odbc_connection | common_reason() </v> + </type> + <desc> + <p>Selects <c>N</c> consecutive rows of the result set. If + <c>Position</c> is <c>next</c> it is semantically equivalent + of calling <c>next/[1,2]</c><c>N</c> times. If + <c>Position</c> is <c>{relative, Pos}</c>, <c>Pos</c> will be + used as an offset from the current cursor position to + determine the first selected row. If <c>Position</c> is + <c>{absolute, Pos}</c>, <c>Pos</c> will be the number of the + first row selected. After this function has returned the + cursor is positioned at the last selected row. If there is + less then <c>N</c> rows left of the result set the length of + <c>Rows</c> will be less than <c>N</c>. If the first row to + select happens to be beyond the last row of the result set, + the returned value will be <c>{selected, ColNames,[]}</c> + e.i. the list of row values is empty indicating that there + is no more data to fetch.</p> + </desc> + </func> + </funcs> + + <section> + <title>REFERENCES</title> + <p>[1]: Microsoft ODBC 3.0, Programmer's Reference and SDK Guide <br></br> + + See also http://msdn.microsoft.com/</p> + </section> + +</erlref> + + + + + diff --git a/lib/odbc/doc/src/odbc_app_arc.fig b/lib/odbc/doc/src/odbc_app_arc.fig new file mode 100644 index 0000000000..15ae385dbb --- /dev/null +++ b/lib/odbc/doc/src/odbc_app_arc.fig @@ -0,0 +1,76 @@ +#FIG 3.1 +Landscape +Center +Inches +1200 2 +5 1 0 1 -1 7 0 0 -1 0.000 0 0 0 0 2737.500 5737.500 2250 5700 2775 5250 3225 5700 +1 3 0 1 -1 7 0 0 -1 0.000 1 0.0000 4200 2700 474 474 4200 2700 3750 2550 +1 3 0 1 -1 7 0 0 -1 0.000 1 0.0000 4275 7500 474 474 4275 7500 3825 7350 +1 3 0 1 -1 7 0 0 -1 0.000 1 0.0000 4200 4125 474 474 4200 4125 3750 3975 +1 3 0 1 -1 7 0 0 -1 0.000 1 0.0000 2325 4275 474 474 2325 4275 1875 4125 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 1 2 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 4200 3150 4200 3600 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3675 4200 2850 4200 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 2100 3825 3750 2550 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 3837 3017 2625 3825 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 2775 3975 3675 3975 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3825 4500 2775 4500 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 1 2 + 0 0 1.00 60.00 120.00 + 0 0 1.00 60.00 120.00 + 4350 4575 4350 7050 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 4125 4575 4125 7125 +2 2 0 1 -1 7 0 0 -1 0.000 0 0 -1 0 0 5 + 2250 5700 3225 5700 3225 6300 2250 6300 2250 5700 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3900 7275 2925 6300 +2 1 0 1 -1 7 0 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3075 5325 3975 4575 +3 2 0 1 -1 7 0 0 -1 0.000 0 0 0 7 + 4125 7200 3975 7350 4200 7425 4050 7575 4275 7650 4125 7725 + 4275 7875 + 0.00 0.00 4001.93 7267.73 3964.43 7305.23 3990.76 7416.74 + 4184.24 7358.26 4214.09 7484.70 4035.91 7515.30 4065.76 7641.74 + 4271.46 7600.21 4277.50 7685.21 4133.64 7671.76 4116.80 7775.50 + 4154.30 7813.00 0.00 0.00 +3 2 0 1 -1 7 0 0 -1 0.000 0 0 0 7 + 4425 7088 4275 7238 4500 7313 4350 7463 4575 7538 4425 7613 + 4575 7763 + 300.00 -112.00 4301.93 7155.73 4264.43 7193.23 4290.76 7304.74 + 4484.24 7246.26 4514.09 7372.70 4335.91 7403.30 4365.76 7529.74 + 4571.46 7488.21 4577.50 7573.21 4433.64 7559.76 4416.80 7663.50 + 4454.30 7701.00 300.00 -112.00 +4 0 -1 0 0 0 12 0.0000 4 180 1290 3675 2100 Erlang Supervisor\001 +4 0 -1 0 0 0 12 0.0000 4 180 1590 4800 4125 Erlang control process\001 +4 0 -1 0 0 0 12 0.0000 4 180 765 3900 8250 C-process\001 +4 0 -1 0 0 0 12 0.0000 4 180 885 900 4200 Erlang client\001 +4 0 -1 0 0 0 12 0.0000 4 135 330 4275 3525 Link\001 +4 0 -1 0 0 0 12 0.6109 4 135 1500 2543 3786 Connection reference\001 +4 0 -1 0 0 0 12 0.0000 4 165 510 3000 3900 request\001 +4 0 -1 0 0 0 12 0.0000 4 135 630 3000 4350 response\001 +4 0 -1 0 0 0 12 0.0000 4 135 555 2850 4650 monitor\001 +4 0 -1 0 0 0 12 0.6109 4 180 1140 2339 3449 Connect request\001 +4 0 -1 0 0 0 12 0.0000 4 180 1260 2850 7800 Supervisor thread\001 +4 0 -1 0 0 0 12 0.0000 4 135 1695 4650 7575 Database handler thread\001 +4 0 -1 0 0 0 12 0.0000 4 165 510 4425 6825 request\001 +4 0 -1 0 0 0 12 0.0000 4 135 630 4425 4950 response\001 +4 0 -1 0 0 0 12 0.0000 4 180 795 2325 6000 Erlang port\001 +4 0 -1 0 0 0 12 0.0000 4 180 1635 1800 6975 Exit signal propagation\001 +4 0 -1 0 0 0 12 0.0000 4 135 705 3375 5850 shutdown\001 +4 0 -1 0 0 0 12 0.0000 4 180 1635 1650 5175 Exit signal propagation\001 diff --git a/lib/odbc/doc/src/odbc_app_arc.gif b/lib/odbc/doc/src/odbc_app_arc.gif Binary files differnew file mode 100644 index 0000000000..724864f9ad --- /dev/null +++ b/lib/odbc/doc/src/odbc_app_arc.gif diff --git a/lib/odbc/doc/src/odbc_app_arc.ps b/lib/odbc/doc/src/odbc_app_arc.ps new file mode 100644 index 0000000000..1ae8bd9649 --- /dev/null +++ b/lib/odbc/doc/src/odbc_app_arc.ps @@ -0,0 +1,284 @@ +%!PS-Adobe-2.0 +%%Title: odbc_app_arc.fig +%%Creator: fig2dev Version 3.1 Patchlevel 2 +%%CreationDate: Thu Jan 8 11:11:36 2004 +%%For: ingela@gildor (Ingela Anderton, UAB/KH/P) +%Magnification: 1.00 +%%Orientation: Landscape +%%BoundingBox: 116 230 497 562 +%%Pages: 1 +%%BeginSetup +%%IncludeFeature: *PageSize Letter +%%EndSetup +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +-1.5 176.0 translate + 90 rotate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def + /DrawEllipse { + /endangle exch def + /startangle exch def + /yrad exch def + /xrad exch def + /y exch def + /x exch def + /savematrix mtrx currentmatrix def + x y tr xrad yrad sc 0 0 1 startangle endangle arc + closepath + savematrix setmatrix + } def + +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def +%%EndProlog + +$F2psBegin +10 setmiterlimit +n 0 612 m 0 0 l 792 0 l 792 612 l cp clip + 0.06000 0.06000 sc +%%Page: 1 1 +/Times-Roman ff 180.00 scf sf +4800 4125 m +gs 1 -1 sc (Erlang control process) col-1 sh gr +/Times-Roman ff 180.00 scf sf +3900 8250 m +gs 1 -1 sc (C-process) col-1 sh gr +/Times-Roman ff 180.00 scf sf +900 4200 m +gs 1 -1 sc (Erlang client) col-1 sh gr +/Times-Roman ff 180.00 scf sf +4275 3525 m +gs 1 -1 sc (Link) col-1 sh gr +/Times-Roman ff 180.00 scf sf +2543 3786 m +gs 1 -1 sc 35.0 rot (Connection reference) col-1 sh gr +/Times-Roman ff 180.00 scf sf +3000 3900 m +gs 1 -1 sc (request) col-1 sh gr +/Times-Roman ff 180.00 scf sf +3000 4350 m +gs 1 -1 sc (response) col-1 sh gr +/Times-Roman ff 180.00 scf sf +2850 4650 m +gs 1 -1 sc (monitor) col-1 sh gr +/Times-Roman ff 180.00 scf sf +2339 3449 m +gs 1 -1 sc 35.0 rot (Connect request) col-1 sh gr +/Times-Roman ff 180.00 scf sf +2850 7800 m +gs 1 -1 sc (Supervisor thread) col-1 sh gr +/Times-Roman ff 180.00 scf sf +4650 7575 m +gs 1 -1 sc (Database handler thread) col-1 sh gr +/Times-Roman ff 180.00 scf sf +4425 6825 m +gs 1 -1 sc (request) col-1 sh gr +/Times-Roman ff 180.00 scf sf +4425 4950 m +gs 1 -1 sc (response) col-1 sh gr +/Times-Roman ff 180.00 scf sf +2325 6000 m +gs 1 -1 sc (Erlang port) col-1 sh gr +/Times-Roman ff 180.00 scf sf +1800 6975 m +gs 1 -1 sc (Exit signal propagation) col-1 sh gr +/Times-Roman ff 180.00 scf sf +3375 5850 m +gs 1 -1 sc (shutdown) col-1 sh gr +/Times-Roman ff 180.00 scf sf +1650 5175 m +gs 1 -1 sc (Exit signal propagation) col-1 sh gr +/Times-Roman ff 180.00 scf sf +3675 2100 m +gs 1 -1 sc (Erlang Supervisor) col-1 sh gr +7.500 slw +% Arc +gs n 2737.5 5737.5 488.9 -175.6 -4.4 arc +gs col-1 s gr + gr + +% Ellipse +n 4200 2700 474 474 0 360 DrawEllipse gs col-1 s gr + +% Ellipse +n 4275 7500 474 474 0 360 DrawEllipse gs col-1 s gr + +% Ellipse +n 4200 4125 474 474 0 360 DrawEllipse gs col-1 s gr + +% Ellipse +n 2325 4275 474 474 0 360 DrawEllipse gs col-1 s gr + +% Polyline +gs clippath +4230 3453 m 4200 3573 l 4170 3453 l 4170 3615 l 4230 3615 l cp clip +4170 3297 m 4200 3177 l 4230 3297 l 4230 3135 l 4170 3135 l cp clip +n 4200 3150 m 4200 3600 l gs col-1 s gr gr + +% arrowhead +n 4170 3297 m 4200 3177 l 4230 3297 l 4200 3297 l 4170 3297 l cp gs 0.00 setgray ef gr col-1 s +% arrowhead +n 4230 3453 m 4200 3573 l 4170 3453 l 4200 3453 l 4230 3453 l cp gs 0.00 setgray ef gr col-1 s +% Polyline +gs clippath +2997 4230 m 2877 4200 l 2997 4170 l 2835 4170 l 2835 4230 l cp clip +n 3675 4200 m 2850 4200 l gs col-1 s gr gr + +% arrowhead +n 2997 4230 m 2877 4200 l 2997 4170 l 2997 4200 l 2997 4230 l cp gs 0.00 setgray ef gr col-1 s +% Polyline +gs clippath +3615 2616 m 3728 2566 l 3652 2664 l 3780 2565 l 3744 2517 l cp clip +n 2100 3825 m 3750 2550 l gs col-1 s gr gr + +% arrowhead +n 3615 2616 m 3728 2566 l 3652 2664 l col-1 s +% Polyline +gs clippath +2764 3768 m 2647 3810 l 2731 3718 l 2596 3808 l 2629 3858 l cp clip +n 3837 3017 m 2625 3825 l gs col-1 s gr gr + +% arrowhead +n 2764 3768 m 2647 3810 l 2731 3718 l col-1 s +% Polyline +gs clippath +3528 3945 m 3648 3975 l 3528 4005 l 3690 4005 l 3690 3945 l cp clip +n 2775 3975 m 3675 3975 l gs col-1 s gr gr + +% arrowhead +n 3528 3945 m 3648 3975 l 3528 4005 l 3528 3975 l 3528 3945 l cp gs 0.00 setgray ef gr col-1 s +% Polyline +gs clippath +2922 4530 m 2802 4500 l 2922 4470 l 2760 4470 l 2760 4530 l cp clip +n 3825 4500 m 2775 4500 l gs col-1 s gr gr + +% arrowhead +n 2922 4530 m 2802 4500 l 2922 4470 l 2922 4500 l 2922 4530 l cp gs 0.00 setgray ef gr col-1 s +% Polyline +gs clippath +4380 6903 m 4350 7023 l 4320 6903 l 4320 7065 l 4380 7065 l cp clip +4320 4722 m 4350 4602 l 4380 4722 l 4380 4560 l 4320 4560 l cp clip +n 4350 4575 m 4350 7050 l gs col-1 s gr gr + +% arrowhead +n 4320 4722 m 4350 4602 l 4380 4722 l col-1 s +% arrowhead +n 4380 6903 m 4350 7023 l 4320 6903 l col-1 s +% Polyline +gs clippath +4155 6978 m 4125 7098 l 4095 6978 l 4095 7140 l 4155 7140 l cp clip +n 4125 4575 m 4125 7125 l gs col-1 s gr gr + +% arrowhead +n 4155 6978 m 4125 7098 l 4095 6978 l col-1 s +% Polyline +n 2250 5700 m 3225 5700 l 3225 6300 l 2250 6300 l cp gs col-1 s gr +% Polyline +gs clippath +3008 6425 m 2944 6319 l 3050 6383 l 2936 6268 l 2893 6311 l cp clip +n 3900 7275 m 2925 6300 l gs col-1 s gr gr + +% arrowhead +n 3008 6425 m 2944 6319 l 3050 6383 l 3029 6404 l 3008 6425 l cp gs 0.00 setgray ef gr col-1 s +% Polyline +gs clippath +3843 4646 m 3954 4592 l 3881 4692 l 4006 4588 l 3967 4542 l cp clip +n 3075 5325 m 3975 4575 l gs col-1 s gr gr + +% arrowhead +n 3843 4646 m 3954 4592 l 3881 4692 l 3862 4669 l 3843 4646 l cp gs 0.00 setgray ef gr col-1 s +% Interp Spline +gs n 4125 7200 m + 4001.9 7267.7 3964.4 7305.2 3975 7350 curveto + 3990.8 7416.7 4184.2 7358.3 4200 7425 curveto + 4214.1 7484.7 4035.9 7515.3 4050 7575 curveto + 4065.8 7641.7 4271.5 7600.2 4275 7650 curveto + 4277.5 7685.2 4133.6 7671.8 4125 7725 curveto + 4116.8 7775.5 4154.3 7813.0 4275 7875 curveto + gs col-1 s gr + gr + +% Interp Spline +gs n 4425 7088 m + 4301.9 7155.7 4264.4 7193.2 4275 7238 curveto + 4290.8 7304.7 4484.2 7246.3 4500 7313 curveto + 4514.1 7372.7 4335.9 7403.3 4350 7463 curveto + 4365.8 7529.7 4571.5 7488.2 4575 7538 curveto + 4577.5 7573.2 4433.6 7559.8 4425 7613 curveto + 4416.8 7663.5 4454.3 7701.0 4575 7763 curveto + gs col-1 s gr + gr + +showpage +$F2psEnd +rs diff --git a/lib/odbc/doc/src/odbc_index.gif b/lib/odbc/doc/src/odbc_index.gif Binary files differnew file mode 100644 index 0000000000..fbbabee5aa --- /dev/null +++ b/lib/odbc/doc/src/odbc_index.gif diff --git a/lib/odbc/doc/src/part.xml b/lib/odbc/doc/src/part.xml new file mode 100644 index 0000000000..2ab981c04f --- /dev/null +++ b/lib/odbc/doc/src/part.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>1999</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Erlang ODBC User's Guide</title> + <prepared>Ingela Anderton</prepared> + <docno></docno> + <date>2002-09-17</date> + <rev>A</rev> + <file>part.sgml</file> + </header> + <description> + <p>The <em>Erlang ODBC Application </em> provides an interface for + accessing relational SQL-databases from Erlang.</p> + </description> + <xi:include href="introduction.xml"/> + <xi:include href="getting_started.xml"/> + <xi:include href="databases.xml"/> + <xi:include href="error_handling.xml"/> +</part> + + diff --git a/lib/odbc/doc/src/part_notes.xml b/lib/odbc/doc/src/part_notes.xml new file mode 100644 index 0000000000..f101cee799 --- /dev/null +++ b/lib/odbc/doc/src/part_notes.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>2004</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>ODBC Release Notes</title> + <prepared>Ingela Anderton Andin</prepared> + <docno></docno> + <date>2004-09-07</date> + <rev></rev> + <file>part_notes.sgml</file> + </header> + <description> + <p>An interface to relational SQL-databases built on ODBC (Open + Database Connectivity). </p> + <p>For information about older versions see + <url href="part_notes_history_frame.html">release notes history</url>.</p> + </description> + <xi:include href="notes.xml"/> +</part> + + diff --git a/lib/odbc/doc/src/part_notes_history.xml b/lib/odbc/doc/src/part_notes_history.xml new file mode 100644 index 0000000000..fa9b3b7dc7 --- /dev/null +++ b/lib/odbc/doc/src/part_notes_history.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part> + <header> + <copyright> + <year>2004</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Odbc</title> + <prepared>Ingela Anderton Andin</prepared> + <docno></docno> + <date>2004-09-30</date> + <rev></rev> + <file>part_notes.sgml</file> + </header> + <include file="notes_history"></include> +</part> + + diff --git a/lib/odbc/doc/src/ref_man.gif b/lib/odbc/doc/src/ref_man.gif Binary files differnew file mode 100644 index 0000000000..b13c4efd53 --- /dev/null +++ b/lib/odbc/doc/src/ref_man.gif diff --git a/lib/odbc/doc/src/ref_man.xml b/lib/odbc/doc/src/ref_man.xml new file mode 100644 index 0000000000..e94febf0f1 --- /dev/null +++ b/lib/odbc/doc/src/ref_man.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE application SYSTEM "application.dtd"> + +<application xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>1999</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>Erlang ODBC Reference Manual</title> + <prepared>Ingela Anderton</prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date>2002-09-13</date> + <rev>A</rev> + <file>application.sgml</file> + </header> + <description> + <p>The <em>Erlang ODBC </em> application provides an interface for + accessing relational SQL-databases from Erlang.</p> + </description> + <xi:include href="odbc.xml"/> +</application> + + diff --git a/lib/odbc/doc/src/user_guide.gif b/lib/odbc/doc/src/user_guide.gif Binary files differnew file mode 100644 index 0000000000..e6275a803d --- /dev/null +++ b/lib/odbc/doc/src/user_guide.gif diff --git a/lib/odbc/doc/src/warning.gif b/lib/odbc/doc/src/warning.gif Binary files differnew file mode 100644 index 0000000000..96af52360e --- /dev/null +++ b/lib/odbc/doc/src/warning.gif diff --git a/lib/odbc/ebin/.gitignore b/lib/odbc/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/ebin/.gitignore diff --git a/lib/odbc/include/odbc.hrl b/lib/odbc/include/odbc.hrl new file mode 100644 index 0000000000..dc0a24192f --- /dev/null +++ b/lib/odbc/include/odbc.hrl @@ -0,0 +1,57 @@ +%% ``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 via the world wide web 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. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +%% AB. All Rights Reserved.'' +%% +%% $Id$ +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This whole file is DEPRECATED!!!! It is part of the old interface to +%% the erlang odbc-application. Some constants that would never come to use +%% has been removed. This file has also been made static. Before it was +%% generated for some peculiar reason. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Result codes +%% ____________ + +-define(SQL_SUCCESS, 0). +-define(SQL_SUCCESS_WITH_INFO, 1). +-define(SQL_INVALID_HANDLE, -2). +-define(SQL_ERROR, -1). +-define(SQL_NO_DATA, 100). +-define(SQL_NEED_DATA, 99). + +%% Values of "Nullable" +%% ____________________ + +-define(SQL_NO_NULLS, 0). +-define(SQL_NULLABLE, 1). +-define(SQL_NULLABLE_UNKNOWN, 2). + +%% Commit/rollback macros +%% ______________________ + +-define(SQL_COMMIT, 0). +-define(SQL_ROLLBACK, 1). + +%% Connection attributes and values +%% ________________________________ + +-define(SQL_ATTR_AUTOCOMMIT, 102). +-define(SQL_AUTOCOMMIT_OFF, 0). +-define(SQL_AUTOCOMMIT_ON, 1). +-define(SQL_ATTR_TRACE, 104). +-define(SQL_OPT_TRACE_OFF, 0). +-define(SQL_OPT_TRACE_ON, 1). +-define(SQL_ATTR_TRACEFILE, 105). diff --git a/lib/odbc/info b/lib/odbc/info new file mode 100644 index 0000000000..846dfc2ba9 --- /dev/null +++ b/lib/odbc/info @@ -0,0 +1,3 @@ +group: dat +short: An interface to relational SQL-databases built on ODBC (Open Database +short: Connectivity). diff --git a/lib/odbc/priv/bin/.gitignore b/lib/odbc/priv/bin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/priv/bin/.gitignore diff --git a/lib/odbc/priv/obj/win32/.gitignore b/lib/odbc/priv/obj/win32/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/odbc/priv/obj/win32/.gitignore 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]}}. + + + + + + diff --git a/lib/odbc/subdirs.mk b/lib/odbc/subdirs.mk new file mode 100644 index 0000000000..1106ee34b6 --- /dev/null +++ b/lib/odbc/subdirs.mk @@ -0,0 +1,3 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +SUB_DIRECTORIES = src c_src doc/src diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk new file mode 100644 index 0000000000..e5daf58815 --- /dev/null +++ b/lib/odbc/vsn.mk @@ -0,0 +1,22 @@ +ODBC_VSN = 2.10.6 + +TICKETS = \ + OTP-8250 \ + OTP-8291 + + +TICKETS_2.10.5 = \ + OTP-7978 + +TICKETS_2.10.4 = \ + OTP-7720 \ + OTP-7721 + +TICKETS_2.10.3 = \ + OTP-7418 +TICKETS_2.10.2 = \ + OTP-7297 +TICKETS_2.10.1 = \ + OTP-7019 \ + OTP-7294 \ + OTP-7307 |