/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2005, 2007, 2010 Free Software Foundation, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <mailutils/mailutils.h> #include <mailutils/sql.h> #include <sql.h> #include <sqlext.h> #include <sqltypes.h> struct mu_odbc_data { SQLHENV env; /* Environment */ SQLHDBC dbc; /* DBC */ /* Result data: */ SQLHSTMT stmt; /* Statement being executed */ mu_list_t result; /* List of returned field values */ char **fnames; /* A list of field names */ size_t fcount; /* Error reporting: */ struct odbc_err_buffer { SQLSMALLINT handle_type; /* Type of the handle */ SQLHANDLE handle; /* Handle that caused the error */ char *what; /* Name of the function that failed */ SQLCHAR *msg; /* Error message buffer */ char *text; /* Error text buffer */ } err; }; static void mu_odbc_diag(struct mu_odbc_data *dp, SQLSMALLINT handle_type, SQLHANDLE handle, char *what) { dp->err.what = what; dp->err.handle_type = handle_type; dp->err.handle = handle; } /* ************************************************************************* */ /* Interface routines */ static int odbc_init (mu_sql_connection_t conn) { struct mu_odbc_data *dp = calloc (1, sizeof (*dp)); if (!dp) return ENOMEM; conn->data = dp; return 0; } static int odbc_destroy (mu_sql_connection_t conn) { struct mu_odbc_data *dp = conn->data; free (dp->err.msg); free (dp->err.text); if (dp->stmt) SQLFreeHandle (SQL_HANDLE_STMT, dp->stmt); free (dp); conn->data = NULL; return 0; } static int odbc_connect (mu_sql_connection_t conn) { struct mu_odbc_data *dp = conn->data; long rc; rc = SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE, &dp->env); if (rc != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_ENV, dp->env, "SQLAllocHandle"); return MU_ERR_SQL; } rc = SQLSetEnvAttr (dp->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); if (rc != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_ENV, dp->dbc, "SQLSetEnvAttr"); return MU_ERR_SQL; } rc = SQLAllocHandle (SQL_HANDLE_DBC, dp->env, &dp->dbc); if (rc != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_DBC, dp->dbc, "SQLAllocHandle"); return MU_ERR_SQL; } rc = SQLConnect(dp->dbc, (SQLCHAR*)conn->dbname, SQL_NTS, (SQLCHAR*)conn->login, SQL_NTS, (SQLCHAR*)conn->password, SQL_NTS); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { mu_odbc_diag (dp, SQL_HANDLE_DBC, dp->dbc, "SQLConnect"); return MU_ERR_SQL; } return 0; } static int odbc_disconnect (mu_sql_connection_t conn) { struct mu_odbc_data *dp = conn->data; SQLDisconnect (dp->dbc); SQLFreeHandle (SQL_HANDLE_ENV, dp->env); return 0; } static int odbc_query (mu_sql_connection_t conn, char *query) { struct mu_odbc_data *dp = conn->data; long rc; if (dp->stmt) SQLFreeHandle (SQL_HANDLE_STMT, dp->stmt); rc = SQLAllocHandle (SQL_HANDLE_STMT, dp->dbc, &dp->stmt); if (rc != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_DBC, dp->dbc, "SQLAllocHandle"); return MU_ERR_SQL; } /* FIXME: In some implementations only default (forward only) cursors may be available. Do we need a sequential access method after all? FIXME2: On SQL_SUCCESS_WITH_INFO no info is output */ rc = SQLSetStmtAttr (dp->stmt, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) SQL_CURSOR_DYNAMIC, 0); if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLSetStmtAttr"); return MU_ERR_SQL; } rc = SQLExecDirect (dp->stmt, (SQLCHAR*) query, SQL_NTS); if (rc != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLExecDirect"); return MU_ERR_SQL; } return 0; } static int odbc_store_result (mu_sql_connection_t conn) { struct mu_odbc_data *dp = conn->data; mu_list_create (&dp->result); return 0; } static int odbc_free_char_data (void *item, void *data MU_ARG_UNUSED) { free (item); return 0; } static int odbc_release_result (mu_sql_connection_t conn) { struct mu_odbc_data *dp = conn->data; mu_list_do (dp->result, odbc_free_char_data, NULL); mu_list_destroy (&dp->result); mu_argcv_free (dp->fcount, dp->fnames); dp->fcount = 0; dp->fnames = NULL; return 0; } static int odbc_num_columns (mu_sql_connection_t conn, size_t *np) { struct mu_odbc_data *dp = conn->data; SQLSMALLINT count; if (dp->fcount == 0) { if (SQLNumResultCols (dp->stmt, &count) != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLNumResultCount"); return MU_ERR_SQL; } } dp->fcount = count; *np = count; return 0; } static int odbc_num_tuples (mu_sql_connection_t conn, size_t *np) { struct mu_odbc_data *dp = conn->data; SQLINTEGER count; if (SQLRowCount (dp->stmt, &count) != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLRowCount"); return MU_ERR_SQL; } *np = count; return 0; } static int odbc_get_column (mu_sql_connection_t conn, size_t nrow, size_t ncol, char **pdata) { struct mu_odbc_data *dp = conn->data; char buffer[1024]; SQLINTEGER size; if (SQLFetchScroll (dp->stmt, SQL_FETCH_ABSOLUTE, nrow + 1) != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLFetchScroll"); return MU_ERR_SQL; } if (SQLGetData (dp->stmt, ncol + 1, SQL_C_CHAR, buffer, sizeof buffer, &size) != SQL_SUCCESS) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLGetData"); return MU_ERR_SQL; } *pdata = strdup (buffer); mu_list_append (dp->result, *pdata); return 0; } /* FIXME: untested */ static int odbc_get_field_number (mu_sql_connection_t conn, const char *fname, size_t *fno) { size_t count; struct mu_odbc_data *dp = conn->data; int i; if (!dp->fnames) { int rc; rc = odbc_num_columns (conn, &count); if (rc) return rc; dp->fnames = calloc(count + 1, sizeof dp->fnames[0]); if (!dp->fnames) return ENOMEM; for (i = 0; i < count; i++) { char *name; SQLRETURN ret; SQLSMALLINT namelen; ret = SQLDescribeCol (dp->stmt, i + 1, NULL, 0, &namelen, NULL, NULL, NULL, NULL); if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLDescribeColl"); return MU_ERR_SQL; } name = malloc (namelen + 1); if (!name) return ENOMEM; dp->fnames[i] = name; ret = SQLDescribeCol (dp->stmt, i + 1, (SQLCHAR*) name, namelen + 1, &namelen, NULL, NULL, NULL, NULL); if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) { mu_odbc_diag (dp, SQL_HANDLE_STMT, dp->stmt, "SQLDescribeColl"); return MU_ERR_SQL; } } dp->fnames[i] = NULL; } else count = dp->fcount; for (i = 0; i < count; i++) { if (strcmp (fname, dp->fnames[i]) == 0) { *fno = i; return 0; } } return MU_ERR_NOENT; } #define DEFAULT_ERROR_BUFFER_SIZE 1024 static const char * odbc_errstr (mu_sql_connection_t conn) { struct mu_odbc_data *dp = conn->data; SQLCHAR state[16]; char nbuf[64]; SQLINTEGER nerror; SQLSMALLINT msglen; size_t length; if (!dp->err.what) return mu_strerror (0); if (!dp->err.msg) { dp->err.msg = malloc (DEFAULT_ERROR_BUFFER_SIZE); if (!dp->err.msg) return mu_strerror (ENOMEM); } SQLGetDiagRec (dp->err.handle_type, dp->err.handle, 1, state, &nerror, dp->err.msg, DEFAULT_ERROR_BUFFER_SIZE, &msglen); snprintf (nbuf, sizeof nbuf, "%d", (int) nerror); length = strlen (dp->err.what) + 1 + strlen ((char*) state) + 1 + strlen (nbuf) + 1 + strlen ((char*) dp->err.msg) + 1; if (dp->err.text) free (dp->err.text); dp->err.text = malloc (length); if (!dp->err.text) return (char*) dp->err.msg; snprintf (dp->err.text, length, "%s %s %s %s", dp->err.what, state, nbuf, dp->err.msg); return dp->err.text; } MU_DECL_SQL_DISPATCH_T(odbc) = { "odbc", 0, odbc_init, odbc_destroy, odbc_connect, odbc_disconnect, odbc_query, odbc_store_result, odbc_release_result, odbc_num_tuples, odbc_num_columns, odbc_get_column, odbc_get_field_number, odbc_errstr, };