s_object * RS_PostgreSQL_getResult(Con_Handle * conHandle) { S_EVALUATOR RS_DBI_connection * con; S_EVALUATOR RS_DBI_resultSet * result; PGconn *my_connection; Res_Handle *rsHandle; Sint res_id; PGresult *my_result; con = RS_DBI_getConnection(conHandle); my_connection = (PGconn *) con->drvConnection; if (con->num_res > 0) { res_id = (Sint) con->resultSetIds[0]; rsHandle = RS_DBI_asResHandle(MGR_ID(conHandle), CON_ID(conHandle), res_id); result = RS_DBI_getResultSet(rsHandle); if (result->completed == 0) { RS_DBI_errorMessage("connection with pending rows, close resultSet before continuing", RS_DBI_ERROR); } else { RS_PostgreSQL_closeResultSet(rsHandle); } } my_result = PQgetResult(my_connection); if(my_result == NULL) return S_NULL_ENTRY; if (strcmp(PQresultErrorMessage(my_result), "") != 0) { char *errResultMsg; const char *omsg; size_t len; omsg = PQerrorMessage(my_connection); len = strlen(omsg); errResultMsg = malloc(len + 80); /* 80 should be larger than the length of "could not ..."*/ snprintf(errResultMsg, len + 80, "could not Retrieve the result : %s", omsg); RS_DBI_errorMessage(errResultMsg, RS_DBI_ERROR); free(errResultMsg); /* Frees the storage associated with a PGresult. * void PQclear(PGresult *res); */ PQclear(my_result); } /* we now create the wrapper and copy values */ PROTECT(rsHandle = RS_DBI_allocResultSet(conHandle)); result = RS_DBI_getResultSet(rsHandle); result->drvResultSet = (void *) my_result; result->rowCount = (Sint) 0; result->isSelect = 0; result->rowsAffected = 0; result->completed = 1; UNPROTECT(1); return rsHandle; }
SEXP RS_MySQL_resultSetInfo(SEXP rsHandle) { RS_DBI_resultSet *result; SEXP output, flds; int n = 6; char *rsDesc[] = {"statement", "isSelect", "rowsAffected", "rowCount", "completed", "fieldDescription"}; SEXPTYPE rsType[] = {STRSXP, INTSXP, INTSXP, INTSXP, INTSXP, VECSXP}; int rsLen[] = {1, 1, 1, 1, 1, 1}; result = RS_DBI_getResultSet(rsHandle); flds = R_NilValue; output = RS_DBI_createNamedList(rsDesc, rsType, rsLen, n); SET_LST_CHR_EL(output,0,0,mkChar(result->statement)); LST_INT_EL(output,1,0) = result->isSelect; LST_INT_EL(output,2,0) = result->rowsAffected; LST_INT_EL(output,3,0) = result->rowCount; LST_INT_EL(output,4,0) = result->completed; if(flds != R_NilValue) SET_ELEMENT(LST_EL(output, 5), (int) 0, flds); return output; }
SEXP RS_MySQL_nextResultSet(SEXP conHandle) { RS_DBI_resultSet *result; SEXP rsHandle; int num_fields, is_select; RS_DBI_connection* con = RS_DBI_getConnection(conHandle); MYSQL* my_connection = con->drvConnection; int rc = mysql_next_result(my_connection); if (rc < 0) { error("no more result sets"); } else if (rc > 0){ error("error in getting next result set"); } /* the following comes verbatim from RS_MySQL_exec() */ MYSQL_RES* my_result = mysql_use_result(my_connection); if (!my_result) my_result = NULL; num_fields = mysql_field_count(my_connection); is_select = TRUE; if (!my_result) { if (num_fields > 0) { error("error in getting next result set"); } else { is_select = FALSE; } } /* we now create the wrapper and copy values */ rsHandle = RS_DBI_allocResultSet(conHandle); result = RS_DBI_getResultSet(rsHandle); result->statement = RS_DBI_copyString("<UNKNOWN>"); result->drvResultSet = (void *) my_result; result->rowCount = (int) 0; result->isSelect = is_select; if (!is_select){ result->rowsAffected = (int) mysql_affected_rows(my_connection); result->completed = 1; } else { result->rowsAffected = (int) -1; result->completed = 0; } if (is_select) result->fields = RS_MySQL_createDataMappings(rsHandle); return rsHandle; }
SEXP rmysql_fields_info(SEXP rsHandle) { RS_DBI_resultSet* result = RS_DBI_getResultSet(rsHandle); RMySQLFields* flds = result->fields; int n = flds->num_fields; // Allocate output SEXP output = PROTECT(allocVector(VECSXP, 4)); SEXP output_nms = PROTECT(allocVector(STRSXP, 4)); SET_NAMES(output, output_nms); UNPROTECT(1); SET_STRING_ELT(output_nms, 0, mkChar("name")); SEXP names = PROTECT(allocVector(STRSXP, n)); for (int j = 0; j < n; j++) { SET_STRING_ELT(names, j, mkChar(flds->name[j])); } SET_VECTOR_ELT(output, 0, names); UNPROTECT(1); SET_STRING_ELT(output_nms, 1, mkChar("Sclass")); SEXP sclass = PROTECT(allocVector(STRSXP, n)); for (int j = 0; j < n; j++) { const char* type = type2char(flds->Sclass[j]); SET_STRING_ELT(sclass, j, mkChar(type)); } SET_VECTOR_ELT(output, 1, sclass); UNPROTECT(1); SET_STRING_ELT(output_nms, 2, mkChar("type")); SEXP types = PROTECT(allocVector(STRSXP, n)); for (int j = 0; j < n; j++) { char* type = rmysql_type(flds->type[j]); SET_STRING_ELT(types, j, mkChar(type)); } SET_VECTOR_ELT(output, 2, types); UNPROTECT(1); SET_STRING_ELT(output_nms, 3, mkChar("length")); SEXP lens = PROTECT(allocVector(INTSXP, n)); for (int j = 0; j < n; j++) { INTEGER(lens)[j] = flds->length[j]; } SET_VECTOR_ELT(output, 3, lens); UNPROTECT(1); UNPROTECT(1); return output; }
void RS_DBI_freeResultSet(SEXP rsHandle) { RS_DBI_connection* con = RS_DBI_getConnection(rsHandle); RS_DBI_resultSet* result = RS_DBI_getResultSet(rsHandle); if(result->drvResultSet) { error("internal error in RS_DBI_freeResultSet: non-freed result->drvResultSet (some memory leaked)"); } if (result->statement) free(result->statement); if (result->fields) rmysql_fields_free(result->fields); free(result); result = NULL; /* update connection's resultSet table */ int indx = RS_DBI_lookup(con->resultSetIds, con->length, RES_ID(rsHandle)); RS_DBI_freeEntry(con->resultSetIds, indx); con->resultSets[indx] = NULL; con->num_res -= 1; }
SEXP RS_MySQL_closeResultSet(SEXP resHandle) { RS_DBI_resultSet *result; MYSQL_RES *my_result; result = RS_DBI_getResultSet(resHandle); my_result = (MYSQL_RES *) result->drvResultSet; if(my_result){ // we need to flush any possibly remaining rows (see Manual Ch 20 p358) MYSQL_ROW row; while((row = mysql_fetch_row(result->drvResultSet))) ; } mysql_free_result(my_result); // need to NULL drvResultSet, otherwise can't free the rsHandle result->drvResultSet = (void *) NULL; RS_DBI_freeResultSet(resHandle); return ScalarLogical(TRUE); }
RS_DBI_fields * RS_PostgreSQL_createDataMappings(Res_Handle * rsHandle) { PGresult *my_result; RS_DBI_connection *con; RS_DBI_resultSet *result; RS_DBI_fields *flds; int j, num_fields, internal_type; char errMsg[128]; result = RS_DBI_getResultSet(rsHandle); my_result = (PGresult *) result->drvResultSet; con = RS_DBI_getConnection(rsHandle); num_fields = PQnfields(my_result); flds = RS_DBI_allocFields(num_fields); /* this returns malloced data (not from R) */ char buff[1000]; /* Buffer to hold the sql query to check whether the given column is nullable */ PGconn *conn; PGresult *res; conn = (PGconn *) con->drvConnection; for (j = 0; j < num_fields; j++) { flds->name[j] = RS_DBI_copyString(PQfname(my_result, j)); flds->type[j] = (int) PQftype(my_result, j); flds->length[j] = (Sint) PQfsize(my_result, j); /* NOTE: PQfmod is -1 incase of no information */ flds->precision[j] = (Sint) PQfmod(my_result, j); flds->scale[j] = (Sint) - 1; /* PQftablecol returns the column number (within its table) of * the column making up the specified query result column.Zero * is returned if the column number is out of range, or if the * specified column is not a simple reference to a table * column, or when using pre-3.0 protocol. So * "if(PQftablecol(my_result,j) !=0)" checks whether the * particular colomn in the result set is column of table or * not. Or else there is no meaning in checking whether a * column is nullable or not if it does not belong to the * table. */ flds->nullOk[j] = (Sint) INT_MIN; /* This should translate to NA in R */ if (PQftablecol(my_result, j) != 0) { /* Code to find whether a row can be nullable or not */ /* we might better just store the table id and column number for lazy evaluation at dbColumnInfo call*/ /* although the database structure can change, we are not in transaction anyway and there is no guarantee in current code */ snprintf(buff, 1000, "select attnotnull from pg_attribute where attrelid=%d and attnum='%d'", PQftable(my_result, j), PQftablecol(my_result, j)); res = PQexec(conn, buff); if (res && (PQntuples(res) > 0)) { const char * attnotnull = PQgetvalue(res, 0, 0); if(strcmp(attnotnull, "f") == 0) { flds->nullOk[j] = (Sint) 1; /* nollOK is TRUE when attnotnull is f*/ } if(strcmp(attnotnull, "t") == 0) { flds->nullOk[j] = (Sint) 0; /* nollOK is FALSE when attnotnull is t*/ } } PQclear(res); } internal_type = (int) PQftype(my_result, j); switch (internal_type) { case BOOLOID: flds->Sclass[j] = LOGICAL_TYPE; break; case BPCHAROID: flds->Sclass[j] = CHARACTER_TYPE; flds->isVarLength[j] = (Sint) 0; break; case VARCHAROID: case TEXTOID: case BYTEAOID: case NAMEOID: case MACADDROID: case INETOID: flds->Sclass[j] = CHARACTER_TYPE; flds->isVarLength[j] = (Sint) 1; break; case INT2OID: case INT4OID: case OIDOID: flds->Sclass[j] = INTEGER_TYPE; break; case INT8OID: if (sizeof(Sint) >= 8) { flds->Sclass[j] = INTEGER_TYPE; } else { flds->Sclass[j] = NUMERIC_TYPE; } break; case NUMERICOID: case FLOAT8OID: case FLOAT4OID: flds->Sclass[j] = NUMERIC_TYPE; break; case DATEOID: case TIMEOID: case TIMETZOID: case TIMESTAMPOID: case TIMESTAMPTZOID: case INTERVALOID: flds->Sclass[j] = CHARACTER_TYPE; /*flds->isVarLength[j] = (Sint) 1; */ break; default: flds->Sclass[j] = CHARACTER_TYPE; flds->isVarLength[j] = (Sint) 1; snprintf(buff, 1000, "select typname, typcategory from pg_type where oid = %d", internal_type); res = PQexec(conn, buff); if (res) { char * typename; char * typecat; int ntuples; ntuples = PQntuples(res); if(ntuples == 1) { typename = PQgetvalue(res, 0, 0); typecat = PQgetvalue(res, 0, 1); if(*typecat == 'E') { /* This is enum, ok */ } else if(*typecat == 'A') { /*This is array, ok */ } else { snprintf(errMsg, 128, "unrecognized PostgreSQL field type %s (id:%d) in column %d", typename, internal_type, j); RS_DBI_errorMessage(errMsg, RS_DBI_WARNING); } } else { snprintf(errMsg, 128, "oid: %d, ntuples: %d", internal_type, ntuples); RS_DBI_errorMessage(errMsg, RS_DBI_WARNING); } PQclear(res); } else {
Res_Handle * RS_PostgreSQL_exec(Con_Handle * conHandle, s_object * statement) { S_EVALUATOR RS_DBI_connection * con; Res_Handle *rsHandle; RS_DBI_resultSet *result; PGconn *my_connection; PGresult *my_result; Sint res_id, is_select=0; char *dyn_statement; con = RS_DBI_getConnection(conHandle); my_connection = (PGconn *) con->drvConnection; dyn_statement = RS_DBI_copyString(CHR_EL(statement, 0)); /* Do we have a pending resultSet in the current connection? * PostgreSQL only allows one resultSet per connection. */ if (con->num_res > 0) { res_id = (Sint) con->resultSetIds[0]; /* recall, PostgreSQL has only 1 res */ rsHandle = RS_DBI_asResHandle(MGR_ID(conHandle), CON_ID(conHandle), res_id); result = RS_DBI_getResultSet(rsHandle); if (result->completed == 0) { free(dyn_statement); RS_DBI_errorMessage("connection with pending rows, close resultSet before continuing", RS_DBI_ERROR); } else { RS_PostgreSQL_closeResultSet(rsHandle); } } /* Here is where we actually run the query */ /* Example: PGresult *PQexec(PGconn *conn, const char *command); */ my_result = PQexec(my_connection, dyn_statement); if (my_result == NULL) { char *errMsg; const char *omsg; size_t len; omsg = PQerrorMessage(my_connection); len = strlen(omsg); free(dyn_statement); errMsg = R_alloc(len + 80, 1); /* 80 should be larger than the length of "could not ..."*/ snprintf(errMsg, len + 80, "could not run statement: %s", omsg); RS_DBI_errorMessage(errMsg, RS_DBI_ERROR); } /* ExecStatusType PQresultStatus(const PGresult *res); */ if (PQresultStatus(my_result) == PGRES_TUPLES_OK) { is_select = (Sint) TRUE; } if (PQresultStatus(my_result) == PGRES_COMMAND_OK) { is_select = (Sint) FALSE; } /* char *PQresultErrorMessage(const PGresult *res); */ if (strcmp(PQresultErrorMessage(my_result), "") != 0) { char *errResultMsg; const char *omsg; size_t len; omsg = PQerrorMessage(my_connection); len = strlen(omsg); errResultMsg = R_alloc(len + 80, 1); /* 80 should be larger than the length of "could not ..."*/ snprintf(errResultMsg, len + 80, "could not Retrieve the result : %s", omsg); /* Frees the storage associated with a PGresult. * void PQclear(PGresult *res); */ PQclear(my_result); free(dyn_statement); RS_DBI_errorMessage(errResultMsg, RS_DBI_ERROR); } /* we now create the wrapper and copy values */ PROTECT(rsHandle = RS_DBI_allocResultSet(conHandle)); result = RS_DBI_getResultSet(rsHandle); result->statement = RS_DBI_copyString(dyn_statement); result->drvResultSet = (void *) my_result; result->rowCount = (Sint) 0; result->isSelect = is_select; /* Returns the number of rows affected by the SQL command. * char *PQcmdTuples(PGresult *res); */ if (!is_select) { result->rowsAffected = (Sint) atoi(PQcmdTuples(my_result)); result->completed = 1; } else { result->rowsAffected = (Sint) - 1; result->completed = 0; } if (is_select) { result->fields = RS_PostgreSQL_createDataMappings(rsHandle); } free(dyn_statement); UNPROTECT(1); return rsHandle; }
RMySQLFields* RS_MySQL_createDataMappings(SEXP rsHandle) { // Fetch MySQL field descriptions RS_DBI_resultSet* result = RS_DBI_getResultSet(rsHandle); MYSQL_RES* my_result = result->drvResultSet; MYSQL_FIELD* select_dp = mysql_fetch_fields(my_result); int num_fields = mysql_num_fields(my_result); // Allocate memory for output object RMySQLFields* flds = malloc(sizeof(RMySQLFields)); if (!flds) { error("Could not allocate memory for database fields"); } flds->num_fields = num_fields; flds->name = calloc(num_fields, sizeof(char *)); flds->type = calloc(num_fields, sizeof(int)); flds->length = calloc(num_fields, sizeof(int)); flds->precision = calloc(num_fields, sizeof(int)); flds->scale = calloc(num_fields, sizeof(int)); flds->nullOk = calloc(num_fields, sizeof(int)); flds->isVarLength = calloc(num_fields, sizeof(int)); flds->Sclass = calloc(num_fields, sizeof(SEXPTYPE)); /* WARNING: TEXT fields are represented as BLOBS (sic), * not VARCHAR or some kind of string type. More troublesome is the * fact that a TEXT fields can be BINARY to indicate case-sensitivity. * The bottom line is that MySQL has a serious deficiency re: text * types (IMHO). A binary object (in SQL92, X/SQL at least) can * store all kinds of non-ASCII, non-printable stuff that can * potentially screw up S and R CHARACTER_TYPE. We are on thin ice. * * I'm aware that I'm introducing a potential bug here by following * the MySQL convention of treating BLOB's as TEXT (I'm symplifying * in order to properly handle commonly-found TEXT fields, at the * risk of core dumping when bona fide Binary objects are being * retrieved. * * Possible workaround: if strlen() of the field equals the * MYSQL_FIELD->length for all rows, then we are probably(?) safe * in considering TEXT a character type (non-binary). */ for (int j = 0; j < num_fields; j++){ /* First, save the name, MySQL internal field name, type, length, etc. */ flds->name[j] = RS_DBI_copyString(select_dp[j].name); flds->type[j] = select_dp[j].type; /* recall that these are enum*/ flds->length[j] = select_dp[j].length; flds->precision[j] = select_dp[j].length; flds->scale[j] = select_dp[j].decimals; flds->nullOk[j] = (!IS_NOT_NULL(select_dp[j].flags)); int internal_type = select_dp[j].type; switch(internal_type) { case FIELD_TYPE_VAR_STRING: case FIELD_TYPE_STRING: flds->Sclass[j] = STRSXP; flds->isVarLength[j] = (int) 1; break; case FIELD_TYPE_TINY: /* 1-byte TINYINT */ case FIELD_TYPE_SHORT: /* 2-byte SMALLINT */ case FIELD_TYPE_INT24: /* 3-byte MEDIUMINT */ flds->Sclass[j] = INTSXP; case FIELD_TYPE_LONG: /* 4-byte INTEGER */ /* if unsigned, turn into numeric (may be too large for ints/long)*/ if(select_dp[j].flags & UNSIGNED_FLAG) { warning("Unsigned INTEGER in col %d imported as numeric", j); flds->Sclass[j] = REALSXP; } else { flds->Sclass[j] = INTSXP; } break; case FIELD_TYPE_LONGLONG: /* 8-byte BIGINT */ flds->Sclass[j] = REALSXP; break; #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50003 /* 5.0.3 */ case FIELD_TYPE_BIT: if(flds->precision[j] <= sizeof(int)) { /* can R int hold the bytes? */ flds->Sclass[j] = INTSXP; } else { flds->Sclass[j] = STRSXP; warning( "BIT field in column %d too long (%d bits) for an R integer (imported as character)", j+1, flds->precision[j] ); } break; #endif flds->Sclass[j] = REALSXP; break; case FIELD_TYPE_DECIMAL: #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50003 /* 5.0.3 */ case FIELD_TYPE_NEWDECIMAL: #endif warning("Decimal MySQL column %d imported as numeric", j); flds->Sclass[j] = REALSXP; break; case FIELD_TYPE_FLOAT: case FIELD_TYPE_DOUBLE: flds->Sclass[j] = REALSXP; break; case FIELD_TYPE_BLOB: /* TODO: how should we bring large ones*/ case FIELD_TYPE_TINY_BLOB: case FIELD_TYPE_MEDIUM_BLOB: case FIELD_TYPE_LONG_BLOB: flds->Sclass[j] = STRSXP; /* Grr! Hate this! */ flds->isVarLength[j] = (int) 1; break; case FIELD_TYPE_DATE: case FIELD_TYPE_TIME: case FIELD_TYPE_DATETIME: case FIELD_TYPE_YEAR: case FIELD_TYPE_NEWDATE: flds->Sclass[j] = STRSXP; flds->isVarLength[j] = (int) 1; break; case FIELD_TYPE_ENUM: flds->Sclass[j] = STRSXP; /* see the MySQL ref. manual */ flds->isVarLength[j] = (int) 1; break; case FIELD_TYPE_SET: flds->Sclass[j] = STRSXP; flds->isVarLength[j] = (int) 0; break; default: flds->Sclass[j] = STRSXP; flds->isVarLength[j] = (int) 1; warning("unrecognized MySQL field type %d in column %d imported as character", internal_type, j); break; } } return flds; }
// output is a named list SEXP RS_MySQL_fetch(SEXP rsHandle, SEXP max_rec) { MySQLDriver *mgr; RS_DBI_resultSet *result; RMySQLFields* flds; MYSQL_RES *my_result; MYSQL_ROW row; SEXP output, s_tmp; unsigned long *lens; int i, j, null_item, expand; int completed; SEXPTYPE *fld_Sclass; int num_rec; int num_fields; result = RS_DBI_getResultSet(rsHandle); flds = result->fields; if(!flds) error("corrupt resultSet, missing fieldDescription"); num_rec = asInteger(max_rec); expand = (num_rec < 0); // dyn expand output to accommodate all rows if(expand || num_rec == 0){ mgr = rmysql_driver(); num_rec = mgr->fetch_default_rec; } num_fields = flds->num_fields; PROTECT(output = NEW_LIST((int) num_fields)); RS_DBI_allocOutput(output, flds, num_rec, 0); fld_Sclass = flds->Sclass; // actual fetching.... my_result = (MYSQL_RES *) result->drvResultSet; completed = (int) 0; for(i = 0; ; i++){ if(i==num_rec){ // exhausted the allocated space if(expand){ // do we extend or return the records fetched so far num_rec = 2 * num_rec; RS_DBI_allocOutput(output, flds, num_rec, expand); } else break; // okay, no more fetching for now } row = mysql_fetch_row(my_result); if(row==NULL){ // either we finish or we encounter an error unsigned int err_no; RS_DBI_connection *con; con = RS_DBI_getConnection(rsHandle); err_no = mysql_errno((MYSQL *) con->drvConnection); completed = (int) (err_no ? -1 : 1); break; } lens = mysql_fetch_lengths(my_result); for(j = 0; j < num_fields; j++){ null_item = (row[j] == NULL); switch((int)fld_Sclass[j]){ case INTSXP: if(null_item) NA_SET(&(LST_INT_EL(output,j,i)), INTSXP); else LST_INT_EL(output,j,i) = (int) atol(row[j]); break; case STRSXP: // BUG: I need to verify that a TEXT field (which is stored as // a BLOB by MySQL!) is indeed char and not a true // Binary obj (MySQL does not truly distinguish them). This // test is very gross. if(null_item) SET_LST_CHR_EL(output,j,i,NA_STRING); else { if((size_t) lens[j] != strlen(row[j])){ warning("internal error: row %d field %d truncated", i, j); } SET_LST_CHR_EL(output,j,i,mkChar(row[j])); } break; case REALSXP: if(null_item) NA_SET(&(LST_NUM_EL(output,j,i)), REALSXP); else LST_NUM_EL(output,j,i) = (double) atof(row[j]); break; default: // error, but we'll try the field as character (!) if(null_item) SET_LST_CHR_EL(output,j,i, NA_STRING); else { warning("unrecognized field type %d in column %d", fld_Sclass[j], j); SET_LST_CHR_EL(output,j,i,mkChar(row[j])); } break; } } } // actual number of records fetched if(i < num_rec){ num_rec = i; // adjust the length of each of the members in the output_list for(j = 0; j<num_fields; j++){ s_tmp = LST_EL(output,j); PROTECT(SET_LENGTH(s_tmp, num_rec)); SET_ELEMENT(output, j, s_tmp); UNPROTECT(1); } } if(completed < 0) warning("error while fetching rows"); result->rowCount += num_rec; result->completed = (int) completed; UNPROTECT(1); return output; }
/* Execute (currently) one sql statement (INSERT, DELETE, SELECT, etc.), * set coercion type mappings between the server internal data types and * S classes. Returns an S handle to a resultSet object. */ SEXP RS_MySQL_exec(SEXP conHandle, SEXP statement) { RS_DBI_connection *con; SEXP rsHandle; RS_DBI_resultSet *result; MYSQL *my_connection; MYSQL_RES *my_result; int num_fields, state; int res_id, is_select; char *dyn_statement; con = RS_DBI_getConnection(conHandle); my_connection = (MYSQL *) con->drvConnection; dyn_statement = RS_DBI_copyString(CHR_EL(statement,0)); /* Do we have a pending resultSet in the current connection? * MySQL only allows one resultSet per connection. */ if(con->num_res>0){ res_id = (int) con->resultSetIds[0]; /* recall, MySQL has only 1 res */ rsHandle = RS_DBI_asResHandle(MGR_ID(conHandle), CON_ID(conHandle), res_id); result = RS_DBI_getResultSet(rsHandle); if(result->completed == 0){ free(dyn_statement); error("connection with pending rows, close resultSet before continuing"); } else RS_MySQL_closeResultSet(rsHandle); } /* Here is where we actually run the query */ state = mysql_query(my_connection, dyn_statement); if(state) { error("could not run statement: %s", mysql_error(my_connection)); } /* Do we need output column/field descriptors? Only for SELECT-like * statements. The MySQL reference manual suggests invoking * mysql_use_result() and if it succeed the statement is SELECT-like * that can use a resultSet. Otherwise call mysql_field_count() * and if it returns zero, the sql was not a SELECT-like statement. * Finally a non-zero means a failed SELECT-like statement. */ my_result = mysql_use_result(my_connection); if(!my_result) my_result = (MYSQL_RES *) NULL; num_fields = (int) mysql_field_count(my_connection); is_select = (int) TRUE; if(!my_result){ if(num_fields>0){ free(dyn_statement); error("error in select/select-like"); } else is_select = FALSE; } /* we now create the wrapper and copy values */ rsHandle = RS_DBI_allocResultSet(conHandle); result = RS_DBI_getResultSet(rsHandle); result->statement = RS_DBI_copyString(dyn_statement); result->drvResultSet = (void *) my_result; result->rowCount = (int) 0; result->isSelect = is_select; if(!is_select){ result->rowsAffected = (int) mysql_affected_rows(my_connection); result->completed = 1; } else { result->rowsAffected = (int) -1; result->completed = 0; } if(is_select) result->fields = RS_MySQL_createDataMappings(rsHandle); free(dyn_statement); return rsHandle; }