Saturday, January 9, 2010

Getting Erlang ODBC to work on Mac Snow Leopard

I started using Erlang a month back and had a difficult time getting it (the ODBC part really) to work on Mac. You can read about Erlang ODBC implementation architecture here. I'm still a novice in the Erlang world, but I'll try and put forth what I have done.

The Erlang release I will be referring to in this post will be R13B02. I can probably summarize the problems I faced with Erlang ODBC to the following few:
  • Mac ODBC implementation with iodbc seems to be having some linking problems with 32 bit compilation and dynamic linkage.
  • Erlang by default compiles in 32 bit mode on Mac.
  • Erlang odbcserver by default compiles with dynamic linkage to odbc library.
  • Erlang odbcserver implementation seems to be having some issues in handling cases where no records are found by a query.
And below are the summarized steps that solve the above problems and get you a working build:
  1. Download otp_src_R13B02-1.tar.gz from erlang.org.
  2. Set LDFLAGS to include Mac CoreFoundation. I'm not sure if this is absolutely essential, but no harm including it.
    export LDFLAGS="-framework CoreFoundation".
  3. Configure the build for 64 bit. This will enable iodbc but seems to disable wxWidgets. For now, iodbc is more important.
    configure --enable-darwin-64bit
  4. Configure will create the build files, but don't start building just yet.
  5. Edit the make file of odbcserver (lib/odbc/c_src/i386-apple-darwin10.2.0/Makefile) manually to get it statically linked to iodbc libraries. The default dynamic linkage does not work in "Snow Leopard".
    • Use iodbc-config to get the flags to add
    • iodbc-config --static-libs
    • Replace LDFLAGS in lib/odbc/c_src/i386-apple-darwin10.2.0/Makefile with what you got from above.
  6. There is probably a bug in odbcserver.c that throws error when a SQL results in no records found. For example, an update that modified 0 records, or a select that fetched 0 records. Download the attached odbcserver.patch (below) and apply it to odbcserver.c.
  7. Run make, make install. By default installs into /usr/local.
  8. Download otp_doc_man_R13B02-1.tar.gz from erlang.org. Go to /usr/local/lib/erlang. Extract the tar file here.
  9. Run erl -man erl to test man pages
  10. Run erl to get the console. Test odbc by doing at least odbc:start() and odbc:connect() and executing a select.


odbcserver.patch

--- odbcserver.c 2009-11-20 19:06:30.000000000 +0530
+++ odbcserver.patched.c 2010-01-07 16:20:38.000000000 +0530
@@ -151,7 +151,7 @@
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(db_state *state, SQLRETURN sql_result);
static db_result_msg encode_result_set(SQLSMALLINT num_of_columns,
db_state *state);
static db_result_msg encode_out_params(db_state *state,
@@ -585,12 +585,12 @@

/* OTP-5759, fails when 0 rows deleted */
if (result == SQL_NO_DATA_FOUND) {
- msg = encode_result(state);
+ msg = encode_result(state, result);
} else {
/* Handle multiple result sets */
do {
ei_x_encode_list_header(&dynamic_buffer(state), 1);
- msg = encode_result(state);
+ msg = encode_result(state, result);
/* We don't want to continue if an error occured */
if (msg.length != 0) {
break;
@@ -749,11 +749,12 @@
byte *sql;
db_result_msg msg;
int i, num_param_values, ver = 0,
- erl_type = 0, index = 0, size = 0, cols = 0;
+ erl_type = 0, index = 0, size = 0, cols = 0;
long long_num_param_values;
param_status param_status;
diagnos diagnos;
- param_array *params;
+ param_array *params;
+ SQLRETURN result;

if (associated_result_set(state)) {
clean_state(state);
@@ -784,10 +785,16 @@
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);
+
+ result = SQLExecDirect(statement_handle(state), sql, SQL_NTS);
+ if (!sql_success(result) || result == SQL_NO_DATA) {
+ diagnos = get_diagnos(SQL_HANDLE_STMT, statement_handle(state));
+ }
+ /* SQL_NO_DATA and SQLSTATE 00000 indicate success for
+ updates/deletes that affect no rows */
+ if(!sql_success(result) &&
+ !(result == SQL_NO_DATA && !strcmp((char *)diagnos.sqlState, INFO))) {
+ msg = encode_error_message(diagnos.error_msg);
} else {
for (i = 0; i < msg =" encode_out_params(state," msg =" encode_result(state);" msg =" encode_result(state," length ="=" buffer =" dynamic_buffer(state).buff;" num_of_columns =" 0;" rowcountptr =" 0;" sql_no_data_found ="=" num_of_columns =" 0;" num_of_columns ="=" sql_no_data_found ="=" rowcountptr =" 0;" record_nr =" 1;" result =" SQLGetDiagRec(handleType,">

No comments: