/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apu.h" #define HAVE_MYSQL_MYSQL_H #if APU_HAVE_MYSQL #include "apu_version.h" #include "apu_config.h" #include #include #ifdef HAVE_MYSQL_H #include #include #elif defined(HAVE_MYSQL_MYSQL_H) #include #include #endif #include "apr_strings.h" #include "apr_lib.h" #include "apr_buckets.h" #include "apr_dbd_internal.h" /* default maximum field size 1 MB */ #define FIELDSIZE 1048575 struct apr_dbd_prepared_t { MYSQL_STMT* stmt; int nargs; int nvals; apr_dbd_type_e *types; }; struct apr_dbd_transaction_t { int mode; int errnum; apr_dbd_t *handle; }; struct apr_dbd_t { MYSQL* conn ; apr_dbd_transaction_t* trans ; unsigned long fldsz; }; struct apr_dbd_results_t { int random; MYSQL_RES *res; MYSQL_STMT *statement; MYSQL_BIND *bind; apr_pool_t *pool; }; struct apr_dbd_row_t { MYSQL_ROW row; apr_dbd_results_t *res; unsigned long *len; }; /* MySQL specific bucket for BLOB types */ typedef struct apr_bucket_lob apr_bucket_lob; /** * A bucket referring to a MySQL BLOB */ struct apr_bucket_lob { /** Number of buckets using this memory */ apr_bucket_refcount refcount; /** The row this bucket refers to */ const apr_dbd_row_t *row; /** The column this bucket refers to */ int col; /** The pool into which any needed structures should * be created while reading from this bucket */ apr_pool_t *readpool; }; static void lob_bucket_destroy(void *data); static apr_status_t lob_bucket_read(apr_bucket *e, const char **str, apr_size_t *len, apr_read_type_e block); static apr_bucket *apr_bucket_lob_make(apr_bucket *b, const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p); static apr_bucket *apr_bucket_lob_create(const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p, apr_bucket_alloc_t *list); static const apr_bucket_type_t apr_bucket_type_lob = { "LOB", 5, APR_BUCKET_DATA, lob_bucket_destroy, lob_bucket_read, apr_bucket_setaside_notimpl, apr_bucket_shared_split, apr_bucket_shared_copy }; static void lob_bucket_destroy(void *data) { apr_bucket_lob *f = data; if (apr_bucket_shared_destroy(f)) { /* no need to destroy database objects here; it will get * done automatically when the pool gets cleaned up */ apr_bucket_free(f); } } static apr_status_t lob_bucket_read(apr_bucket *e, const char **str, apr_size_t *len, apr_read_type_e block) { apr_bucket_lob *a = e->data; const apr_dbd_row_t *row = a->row; apr_dbd_results_t *res = row->res; int col = a->col; apr_bucket *b = NULL; int rv; apr_size_t blength = e->length; /* bytes remaining in file past offset */ apr_off_t boffset = e->start; MYSQL_BIND *bind = &res->bind[col]; *str = NULL; /* in case we die prematurely */ /* fetch from offset if not at the beginning */ if (boffset > 0) { rv = mysql_stmt_fetch_column(res->statement, bind, col, (unsigned long) boffset); if (rv != 0) { return APR_EGENERAL; } } blength -= blength > bind->buffer_length ? bind->buffer_length : blength; *len = e->length - blength; *str = bind->buffer; /* allocate new buffer, since we used this one for the bucket */ bind->buffer = apr_palloc(res->pool, bind->buffer_length); /* * Change the current bucket to refer to what we read, * even if we read nothing because we hit EOF. */ apr_bucket_pool_make(e, *str, *len, res->pool); /* If we have more to read from the field, then create another bucket */ if (blength > 0) { /* for efficiency, we can just build a new apr_bucket struct * to wrap around the existing LOB bucket */ b = apr_bucket_alloc(sizeof(*b), e->list); b->start = boffset + *len; b->length = blength; b->data = a; b->type = &apr_bucket_type_lob; b->free = apr_bucket_free; b->list = e->list; APR_BUCKET_INSERT_AFTER(e, b); } else { lob_bucket_destroy(a); } return APR_SUCCESS; } static apr_bucket *apr_bucket_lob_make(apr_bucket *b, const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p) { apr_bucket_lob *f; f = apr_bucket_alloc(sizeof(*f), b->list); f->row = row; f->col = col; f->readpool = p; b = apr_bucket_shared_make(b, f, offset, len); b->type = &apr_bucket_type_lob; return b; } static apr_bucket *apr_bucket_lob_create(const apr_dbd_row_t *row, int col, apr_off_t offset, apr_size_t len, apr_pool_t *p, apr_bucket_alloc_t *list) { apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); APR_BUCKET_INIT(b); b->free = apr_bucket_free; b->list = list; return apr_bucket_lob_make(b, row, col, offset, len, p); } static apr_status_t free_result(void *data) { mysql_free_result(data); return APR_SUCCESS; } static int dbd_mysql_select(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **results, const char *query, int seek) { int sz; int ret; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } ret = mysql_query(sql->conn, query); if (!ret) { if (sz = mysql_field_count(sql->conn), sz > 0) { if (!*results) { *results = apr_palloc(pool, sizeof(apr_dbd_results_t)); } (*results)->random = seek; (*results)->statement = NULL; (*results)->pool = pool; if (seek) { (*results)->res = mysql_store_result(sql->conn); } else { (*results)->res = mysql_use_result(sql->conn); } apr_pool_cleanup_register(pool, (*results)->res, free_result,apr_pool_cleanup_null); } } else { ret = mysql_errno(sql->conn); } if (TXN_NOTICE_ERRORS(sql->trans)) { sql->trans->errnum = ret; } return ret; } static const char *dbd_mysql_get_name(const apr_dbd_results_t *res, int n) { if ((n < 0) || (n >= (int) mysql_num_fields(res->res))) { return NULL; } return mysql_fetch_fields(res->res)[n].name; } static int dbd_mysql_get_row(apr_pool_t *pool, apr_dbd_results_t *res, apr_dbd_row_t **row, int rownum) { MYSQL_ROW r = NULL; int ret = 0; if (res->statement) { if (res->random) { if (rownum > 0) { mysql_stmt_data_seek(res->statement, (my_ulonglong) --rownum); } else { return -1; /* invalid row */ } } ret = mysql_stmt_fetch(res->statement); switch (ret) { case 1: ret = mysql_stmt_errno(res->statement); break; case MYSQL_NO_DATA: ret = -1; break; default: ret = 0; /* bad luck - get_entry will deal with this */ break; } } else { if (res->random) { if (rownum > 0) { mysql_data_seek(res->res, (my_ulonglong) --rownum); } else { return -1; /* invalid row */ } } r = mysql_fetch_row(res->res); if (r == NULL) { ret = -1; } } if (ret == 0) { if (!*row) { *row = apr_palloc(pool, sizeof(apr_dbd_row_t)); } (*row)->row = r; (*row)->res = res; (*row)->len = mysql_fetch_lengths(res->res); } else { apr_pool_cleanup_run(pool, res->res, free_result); } return ret; } #if 0 /* An improved API that was proposed but not followed up */ static int dbd_mysql_get_entry(const apr_dbd_row_t *row, int n, apr_dbd_datum_t *val) { MYSQL_BIND *bind; if (row->res->statement) { bind = &row->res->bind[n]; if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) { val->type = APR_DBD_VALUE_NULL; return -1; } if (*bind->is_null) { val->type = APR_DBD_VALUE_NULL; return -1; } else { val->type = APR_DBD_VALUE_STRING; val->value.stringval = bind->buffer; } } else { val->type = APR_DBD_VALUE_STRING; val->value.stringval = row->row[n]; } return 0; } #else static const char *dbd_mysql_get_entry(const apr_dbd_row_t *row, int n) { MYSQL_BIND *bind; if (row->res->statement) { bind = &row->res->bind[n]; if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) { return NULL; } if (*bind->is_null) { return NULL; } else { return bind->buffer; } } else { return row->row[n]; } return NULL; } #endif static apr_status_t dbd_mysql_datum_get(const apr_dbd_row_t *row, int n, apr_dbd_type_e type, void *data) { if (row->res->statement) { MYSQL_BIND *bind = &row->res->bind[n]; unsigned long len = *bind->length; if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) { return APR_EGENERAL; } if (*bind->is_null) { return APR_ENOENT; } switch (type) { case APR_DBD_TYPE_TINY: *(char*)data = atoi(bind->buffer); break; case APR_DBD_TYPE_UTINY: *(unsigned char*)data = atoi(bind->buffer); break; case APR_DBD_TYPE_SHORT: *(short*)data = atoi(bind->buffer); break; case APR_DBD_TYPE_USHORT: *(unsigned short*)data = atoi(bind->buffer); break; case APR_DBD_TYPE_INT: *(int*)data = atoi(bind->buffer); break; case APR_DBD_TYPE_UINT: *(unsigned int*)data = atoi(bind->buffer); break; case APR_DBD_TYPE_LONG: *(long*)data = atol(bind->buffer); break; case APR_DBD_TYPE_ULONG: *(unsigned long*)data = atol(bind->buffer); break; case APR_DBD_TYPE_LONGLONG: *(apr_int64_t*)data = apr_atoi64(bind->buffer); break; case APR_DBD_TYPE_ULONGLONG: *(apr_uint64_t*)data = apr_atoi64(bind->buffer); break; case APR_DBD_TYPE_FLOAT: *(float*)data = (float) atof(bind->buffer); break; case APR_DBD_TYPE_DOUBLE: *(double*)data = atof(bind->buffer); break; case APR_DBD_TYPE_STRING: case APR_DBD_TYPE_TEXT: case APR_DBD_TYPE_TIME: case APR_DBD_TYPE_DATE: case APR_DBD_TYPE_DATETIME: case APR_DBD_TYPE_TIMESTAMP: case APR_DBD_TYPE_ZTIMESTAMP: *((char*)bind->buffer+bind->buffer_length-1) = '\0'; *(char**)data = bind->buffer; break; case APR_DBD_TYPE_BLOB: case APR_DBD_TYPE_CLOB: { apr_bucket *e; apr_bucket_brigade *b = (apr_bucket_brigade*)data; e = apr_bucket_lob_create(row, n, 0, len, row->res->pool, b->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); } break; case APR_DBD_TYPE_NULL: *(void**)data = NULL; break; default: return APR_EGENERAL; } } else { if (row->row[n] == NULL) { return APR_ENOENT; } switch (type) { case APR_DBD_TYPE_TINY: *(char*)data = atoi(row->row[n]); break; case APR_DBD_TYPE_UTINY: *(unsigned char*)data = atoi(row->row[n]); break; case APR_DBD_TYPE_SHORT: *(short*)data = atoi(row->row[n]); break; case APR_DBD_TYPE_USHORT: *(unsigned short*)data = atoi(row->row[n]); break; case APR_DBD_TYPE_INT: *(int*)data = atoi(row->row[n]); break; case APR_DBD_TYPE_UINT: *(unsigned int*)data = atoi(row->row[n]); break; case APR_DBD_TYPE_LONG: *(long*)data = atol(row->row[n]); break; case APR_DBD_TYPE_ULONG: *(unsigned long*)data = atol(row->row[n]); break; case APR_DBD_TYPE_LONGLONG: *(apr_int64_t*)data = apr_atoi64(row->row[n]); break; case APR_DBD_TYPE_ULONGLONG: *(apr_uint64_t*)data = apr_atoi64(row->row[n]); break; case APR_DBD_TYPE_FLOAT: *(float*)data = (float) atof(row->row[n]); break; case APR_DBD_TYPE_DOUBLE: *(double*)data = atof(row->row[n]); break; case APR_DBD_TYPE_STRING: case APR_DBD_TYPE_TEXT: case APR_DBD_TYPE_TIME: case APR_DBD_TYPE_DATE: case APR_DBD_TYPE_DATETIME: case APR_DBD_TYPE_TIMESTAMP: case APR_DBD_TYPE_ZTIMESTAMP: *(char**)data = row->row[n]; break; case APR_DBD_TYPE_BLOB: case APR_DBD_TYPE_CLOB: { apr_bucket *e; apr_bucket_brigade *b = (apr_bucket_brigade*)data; e = apr_bucket_pool_create(row->row[n], row->len[n], row->res->pool, b->bucket_alloc); APR_BRIGADE_INSERT_TAIL(b, e); } break; case APR_DBD_TYPE_NULL: *(void**)data = NULL; break; default: return APR_EGENERAL; } } return 0; } static const char *dbd_mysql_error(apr_dbd_t *sql, int n) { return mysql_error(sql->conn); } static int dbd_mysql_query(apr_dbd_t *sql, int *nrows, const char *query) { int ret; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } ret = mysql_query(sql->conn, query); if (ret != 0) { ret = mysql_errno(sql->conn); } *nrows = (int) mysql_affected_rows(sql->conn); if (TXN_NOTICE_ERRORS(sql->trans)) { sql->trans->errnum = ret; } return ret; } static const char *dbd_mysql_escape(apr_pool_t *pool, const char *arg, apr_dbd_t *sql) { unsigned long len = strlen(arg); char *ret = apr_palloc(pool, 2*len + 1); mysql_real_escape_string(sql->conn, ret, arg, len); return ret; } static apr_status_t stmt_close(void *data) { mysql_stmt_close(data); return APR_SUCCESS; } static int dbd_mysql_prepare(apr_pool_t *pool, apr_dbd_t *sql, const char *query, const char *label, int nargs, int nvals, apr_dbd_type_e *types, apr_dbd_prepared_t **statement) { /* Translate from apr_dbd to native query format */ int ret; if (!*statement) { *statement = apr_palloc(pool, sizeof(apr_dbd_prepared_t)); } (*statement)->stmt = mysql_stmt_init(sql->conn); if ((*statement)->stmt) { apr_pool_cleanup_register(pool, (*statement)->stmt, stmt_close, apr_pool_cleanup_null); ret = mysql_stmt_prepare((*statement)->stmt, query, strlen(query)); if (ret != 0) { ret = mysql_stmt_errno((*statement)->stmt); } (*statement)->nargs = nargs; (*statement)->nvals = nvals; (*statement)->types = types; return ret; } return CR_OUT_OF_MEMORY; } static void dbd_mysql_bind(apr_dbd_prepared_t *statement, const char **values, MYSQL_BIND *bind) { int i, j; for (i = 0, j = 0; i < statement->nargs; i++, j++) { bind[i].length = &bind[i].buffer_length; bind[i].is_unsigned = 0; bind[i].is_null = NULL; if (values[j] == NULL) { bind[i].buffer_type = MYSQL_TYPE_NULL; } else { switch (statement->types[i]) { case APR_DBD_TYPE_BLOB: case APR_DBD_TYPE_CLOB: bind[i].buffer_type = MYSQL_TYPE_LONG_BLOB; bind[i].buffer = (void*)values[j]; bind[i].buffer_length = atol(values[++j]); /* skip table and column */ j += 2; break; default: bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; bind[i].buffer = (void*)values[j]; bind[i].buffer_length = strlen(values[j]); break; } } } return; } static int dbd_mysql_pquery_internal(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, MYSQL_BIND *bind) { int ret; ret = mysql_stmt_bind_param(statement->stmt, bind); if (ret != 0) { *nrows = 0; ret = mysql_stmt_errno(statement->stmt); } else { ret = mysql_stmt_execute(statement->stmt); if (ret != 0) { ret = mysql_stmt_errno(statement->stmt); } *nrows = (int) mysql_stmt_affected_rows(statement->stmt); } return ret; } static int dbd_mysql_pquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, const char **values) { MYSQL_BIND *bind; int ret; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND)); dbd_mysql_bind(statement, values, bind); ret = dbd_mysql_pquery_internal(pool, sql, nrows, statement, bind); if (TXN_NOTICE_ERRORS(sql->trans)) { sql->trans->errnum = ret; } return ret; } static int dbd_mysql_pvquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, va_list args) { const char **values; int i; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const char*); } return dbd_mysql_pquery(pool, sql, nrows, statement, values); } static int dbd_mysql_pselect_internal(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, MYSQL_BIND *bind) { int nfields, i; my_bool *is_nullr; #if MYSQL_VERSION_ID >= 50000 my_bool *error; #endif int ret; unsigned long *length, maxlen; ret = mysql_stmt_bind_param(statement->stmt, bind); if (ret == 0) { ret = mysql_stmt_execute(statement->stmt); if (!ret) { if (!*res) { *res = apr_pcalloc(pool, sizeof(apr_dbd_results_t)); } (*res)->random = random; (*res)->statement = statement->stmt; (*res)->res = mysql_stmt_result_metadata(statement->stmt); (*res)->pool = pool; apr_pool_cleanup_register(pool, (*res)->res, free_result, apr_pool_cleanup_null); nfields = mysql_num_fields((*res)->res); if (!(*res)->bind) { (*res)->bind = apr_palloc(pool, nfields*sizeof(MYSQL_BIND)); length = apr_pcalloc(pool, nfields*sizeof(unsigned long)); #if MYSQL_VERSION_ID >= 50000 error = apr_palloc(pool, nfields*sizeof(my_bool)); #endif is_nullr = apr_pcalloc(pool, nfields*sizeof(my_bool)); for ( i = 0; i < nfields; ++i ) { maxlen = ((*res)->res->fields[i].length < sql->fldsz ? (*res)->res->fields[i].length : sql->fldsz) + 1; if ((*res)->res->fields[i].type == MYSQL_TYPE_BLOB) { (*res)->bind[i].buffer_type = MYSQL_TYPE_LONG_BLOB; } else { (*res)->bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; } (*res)->bind[i].buffer_length = maxlen; (*res)->bind[i].length = &length[i]; (*res)->bind[i].buffer = apr_palloc(pool, maxlen); (*res)->bind[i].is_null = is_nullr+i; #if MYSQL_VERSION_ID >= 50000 (*res)->bind[i].error = error+i; #endif } } ret = mysql_stmt_bind_result(statement->stmt, (*res)->bind); if (!ret) { ret = mysql_stmt_store_result(statement->stmt); } } } if (ret != 0) { ret = mysql_stmt_errno(statement->stmt); } return ret; } static int dbd_mysql_pselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, const char **args) { int ret; MYSQL_BIND *bind; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND)); dbd_mysql_bind(statement, args, bind); ret = dbd_mysql_pselect_internal(pool, sql, res, statement, random, bind); if (TXN_NOTICE_ERRORS(sql->trans)) { sql->trans->errnum = ret; } return ret; } static int dbd_mysql_pvselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, va_list args) { const char **values; int i; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const char*); } return dbd_mysql_pselect(pool, sql, res, statement, random, values); } static void dbd_mysql_bbind(apr_pool_t *pool, apr_dbd_prepared_t *statement, const void **values, MYSQL_BIND *bind) { void *arg; int i, j; apr_dbd_type_e type; for (i = 0, j = 0; i < statement->nargs; i++, j++) { arg = (void *)values[j]; bind[i].length = &bind[i].buffer_length; bind[i].is_null = NULL; type = (arg == NULL ? APR_DBD_TYPE_NULL : statement->types[i]); switch (type) { case APR_DBD_TYPE_TINY: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_TINY; bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_UTINY: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_TINY; bind[i].is_unsigned = 1; break; case APR_DBD_TYPE_SHORT: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_SHORT; bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_USHORT: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_SHORT; bind[i].is_unsigned = 1; break; case APR_DBD_TYPE_INT: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_LONG; bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_UINT: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_LONG; bind[i].is_unsigned = 1; break; case APR_DBD_TYPE_LONG: if (sizeof(int) == sizeof(long)) { bind[i].buffer = arg; } else { bind[i].buffer = apr_palloc(pool, sizeof(int)); *(int*)bind[i].buffer = *(long*)arg; } bind[i].buffer_type = MYSQL_TYPE_LONG; bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_ULONG: if (sizeof(unsigned int) == sizeof(unsigned long)) { bind[i].buffer = arg; } else { bind[i].buffer = apr_palloc(pool, sizeof(unsigned int)); *(unsigned int*)bind[i].buffer = *(unsigned long*)arg; } bind[i].buffer_type = MYSQL_TYPE_LONG; bind[i].is_unsigned = 1; break; case APR_DBD_TYPE_LONGLONG: if (sizeof(my_ulonglong) == sizeof(apr_int64_t)) { bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_LONGLONG; } else { /* have to downsize, long long is not portable */ bind[i].buffer = apr_palloc(pool, sizeof(long)); *(long*)bind[i].buffer = (long) *(apr_int64_t*)arg; bind[i].buffer_type = MYSQL_TYPE_LONG; } bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_ULONGLONG: if (sizeof(my_ulonglong) == sizeof(apr_uint64_t)) { bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_LONGLONG; } else { /* have to downsize, long long is not portable */ bind[i].buffer = apr_palloc(pool, sizeof(long)); *(unsigned long*)bind[i].buffer = (unsigned long) *(apr_uint64_t*)arg; bind[i].buffer_type = MYSQL_TYPE_LONG; } bind[i].is_unsigned = 1; break; case APR_DBD_TYPE_FLOAT: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_FLOAT; bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_DOUBLE: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_DOUBLE; bind[i].is_unsigned = 0; break; case APR_DBD_TYPE_STRING: case APR_DBD_TYPE_TEXT: case APR_DBD_TYPE_TIME: case APR_DBD_TYPE_DATE: case APR_DBD_TYPE_DATETIME: case APR_DBD_TYPE_TIMESTAMP: case APR_DBD_TYPE_ZTIMESTAMP: bind[i].buffer = arg; bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; bind[i].is_unsigned = 0; bind[i].buffer_length = strlen((const char *)arg); break; case APR_DBD_TYPE_BLOB: case APR_DBD_TYPE_CLOB: bind[i].buffer = (void *)arg; bind[i].buffer_type = MYSQL_TYPE_LONG_BLOB; bind[i].is_unsigned = 0; bind[i].buffer_length = *(apr_size_t*)values[++j]; /* skip table and column */ j += 2; break; case APR_DBD_TYPE_NULL: default: bind[i].buffer_type = MYSQL_TYPE_NULL; break; } } return; } static int dbd_mysql_pbquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, const void **values) { MYSQL_BIND *bind; int ret; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND)); dbd_mysql_bbind(pool, statement, values, bind); ret = dbd_mysql_pquery_internal(pool, sql, nrows, statement, bind); if (TXN_NOTICE_ERRORS(sql->trans)) { sql->trans->errnum = ret; } return ret; } static int dbd_mysql_pvbquery(apr_pool_t *pool, apr_dbd_t *sql, int *nrows, apr_dbd_prepared_t *statement, va_list args) { const void **values; int i; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const void*); } return dbd_mysql_pbquery(pool, sql, nrows, statement, values); } static int dbd_mysql_pbselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, const void **args) { int ret; MYSQL_BIND *bind; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } bind = apr_palloc(pool, statement->nargs * sizeof(MYSQL_BIND)); dbd_mysql_bbind(pool, statement, args, bind); ret = dbd_mysql_pselect_internal(pool, sql, res, statement, random, bind); if (TXN_NOTICE_ERRORS(sql->trans)) { sql->trans->errnum = ret; } return ret; } static int dbd_mysql_pvbselect(apr_pool_t *pool, apr_dbd_t *sql, apr_dbd_results_t **res, apr_dbd_prepared_t *statement, int random, va_list args) { const void **values; int i; if (sql->trans && sql->trans->errnum) { return sql->trans->errnum; } values = apr_palloc(pool, sizeof(*values) * statement->nvals); for (i = 0; i < statement->nvals; i++) { values[i] = va_arg(args, const void*); } return dbd_mysql_pbselect(pool, sql, res, statement, random, values); } static int dbd_mysql_end_transaction(apr_dbd_transaction_t *trans) { int ret = -1; if (trans) { /* rollback on error or explicit rollback request */ if (trans->errnum || TXN_DO_ROLLBACK(trans)) { trans->errnum = 0; ret = mysql_rollback(trans->handle->conn); } else { ret = mysql_commit(trans->handle->conn); } } ret |= mysql_autocommit(trans->handle->conn, 1); trans->handle->trans = NULL; return ret; } /* Whether or not transactions work depends on whether the * underlying DB supports them within MySQL. Unfortunately * it fails silently with the default InnoDB. */ static int dbd_mysql_transaction(apr_pool_t *pool, apr_dbd_t *handle, apr_dbd_transaction_t **trans) { /* Don't try recursive transactions here */ if (handle->trans) { dbd_mysql_end_transaction(handle->trans) ; } if (!*trans) { *trans = apr_pcalloc(pool, sizeof(apr_dbd_transaction_t)); } (*trans)->errnum = mysql_autocommit(handle->conn, 0); (*trans)->handle = handle; handle->trans = *trans; return (*trans)->errnum; } static int dbd_mysql_transaction_mode_get(apr_dbd_transaction_t *trans) { if (!trans) return APR_DBD_TRANSACTION_COMMIT; return trans->mode; } static int dbd_mysql_transaction_mode_set(apr_dbd_transaction_t *trans, int mode) { if (!trans) return APR_DBD_TRANSACTION_COMMIT; return trans->mode = (mode & TXN_MODE_BITS); } static apr_dbd_t *dbd_mysql_open(apr_pool_t *pool, const char *params, const char **error) { static const char *const delims = " \r\n\t;|,"; const char *ptr; int i; const char *key; size_t klen; const char *value; size_t vlen; #if MYSQL_VERSION_ID >= 50013 my_bool do_reconnect = 1; #endif MYSQL *real_conn; unsigned long flags = 0; struct { const char *field; const char *value; } fields[] = { {"host", NULL}, {"user", NULL}, {"pass", NULL}, {"dbname", NULL}, {"port", NULL}, {"sock", NULL}, {"flags", NULL}, {"fldsz", NULL}, {"group", NULL}, {"reconnect", NULL}, {NULL, NULL} }; unsigned int port = 0; apr_dbd_t *sql = apr_pcalloc(pool, sizeof(apr_dbd_t)); sql->fldsz = FIELDSIZE; sql->conn = mysql_init(sql->conn); if ( sql->conn == NULL ) { return NULL; } for (ptr = strchr(params, '='); ptr; ptr = strchr(ptr, '=')) { /* don't dereference memory that may not belong to us */ if (ptr == params) { ++ptr; continue; } for (key = ptr-1; apr_isspace(*key); --key); klen = 0; while (apr_isalpha(*key)) { /* don't parse backwards off the start of the string */ if (key == params) { --key; ++klen; break; } --key; ++klen; } ++key; for (value = ptr+1; apr_isspace(*value); ++value); vlen = strcspn(value, delims); for (i = 0; fields[i].field != NULL; i++) { if (!strncasecmp(fields[i].field, key, klen)) { fields[i].value = apr_pstrndup(pool, value, vlen); break; } } ptr = value+vlen; } if (fields[4].value != NULL) { port = atoi(fields[4].value); } if (fields[6].value != NULL && !strcmp(fields[6].value, "CLIENT_FOUND_ROWS")) { flags |= CLIENT_FOUND_ROWS; /* only option we know */ } if (fields[7].value != NULL) { sql->fldsz = atol(fields[7].value); } if (fields[8].value != NULL) { mysql_options(sql->conn, MYSQL_READ_DEFAULT_GROUP, fields[8].value); } #if MYSQL_VERSION_ID >= 50013 if (fields[9].value != NULL) { do_reconnect = atoi(fields[9].value) ? 1 : 0; } #endif #if MYSQL_VERSION_ID >= 50013 /* the MySQL manual says this should be BEFORE mysql_real_connect */ mysql_options(sql->conn, MYSQL_OPT_RECONNECT, &do_reconnect); #endif real_conn = mysql_real_connect(sql->conn, fields[0].value, fields[1].value, fields[2].value, fields[3].value, port, fields[5].value, flags); if(real_conn == NULL) { if (error) { *error = apr_pstrdup(pool, mysql_error(sql->conn)); } mysql_close(sql->conn); return NULL; } #if MYSQL_VERSION_ID >= 50013 /* Some say this should be AFTER mysql_real_connect */ mysql_options(sql->conn, MYSQL_OPT_RECONNECT, &do_reconnect); #endif return sql; } static apr_status_t dbd_mysql_close(apr_dbd_t *handle) { mysql_close(handle->conn); return APR_SUCCESS; } static apr_status_t dbd_mysql_check_conn(apr_pool_t *pool, apr_dbd_t *handle) { return mysql_ping(handle->conn) ? APR_EGENERAL : APR_SUCCESS; } static int dbd_mysql_select_db(apr_pool_t *pool, apr_dbd_t* handle, const char* name) { return mysql_select_db(handle->conn, name); } static void *dbd_mysql_native(apr_dbd_t *handle) { return handle->conn; } static int dbd_mysql_num_cols(apr_dbd_results_t *res) { if (res->statement) { return mysql_stmt_field_count(res->statement); } else { return mysql_num_fields(res->res); } } static int dbd_mysql_num_tuples(apr_dbd_results_t *res) { if (res->random) { if (res->statement) { return (int) mysql_stmt_num_rows(res->statement); } else { return (int) mysql_num_rows(res->res); } } else { return -1; } } static apr_status_t thread_end(void *data) { mysql_thread_end(); return APR_SUCCESS; } static void dbd_mysql_init(apr_pool_t *pool) { my_init(); mysql_thread_init(); /* FIXME: this is a guess; find out what it really does */ apr_pool_cleanup_register(pool, NULL, thread_end, apr_pool_cleanup_null); } APU_MODULE_DECLARE_DATA const apr_dbd_driver_t apr_dbd_mysql_driver = { "mysql", dbd_mysql_init, dbd_mysql_native, dbd_mysql_open, dbd_mysql_check_conn, dbd_mysql_close, dbd_mysql_select_db, dbd_mysql_transaction, dbd_mysql_end_transaction, dbd_mysql_query, dbd_mysql_select, dbd_mysql_num_cols, dbd_mysql_num_tuples, dbd_mysql_get_row, dbd_mysql_get_entry, dbd_mysql_error, dbd_mysql_escape, dbd_mysql_prepare, dbd_mysql_pvquery, dbd_mysql_pvselect, dbd_mysql_pquery, dbd_mysql_pselect, dbd_mysql_get_name, dbd_mysql_transaction_mode_get, dbd_mysql_transaction_mode_set, "?", dbd_mysql_pvbquery, dbd_mysql_pvbselect, dbd_mysql_pbquery, dbd_mysql_pbselect, dbd_mysql_datum_get }; #endif