--- /dev/null
+#ident "$Header$"
+
+#include <sys/types.h>
+#ifdef LBS_DB_PROFILE
+#include <sys/time.h>
+#endif
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <dlfcn.h>
+#include <pthread.h>
+#include <syslog.h>
+
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <mysql_version.h>
+#include <errmsg.h>
+
+#include "glite/lbu/trio.h"
+#include "db.h"
+
+
+#define GLITE_LBU_MYSQL_INDEX_VERSION 40001
+#define GLITE_LBU_MYSQL_PREPARED_VERSION 40102
+#define BUF_INSERT_ROW_ALLOC_BLOCK 1000
+#ifndef GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH
+#define GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH 256
+#endif
+
+#define CLR_ERR(CTX) lbu_clrerr((CTX))
+#define ERR(CTX, CODE, ARGS...) lbu_err((CTX), (CODE), __FUNCTION__, __LINE__, ##ARGS)
+#define STATUS(CTX) ((CTX)->err.code)
+#define MY_ERR(CTX) myerr((CTX), __FUNCTION__, __LINE__)
+#define MY_ERRSTMT(STMT) myerrstmt((STMT), __FUNCTION__, __LINE__)
+#define MY_ISOKSTMT(STMT, RETRY) myisokstmt((STMT), __FUNCTION__, __LINE__, (RETRY))
+
+#define USE_TRANS(CTX) ((CTX->caps & GLITE_LBU_DB_CAP_TRANSACTIONS) != 0)
+#define LOAD(SYM, SYM2) if ((*(void **)(&db_handle.SYM) = dlsym(db_handle.lib, SYM2)) == NULL) { \
+ err = ERR(*ctx, ENOENT, "can't load symbol %s from mysql library (%s)", SYM2, dlerror()); \
+ break; \
+}
+
+#define dprintf(CTX, FMT...) if (CTX->caps & GLITE_LBU_DB_CAP_ERRORS) fprintf(stderr, ##FMT)
+
+
+struct glite_lbu_DBContext_s {
+ MYSQL *mysql;
+ const char *cs;
+ int caps;
+ struct {
+ int code;
+ char *desc;
+ } err;
+ int in_transaction; /* this flag is set whenever we are in DB transaction */
+};
+
+
+struct glite_lbu_Statement_s {
+ glite_lbu_DBContext ctx;
+
+ /* for simple commands */
+ MYSQL_RES *result;
+
+ /* for prepared commands */
+ MYSQL_STMT *stmt;
+ unsigned long nrfields;
+ char *sql;
+};
+
+
+struct glite_lbu_bufInsert_s {
+ glite_lbu_DBContext ctx;
+ char *table_name;
+ char *columns; /* names of columns to be inserted into
+ * (values separated with commas) */
+ char **rows; /* each row hold string of one row to be inserted
+ * (values separated with commas) */
+ long rec_num, /* actual number of rows in structure */
+ rec_size; /* approx. size of a real insert string */
+ long size_limit, /* size and # of records limit which trigger */
+ record_limit; /* real insert; zero means unlimitted */
+};
+
+
+/*
+ * mapping glite DB types to mysql types
+ */
+int glite_type_to_mysql[] = {
+ MYSQL_TYPE_NULL,
+ MYSQL_TYPE_TINY,
+ MYSQL_TYPE_LONG,
+ MYSQL_TYPE_TINY_BLOB,
+ MYSQL_TYPE_TINY_BLOB,
+ MYSQL_TYPE_BLOB,
+ MYSQL_TYPE_BLOB,
+ MYSQL_TYPE_MEDIUM_BLOB,
+ MYSQL_TYPE_MEDIUM_BLOB,
+ MYSQL_TYPE_LONG_BLOB,
+ MYSQL_TYPE_LONG_BLOB,
+ MYSQL_TYPE_VAR_STRING,
+ MYSQL_TYPE_STRING,
+ MYSQL_TYPE_DATE,
+ MYSQL_TYPE_TIME,
+ MYSQL_TYPE_DATETIME,
+ MYSQL_TYPE_TIMESTAMP,
+};
+
+
+typedef struct {
+ void *lib;
+ pthread_mutex_t lock;
+
+ void *(*mysql_init)(void *);
+ unsigned long (*mysql_get_client_version)(void);
+ int (*mysql_options)(MYSQL *mysql, enum mysql_option option, const char *arg);
+ unsigned int (*mysql_errno)(MYSQL *mysql);
+ const char *(*mysql_error)(MYSQL *mysql);
+ unsigned int (*mysql_stmt_errno)(MYSQL_STMT *stmt);
+ const char *(*mysql_stmt_error)(MYSQL_STMT *stmt);
+ MYSQL *(*mysql_real_connect)(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag);
+ void (*mysql_close)(MYSQL *mysql);
+ int (*mysql_query)(MYSQL *mysql, const char *stmt_str);
+ MYSQL_RES *(*mysql_store_result)(MYSQL *mysql);
+ void (*mysql_free_result)(MYSQL_RES *result);
+ my_ulonglong (*mysql_affected_rows)(MYSQL *mysql);
+ my_bool (*mysql_stmt_close)(MYSQL_STMT *);
+ unsigned int (*mysql_num_fields)(MYSQL_RES *result);
+ unsigned long *(*mysql_fetch_lengths)(MYSQL_RES *result);
+ my_bool (*mysql_stmt_bind_result)(MYSQL_STMT *stmt, MYSQL_BIND *bind);
+ int (*mysql_stmt_prepare)(MYSQL_STMT *stmt, const char *stmt_str, unsigned long length);
+ int (*mysql_stmt_store_result)(MYSQL_STMT *stmt);
+ MYSQL_ROW (*mysql_fetch_row)(MYSQL_RES *result);
+ MYSQL_FIELD *(*mysql_fetch_field)(MYSQL_RES *result);
+ const char *(*mysql_get_server_info)(MYSQL *mysql);
+ MYSQL_STMT *(*mysql_stmt_init)(MYSQL *mysql);
+ my_bool (*mysql_stmt_bind_param)(MYSQL_STMT *stmt, MYSQL_BIND *bind);
+ int (*mysql_stmt_execute)(MYSQL_STMT *stmt);
+ int (*mysql_stmt_fetch)(MYSQL_STMT *stmt);
+ my_ulonglong (*mysql_stmt_insert_id)(MYSQL_STMT *stmt);
+ my_ulonglong (*mysql_stmt_affected_rows)(MYSQL_STMT *stmt);
+ MYSQL_RES *(*mysql_stmt_result_metadata)(MYSQL_STMT *stmt);
+ int (*mysql_stmt_fetch_column)(MYSQL_STMT *stmt, MYSQL_BIND *bind, unsigned int column, unsigned long offset);
+} mysql_lib_handle_t;
+
+
+static mysql_lib_handle_t db_handle = {
+ lib: NULL,
+ lock: PTHREAD_MUTEX_INITIALIZER
+};
+
+
+static int lbu_clrerr(glite_lbu_DBContext ctx);
+static int lbu_err(glite_lbu_DBContext ctx, int code, const char *func, int line, const char *desc, ...);
+static int myerr(glite_lbu_DBContext ctx, const char *source, int line);
+static int myerrstmt(glite_lbu_Statement stmt, const char *source, int line);
+static int myisokstmt(glite_lbu_Statement stmt, const char *source, int line, int *retry);
+static int db_connect(glite_lbu_DBContext ctx, const char *cs, MYSQL **mysql);
+static void db_close(MYSQL *mysql);
+static int transaction_test(glite_lbu_DBContext ctx);
+static int FetchRowSimple(glite_lbu_DBContext ctx, MYSQL_RES *result, unsigned long *lengths, char **results);
+static int FetchRowPrepared(glite_lbu_DBContext ctx, glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results);
+static void set_time(MYSQL_TIME *mtime, const double time);
+static void glite_lbu_DBCleanup(void);
+static void glite_lbu_FreeStmt_int(glite_lbu_Statement stmt);
+
+
+/* ---- common ---- */
+
+
+int glite_lbu_DBError(glite_lbu_DBContext ctx, char **text, char **desc) {
+ if (text) *text = strdup(strerror(ctx->err.code));
+ if (desc) {
+ if (ctx->err.desc) *desc = strdup(ctx->err.desc);
+ else *desc = NULL;
+ }
+
+ return ctx->err.code;
+}
+
+
+int glite_lbu_InitDBContext(glite_lbu_DBContext *ctx) {
+ int err = 0;
+ unsigned int ver_u;
+
+ *ctx = calloc(1, sizeof **ctx);
+ if (!*ctx) return ENOMEM;
+
+ /* dynamic load the mysql library */
+ pthread_mutex_lock(&db_handle.lock);
+ if (!db_handle.lib) {
+ db_handle.lib = dlopen(MYSQL_SONAME, RTLD_LAZY | RTLD_LOCAL);
+ if (!db_handle.lib) return ERR(*ctx, ENOENT, "dlopen(): " MYSQL_SONAME ": %s", dlerror());
+ do {
+ LOAD(mysql_init, "mysql_init");
+ LOAD(mysql_get_client_version, "mysql_get_client_version");
+ LOAD(mysql_options, "mysql_options");
+ LOAD(mysql_errno, "mysql_errno");
+ LOAD(mysql_error, "mysql_error");
+ LOAD(mysql_stmt_errno, "mysql_stmt_errno");
+ LOAD(mysql_stmt_error, "mysql_stmt_error");
+ LOAD(mysql_real_connect, "mysql_real_connect");
+ LOAD(mysql_close, "mysql_close");
+ LOAD(mysql_query, "mysql_query");
+ LOAD(mysql_store_result, "mysql_store_result");
+ LOAD(mysql_free_result, "mysql_free_result");
+ LOAD(mysql_affected_rows, "mysql_affected_rows");
+ LOAD(mysql_stmt_close, "mysql_stmt_close");
+ LOAD(mysql_num_fields, "mysql_num_fields");
+ LOAD(mysql_fetch_lengths, "mysql_fetch_lengths");
+ LOAD(mysql_stmt_bind_result, "mysql_stmt_bind_result");
+ LOAD(mysql_stmt_prepare, "mysql_stmt_prepare");
+ LOAD(mysql_stmt_store_result, "mysql_stmt_store_result");
+ LOAD(mysql_fetch_row, "mysql_fetch_row");
+ LOAD(mysql_fetch_field, "mysql_fetch_field");
+ LOAD(mysql_get_server_info, "mysql_get_server_info");
+ LOAD(mysql_stmt_init, "mysql_stmt_init");
+ LOAD(mysql_stmt_bind_param, "mysql_stmt_bind_param");
+ LOAD(mysql_stmt_execute, "mysql_stmt_execute");
+ LOAD(mysql_stmt_fetch, "mysql_stmt_fetch");
+ LOAD(mysql_stmt_insert_id, "mysql_stmt_insert_id");
+ LOAD(mysql_stmt_affected_rows, "mysql_stmt_affected_rows");
+ LOAD(mysql_stmt_result_metadata, "mysql_stmt_result_metadata");
+ LOAD(mysql_stmt_fetch_column, "mysql_stmt_fetch_column");
+
+ // check the runtime version
+ ver_u = db_handle.mysql_get_client_version();
+ if (ver_u != MYSQL_VERSION_ID) {
+ fprintf(stderr,"Warning: MySQL library version mismatch (compiled '%lu', runtime '%lu')", MYSQL_VERSION_ID, ver_u);
+ syslog(LOG_WARNING,"MySQL library version mismatch (compiled '%lu', runtime '%lu')", MYSQL_VERSION_ID, ver_u);
+ }
+
+ pthread_mutex_unlock(&db_handle.lock);
+ atexit(glite_lbu_DBCleanup);
+ } while(0);
+
+ if (err) {
+ dlclose(db_handle.lib);
+ db_handle.lib = NULL;
+ pthread_mutex_unlock(&db_handle.lock);
+ return err;
+ }
+ } else pthread_mutex_unlock(&db_handle.lock);
+
+ return 0;
+}
+
+
+void glite_lbu_FreeDBContext(glite_lbu_DBContext ctx) {
+ if (ctx) {
+ assert(ctx->mysql == NULL);
+ free(ctx->err.desc);
+ free(ctx);
+ }
+}
+
+
+int glite_lbu_DBConnect(glite_lbu_DBContext ctx, const char *cs) {
+ if (db_connect(ctx, cs, &ctx->mysql) != 0 ||
+ glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0 ||
+ glite_lbu_ExecSQL(ctx, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ", NULL) < 0)
+ return STATUS(ctx);
+ else
+ return 0;
+}
+
+
+void glite_lbu_DBClose(glite_lbu_DBContext ctx) {
+ db_close(ctx->mysql);
+ ctx->mysql = NULL;
+ CLR_ERR(ctx);
+}
+
+
+int glite_lbu_DBQueryCaps(glite_lbu_DBContext ctx) {
+ MYSQL *m = ctx->mysql;
+ int major,minor,sub,version,caps,origcaps;
+ const char *ver_s;
+
+ origcaps = ctx->caps;
+ caps = 0;
+
+ ver_s = db_handle.mysql_get_server_info(m);
+ if (!ver_s || 3 != sscanf(ver_s,"%d.%d.%d",&major,&minor,&sub))
+ return ERR(ctx, EINVAL, "problem retreiving MySQL version");
+ version = 10000*major + 100*minor + sub;
+
+ if (version >= GLITE_LBU_MYSQL_INDEX_VERSION) caps |= GLITE_LBU_DB_CAP_INDEX;
+ if (version >= GLITE_LBU_MYSQL_PREPARED_VERSION) caps |= GLITE_LBU_DB_CAP_PREPARED;
+
+ CLR_ERR(ctx);
+ transaction_test(ctx);
+ if ((ctx->caps & GLITE_LBU_DB_CAP_TRANSACTIONS)) caps |= GLITE_LBU_DB_CAP_TRANSACTIONS;
+
+ ctx->caps = origcaps;
+ if (STATUS(ctx) == 0) return caps;
+ else return -1;
+}
+
+
+void glite_lbu_DBSetCaps(glite_lbu_DBContext ctx, int caps) {
+ ctx->caps = caps;
+}
+
+
+int glite_lbu_Transaction(glite_lbu_DBContext ctx) {
+ CLR_ERR(ctx);
+ if (USE_TRANS(ctx)) {
+ if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=0", NULL) < 0) goto err;
+ if (glite_lbu_ExecSQL(ctx, "BEGIN", NULL) < 0) goto err;
+ ctx->in_transaction = 1;
+ }
+err:
+ return STATUS(ctx);
+}
+
+
+int glite_lbu_Commit(glite_lbu_DBContext ctx) {
+ CLR_ERR(ctx);
+ if (USE_TRANS(ctx)) {
+ if (glite_lbu_ExecSQL(ctx, "COMMIT", NULL) < 0) goto err;
+ if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0) goto err;
+ ctx->in_transaction = 0;
+ }
+err:
+ return STATUS(ctx);
+}
+
+
+int glite_lbu_Rollback(glite_lbu_DBContext ctx) {
+ CLR_ERR(ctx);
+ if (USE_TRANS(ctx)) {
+ if (glite_lbu_ExecSQL(ctx, "ROLLBACK", NULL) < 0) goto err;
+ if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0) goto err;
+ ctx->in_transaction = 0;
+ }
+err:
+ return STATUS(ctx);
+}
+
+
+int glite_lbu_FetchRow(glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results) {
+ memset(results, 0, n * sizeof(*results));
+ if (stmt->result) return FetchRowSimple(stmt->ctx, stmt->result, lengths, results);
+ else return FetchRowPrepared(stmt->ctx, stmt, n, lengths, results);
+}
+
+
+static void glite_lbu_FreeStmt_int(glite_lbu_Statement stmt) {
+ if (stmt) {
+ if (stmt->result) db_handle.mysql_free_result(stmt->result);
+ if (stmt->stmt) db_handle.mysql_stmt_close(stmt->stmt);
+ free(stmt->sql);
+ }
+}
+
+
+void glite_lbu_FreeStmt(glite_lbu_Statement *stmt) {
+ glite_lbu_FreeStmt_int(*stmt);
+ free(*stmt);
+ *stmt = NULL;
+}
+
+
+int glite_lbu_QueryIndices(glite_lbu_DBContext ctx, const char *table, char ***key_names, char ****column_names) {
+ glite_lbu_Statement stmt = NULL;
+
+ size_t i,j,ret;
+
+/* XXX: "show index from" columns. Matches at least MySQL 4.0.11 */
+ char *sql, *showcol[12];
+ int Key_name,Seq_in_index,Column_name,Sub_part;
+
+ char **keys = NULL;
+ size_t *cols = NULL;
+ char **col_names = NULL;
+
+ size_t nkeys = 0;
+
+ char ***idx = NULL;
+
+ Key_name = Seq_in_index = Column_name = Sub_part = -1;
+
+ asprintf(&sql, "show index from %s", table);
+ if (glite_lbu_ExecSQL(ctx,sql,&stmt)<0) {
+ free(sql);
+ return STATUS(ctx);
+ }
+ free(sql);
+
+ while ((ret = glite_lbu_FetchRow(stmt,sizeof(showcol)/sizeof(showcol[0]),NULL,showcol)) > 0) {
+ assert(ret <= (int)(sizeof showcol/sizeof showcol[0]));
+
+ if (!col_names) {
+ col_names = malloc(ret * sizeof col_names[0]);
+ glite_lbu_QueryColumns(stmt,col_names);
+ for (i=0; i<ret; i++)
+ if (!strcasecmp(col_names[i],"Key_name")) Key_name = i;
+ else if (!strcasecmp(col_names[i],"Seq_in_index")) Seq_in_index = i;
+ else if (!strcasecmp(col_names[i],"Column_name")) Column_name = i;
+ else if (!strcasecmp(col_names[i],"Sub_part")) Sub_part = i;
+
+ assert(Key_name >= 0 && Seq_in_index >= 0 &&
+ Column_name >= 0 && Sub_part >= 0);
+
+ }
+
+ for (i=0; i<nkeys && strcasecmp(showcol[Key_name],keys[i]); i++);
+
+ if (i == nkeys) {
+ keys = realloc(keys,(i+2) * sizeof keys[0]);
+ keys[i] = strdup(showcol[Key_name]);
+//printf("** KEY [%d] %s\n", i, keys[i]);
+ keys[i+1] = NULL;
+ cols = realloc(cols,(i+1) * sizeof cols[0]);
+ cols[i] = 0;
+ idx = realloc(idx,(i+2) * sizeof idx[0]);
+ idx[i] = idx[i+1] = NULL;
+ nkeys++;
+ }
+
+ j = atoi(showcol[Seq_in_index])-1;
+ if (cols[i] <= j) {
+ cols[i] = j+1;
+ idx[i] = realloc(idx[i],(j+2)*sizeof idx[i][0]);
+ memset(&idx[i][j+1],0,sizeof idx[i][0]);
+ }
+ idx[i][j] = strdup(showcol[Column_name]);
+//printf("****** [%d, %d] %s\n", i, j, idx[i][j]);
+//FIXME: needed?idx[i][j].value.i = atoi(showcol[Sub_part]);
+ for (i = 0; i<ret; i++) free(showcol[i]);
+ }
+
+ glite_lbu_FreeStmt(&stmt);
+ free(cols);
+ free(col_names);
+
+ if (ret == 0) CLR_ERR(ctx);
+ else {
+ free(keys);
+ keys = NULL;
+ for (i = 0; idx[i]; i++) {
+ for (j = 0; idx[i][j]; j++) free(idx[i][j]);
+ free(idx[i]);
+ }
+ free(idx);
+ idx = NULL;
+ }
+
+ if (key_names) *key_names = keys;
+ else {
+ for (i = 0; keys[i]; i++) free(keys[i]);
+ free(keys);
+ }
+ *column_names = idx;
+
+ return STATUS(ctx);
+}
+
+
+/* ---- simple ---- */
+
+int glite_lbu_ExecSQL(glite_lbu_DBContext ctx, const char *cmd, glite_lbu_Statement *stmt) {
+ int merr;
+ int retry_nr = 0;
+ int do_reconnect = 0;
+#ifdef LBS_DB_PROFILE
+ struct timeval start,end;
+ int pid;
+
+ static struct timeval sum = {
+ tv_sec: 0,
+ tv_usec: 0
+ };
+#endif
+
+ CLR_ERR(ctx);
+
+ if (stmt) *stmt = NULL;
+
+#ifdef LBS_DB_PROFILE
+ gettimeofday(&start,NULL);
+#endif
+
+ while (retry_nr == 0 || do_reconnect) {
+ do_reconnect = 0;
+ if (db_handle.mysql_query(ctx->mysql, cmd)) {
+ /* error occured */
+ switch (merr = db_handle.mysql_errno(ctx->mysql)) {
+ case 0:
+ break;
+ case ER_DUP_ENTRY:
+ ERR(ctx, EEXIST, db_handle.mysql_error(ctx->mysql));
+ return -1;
+ break;
+ case CR_SERVER_LOST:
+ case CR_SERVER_GONE_ERROR:
+ if (ctx->in_transaction) {
+ ERR(ctx, ERESTART, db_handle.mysql_error(ctx->mysql));
+ return -1;
+ }
+ else if (retry_nr <= 0)
+ do_reconnect = 1;
+ break;
+ case ER_LOCK_DEADLOCK:
+ ERR(ctx, EDEADLOCK, db_handle.mysql_error(ctx->mysql));
+ return -1;
+ break;
+ default:
+ MY_ERR(ctx);
+ return -1;
+ break;
+ }
+ }
+ retry_nr++;
+ }
+
+ if (stmt) {
+ *stmt = calloc(1, sizeof(**stmt));
+ if (!*stmt) {
+ ERR(ctx, ENOMEM, NULL);
+ return -1;
+ }
+ (**stmt).ctx = ctx;
+ (**stmt).result = db_handle.mysql_store_result(ctx->mysql);
+ if (!(**stmt).result) {
+ if (db_handle.mysql_errno(ctx->mysql)) {
+ MY_ERR(ctx);
+ *stmt = NULL;
+ return -1;
+ }
+ }
+ } else {
+ MYSQL_RES *r = db_handle.mysql_store_result(ctx->mysql);
+ db_handle.mysql_free_result(r);
+ }
+#ifdef LBS_DB_PROFILE
+ pid = getpid();
+ gettimeofday(&end,NULL);
+ end.tv_usec -= start.tv_usec;
+ end.tv_sec -= start.tv_sec;
+ if (end.tv_usec < 0) { end.tv_sec--; end.tv_usec += 1000000; }
+
+ sum.tv_usec += end.tv_usec;
+ sum.tv_sec += end.tv_sec + sum.tv_usec / 1000000;
+ sum.tv_usec -= 1000000 * (sum.tv_usec / 1000000);
+ fprintf(stderr,"[%d] %s\n[%d] %3ld.%06ld (sum: %3ld.%06ld)\n",pid,cmd,pid,end.tv_sec,end.tv_usec,sum.tv_sec,sum.tv_usec);
+#endif
+
+ return db_handle.mysql_affected_rows(ctx->mysql);
+}
+
+
+int glite_lbu_QueryColumns(glite_lbu_Statement stmt, char **cols)
+{
+ int i = 0;
+ MYSQL_FIELD *f;
+
+ CLR_ERR(stmt->ctx);
+ if (!stmt->result) return ERR(stmt->ctx, EINVAL, "QueryColumns implemented only for simple API");
+ while ((f = db_handle.mysql_fetch_field(stmt->result))) cols[i++] = f->name;
+ return i == 0;
+}
+
+
+void glite_lbu_TimeToDB(time_t t, char **str) {
+ struct tm *tm = gmtime(&t);
+
+ asprintf(str,"'%4d-%02d-%02d %02d:%02d:%02d'",tm->tm_year+1900,tm->tm_mon+1,
+ tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
+}
+
+
+void glite_lbu_TimestampToDB(double t, char **str) {
+ time_t tsec = t;
+ struct tm *tm = gmtime(&tsec);
+
+ t = t - tsec + tm->tm_sec;
+ asprintf(str,"'%4d-%02d-%02d %02d:%02d:%02.09f'",tm->tm_year+1900,tm->tm_mon+1,
+ tm->tm_mday,tm->tm_hour,tm->tm_min,t);
+}
+
+
+time_t glite_lbu_DBToTime(const char *str) {
+ struct tm tm;
+
+ memset(&tm,0,sizeof(tm));
+ setenv("TZ","UTC",1); tzset();
+ sscanf(str,"%4d-%02d-%02d %02d:%02d:%02d",
+ &tm.tm_year,&tm.tm_mon,&tm.tm_mday,
+ &tm.tm_hour,&tm.tm_min,&tm.tm_sec);
+ tm.tm_year -= 1900;
+ tm.tm_mon--;
+
+ return mktime(&tm);
+}
+
+/* ---- prepared --- */
+
+int glite_lbu_PrepareStmt(glite_lbu_DBContext ctx, const char *sql, glite_lbu_Statement *stmt) {
+ int ret, retry;
+ MYSQL_RES *meta;
+
+ // init
+ *stmt = calloc(1, sizeof(**stmt));
+ (*stmt)->ctx = ctx;
+
+ // create the SQL command
+ if (((*stmt)->stmt = db_handle.mysql_stmt_init(ctx->mysql)) == NULL)
+ return MY_ERRSTMT(*stmt);
+
+ // prepare the SQL command
+ retry = 1;
+ do {
+ db_handle.mysql_stmt_prepare((*stmt)->stmt, sql, strlen(sql));
+ ret = MY_ISOKSTMT(*stmt, &retry);
+ } while (ret == 0);
+ if (ret == -1) goto failed;
+
+ // number of fields (0 for no results)
+ if ((meta = db_handle.mysql_stmt_result_metadata((*stmt)->stmt)) != NULL) {
+ (*stmt)->nrfields = db_handle.mysql_num_fields(meta);
+ db_handle.mysql_free_result(meta);
+ } else
+ (*stmt)->nrfields = 0;
+
+ // remember the command
+ (*stmt)->sql = strdup(sql);
+
+ return CLR_ERR(ctx);
+
+failed:
+ glite_lbu_FreeStmt(stmt);
+ return STATUS(ctx);
+}
+
+
+int glite_lbu_ExecPreparedStmt_v(glite_lbu_Statement stmt, int n, va_list ap) {
+ int i, prepare_retry;
+ glite_lbu_DBType type;
+ char *pchar;
+ int *pint;
+ long int *plint;
+ MYSQL_TIME *ptime;
+ glite_lbu_DBContext ctx;
+ int ret, retry;
+ MYSQL_BIND *binds = NULL;
+ void **data = NULL;
+ unsigned long *lens;
+ glite_lbu_Statement newstmt;
+
+ // gather parameters
+ if (n) {
+ binds = calloc(n, sizeof(MYSQL_BIND));
+ data = calloc(n, sizeof(void *));
+ lens = calloc(n, sizeof(unsigned long *));
+ }
+ for (i = 0; i < n; i++) {
+ type = va_arg(ap, glite_lbu_DBType);
+ switch (type) {
+ case GLITE_LBU_DB_TYPE_TINYINT:
+ pchar = binds[i].buffer = data[i] = malloc(sizeof(char));
+ *pchar = va_arg(ap, int);
+ break;
+
+ case GLITE_LBU_DB_TYPE_INT:
+ plint = binds[i].buffer = data[i] = malloc(sizeof(long int));
+ *plint = va_arg(ap, long int);
+ break;
+
+ case GLITE_LBU_DB_TYPE_BOOLEAN:
+ pint = binds[i].buffer = data[i] = malloc(sizeof(int));
+ *pint = va_arg(ap, int) ? 1 : 0;
+ break;
+
+ case GLITE_LBU_DB_TYPE_TINYBLOB:
+ case GLITE_LBU_DB_TYPE_TINYTEXT:
+ case GLITE_LBU_DB_TYPE_BLOB:
+ case GLITE_LBU_DB_TYPE_TEXT:
+ case GLITE_LBU_DB_TYPE_MEDIUMBLOB:
+ case GLITE_LBU_DB_TYPE_MEDIUMTEXT:
+ case GLITE_LBU_DB_TYPE_LONGBLOB:
+ case GLITE_LBU_DB_TYPE_LONGTEXT:
+ binds[i].buffer = va_arg(ap, void *);
+ binds[i].length = &lens[i];
+ lens[i] = va_arg(ap, unsigned long);
+ break;
+
+ case GLITE_LBU_DB_TYPE_VARCHAR:
+ case GLITE_LBU_DB_TYPE_CHAR:
+ binds[i].buffer = va_arg(ap, char *);
+ binds[i].length = &lens[i];
+ lens[i] = binds[i].buffer ? strlen((char *)binds[i].buffer) : 0;
+ break;
+
+ case GLITE_LBU_DB_TYPE_DATE:
+ case GLITE_LBU_DB_TYPE_TIME:
+ case GLITE_LBU_DB_TYPE_DATETIME:
+ ptime = binds[i].buffer = data[i] = malloc(sizeof(MYSQL_TIME));
+ set_time(ptime, va_arg(ap, time_t));
+ break;
+
+ case GLITE_LBU_DB_TYPE_TIMESTAMP:
+ ptime = binds[i].buffer = data[i] = malloc(sizeof(MYSQL_TIME));
+ set_time(ptime, va_arg(ap, double));
+ break;
+
+ case GLITE_LBU_DB_TYPE_NULL:
+ break;
+
+ default:
+ assert("unimplemented parameter assign" == NULL);
+ break;
+ }
+ binds[i].buffer_type = glite_type_to_mysql[type];
+ }
+
+ prepare_retry = 2;
+ do {
+ // bind parameters
+ if (n) {
+ if (db_handle.mysql_stmt_bind_param(stmt->stmt, binds) != 0) {
+ MY_ERRSTMT(stmt);
+ ret = -1;
+ goto statement_failed;
+ }
+ }
+
+ // run
+ ctx = stmt->ctx;
+ retry = 1;
+ do {
+ db_handle.mysql_stmt_execute(stmt->stmt);
+ ret = MY_ISOKSTMT(stmt, &retry);
+ } while (ret == 0);
+ statement_failed:
+ if (ret == -1) {
+ if (db_handle.mysql_stmt_errno(stmt->stmt) == ER_UNKNOWN_STMT_HANDLER) {
+ // expired the prepared command ==> restore it
+ if (glite_lbu_PrepareStmt(stmt->ctx, stmt->sql, &newstmt) == -1) goto failed;
+ glite_lbu_FreeStmt_int(stmt);
+ memcpy(stmt, newstmt, sizeof(struct glite_lbu_Statement_s));
+ prepare_retry--;
+ ret = 0;
+ } else goto failed;
+ }
+ } while (ret == 0 && prepare_retry > 0);
+
+ // result
+ retry = 1;
+ do {
+ db_handle.mysql_stmt_store_result(stmt->stmt);
+ ret = MY_ISOKSTMT(stmt, &retry);
+ } while (ret == 0);
+ if (ret == -1) goto failed;
+
+ // free params
+ if (n) {
+ for (i = 0; i < n; i++) free(data[i]);
+ free(data);
+ free(binds);
+ free(lens);
+ }
+ CLR_ERR(ctx);
+ return db_handle.mysql_stmt_affected_rows(stmt->stmt);
+
+failed:
+ for (i = 0; i < n; i++) free(data[i]);
+ free(data);
+ free(binds);
+ free(lens);
+ return -1;
+}
+
+
+int glite_lbu_ExecPreparedStmt(glite_lbu_Statement stmt, int n, ...) {
+ va_list ap;
+ int retval;
+
+ va_start(ap, n);
+ retval = glite_lbu_ExecPreparedStmt_v(stmt, n, ap);
+ va_end(ap);
+
+ return retval;
+}
+
+
+int glite_lbu_bufferedInsertInit(glite_lbu_DBContext ctx, glite_lbu_bufInsert *bi, const char *table_name, long size_limit, long record_limit, const char *columns)
+{
+ *bi = calloc(1, sizeof(*bi));
+ (*bi)->ctx = ctx;
+ (*bi)->table_name = strdup(table_name);
+ (*bi)->columns = strdup(columns);
+ (*bi)->rec_num = 0;
+ (*bi)->rec_size = 0;
+ (*bi)->rows = calloc(record_limit, sizeof(*((*bi)->rows)) );
+ (*bi)->size_limit = size_limit;
+ (*bi)->record_limit = record_limit;
+
+ return CLR_ERR(ctx);
+}
+
+
+static int flush_bufferd_insert(glite_lbu_bufInsert bi)
+{
+ char *stmt, *vals, *temp;
+ long i;
+
+
+ if (!bi->rec_num)
+ return STATUS(bi->ctx);
+
+ asprintf(&vals,"(%s)", bi->rows[0]);
+ for (i=1; i < bi->rec_num; i++) {
+ // XXX: use string add (preallocated memory)
+ asprintf(&temp,"%s,(%s)", vals, bi->rows[i]);
+ free(vals); vals = temp; temp = NULL;
+ free(bi->rows[i]);
+ bi->rows[i] = NULL;
+ }
+
+ trio_asprintf(&stmt, "insert into %|Ss(%|Ss) values %s;",
+ bi->table_name, bi->columns, vals);
+
+ if (glite_lbu_ExecSQL(bi->ctx,stmt,NULL) < 0) {
+ if (STATUS(bi->ctx) == EEXIST)
+ CLR_ERR(bi->ctx);
+ }
+
+ /* reset bi counters */
+ bi->rec_size = 0;
+ bi->rec_num = 0;
+
+ free(vals);
+ free(stmt);
+
+ return STATUS(bi->ctx);
+}
+
+
+int glite_lbu_bufferedInsert(glite_lbu_bufInsert bi, const char *row)
+{
+ bi->rows[bi->rec_num++] = strdup(row);
+ bi->rec_size += strlen(row);
+
+ if ((bi->size_limit && bi->rec_size >= bi->size_limit) ||
+ (bi->record_limit && bi->rec_num >= bi->record_limit))
+ {
+ if (flush_bufferd_insert(bi))
+ return STATUS(bi->ctx);
+ }
+
+ return CLR_ERR(bi->ctx);
+}
+
+
+static void free_buffered_insert(glite_lbu_bufInsert bi) {
+ long i;
+
+ free(bi->table_name);
+ free(bi->columns);
+ for (i=0; i < bi->rec_num; i++) {
+ free(bi->rows[i]);
+ }
+ free(bi->rows);
+}
+
+
+int glite_lbu_bufferedInsertClose(glite_lbu_bufInsert bi)
+{
+ if (flush_bufferd_insert(bi))
+ return STATUS(bi->ctx);
+ free_buffered_insert(bi);
+
+ return CLR_ERR(bi->ctx);
+}
+
+
+long int glite_lbu_Lastid(glite_lbu_Statement stmt) {
+ my_ulonglong i;
+
+ CLR_ERR(stmt->ctx);
+ i = db_handle.mysql_stmt_insert_id(stmt->stmt);
+ assert(i < ((unsigned long int)-1) >> 1);
+ return (long int)i;
+}
+
+
+/*
+ * helping compatibility function: clear error from the context
+ */
+static int lbu_clrerr(glite_lbu_DBContext ctx) {
+ ctx->err.code = 0;
+ if (ctx->err.desc) {
+ free(ctx->err.desc);
+ ctx->err.desc = NULL;
+ }
+ return 0;
+}
+
+
+/*
+ * helping compatibility function: sets error on the context
+ */
+static int lbu_err(glite_lbu_DBContext ctx, int code, const char *func, int line, const char *desc, ...) {
+ va_list ap;
+
+ if (code) {
+ ctx->err.code = code;
+ free(ctx->err.desc);
+ if (desc) {
+ va_start(ap, desc);
+ vasprintf(&ctx->err.desc, desc, ap);
+ va_end(ap);
+ } else
+ ctx->err.desc = NULL;
+ dprintf(ctx, "[db %d] %s:%d %s\n", getpid(), func, line, desc ? ctx->err.desc : "");
+ return code;
+ } else
+ return ctx->err.code;
+}
+
+
+/*
+ * helping function: find oud mysql error and sets on the context
+ */
+static int myerr(glite_lbu_DBContext ctx, const char *source, int line) {
+ return lbu_err(ctx, EIO, source, line, db_handle.mysql_error(ctx->mysql));
+}
+
+
+/*
+ * helping function: find oud mysql stmt error and sets on the context
+ */
+static int myerrstmt(glite_lbu_Statement stmt, const char *source, int line) {
+ return lbu_err(stmt->ctx, EIO, source, line, db_handle.mysql_stmt_error(stmt->stmt));
+}
+
+
+/*
+ * helping function: error handle
+ *
+ * \return -1 failed
+ * \return 0 retry
+ * \return 1 OK
+ */
+static int myisokstmt(glite_lbu_Statement stmt, const char *source, int line, int *retry) {
+ switch (db_handle.mysql_stmt_errno(stmt->stmt)) {
+ case 0:
+ return 1;
+ break;
+ case ER_DUP_ENTRY:
+ lbu_err(stmt->ctx, EEXIST, source, line, db_handle.mysql_stmt_error(stmt->stmt));
+ return -1;
+ break;
+ case CR_SERVER_LOST:
+ case CR_SERVER_GONE_ERROR:
+ if (*retry > 0) {
+ (*retry)--;
+ return 0;
+ } else {
+ myerrstmt(stmt, source, line);
+ return -1;
+ }
+ break;
+ default:
+ myerrstmt(stmt, source, line);
+ return -1;
+ break;
+ }
+}
+
+
+/*
+ * mysql connect
+ */
+static int db_connect(glite_lbu_DBContext ctx, const char *cs, MYSQL **mysql) {
+ char *buf = NULL;
+ char *host,*user,*pw,*db;
+ char *slash,*at,*colon;
+ int ret;
+#if MYSQL_VERSION_ID >= 50013
+ my_bool reconnect = 1;
+#endif
+
+ // needed for SQL result parameters
+ assert(sizeof(int) >= sizeof(my_bool));
+
+ if (!cs) return ERR(ctx, EINVAL, "connect string not specified");
+
+ if (!(*mysql = db_handle.mysql_init(NULL))) return ERR(ctx, ENOMEM, NULL);
+
+ db_handle.mysql_options(*mysql, MYSQL_READ_DEFAULT_FILE, "my");
+#if MYSQL_VERSION_ID >= 50013
+ /* XXX: may result in weird behaviour in the middle of transaction */
+ db_handle.mysql_options(*mysql, MYSQL_OPT_RECONNECT, &reconnect);
+#endif
+
+ host = user = pw = db = NULL;
+
+ buf = strdup(cs);
+ slash = strchr(buf,'/');
+ at = strrchr(buf,'@');
+ colon = strrchr(buf,':');
+
+ if (!slash || !at || !colon) {
+ free(buf);
+ db_close(*mysql);
+ *mysql = NULL;
+ return ERR(ctx, EINVAL, "Invalid DB connect string");
+ }
+
+ *slash = *at = *colon = 0;
+ host = at+1;
+ user = buf;
+ pw = slash+1;
+ db = colon+1;
+
+ /* ljocha: CLIENT_FOUND_ROWS added to make authorization check
+ * working in update_notif().
+ * Hope it does not break anything else */
+ if (!db_handle.mysql_real_connect(*mysql,host,user,pw,db,0,NULL,CLIENT_FOUND_ROWS)) {
+ char *desc;
+ free(buf);
+ ret = MY_ERR(ctx);
+ desc = ctx->err.desc;
+ ctx->err.desc = NULL;
+ glite_lbu_DBClose(ctx);
+ ctx->err.desc = desc;
+ return ctx->err.code = ret;
+ }
+ free(buf);
+
+ ctx->cs = cs;
+ return CLR_ERR(ctx);
+}
+
+
+/*
+ * mysql close
+ */
+static void db_close(MYSQL *mysql) {
+ if (mysql) db_handle.mysql_close(mysql);
+}
+
+
+/*
+ * test transactions capability:
+ */
+static int transaction_test(glite_lbu_DBContext ctx) {
+ glite_lbu_Statement stmt;
+ char *table[1] = { NULL }, *res[2] = { NULL, NULL }, *cmd = NULL;
+ int retval;
+
+ ctx->caps &= ~GLITE_LBU_DB_CAP_TRANSACTIONS;
+
+ if ((retval = glite_lbu_ExecSQL(ctx, "SHOW TABLES", &stmt)) <= 0 || glite_lbu_FetchRow(stmt, 1, NULL, table) < 0) goto quit;
+ glite_lbu_FreeStmt(&stmt);
+
+ trio_asprintf(&cmd, "SHOW CREATE TABLE %|Ss", table[0]);
+ if (glite_lbu_ExecSQL(ctx, cmd, &stmt) <= 0 || (retval = glite_lbu_FetchRow(stmt, 2, NULL, res)) < 0 ) goto quit;
+ if (retval != 2 || strcmp(res[0], table[0])) {
+ ERR(ctx, EIO, "unexpected show create result");
+ goto quit;
+ }
+
+ if (strstr(res[1],"ENGINE=InnoDB"))
+ ctx->caps |= GLITE_LBU_DB_CAP_TRANSACTIONS;
+
+#ifdef LBS_DB_PROFILE
+ fprintf(stderr, "[%d] use_transactions = %d\n", getpid(), USE_TRANS(ctx));
+#endif
+
+quit:
+ glite_lbu_FreeStmt(&stmt);
+ free(table[0]);
+ free(res[0]);
+ free(res[1]);
+ free(cmd);
+ return STATUS(ctx);
+}
+
+
+/*
+ * simple version of the fetch
+ */
+static int FetchRowSimple(glite_lbu_DBContext ctx, MYSQL_RES *result, unsigned long *lengths, char **results) {
+ MYSQL_ROW row;
+ unsigned int nr, i;
+ unsigned long *len;
+
+ CLR_ERR(ctx);
+
+ if (!(row = db_handle.mysql_fetch_row(result))) {
+ if (db_handle.mysql_errno((MYSQL *) ctx->mysql)) {
+ MY_ERR(ctx);
+ return -1;
+ } else return 0;
+ }
+
+ nr = db_handle.mysql_num_fields(result);
+ len = db_handle.mysql_fetch_lengths(result);
+ for (i=0; i<nr; i++) {
+ if (lengths) lengths[i] = len[i];
+ if (len[i]) {
+ results[i] = malloc(len[i] + 1);
+ memcpy(results[i], row[i], len[i]);
+ results[i][len[i]] = '\000';
+ } else
+ results[i] = strdup("");
+ }
+
+ return nr;
+}
+
+
+/*
+ * prepared version of the fetch
+ */
+static int FetchRowPrepared(glite_lbu_DBContext ctx, glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results) {
+ int ret, retry;
+ unsigned int i;
+ MYSQL_BIND *binds = NULL;
+ unsigned long *lens = NULL;
+
+ if (n != stmt->nrfields) {
+ ERR(ctx, EINVAL, "bad number of result fields");
+ return -1;
+ }
+
+ // space for results
+ if (n) binds = calloc(n, sizeof(MYSQL_BIND));
+ if (!lengths) {
+ lens = calloc(n, sizeof(unsigned long));
+ lengths = lens;
+ }
+ for (i = 0; i < n; i++) {
+ binds[i].buffer_type = MYSQL_TYPE_VAR_STRING;
+ binds[i].buffer_length = GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH - 1;
+ binds[i].length = &lengths[i];
+ binds[i].buffer = results[i] = calloc(1, GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH);
+ }
+ if (db_handle.mysql_stmt_bind_result(stmt->stmt, binds) != 0) goto failedstmt;
+
+ // fetch data, all can be truncated
+ retry = 1;
+ do {
+ switch(db_handle.mysql_stmt_fetch(stmt->stmt)) {
+#ifdef MYSQL_DATA_TRUNCATED
+ case MYSQL_DATA_TRUNCATED:
+#endif
+ case 0:
+ ret = 1; break;
+ case 1: ret = MY_ISOKSTMT(stmt, &retry); break;
+ case MYSQL_NO_DATA: ret = 0; goto quit; /* it's OK */
+ default: ERR(ctx, EIO, "other fetch error"); goto failed;
+ }
+ } while (ret == 0);
+ if (ret == -1) goto failed;
+
+ // check if all fileds had enough buffer space
+ for (i = 0; i < n; i++) {
+ // fetch the rest if needed
+ if (lengths[i] > binds[i].buffer_length) {
+ unsigned int fetched;
+
+ fetched = binds[i].buffer_length;
+ if ((results[i] = realloc(results[i], lengths[i] + 1)) == NULL) {
+ ERR(ctx, ENOMEM, "insufficient memory for field data");
+ goto failed;
+ }
+ results[i][lengths[i]] = '\000';
+ binds[i].buffer = results[i] + fetched;
+ binds[i].buffer_length = lengths[i] - fetched;
+
+ retry = 1;
+ do {
+ switch (db_handle.mysql_stmt_fetch_column(stmt->stmt, binds + i, i, fetched)) {
+ case 0: ret = 1; break;
+ case 1: ret = MY_ISOKSTMT(stmt, &retry); break;
+ case MYSQL_NO_DATA: ret = 0; goto quit; /* it's OK */
+ default: ERR(ctx, EIO, "other fetch error"); goto failed;
+ }
+ } while (ret == 0);
+ if (ret == -1) goto failed;
+ }
+ }
+
+ CLR_ERR(ctx);
+ free(binds);
+ free(lens);
+ return n;
+
+failedstmt:
+ MY_ERRSTMT(stmt);
+failed:
+ ret = -1;
+quit:
+ free(binds);
+ free(lens);
+ for (i = 0; i < n; i++) {
+ free(results[i]);
+ results[i] = NULL;
+ }
+ return ret;
+}
+
+
+static void set_time(MYSQL_TIME *mtime, const double time) {
+ struct tm tm;
+ time_t itime;
+
+ itime = time;
+ gmtime_r(&itime, &tm);
+ memset(mtime, 0, sizeof *mtime);
+ mtime->year = tm.tm_year + 1900;
+ mtime->month = tm.tm_mon + 1;
+ mtime->day = tm.tm_mday;
+ mtime->hour = tm.tm_hour;
+ mtime->minute = tm.tm_min;
+ mtime->second = tm.tm_sec;
+ mtime->second_part = (time - itime) * 1000;
+}
+
+
+static void glite_lbu_DBCleanup(void) {
+ pthread_mutex_lock(&db_handle.lock);
+ if (db_handle.lib) {
+ dlclose(db_handle.lib);
+ db_handle.lib = NULL;
+ }
+ pthread_mutex_unlock(&db_handle.lock);
+}
+
+