/** * Retrieve a result set * \param _h handle to the database * \param _r result set that should be retrieved * \return zero on success, negative value on failure */ static int db_mysql_store_result(const db1_con_t* _h, db1_res_t** _r) { int code; if ((!_h) || (!_r)) { LM_ERR("invalid parameter value\n"); return -1; } *_r = db_mysql_new_result(); if (*_r == 0) { LM_ERR("no memory left\n"); return -2; } RES_RESULT(*_r) = mysql_store_result(CON_CONNECTION(_h)); if (!RES_RESULT(*_r)) { if (mysql_field_count(CON_CONNECTION(_h)) == 0) { (*_r)->col.n = 0; (*_r)->n = 0; goto done; } else { LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h))); code = mysql_errno(CON_CONNECTION(_h)); if (code == CR_SERVER_GONE_ERROR || code == CR_SERVER_LOST) { counter_inc(mysql_cnts_h.driver_err); } db_mysql_free_result(_h, *_r); *_r = 0; return -3; } } if (db_mysql_convert_result(_h, *_r) < 0) { LM_ERR("error while converting result\n"); LM_DBG("freeing result set at %p\n", _r); /* all mem on Kamailio API side is already freed by * db_mysql_convert_result in case of error, but we also need * to free the mem from the mysql lib side, internal pkg for it * and *_r */ db_mysql_free_result(_h, *_r); *_r = 0; #if (MYSQL_VERSION_ID >= 40100) while( mysql_more_results(CON_CONNECTION(_h)) && mysql_next_result(CON_CONNECTION(_h)) == 0 ) { MYSQL_RES *res = mysql_store_result( CON_CONNECTION(_h) ); mysql_free_result(res); } #endif return -4; } done: #if (MYSQL_VERSION_ID >= 40100) while( mysql_more_results(CON_CONNECTION(_h)) && mysql_next_result(CON_CONNECTION(_h)) == 0 ) { MYSQL_RES *res = mysql_store_result( CON_CONNECTION(_h) ); mysql_free_result(res); } #endif return 0; }
/*! * \brief Convert a row from result into DB API representation * \param _h database connection * \param _res database result in the DB API representation * \param _r database result row * \return 0 on success, -1 on failure */ int db_mysql_convert_row(const db1_con_t* _h, db1_res_t* _res, db_row_t* _r) { unsigned long* lengths; int i; if ((!_h) || (!_res) || (!_r)) { LM_ERR("invalid parameter value\n"); return -1; } if (db_allocate_row(_res, _r) != 0) { LM_ERR("could not allocate row"); return -2; } lengths = mysql_fetch_lengths(RES_RESULT(_res)); for(i = 0; i < RES_COL_N(_res); i++) { if (db_str2val(RES_TYPES(_res)[i], &(ROW_VALUES(_r)[i]), ((MYSQL_ROW)RES_ROW(_res))[i], lengths[i], 0) < 0) { LM_ERR("failed to convert value\n"); LM_DBG("free row at %p\n", _r); db_free_row(_r); return -3; } } return 0; }
/** * Release a result set from memory. * \param _h handle to the database * \param _r result set that should be freed * \return zero on success, negative value on failure */ int db_mysql_free_result(const db1_con_t* _h, db1_res_t* _r) { if ((!_h) || (!_r)) { LM_ERR("invalid parameter value\n"); return -1; } mysql_free_result(RES_RESULT(_r)); RES_RESULT(_r) = 0; pkg_free(RES_PTR(_r)); if (db_free_result(_r) < 0) { LM_ERR("unable to free result structure\n"); return -1; } return 0; }
/*! * \brief Convert rows from mysql to db API representation * \param _h database connection * \param _r database result set * \return 0 on success, negative on failure */ static inline int db_mysql_convert_rows(const db1_con_t* _h, db1_res_t* _r) { int row; if ((!_h) || (!_r)) { LM_ERR("invalid parameter\n"); return -1; } RES_ROW_N(_r) = mysql_num_rows(RES_RESULT(_r)); if (!RES_ROW_N(_r)) { LM_DBG("no rows returned from the query\n"); RES_ROWS(_r) = 0; return 0; } if (db_allocate_rows(_r) < 0) { LM_ERR("could not allocate rows"); RES_ROW_N(_r) = 0; return -2; } for(row = 0; row < RES_ROW_N(_r); row++) { RES_ROW(_r) = mysql_fetch_row(RES_RESULT(_r)); if (!RES_ROW(_r)) { LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h))); RES_ROW_N(_r) = row; db_free_rows(_r); return -3; } if (db_mysql_convert_row(_h, _r, &(RES_ROWS(_r)[row])) < 0) { LM_ERR("error while converting row #%d\n", row); RES_ROW_N(_r) = row; db_free_rows(_r); return -4; } } return 0; }
/*! * \brief Get and convert columns from a result * * Get and convert columns from a result, fills the result structure * with data from the database. * \param _h database connection * \param _r database result set * \return 0 on success, negative on failure */ int db_mysql_get_columns(const db1_con_t* _h, db1_res_t* _r) { int col; MYSQL_FIELD* fields; if ((!_h) || (!_r)) { LM_ERR("invalid parameter\n"); return -1; } RES_COL_N(_r) = mysql_field_count(CON_CONNECTION(_h)); if (!RES_COL_N(_r)) { LM_ERR("no columns returned from the query\n"); return -2; } else { LM_DBG("%d columns returned from the query\n", RES_COL_N(_r)); } if (db_allocate_columns(_r, RES_COL_N(_r)) != 0) { RES_COL_N(_r) = 0; LM_ERR("could not allocate columns\n"); return -3; } fields = mysql_fetch_fields(RES_RESULT(_r)); for(col = 0; col < RES_COL_N(_r); col++) { RES_NAMES(_r)[col] = (str*)pkg_malloc(sizeof(str)); if (! RES_NAMES(_r)[col]) { LM_ERR("no private memory left\n"); db_free_columns(_r); return -4; } LM_DBG("allocate %lu bytes for RES_NAMES[%d] at %p\n", (unsigned long)sizeof(str), col, RES_NAMES(_r)[col]); /* The pointer that is here returned is part of the result structure. */ RES_NAMES(_r)[col]->s = fields[col].name; RES_NAMES(_r)[col]->len = strlen(fields[col].name); LM_DBG("RES_NAMES(%p)[%d]=[%.*s]\n", RES_NAMES(_r)[col], col, RES_NAMES(_r)[col]->len, RES_NAMES(_r)[col]->s); switch(fields[col].type) { case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: case MYSQL_TYPE_INT24: case MYSQL_TYPE_TIMESTAMP: LM_DBG("use DB1_INT result type\n"); RES_TYPES(_r)[col] = DB1_INT; break; case MYSQL_TYPE_LONGLONG: LM_DBG("use DB1_BIGINT result type\n"); RES_TYPES(_r)[col] = DB1_BIGINT; break; case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: LM_DBG("use DB1_DOUBLE result type\n"); RES_TYPES(_r)[col] = DB1_DOUBLE; break; case MYSQL_TYPE_DATETIME: LM_DBG("use DB1_DATETIME result type\n"); RES_TYPES(_r)[col] = DB1_DATETIME; break; case MYSQL_TYPE_BLOB: LM_DBG("use DB1_BLOB result type\n"); RES_TYPES(_r)[col] = DB1_BLOB; break; case FIELD_TYPE_SET: LM_DBG("use DB1_BITMAP result type\n"); RES_TYPES(_r)[col] = DB1_BITMAP; break; case MYSQL_TYPE_DECIMAL: #if MYSQL_VERSION_ID > 49999 case MYSQL_TYPE_NEWDECIMAL: #endif case MYSQL_TYPE_STRING: case MYSQL_TYPE_VAR_STRING: LM_DBG("use DB1_STRING result type\n"); RES_TYPES(_r)[col] = DB1_STRING; break; default: LM_WARN("unhandled data type column (%.*s) type id (%d), " "use DB1_STRING as default\n", RES_NAMES(_r)[col]->len, RES_NAMES(_r)[col]->s, fields[col].type); RES_TYPES(_r)[col] = DB1_STRING; break; } } return 0; }
/** * \brief Gets a partial result set, fetch rows from a result * * Gets a partial result set, fetch a number of rows from a database result. * This function initialize the given result structure on the first run, and * fetches the nrows number of rows. On subsequenting runs, it uses the * existing result and fetches more rows, until it reaches the end of the * result set. Because of this the result needs to be null in the first * invocation of the function. If the number of wanted rows is zero, the * function returns anything with a result of zero. * \param _h structure representing the database connection * \param _r pointer to a structure representing the result * \param nrows number of fetched rows * \return zero on success, negative value on failure */ int db_mysql_fetch_result(const db1_con_t* _h, db1_res_t** _r, const int nrows) { int rows, i, code; if (!_h || !_r || nrows < 0) { LM_ERR("Invalid parameter value\n"); return -1; } /* exit if the fetch count is zero */ if (nrows == 0) { db_mysql_free_result(_h, *_r); *_r = 0; return 0; } if(*_r==0) { /* Allocate a new result structure */ *_r = db_mysql_new_result(); if (*_r == 0) { LM_ERR("no memory left\n"); return -2; } RES_RESULT(*_r) = mysql_store_result(CON_CONNECTION(_h)); if (!RES_RESULT(*_r)) { if (mysql_field_count(CON_CONNECTION(_h)) == 0) { (*_r)->col.n = 0; (*_r)->n = 0; return 0; } else { LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h))); code = mysql_errno(CON_CONNECTION(_h)); if (code == CR_SERVER_GONE_ERROR || code == CR_SERVER_LOST) { counter_inc(mysql_cnts_h.driver_err); } db_mysql_free_result(_h, *_r); *_r = 0; return -3; } } if (db_mysql_get_columns(_h, *_r) < 0) { LM_ERR("error while getting column names\n"); return -4; } RES_NUM_ROWS(*_r) = mysql_num_rows(RES_RESULT(*_r)); if (!RES_NUM_ROWS(*_r)) { LM_DBG("no rows returned from the query\n"); RES_ROWS(*_r) = 0; return 0; } } else { /* free old rows */ if(RES_ROWS(*_r)!=0) db_free_rows(*_r); RES_ROWS(*_r) = 0; RES_ROW_N(*_r) = 0; } /* determine the number of rows remaining to be processed */ rows = RES_NUM_ROWS(*_r) - RES_LAST_ROW(*_r); /* If there aren't any more rows left to process, exit */ if(rows<=0) return 0; /* if the fetch count is less than the remaining rows to process */ /* set the number of rows to process (during this call) equal to the fetch count */ if(nrows < rows) rows = nrows; RES_ROW_N(*_r) = rows; LM_DBG("converting row %d of %d count %d\n", RES_LAST_ROW(*_r), RES_NUM_ROWS(*_r), RES_ROW_N(*_r)); RES_ROWS(*_r) = (struct db_row*)pkg_malloc(sizeof(db_row_t) * rows); if (!RES_ROWS(*_r)) { LM_ERR("no memory left\n"); return -5; } for(i = 0; i < rows; i++) { RES_ROW(*_r) = mysql_fetch_row(RES_RESULT(*_r)); if (!RES_ROW(*_r)) { LM_ERR("driver error: %s\n", mysql_error(CON_CONNECTION(_h))); RES_ROW_N(*_r) = i; db_free_rows(*_r); return -6; } if (db_mysql_convert_row(_h, *_r, &(RES_ROWS(*_r)[i])) < 0) { LM_ERR("error while converting row #%d\n", i); RES_ROW_N(*_r) = i; db_free_rows(*_r); return -7; } } /* update the total number of rows processed */ RES_LAST_ROW(*_r) += rows; return 0; }