SEXP rsqlite_query_send(SEXP handle, SEXP statement, SEXP bind_data) { SQLiteConnection* con = rsqlite_connection_from_handle(handle); sqlite3* db_connection = con->drvConnection; sqlite3_stmt* db_statement = NULL; int state, bind_count; int rows = 0, cols = 0; if (con->resultSet) { if (con->resultSet->completed != 1) warning("Closing result set with pending rows"); rsqlite_result_free(con); } rsqlite_result_alloc(con); SQLiteResult* res = con->resultSet; /* allocate and init a new result set */ res->completed = 0; char* dyn_statement = RS_DBI_copyString(CHAR(asChar(statement))); res->statement = dyn_statement; res->drvResultSet = db_statement; state = sqlite3_prepare_v2(db_connection, dyn_statement, -1, &db_statement, NULL); if (state != SQLITE_OK) { exec_error(con, "error in statement"); } if (db_statement == NULL) { exec_error(con, "nothing to execute"); } res->drvResultSet = (void*) db_statement; bind_count = sqlite3_bind_parameter_count(db_statement); if (bind_count > 0 && bind_data != R_NilValue) { rows = GET_LENGTH(GET_ROWNAMES(bind_data)); cols = GET_LENGTH(bind_data); } res->isSelect = sqlite3_column_count(db_statement) > 0; res->rowCount = 0; /* fake's cursor's row count */ res->rowsAffected = -1; /* no rows affected */ rsqlite_exception_set(con, state, "OK"); if (res->isSelect) { if (bind_count > 0) { select_prepared_query(db_statement, bind_data, bind_count, rows, con); } } else { if (bind_count > 0) { non_select_prepared_query(db_statement, bind_data, bind_count, rows, con); } else { state = sqlite3_step(db_statement); if (state != SQLITE_DONE) { exec_error(con, "rsqlite_query_send: could not execute1"); } } res->completed = 1; res->rowsAffected = sqlite3_changes(db_connection); } return handle; }
s_object * RS_PostgreSQL_CopyIn(Con_Handle * conHandle, s_object * filename) { S_EVALUATOR RS_DBI_connection * con; PGconn *my_connection; char *dyn_filename; char copybuf[COPY_IN_BUFSIZE]; FILE* filehandle; size_t len; int pqretcode; con = RS_DBI_getConnection(conHandle); my_connection = (PGconn *) con->drvConnection; dyn_filename = RS_DBI_copyString(CHR_EL(filename, 0)); filehandle=fopen(dyn_filename, "r"); if(filehandle == NULL){ char errmsg[1024]; snprintf(errmsg, 1024, "could not open file: %s", dyn_filename); RS_DBI_errorMessage(dyn_filename, RS_DBI_ERROR); return S_NULL_ENTRY; } while((len = fread(copybuf,1,COPY_IN_BUFSIZE, filehandle))){ pqretcode = PQputCopyData(my_connection, copybuf, len); chkpqcopydataerr(my_connection, pqretcode); } PQputCopyEnd(my_connection, NULL); fclose(filehandle); free(dyn_filename); return S_NULL_ENTRY; }
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; }
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; }
Con_Handle * RS_PostgreSQL_newConnection(Mgr_Handle * mgrHandle, s_object * con_params) { S_EVALUATOR RS_DBI_connection * con; RS_PostgreSQL_conParams *conParams; Con_Handle *conHandle; PGconn *my_connection; const char *user = NULL, *password = NULL, *host = NULL, *dbname = NULL, *port = NULL, *tty = NULL, *options = NULL; if (!is_validHandle(mgrHandle, MGR_HANDLE_TYPE)) { RS_DBI_errorMessage("invalid PostgreSQLManager", RS_DBI_ERROR); } user = CHR_EL(con_params, 0); password = CHR_EL(con_params, 1); host = CHR_EL(con_params, 2); dbname = CHR_EL(con_params, 3); port = CHR_EL(con_params, 4); tty = CHR_EL(con_params, 5); options = CHR_EL(con_params, 6); my_connection = PQsetdbLogin(host, port, options, tty, dbname, user, password); conParams = RS_postgresql_allocConParams(); /* save actual connection parameters */ conParams->user = RS_DBI_copyString(PQuser(my_connection)); conParams->password = RS_DBI_copyString(PQpass(my_connection)); { const char *tmphost = PQhost(my_connection); if (tmphost) { conParams->host = RS_DBI_copyString(tmphost); } else { conParams->host = RS_DBI_copyString(""); } } conParams->dbname = RS_DBI_copyString(PQdb(my_connection)); conParams->port = RS_DBI_copyString(PQport(my_connection)); conParams->tty = RS_DBI_copyString(PQtty(my_connection)); conParams->options = RS_DBI_copyString(PQoptions(my_connection)); if (PQstatus(my_connection) != CONNECTION_OK) { char buf[1000]; sprintf(buf, "could not connect %s@%s on dbname \"%s\"\n", PQuser(my_connection), host?host:"local", PQdb(my_connection)); PQfinish(my_connection); my_connection = NULL; RS_PostgreSQL_freeConParams(conParams); /*free BEFORE emitting err message that do not come back */ RS_DBI_errorMessage(buf, RS_DBI_ERROR); return R_NilValue; /* don't reach here as it goes back to R proc */ } PROTECT(conHandle = RS_DBI_allocConnection(mgrHandle, (Sint) 1)); /* The second argument (1) specifies the number of result sets allocated */ con = RS_DBI_getConnection(conHandle); if (my_connection && !con) { PQfinish(my_connection); my_connection = NULL; RS_PostgreSQL_freeConParams(conParams); conParams = (RS_PostgreSQL_conParams *) NULL; RS_DBI_errorMessage("could not alloc space for connection object", RS_DBI_ERROR); } if(con) { con->drvConnection = (void *) my_connection; con->conParams = (void *) conParams; } UNPROTECT(1); return conHandle; }
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; }
SEXP RS_PostgreSQL_pqexec(Con_Handle * conHandle, s_object * statement) { S_EVALUATOR RS_DBI_connection * con; SEXP retval; 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)); /* 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 = malloc(len + 80); /* 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); free(errMsg); } if (PQresultStatus(my_result) == PGRES_TUPLES_OK) { is_select = (Sint) TRUE; } if (PQresultStatus(my_result) == PGRES_COMMAND_OK) { is_select = (Sint) FALSE; } if (strcmp(PQresultErrorMessage(my_result), "") != 0) { free(dyn_statement); 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); } free(dyn_statement); PROTECT(retval = allocVector(LGLSXP, 1)); LOGICAL(retval)[0] = is_select; UNPROTECT(1); return retval; }
/* 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; }