/* Module: results.c * * Description: This module contains functions related to * retrieving result information through the ODBC API. * * Classes: n/a * * API functions: SQLRowCount, SQLNumResultCols, SQLDescribeCol, SQLColAttributes, * SQLGetData, SQLFetch, SQLExtendedFetch, * SQLMoreResults(NI), SQLSetPos, SQLSetScrollOptions(NI), * SQLSetCursorName, SQLGetCursorName * * Comments: See "notice.txt" for copyright and license information. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <string.h> #include "psqlodbc.h" #include "dlg_specific.h" #include "environ.h" #include "connection.h" #include "statement.h" #include "bind.h" #include "qresult.h" #include "convert.h" #include "pgtypes.h" #include <stdio.h> #ifndef WIN32 #include "iodbc.h" #include "isqlext.h" #else #include <windows.h> #include <sqlext.h> #endif extern GLOBAL_VALUES globals; RETCODE SQL_API SQLRowCount( HSTMT hstmt, SDWORD FAR *pcrow) { static char *func = "SQLRowCount"; StatementClass *stmt = (StatementClass *) hstmt; QResultClass *res; char *msg, *ptr; if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } if (stmt->manual_result) { if (pcrow) *pcrow = -1; return SQL_SUCCESS; } if (stmt->statement_type == STMT_TYPE_SELECT) { if (stmt->status == STMT_FINISHED) { res = SC_get_Result(stmt); if (res && pcrow) { *pcrow = globals.use_declarefetch ? -1 : QR_get_num_tuples(res); return SQL_SUCCESS; } } } else { res = SC_get_Result(stmt); if (res && pcrow) { msg = QR_get_command(res); mylog("*** msg = '%s'\n", msg); trim(msg); /* get rid of trailing spaces */ ptr = strrchr(msg, ' '); if (ptr) { *pcrow = atoi(ptr + 1); mylog("**** SQLRowCount(): THE ROWS: *pcrow = %d\n", *pcrow); } else { *pcrow = -1; mylog("**** SQLRowCount(): NO ROWS: *pcrow = %d\n", *pcrow); } return SQL_SUCCESS; } } SC_log_error(func, "Bad return value", stmt); return SQL_ERROR; } /* This returns the number of columns associated with the database */ /* attached to "hstmt". */ RETCODE SQL_API SQLNumResultCols( HSTMT hstmt, SWORD FAR *pccol) { static char *func = "SQLNumResultCols"; StatementClass *stmt = (StatementClass *) hstmt; QResultClass *result; char parse_ok; if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } SC_clear_error(stmt); parse_ok = FALSE; if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT) { if (stmt->parse_status == STMT_PARSE_NONE) { mylog("SQLNumResultCols: calling parse_statement on stmt=%u\n", stmt); parse_statement(stmt); } if (stmt->parse_status != STMT_PARSE_FATAL) { parse_ok = TRUE; *pccol = stmt->nfld; mylog("PARSE: SQLNumResultCols: *pccol = %d\n", *pccol); } } if (!parse_ok) { SC_pre_execute(stmt); result = SC_get_Result(stmt); mylog("SQLNumResultCols: result = %u, status = %d, numcols = %d\n", result, stmt->status, result != NULL ? QR_NumResultCols(result) : -1); if ((!result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) { /* no query has been executed on this statement */ stmt->errornumber = STMT_SEQUENCE_ERROR; stmt->errormsg = "No query has been executed with that handle"; SC_log_error(func, "", stmt); return SQL_ERROR; } *pccol = QR_NumResultCols(result); } return SQL_SUCCESS; } /* - - - - - - - - - */ /* Return information about the database column the user wants */ /* information about. */ RETCODE SQL_API SQLDescribeCol( HSTMT hstmt, UWORD icol, UCHAR FAR *szColName, SWORD cbColNameMax, SWORD FAR *pcbColName, SWORD FAR *pfSqlType, UDWORD FAR *pcbColDef, SWORD FAR *pibScale, SWORD FAR *pfNullable) { static char *func = "SQLDescribeCol"; /* gets all the information about a specific column */ StatementClass *stmt = (StatementClass *) hstmt; QResultClass *res; char *col_name = NULL; Int4 fieldtype = 0; int precision = 0; ConnInfo *ci; char parse_ok; char buf[255]; int len = 0; RETCODE result; mylog("%s: entering...\n", func); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } ci = &(stmt->hdbc->connInfo); SC_clear_error(stmt); /* * Dont check for bookmark column. This is the responsibility of the * driver manager. */ icol--; /* use zero based column numbers */ parse_ok = FALSE; if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT) { if (stmt->parse_status == STMT_PARSE_NONE) { mylog("SQLDescribeCol: calling parse_statement on stmt=%u\n", stmt); parse_statement(stmt); } mylog("PARSE: DescribeCol: icol=%d, stmt=%u, stmt->nfld=%d, stmt->fi=%u\n", icol, stmt, stmt->nfld, stmt->fi); if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol]) { if (icol >= stmt->nfld) { stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; stmt->errormsg = "Invalid column number in DescribeCol."; SC_log_error(func, "", stmt); return SQL_ERROR; } mylog("DescribeCol: getting info for icol=%d\n", icol); fieldtype = stmt->fi[icol]->type; col_name = stmt->fi[icol]->name; precision = stmt->fi[icol]->precision; mylog("PARSE: fieldtype=%d, col_name='%s', precision=%d\n", fieldtype, col_name, precision); if (fieldtype > 0) parse_ok = TRUE; } } /* * If couldn't parse it OR the field being described was not parsed * (i.e., because it was a function or expression, etc, then do it the * old fashioned way. */ if (!parse_ok) { SC_pre_execute(stmt); res = SC_get_Result(stmt); mylog("**** SQLDescribeCol: res = %u, stmt->status = %d, !finished=%d, !premature=%d\n", res, stmt->status, stmt->status != STMT_FINISHED, stmt->status != STMT_PREMATURE); if ((NULL == res) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) { /* no query has been executed on this statement */ stmt->errornumber = STMT_SEQUENCE_ERROR; stmt->errormsg = "No query has been assigned to this statement."; SC_log_error(func, "", stmt); return SQL_ERROR; } if (icol >= QR_NumResultCols(res)) { stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; stmt->errormsg = "Invalid column number in DescribeCol."; sprintf(buf, "Col#=%d, #Cols=%d", icol, QR_NumResultCols(res)); SC_log_error(func, buf, stmt); return SQL_ERROR; } col_name = QR_get_fieldname(res, icol); fieldtype = QR_get_field_type(res, icol); precision = pgtype_precision(stmt, fieldtype, icol, globals.unknown_sizes); /* atoi(ci->unknown_sizes * ) */ } mylog("describeCol: col %d fieldname = '%s'\n", icol, col_name); mylog("describeCol: col %d fieldtype = %d\n", icol, fieldtype); mylog("describeCol: col %d precision = %d\n", icol, precision); result = SQL_SUCCESS; /************************/ /* COLUMN NAME */ /************************/ len = strlen(col_name); if (pcbColName) *pcbColName = len; if (szColName) { strncpy_null(szColName, col_name, cbColNameMax); if (len >= cbColNameMax) { result = SQL_SUCCESS_WITH_INFO; stmt->errornumber = STMT_TRUNCATED; stmt->errormsg = "The buffer was too small for the result."; } } /************************/ /* SQL TYPE */ /************************/ if (pfSqlType) { *pfSqlType = pgtype_to_sqltype(stmt, fieldtype); mylog("describeCol: col %d *pfSqlType = %d\n", icol, *pfSqlType); } /************************/ /* PRECISION */ /************************/ if (pcbColDef) { if (precision < 0) precision = 0; /* "I dont know" */ *pcbColDef = precision; mylog("describeCol: col %d *pcbColDef = %d\n", icol, *pcbColDef); } /************************/ /* SCALE */ /************************/ if (pibScale) { Int2 scale; scale = pgtype_scale(stmt, fieldtype, icol); if (scale == -1) scale = 0; *pibScale = scale; mylog("describeCol: col %d *pibScale = %d\n", icol, *pibScale); } /************************/ /* NULLABILITY */ /************************/ if (pfNullable) { *pfNullable = (parse_ok) ? stmt->fi[icol]->nullable : pgtype_nullable(stmt, fieldtype); mylog("describeCol: col %d *pfNullable = %d\n", icol, *pfNullable); } return result; } /* Returns result column descriptor information for a result set. */ RETCODE SQL_API SQLColAttributes( HSTMT hstmt, UWORD icol, UWORD fDescType, PTR rgbDesc, SWORD cbDescMax, SWORD FAR *pcbDesc, SDWORD FAR *pfDesc) { static char *func = "SQLColAttributes"; StatementClass *stmt = (StatementClass *) hstmt; Int4 field_type = 0; ConnInfo *ci; int unknown_sizes; int cols = 0; char parse_ok; RETCODE result; char *p = NULL; int len = 0, value = 0; mylog("%s: entering...\n", func); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } ci = &(stmt->hdbc->connInfo); /* * Dont check for bookmark column. This is the responsibility of the * driver manager. For certain types of arguments, the column number * is ignored anyway, so it may be 0. */ icol--; unknown_sizes = globals.unknown_sizes; /* atoi(ci->unknown_sizes); * */ if (unknown_sizes == UNKNOWNS_AS_DONTKNOW) /* not appropriate for * SQLColAttributes() */ unknown_sizes = UNKNOWNS_AS_MAX; parse_ok = FALSE; if (globals.parse && stmt->statement_type == STMT_TYPE_SELECT) { if (stmt->parse_status == STMT_PARSE_NONE) { mylog("SQLColAttributes: calling parse_statement\n"); parse_statement(stmt); } cols = stmt->nfld; /* * Column Count is a special case. The Column number is ignored * in this case. */ if (fDescType == SQL_COLUMN_COUNT) { if (pfDesc) *pfDesc = cols; return SQL_SUCCESS; } if (stmt->parse_status != STMT_PARSE_FATAL && stmt->fi && stmt->fi[icol]) { if (icol >= cols) { stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; stmt->errormsg = "Invalid column number in ColAttributes."; SC_log_error(func, "", stmt); return SQL_ERROR; } field_type = stmt->fi[icol]->type; if (field_type > 0) parse_ok = TRUE; } } if (!parse_ok) { SC_pre_execute(stmt); mylog("**** SQLColAtt: result = %u, status = %d, numcols = %d\n", stmt->result, stmt->status, stmt->result != NULL ? QR_NumResultCols(stmt->result) : -1); if ((NULL == stmt->result) || ((stmt->status != STMT_FINISHED) && (stmt->status != STMT_PREMATURE))) { stmt->errormsg = "Can't get column attributes: no result found."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } cols = QR_NumResultCols(stmt->result); /* * Column Count is a special case. The Column number is ignored * in this case. */ if (fDescType == SQL_COLUMN_COUNT) { if (pfDesc) *pfDesc = cols; return SQL_SUCCESS; } if (icol >= cols) { stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; stmt->errormsg = "Invalid column number in ColAttributes."; SC_log_error(func, "", stmt); return SQL_ERROR; } field_type = QR_get_field_type(stmt->result, icol); } mylog("colAttr: col %d field_type = %d\n", icol, field_type); switch (fDescType) { case SQL_COLUMN_AUTO_INCREMENT: value = pgtype_auto_increment(stmt, field_type); if (value == -1) /* non-numeric becomes FALSE (ODBC Doc) */ value = FALSE; break; case SQL_COLUMN_CASE_SENSITIVE: value = pgtype_case_sensitive(stmt, field_type); break; /* * This special case is handled above. * * case SQL_COLUMN_COUNT: */ case SQL_COLUMN_DISPLAY_SIZE: value = (parse_ok) ? stmt->fi[icol]->display_size : pgtype_display_size(stmt, field_type, icol, unknown_sizes); mylog("SQLColAttributes: col %d, display_size= %d\n", icol, value); break; case SQL_COLUMN_LABEL: if (parse_ok && stmt->fi[icol]->alias[0] != '\0') { p = stmt->fi[icol]->alias; mylog("SQLColAttr: COLUMN_LABEL = '%s'\n", p); break; } /* otherwise same as column name -- FALL * THROUGH!!! */ case SQL_COLUMN_NAME: p = (parse_ok) ? stmt->fi[icol]->name : QR_get_fieldname(stmt->result, icol); mylog("SQLColAttr: COLUMN_NAME = '%s'\n", p); break; case SQL_COLUMN_LENGTH: value = (parse_ok) ? stmt->fi[icol]->length : pgtype_length(stmt, field_type, icol, unknown_sizes); mylog("SQLColAttributes: col %d, length = %d\n", icol, value); break; case SQL_COLUMN_MONEY: value = pgtype_money(stmt, field_type); break; case SQL_COLUMN_NULLABLE: value = (parse_ok) ? stmt->fi[icol]->nullable : pgtype_nullable(stmt, field_type); break; case SQL_COLUMN_OWNER_NAME: p = ""; break; case SQL_COLUMN_PRECISION: value = (parse_ok) ? stmt->fi[icol]->precision : pgtype_precision(stmt, field_type, icol, unknown_sizes); mylog("SQLColAttributes: col %d, precision = %d\n", icol, value); break; case SQL_COLUMN_QUALIFIER_NAME: p = ""; break; case SQL_COLUMN_SCALE: value = pgtype_scale(stmt, field_type, icol); break; case SQL_COLUMN_SEARCHABLE: value = pgtype_searchable(stmt, field_type); break; case SQL_COLUMN_TABLE_NAME: p = (parse_ok && stmt->fi[icol]->ti) ? stmt->fi[icol]->ti->name : ""; mylog("SQLColAttr: TABLE_NAME = '%s'\n", p); break; case SQL_COLUMN_TYPE: value = pgtype_to_sqltype(stmt, field_type); break; case SQL_COLUMN_TYPE_NAME: p = pgtype_to_name(stmt, field_type); break; case SQL_COLUMN_UNSIGNED: value = pgtype_unsigned(stmt, field_type); if (value == -1) /* non-numeric becomes TRUE (ODBC Doc) */ value = TRUE; break; case SQL_COLUMN_UPDATABLE: /* * Neither Access or Borland care about this. * * if (field_type == PG_TYPE_OID) pfDesc = SQL_ATTR_READONLY; * else */ value = SQL_ATTR_WRITE; mylog("SQLColAttr: UPDATEABLE = %d\n", value); break; } result = SQL_SUCCESS; if (p) { /* char/binary data */ len = strlen(p); if (rgbDesc) { strncpy_null((char *) rgbDesc, p, (size_t) cbDescMax); if (len >= cbDescMax) { result = SQL_SUCCESS_WITH_INFO; stmt->errornumber = STMT_TRUNCATED; stmt->errormsg = "The buffer was too small for the result."; } } if (pcbDesc) *pcbDesc = len; } else { /* numeric data */ if (pfDesc) *pfDesc = value; } return result; } /* Returns result data for a single column in the current row. */ RETCODE SQL_API SQLGetData( HSTMT hstmt, UWORD icol, SWORD fCType, PTR rgbValue, SDWORD cbValueMax, SDWORD FAR *pcbValue) { static char *func = "SQLGetData"; QResultClass *res; StatementClass *stmt = (StatementClass *) hstmt; int num_cols, num_rows; Int4 field_type; void *value = NULL; int result; char get_bookmark = FALSE; mylog("SQLGetData: enter, stmt=%u\n", stmt); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } res = stmt->result; if (STMT_EXECUTING == stmt->status) { stmt->errormsg = "Can't get data while statement is still executing."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->status != STMT_FINISHED) { stmt->errornumber = STMT_STATUS_ERROR; stmt->errormsg = "GetData can only be called after the successful execution on a SQL statement"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (icol == 0) { if (stmt->options.use_bookmarks == SQL_UB_OFF) { stmt->errornumber = STMT_COLNUM_ERROR; stmt->errormsg = "Attempt to retrieve bookmark with bookmark usage disabled"; SC_log_error(func, "", stmt); return SQL_ERROR; } /* Make sure it is the bookmark data type */ if (fCType != SQL_C_BOOKMARK) { stmt->errormsg = "Column 0 is not of type SQL_C_BOOKMARK"; stmt->errornumber = STMT_PROGRAM_TYPE_OUT_OF_RANGE; SC_log_error(func, "", stmt); return SQL_ERROR; } get_bookmark = TRUE; } else { /* use zero-based column numbers */ icol--; /* make sure the column number is valid */ num_cols = QR_NumResultCols(res); if (icol >= num_cols) { stmt->errormsg = "Invalid column number."; stmt->errornumber = STMT_INVALID_COLUMN_NUMBER_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } } if (stmt->manual_result || !globals.use_declarefetch) { /* make sure we're positioned on a valid row */ num_rows = QR_get_num_tuples(res); if ((stmt->currTuple < 0) || (stmt->currTuple >= num_rows)) { stmt->errormsg = "Not positioned on a valid row for GetData."; stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } mylog(" num_rows = %d\n", num_rows); if (!get_bookmark) { if (stmt->manual_result) value = QR_get_value_manual(res, stmt->currTuple, icol); else value = QR_get_value_backend_row(res, stmt->currTuple, icol); mylog(" value = '%s'\n", value); } } else { /* it's a SOCKET result (backend data) */ if (stmt->currTuple == -1 || !res || !res->tupleField) { stmt->errormsg = "Not positioned on a valid row for GetData."; stmt->errornumber = STMT_INVALID_CURSOR_STATE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } if (!get_bookmark) value = QR_get_value_backend(res, icol); mylog(" socket: value = '%s'\n", value); } if (get_bookmark) { *((UDWORD *) rgbValue) = SC_get_bookmark(stmt); if (pcbValue) *pcbValue = 4; return SQL_SUCCESS; } field_type = QR_get_field_type(res, icol); mylog("**** SQLGetData: icol = %d, fCType = %d, field_type = %d, value = '%s'\n", icol, fCType, field_type, value); stmt->current_col = icol; result = copy_and_convert_field(stmt, field_type, value, fCType, rgbValue, cbValueMax, pcbValue); stmt->current_col = -1; switch (result) { case COPY_OK: return SQL_SUCCESS; case COPY_UNSUPPORTED_TYPE: stmt->errormsg = "Received an unsupported type from Postgres."; stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; case COPY_UNSUPPORTED_CONVERSION: stmt->errormsg = "Couldn't handle the necessary data type conversion."; stmt->errornumber = STMT_RESTRICTED_DATA_TYPE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; case COPY_RESULT_TRUNCATED: stmt->errornumber = STMT_TRUNCATED; stmt->errormsg = "The buffer was too small for the result."; return SQL_SUCCESS_WITH_INFO; case COPY_GENERAL_ERROR: /* error msg already filled in */ SC_log_error(func, "", stmt); return SQL_ERROR; case COPY_NO_DATA_FOUND: /* SC_log_error(func, "no data found", stmt); */ return SQL_NO_DATA_FOUND; default: stmt->errormsg = "Unrecognized return value from copy_and_convert_field."; stmt->errornumber = STMT_INTERNAL_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } } /* Returns data for bound columns in the current row ("hstmt->iCursor"), */ /* advances the cursor. */ RETCODE SQL_API SQLFetch( HSTMT hstmt) { static char *func = "SQLFetch"; StatementClass *stmt = (StatementClass *) hstmt; QResultClass *res; mylog("SQLFetch: stmt = %u, stmt->result= %u\n", stmt, stmt->result); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } SC_clear_error(stmt); if (!(res = stmt->result)) { stmt->errormsg = "Null statement result in SQLFetch."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } /* Not allowed to bind a bookmark column when using SQLFetch. */ if (stmt->bookmark.buffer) { stmt->errornumber = STMT_COLNUM_ERROR; stmt->errormsg = "Not allowed to bind a bookmark column when using SQLFetch"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->status == STMT_EXECUTING) { stmt->errormsg = "Can't fetch while statement is still executing."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->status != STMT_FINISHED) { stmt->errornumber = STMT_STATUS_ERROR; stmt->errormsg = "Fetch can only be called after the successful execution on a SQL statement"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->bindings == NULL) { /* just to avoid a crash if the user insists on calling this */ /* function even if SQL_ExecDirect has reported an Error */ stmt->errormsg = "Bindings were not allocated properly."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } QR_set_rowset_size(res, 1); QR_inc_base(res, stmt->last_fetch_count); return SC_fetch(stmt); } /* This fetchs a block of data (rowset). */ RETCODE SQL_API SQLExtendedFetch( HSTMT hstmt, UWORD fFetchType, SDWORD irow, UDWORD FAR *pcrow, UWORD FAR *rgfRowStatus) { static char *func = "SQLExtendedFetch"; StatementClass *stmt = (StatementClass *) hstmt; QResultClass *res; int num_tuples, i, save_rowset_size; RETCODE result; char truncated, error; mylog("SQLExtendedFetch: stmt=%u\n", stmt); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } if (globals.use_declarefetch && !stmt->manual_result) { if (fFetchType != SQL_FETCH_NEXT) { stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; stmt->errormsg = "Unsupported fetch type for SQLExtendedFetch with UseDeclareFetch option."; return SQL_ERROR; } } SC_clear_error(stmt); if (!(res = stmt->result)) { stmt->errormsg = "Null statement result in SQLExtendedFetch."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } /* * If a bookmark colunmn is bound but bookmark usage is off, then * error */ if (stmt->bookmark.buffer && stmt->options.use_bookmarks == SQL_UB_OFF) { stmt->errornumber = STMT_COLNUM_ERROR; stmt->errormsg = "Attempt to retrieve bookmark with bookmark usage disabled"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->status == STMT_EXECUTING) { stmt->errormsg = "Can't fetch while statement is still executing."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->status != STMT_FINISHED) { stmt->errornumber = STMT_STATUS_ERROR; stmt->errormsg = "ExtendedFetch can only be called after the successful execution on a SQL statement"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (stmt->bindings == NULL) { /* just to avoid a crash if the user insists on calling this */ /* function even if SQL_ExecDirect has reported an Error */ stmt->errormsg = "Bindings were not allocated properly."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } /* Initialize to no rows fetched */ if (rgfRowStatus) for (i = 0; i < stmt->options.rowset_size; i++) *(rgfRowStatus + i) = SQL_ROW_NOROW; if (pcrow) *pcrow = 0; num_tuples = QR_get_num_tuples(res); /* Save and discard the saved rowset size */ save_rowset_size = stmt->save_rowset_size; stmt->save_rowset_size = -1; switch (fFetchType) { case SQL_FETCH_NEXT: /* * From the odbc spec... If positioned before the start of the * RESULT SET, then this should be equivalent to * SQL_FETCH_FIRST. */ if (stmt->rowset_start < 0) stmt->rowset_start = 0; else { stmt->rowset_start += (save_rowset_size > 0 ? save_rowset_size : stmt->options.rowset_size); } mylog("SQL_FETCH_NEXT: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); break; case SQL_FETCH_PRIOR: mylog("SQL_FETCH_PRIOR: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); /* * From the odbc spec... If positioned after the end of the * RESULT SET, then this should be equivalent to * SQL_FETCH_LAST. */ if (stmt->rowset_start >= num_tuples) { stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size); } else { stmt->rowset_start -= stmt->options.rowset_size; } break; case SQL_FETCH_FIRST: mylog("SQL_FETCH_FIRST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); stmt->rowset_start = 0; break; case SQL_FETCH_LAST: mylog("SQL_FETCH_LAST: num_tuples=%d, currtuple=%d\n", num_tuples, stmt->currTuple); stmt->rowset_start = num_tuples <= 0 ? 0 : (num_tuples - stmt->options.rowset_size); break; case SQL_FETCH_ABSOLUTE: mylog("SQL_FETCH_ABSOLUTE: num_tuples=%d, currtuple=%d, irow=%d\n", num_tuples, stmt->currTuple, irow); /* Position before result set, but dont fetch anything */ if (irow == 0) { stmt->rowset_start = -1; stmt->currTuple = -1; return SQL_NO_DATA_FOUND; } /* Position before the desired row */ else if (irow > 0) stmt->rowset_start = irow - 1; /* Position with respect to the end of the result set */ else stmt->rowset_start = num_tuples + irow; break; case SQL_FETCH_RELATIVE: /* * Refresh the current rowset -- not currently implemented, * but lie anyway */ if (irow == 0) break; stmt->rowset_start += irow; break; case SQL_FETCH_BOOKMARK: stmt->rowset_start = irow - 1; break; default: SC_log_error(func, "Unsupported SQLExtendedFetch Direction", stmt); return SQL_ERROR; } /***********************************/ /* CHECK FOR PROPER CURSOR STATE */ /***********************************/ /* * Handle Declare Fetch style specially because the end is not really * the end... */ if (globals.use_declarefetch && !stmt->manual_result) { if (QR_end_tuples(res)) return SQL_NO_DATA_FOUND; } else { /* If *new* rowset is after the result_set, return no data found */ if (stmt->rowset_start >= num_tuples) { stmt->rowset_start = num_tuples; return SQL_NO_DATA_FOUND; } } /* If *new* rowset is prior to result_set, return no data found */ if (stmt->rowset_start < 0) { if (stmt->rowset_start + stmt->options.rowset_size <= 0) { stmt->rowset_start = -1; return SQL_NO_DATA_FOUND; } else { /* overlap with beginning of result set, * so get first rowset */ stmt->rowset_start = 0; } } /* currTuple is always 1 row prior to the rowset */ stmt->currTuple = stmt->rowset_start - 1; /* increment the base row in the tuple cache */ QR_set_rowset_size(res, stmt->options.rowset_size); QR_inc_base(res, stmt->last_fetch_count); /* Physical Row advancement occurs for each row fetched below */ mylog("SQLExtendedFetch: new currTuple = %d\n", stmt->currTuple); truncated = error = FALSE; for (i = 0; i < stmt->options.rowset_size; i++) { stmt->bind_row = i; /* set the binding location */ result = SC_fetch(stmt); /* Determine Function status */ if (result == SQL_NO_DATA_FOUND) break; else if (result == SQL_SUCCESS_WITH_INFO) truncated = TRUE; else if (result == SQL_ERROR) error = TRUE; /* Determine Row Status */ if (rgfRowStatus) { if (result == SQL_ERROR) *(rgfRowStatus + i) = SQL_ROW_ERROR; else *(rgfRowStatus + i) = SQL_ROW_SUCCESS; } } /* Save the fetch count for SQLSetPos */ stmt->last_fetch_count = i; /* Reset next binding row */ stmt->bind_row = 0; /* Move the cursor position to the first row in the result set. */ stmt->currTuple = stmt->rowset_start; /* For declare/fetch, need to reset cursor to beginning of rowset */ if (globals.use_declarefetch && !stmt->manual_result) QR_set_position(res, 0); /* Set the number of rows retrieved */ if (pcrow) *pcrow = i; if (i == 0) return SQL_NO_DATA_FOUND; /* Only DeclareFetch should wind * up here */ else if (error) return SQL_ERROR; else if (truncated) return SQL_SUCCESS_WITH_INFO; else return SQL_SUCCESS; } /* This determines whether there are more results sets available for */ /* the "hstmt". */ /* CC: return SQL_NO_DATA_FOUND since we do not support multiple result sets */ RETCODE SQL_API SQLMoreResults( HSTMT hstmt) { return SQL_NO_DATA_FOUND; } /* This positions the cursor within a rowset, that was positioned using SQLExtendedFetch. */ /* This will be useful (so far) only when using SQLGetData after SQLExtendedFetch. */ RETCODE SQL_API SQLSetPos( HSTMT hstmt, UWORD irow, UWORD fOption, UWORD fLock) { static char *func = "SQLSetPos"; StatementClass *stmt = (StatementClass *) hstmt; QResultClass *res; int num_cols, i; BindInfoClass *bindings = stmt->bindings; if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } if (fOption != SQL_POSITION && fOption != SQL_REFRESH) { stmt->errornumber = STMT_NOT_IMPLEMENTED_ERROR; stmt->errormsg = "Only SQL_POSITION/REFRESH is supported for SQLSetPos"; SC_log_error(func, "", stmt); return SQL_ERROR; } if (!(res = stmt->result)) { stmt->errormsg = "Null statement result in SQLSetPos."; stmt->errornumber = STMT_SEQUENCE_ERROR; SC_log_error(func, "", stmt); return SQL_ERROR; } num_cols = QR_NumResultCols(res); if (irow == 0) { stmt->errornumber = STMT_ROW_OUT_OF_RANGE; stmt->errormsg = "Driver does not support Bulk operations."; SC_log_error(func, "", stmt); return SQL_ERROR; } if (irow > stmt->last_fetch_count) { stmt->errornumber = STMT_ROW_OUT_OF_RANGE; stmt->errormsg = "Row value out of range"; SC_log_error(func, "", stmt); return SQL_ERROR; } irow--; /* Reset for SQLGetData */ for (i = 0; i < num_cols; i++) bindings[i].data_left = -1; QR_set_position(res, irow); stmt->currTuple = stmt->rowset_start + irow; return SQL_SUCCESS; } /* Sets options that control the behavior of cursors. */ RETCODE SQL_API SQLSetScrollOptions( HSTMT hstmt, UWORD fConcurrency, SDWORD crowKeyset, UWORD crowRowset) { static char *func = "SQLSetScrollOptions"; SC_log_error(func, "Function not implemented", (StatementClass *) hstmt); return SQL_ERROR; } /* Set the cursor name on a statement handle */ RETCODE SQL_API SQLSetCursorName( HSTMT hstmt, UCHAR FAR *szCursor, SWORD cbCursor) { static char *func = "SQLSetCursorName"; StatementClass *stmt = (StatementClass *) hstmt; int len; mylog("SQLSetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d\n", hstmt, szCursor, cbCursor); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } len = (cbCursor == SQL_NTS) ? strlen(szCursor) : cbCursor; if (len <= 0 || len > sizeof(stmt->cursor_name) - 1) { stmt->errornumber = STMT_INVALID_CURSOR_NAME; stmt->errormsg = "Invalid Cursor Name"; SC_log_error(func, "", stmt); return SQL_ERROR; } strncpy_null(stmt->cursor_name, szCursor, len + 1); return SQL_SUCCESS; } /* Return the cursor name for a statement handle */ RETCODE SQL_API SQLGetCursorName( HSTMT hstmt, UCHAR FAR *szCursor, SWORD cbCursorMax, SWORD FAR *pcbCursor) { static char *func = "SQLGetCursorName"; StatementClass *stmt = (StatementClass *) hstmt; int len = 0; RETCODE result; mylog("SQLGetCursorName: hstmt=%u, szCursor=%u, cbCursorMax=%d, pcbCursor=%u\n", hstmt, szCursor, cbCursorMax, pcbCursor); if (!stmt) { SC_log_error(func, "", NULL); return SQL_INVALID_HANDLE; } if (stmt->cursor_name[0] == '\0') { stmt->errornumber = STMT_NO_CURSOR_NAME; stmt->errormsg = "No Cursor name available"; SC_log_error(func, "", stmt); return SQL_ERROR; } result = SQL_SUCCESS; len = strlen(stmt->cursor_name); if (szCursor) { strncpy_null(szCursor, stmt->cursor_name, cbCursorMax); if (len >= cbCursorMax) { result = SQL_SUCCESS_WITH_INFO; stmt->errornumber = STMT_TRUNCATED; stmt->errormsg = "The buffer was too small for the result."; } } if (pcbCursor) *pcbCursor = len; return result; }